Неожиданное разрешение exec от mmap, когда файлы сборки включены в проект

Я бьюсь головой об стену этим.

В моем проекте, когда я выделяю память с помощью mmap, отображение (/proc/self/maps) показывает, что это читаемая и исполняемая область , несмотря на то, что я запрашивал только читаемую память.

Изучив strace (который выглядел хорошо) и другие средства отладки, я смог определить единственное, что, по-видимому, позволяет избежать этой странной проблемы: удаление файлов сборки из проекта и оставление только чистого C. (что?!)

Итак, вот мой странный пример: я работаю над Ubunbtu 19.04 и gcc по умолчанию.

Если вы компилируете целевой исполняемый файл с файлом ASM (который является пустым), то mmap возвращает читаемую и исполняемую область, если вы строите без нее, она ведет себя правильно. Смотрите вывод /proc/self/maps, который я встроил в мой пример.

example.c

#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: это пустой файл!

Выходы

С ASM включена версия

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

Без включенной версии ASM

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

Ответы

Ответ 1

В Linux есть исполнительный домен, который называется READ_IMPLIES_EXEC, поэтому всем страницам, выделенным с помощью PROT_READ, также присваивается PROT_EXEC. Эта программа покажет вам, включена ли она для себя:

#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;
}

Если вы скомпилируете это вместе с пустым файлом .s, вы увидите, что он включен, но без него он будет отключен. Начальное значение этого происходит из метаинформации ELF в вашем двоичном файле. Сделайте readelf -Wl example. Вы увидите эту строку при компиляции без пустого файла .s:

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

Но этот, когда вы скомпилировали с ним:

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

Заметьте RWE вместо просто RW. Причина этого заключается в том, что компоновщик предполагает, что ваши файлы сборки требуют read-implies-exec, если он явно не сказал, что они этого не делают, а если какой-либо части вашей программы требуется read-implies-exec, то он включен для всей вашей программы., Файлы сборки, которые компилирует GCC, сообщают, что в этом нет необходимости, с этой строкой (вы увидите это, если скомпилируете с помощью -S):

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

Поместите эту строку в example.s, и она будет сообщать компоновщику, что он ему тоже не нужен, и тогда ваша программа будет работать так, как ожидалось.

Ответ 2

В качестве альтернативы модификации ваших сборочных файлов с помощью специфических для GNU вариантов директив раздела, вы можете добавить -Wa,--noexecstack в командную строку для сборки сборочных файлов. Например, посмотрите, как я это делаю в musl configure:

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

Я полагаю, что по крайней мере некоторые версии clang со встроенным ассемблером могут требовать, чтобы он был передан как --noexecstack (без -Wa), поэтому ваш скрипт configure, вероятно, должен проверить оба и увидеть, что принято.

Вы также можете использовать -Wl,-z,noexecstack во время соединения (в LDFLAGS), чтобы получить тот же результат. Недостаток этого состоит в том, что это не помогает, если ваш проект создает статические (.a) библиотечные файлы для использования другим программным обеспечением, так как тогда вы не управляете параметрами времени соединения, когда он используется другими программами.