Behaviour of PROT_READ and PROT_WRITE with mprotec

2019-01-25 18:07发布

问题:

I've been trying to use mprotect against reading first, and then writing.

Is here my code

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

Under Linux here are the results:

Here is the output for PROT_WRITE:

$ ./main 
a = 42
a = 24

and for PROT_READ

$ ./main 
a = 42
Segmentation fault

Under Mac OS X 10.7:

Here is the output for PROT_WRITE:

$ ./main 
a = 42
a = 24

and for PROT_READ

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

So far, I understand that OSX / Linux behavior might be different, but I don't understand why PROT_WRITE does not crash the program when reading the value with printf.

Can someone explain this part?

回答1:

There are two things that you are observing:

  1. mprotect was not designed to be used with heap pages. Linux and OS X have slightly different handling of the heap (remember that OS X uses the Mach VM). OS X does not like it's heap pages to be tampered with.

    You can get identical behaviour on both OSes if you allocate your page via mmap

    a = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
    if (a == MAP_FAILED) 
        perror("mmap");
    
  2. This is a restriction of your MMU (x86 in my case). The MMU in x86 does not support writable, but not readable pages. Thus setting

    mprotect(a, pagesize, PROT_WRITE)
    

    does nothing. while

    mprotect(a, pagesize, PROT_READ)
    

    removed write priveledges and you get a SIGSEGV as expected.

Also although it doesn't seem to be an issue here, you should either compile your code with -O0 or set a to volatile int * to avoid any compiler optimisations.



回答2:

Most operating systems and/or cpu architectures automatically make something readable when it writeable, so PROT_WRITE most often implies PROT_READ as well. It's simply not possible to make something writeable without making it readable. The reasons can be speculated on, either it's not worth the effort to make an additional readability bit in the MMU and caches, or as it was on some earlier architectures, you actually need to read through the MMU into a cache before you can write, so making something unreadable automatically makes it unwriteable.

Also, it's likely that printf tries to allocate from memory that you damaged with mprotect. You want to allocate a full page from libc when you're changing its protection, otherwise you'll be changing the protection of a page that you don't own fully and libc doesn't expect it to be protected. On your MacOS test with PROT_READ this is what happens. printf allocates some internal structures, tries to access them and crashes when they are read only.



标签: c mprotect