Поведение PROT_READ и PROT_WRITE с mprotect

Я пытался использовать mprotect сначала против чтения, а затем записи.

Здесь мой код

#include <sys/types.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(void)
{
    int pagesize = sysconf(_SC_PAGE_SIZE);
    int *a;
    if (posix_memalign((void**)&a, pagesize, sizeof(int)) != 0)
        perror("memalign");

    *a = 42;
    if (mprotect(a, pagesize, PROT_WRITE) == -1) /* Resp. PROT_READ */
        perror("mprotect");

    printf("a = %d\n", *a);
    *a = 24;
    printf("a = %d\n", *a);
    free (a);
    return 0;
}

Под Linux вот результаты:

Вот вывод для PROT_WRITE:

$ ./main 
a = 42
a = 24

и для PROT_READ

$ ./main 
a = 42
Segmentation fault

В Mac OS X 10.7:

Вот вывод для PROT_WRITE:

$ ./main 
a = 42
a = 24

и для PROT_READ

$ ./main 
[1] 2878 bus error ./main

Пока я понимаю, что поведение OSX/Linux может отличаться, но я не понимаю, почему PROT_WRITE не крашит программу при чтении значения с printf.

Кто-нибудь может объяснить эту часть?


person Aif    schedule 16.09.2013    source источник
comment
Почему вы ожидаете, что PROT_WRITE выйдет из строя?   -  person Art    schedule 16.09.2013
comment
потому что только с флагом PROT_WRITE память должна быть нечитаемой, насколько мне известно. Если вы хотите доступ RW, вам нужен флаг PROT_WRITE | PROT_READ   -  person Mali    schedule 16.09.2013
comment
@Mali правильно, это имело бы смысл как вопрос, если бы он ожидал сбоя при чтении аргумента первого printf, а не при перезаписи значения с помощью *a = 24. Во всяком случае, я попытался охватить все это в своем ответе.   -  person Art    schedule 16.09.2013


Ответы (2)


Есть две вещи, которые вы наблюдаете:

  1. mprotect не предназначен для использования со страницами кучи. Linux и OS X имеют несколько различную обработку кучи (помните, что OS X использует виртуальную машину Mach). OS X не любит, когда в нее вмешиваются страницы кучи.

    Вы можете получить одинаковое поведение на обеих ОС, если вы разместите свою страницу через mmap

    a = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (a == MAP_FAILED) 
        perror("mmap");
    
  2. Это ограничение вашего MMU (x86 в моем случае). MMU в x86 не поддерживает страницы, доступные для записи, но не для чтения. Таким образом, установка

    mprotect(a, pagesize, PROT_WRITE)
    

    ничего не делает. пока

    mprotect(a, pagesize, PROT_READ)
    

    удалены права записи, и вы получите SIGSEGV, как и ожидалось.

Кроме того, хотя здесь это не кажется проблемой, вы должны либо скомпилировать свой код с -O0, либо установить a в volatile int *, чтобы избежать какой-либо оптимизации компилятора.

person Sergey L.    schedule 16.09.2013
comment
1. откуда берется память mmap если не из кучи? 2. Я разобрался с этим, но не нашел ни источников, ни намека на это. Вот что я хочу решить! Спасибо, в любом случае. - person Aif; 16.09.2013
comment
Память на mmap поступает из свободной области вашей виртуальной машины. Это отличается в том смысле, что куча управляется библиотекой malloc пользовательского пространства (которая, в свою очередь, вызывает mmap) и позволяет выделять фрагменты байтов, а виртуальная машина управляется ядром и разрешает выделение фрагментов страниц. - person Sergey L.; 16.09.2013
comment
2. Я только что прошел дизайн MMU на x86: биты доступа оформлены не как rwx, а как нет доступа, защищены от записи и исполняемые. Невозможно настроить запись страницы, но не чтение физически на оборудовании. - person Sergey L.; 16.09.2013
comment
Это не просто x86. Я работал с MMU на 5-6 различных процессорных архитектурах, и ни один из них не защищал от чтения. Это не очень полезная функция, на которую можно тратить кремний, тем более, что вам понадобится какая-то очень сложная логика, чтобы убедиться, что чтение из MMU в кеш работает, не разрешая чтение из кеша, а также реализовать логику для предотвращения некэшированных чтений через MMU. . Это того не стоит. Биты в тегах кеша дороги, биты в PTE дороги, память только для записи не очень полезна, поэтому (почти?) никто этим не занимается. - person Art; 16.09.2013
comment
К вашему сведению, я получаю Bus error: 10 на 10.11.6. Может ли кто-нибудь еще сообщить, работает ли это решение для них или оно действительно устарело? - person Micrified; 16.04.2018

Большинство операционных систем и/или процессорных архитектур автоматически делают что-то доступным для чтения, когда оно доступно для записи, поэтому PROT_WRITE чаще всего также подразумевает PROT_READ. Просто невозможно сделать что-то пригодным для записи, не сделав это читаемым. О причинах можно размышлять: либо не стоит делать дополнительный бит читаемости в MMU и кешах, либо, как это было на некоторых более ранних архитектурах, вам действительно нужно прочитать MMU в кеш, прежде чем вы сможете писать, поэтому создание чего-то нечитаемого автоматически делает его нечитаемым.

Кроме того, вполне вероятно, что printf пытается выделить из памяти, которую вы повредили с помощью mprotect. Вы хотите выделить полную страницу из libc при изменении ее защиты, иначе вы измените защиту страницы, которой вы не владеете полностью, и libc не ожидает, что она будет защищена. В вашем тесте MacOS с PROT_READ происходит вот что. printf выделяет некоторые внутренние структуры, пытается получить к ним доступ и падает, когда они доступны только для чтения.

person Art    schedule 16.09.2013
comment
printf/stdout буферизуется строкой, поэтому он хорош, пока устанавливает разрыв строки в конце каждого вывода. Тем не менее, печать на stderr может быть лучшей идеей. - person Sergey L.; 16.09.2013
comment
Вот и я тоже. Но MacOS, похоже, не согласен. printf в MacOS не сбрасывается при сбое после первого вызова. - person Art; 16.09.2013
comment
Моя OS X 10.7 делает это, как только я выделяю страницу через mmap. Даже если я изменю его на stderr, он все равно вылетит перед первой печатью на posix_memalign/mprotect(PROT_READ). - person Sergey L.; 16.09.2013
comment
У меня работает с posix_memalign. Страницы кучи не являются чем-то особенным в MacOS. Он правильно печатает с stderr и _IONBF на stdout, но по какой-то причине не _IOLBF. Странный. - person Art; 16.09.2013
comment
Ой. Я понимаю. Защита этой страницы, скорее всего, сломает некоторые внутренние функции malloc. Вот почему printf падает, когда у него есть какая-либо буферизация. - person Art; 16.09.2013
comment
@Art: вы правы, я исправил вопрос ... почему printf может работать, тогда как регион должен быть доступен только для записи. - person Aif; 16.09.2013