Quyền thực thi bất ngờ từ mmap khi tập tin lắp ráp có trong dự án


94

Tôi đang đập đầu vào tường với cái này.

Trong dự án của tôi, khi tôi phân bổ bộ nhớ với mmapánh xạ ( /proc/self/maps) cho thấy đó là vùng có thể đọc và thực thi được mặc dù tôi chỉ yêu cầu bộ nhớ có thể đọc được.

Sau khi xem xét strace (trông có vẻ tốt) và gỡ lỗi khác, tôi đã có thể xác định điều duy nhất có vẻ để tránh vấn đề kỳ lạ này: xóa các tệp lắp ráp khỏi dự án và chỉ để lại C. thuần túy (gì?!)

Vì vậy, đây là ví dụ kỳ lạ của tôi, tôi đang làm việc trên Ubunbtu 19.04 và gcc mặc định.

Nếu bạn biên dịch đích thực thi với tệp ASM (trống) thì mmaptrả về một vùng có thể đọc và thực thi được, nếu bạn xây dựng mà không có thì nó sẽ hoạt động chính xác. Xem kết quả /proc/self/mapsmà tôi đã nhúng trong ví dụ của mình.

ví dụ

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main()
{
    void* p;
    p = mmap(NULL, 8192,PROT_READ,MAP_ANONYMOUS|MAP_PRIVATE,-1,0);

    {
        FILE *f;
        char line[512], s_search[17];
        snprintf(s_search,16,"%lx",(long)p);
        f = fopen("/proc/self/maps","r");
        while (fgets(line,512,f))
        {
            if (strstr(line,s_search)) fputs(line,stderr);
        }

        fclose(f);
    }

    return 0;
}

example.s : Là một tập tin trống!

Đầu ra

Với phiên bản bao gồm ASM

VirtualBox:~/mechanics/build$ gcc example.c example.s -o example && ./example
7f78d6e08000-7f78d6e0a000 r-xp 00000000 00:00 0 

Không có phiên bản bao gồm ASM

VirtualBox:~/mechanics/build$ gcc example.c -o example && ./example
7f1569296000-7f1569298000 r--p 00000000 00:00 0 

5
Điều này thực sự kỳ lạ.
fuz

6
Tôi đã quản lý để tái tạo điều này chỉ bằng GCC (không có CMake), vì vậy tôi đã chỉnh sửa câu hỏi để làm cho ví dụ tối thiểu hơn.
Joseph Sible-Phục hồi Monica


Bạn có thể đúng, một phần câu trả lời của anh ấy phải xoay quanh READ_IMPLIES_EXEC persona
Ben Hirschberg

Lắp ráp các tập tin nguồn của bạn với -Wa,--noexecstack.
jww

Câu trả lời:


90

Linux có một miền thực thi được gọi READ_IMPLIES_EXEC, khiến cho tất cả các trang được phân bổ PROT_READcũng được đưa ra PROT_EXEC. Chương trình này sẽ cho bạn biết liệu điều đó có được kích hoạt hay không:

#include <stdio.h>
#include <sys/personality.h>

int main(void) {
    printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
    return 0;
}

Nếu bạn biên dịch cùng với một .stệp trống , bạn sẽ thấy rằng nó được bật, nhưng không có nó, nó sẽ bị vô hiệu hóa. Giá trị ban đầu của điều này đến từ thông tin meta ELF trong tệp nhị phân của bạn . Làm readelf -Wl example. Bạn sẽ thấy dòng này khi bạn biên dịch mà không có .stệp trống :

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10

Nhưng cái này khi bạn biên dịch với nó:

  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

Lưu ý RWEthay vì chỉ RW. Lý do cho điều này là do trình liên kết giả định rằng các tệp lắp ráp của bạn yêu cầu đọc-implies-exec trừ khi nó nói rõ rằng họ không, và nếu bất kỳ phần nào trong chương trình của bạn yêu cầu read-implies-exec, thì nó được kích hoạt cho toàn bộ chương trình của bạn . Các tập tin lắp ráp mà GCC biên dịch nói với nó rằng nó không cần điều này, với dòng này (bạn sẽ thấy điều này nếu bạn biên dịch với -S):

        .section        .note.GNU-stack,"",@progbits

Đặt dòng đó vào example.s, và nó sẽ phục vụ để nói với người liên kết rằng nó cũng không cần, và chương trình của bạn sẽ hoạt động như mong đợi.


13
Holy crap, đó là một mặc định kỳ lạ. Tôi đoán rằng chuỗi công cụ đã tồn tại trước noexec và làm cho noexec trở thành mặc định có thể có những thứ bị hỏng. Bây giờ tôi tò mò về cách các nhà lắp ráp khác như NASM / YASM tạo .ocác tệp! Nhưng dù sao, tôi đoán đây là cơ chế gcc -zexecstacksử dụng, và tại sao nó không chỉ là ngăn xếp mà là mọi thứ có thể thực thi được.
Peter Cordes

23
@Peter - Đó là lý do tại sao các dự án như Botan, Crypto ++ và OpenSSL, sử dụng trình biên dịch, thêm -Wa,--noexecstack. Tôi nghĩ rằng đó là một cạnh sắc nét rất khó chịu. Mất im lặng các ngăn xếp nx nên là một lỗ hổng bảo mật. Những người Binutil nên sửa nó.
jww

14
@jww Đây thực sự là một vấn đề bảo mật, kỳ lạ là không ai báo cáo trước đó
Ben Hirschberg

4
+1, nhưng câu trả lời này sẽ tốt hơn nhiều nếu ý nghĩa / logic của dòng .note.GNU-stack,"",@progbitsđược giải thích - ngay bây giờ nó mờ đục, tương đương với "chuỗi ký tự ma thuật này gây ra hiệu ứng này", nhưng chuỗi rõ ràng trông giống như nó có một số loại ngữ nghĩa.
mtraceur

33

Thay thế cho việc sửa đổi các tệp lắp ráp của bạn bằng các biến thể chỉ thị của phần dành riêng cho GNU, bạn có thể thêm -Wa,--noexecstackvào dòng lệnh của mình để xây dựng các tệp lắp ráp. Ví dụ: xem cách tôi thực hiện trong musl's configure:

https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a

Tôi tin rằng ít nhất một số phiên bản clang với trình biên dịch tích hợp có thể yêu cầu nó được chuyển qua --noexecstack(không có -Wa), vì vậy tập lệnh cấu hình của bạn có thể nên kiểm tra cả hai và xem cái nào được chấp nhận.

Bạn cũng có thể sử dụng -Wl,-z,noexecstacktại thời điểm liên kết (trong LDFLAGS) để có được kết quả tương tự. Nhược điểm của điều này là nó không hữu ích nếu dự án của bạn tạo các .atệp thư viện tĩnh ( ) để sử dụng bởi phần mềm khác, vì sau đó bạn không kiểm soát các tùy chọn thời gian liên kết khi được các chương trình khác sử dụng.


1
Hmm ... Tôi không biết rằng bạn là Rich Felker trước khi đọc bài viết này. Tại sao không phải là tên hiển thị của bạn dalias?
SS Anne
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.