Неожиданное разрешение 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
) библиотечные файлы для использования другим программным обеспечением, так как тогда вы не управляете параметрами времени соединения, когда он используется другими программами.