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

94

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

В моем проекте, когда я выделяю память с mmapпомощью mapping ( /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 
Бен Хиршберг
источник
5
Это серьезно странно.
fuz
6
Мне удалось воспроизвести это только с помощью GCC (без CMake), поэтому я отредактировал вопрос, чтобы сделать пример более минималистичным.
Джозеф Сибл-Восстановить Монику
2
Возможно, связано stackoverflow.com/questions/32730643/…
Сами Кухмонен
Возможно, вы правы, часть его ответа должна быть вокруг персонажа READ_IMPLIES_EXEC
Бена Хиршберга
Соберите ваши исходные файлы с -Wa,--noexecstack.
jww

Ответы:

90

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, и она будет сообщать компоновщику, что он тоже не нужен, и тогда ваша программа будет работать так, как ожидалось.

Джозеф Сибл-Восстановить Монику
источник
13
Черт возьми, это странный дефолт. Я полагаю, что цепочка инструментов существовала до noexec, и установка noexec по умолчанию могла привести к поломке. Теперь мне интересно, как другие ассемблеры, такие как NASM / YASM, создают .oфайлы! Но в любом случае, я думаю, что это механизм, который gcc -zexecstackиспользует, и почему он делает не просто стек, а все исполняемым.
Питер Кордес
23
@Peter - Вот почему такие проекты, как Botan, Crypto ++ и OpenSSL, которые используют ассемблер, добавляют -Wa,--noexecstack. Я думаю, что это очень противный острый край. Тихая потеря nx-стеков должна быть уязвимостью безопасности. Люди Binutil должны это исправить.
jww
14
@jww Это действительно проблема безопасности, странно, что никто не сообщил об этом раньше
Бен Хиршберг
4
+1, но этот ответ был бы намного лучше, если бы .note.GNU-stack,"",@progbitsобъяснили смысл / логику строки - сейчас она непрозрачна, что эквивалентно «эта волшебная строка символов вызывает этот эффект», но строка явно выглядит так, как будто имеет какой-то вид семантика.
mtraceur
33

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

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

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

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

R .. GitHub СТОП, ПОМОГАЯ ЛЬДУ
источник
1
Хм ... Я не знал, что вы были Рич Фелкер, прежде чем читать этот пост. Почему ваш псевдоним не отображается?
SS Anne