Поведение 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,

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

2 ответа

Решение

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

  1. mprotect не был предназначен для использования с кучей страниц. В Linux и OS X обработка кучи несколько отличается (помните, что OS X использует Mach VM). 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 * чтобы избежать каких-либо оптимизаций компилятора.

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

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

Другие вопросы по тегам