7

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?

Aif
  • 11,015
  • 1
  • 30
  • 44
  • Why would you expect `PROT_WRITE` to crash? – Art Sep 16 '13 at 13:22
  • because with `PROT_WRITE` flag only, memory is supposed to be unreadable AFAIK. If you want rw access, you need `PROT_WRITE | PROT_READ` flag – Mali Sep 16 '13 at 13:31
  • @Mali right, that would make sense as a question if he was expecting a crash on reading in the argument to the first printf, not when overwriting the value with `*a = 24`. Anyway, I attempted to cover all of this in my answer. – Art Sep 16 '13 at 13:33

2 Answers2

11

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.

Sergey L.
  • 21,822
  • 5
  • 49
  • 75
  • 1. where does the memory of `mmap` comes from if not from heap? 2. I figured this out, but I didn't found any sources nor hint about that. That's what I'de like to solve! Thanks anyway. – Aif Sep 16 '13 at 13:48
  • 2
    The memory on `mmap` comes from a free area in your VM. It is different in the sense that the heap is managed by a user-space `malloc` library (which in turn calls `mmap`) and allows allocation in chunks of bytes and the VM is managed by the kernel and only allows allocations in chunks of pages. – Sergey L. Sep 16 '13 at 13:56
  • 1
    2. I have just went through the MMU design on x86: The access bits are not designed as `rwx`, but as "no access", "write protected" and "executable". There is no way to set a page "write, but not read" physically on the hardware. – Sergey L. Sep 16 '13 at 13:59
  • 2
    It's not just x86. I've worked with MMUs on 5-6 different cpu architectures and none of them did read protection. It's not a useful feature to waste silicon on, especially since you'd need some really hairy logic to make sure that reads through the MMU into a cache work without allowing reads from the cache, while also implementing logic to prevent uncached reads through the MMU. It's just not worth it. Bits in cache tags are expensive, bits in PTEs are expensive, write-only memory is not very useful, so (almost?) no one does this. – Art Sep 16 '13 at 14:09
  • Just FYI, I'm getting `Bus error: 10` on `10.11.6`. Can anyone else report if this solution still works for them, or is it genuinely outdated? – Micrified Apr 16 '18 at 12:25
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.

Art
  • 19,807
  • 1
  • 34
  • 60
  • `printf/stdout` is line buffered, so he is good as long as he sets a line break at the end of each output. Still printing to `stderr` may be a better idea. – Sergey L. Sep 16 '13 at 13:34
  • That's what I though too. But MacOS doesn't seem to agree. `printf` on MacOS doesn't flush when crashing after the first call. – Art Sep 16 '13 at 13:35
  • My OS X 10.7 does once I allocate the page through `mmap`. Even if I change it to `stderr` it still crashes before the first print on `posix_memalign/mprotect(PROT_READ)`. – Sergey L. Sep 16 '13 at 13:37
  • Works for me with posix_memalign. Heap pages are not that special on MacOS. It prints things correctly with stderr and `_IONBF` on stdout, but not `_IOLBF` for some reason. Weird. – Art Sep 16 '13 at 13:41
  • Oh. I get it. Protecting that page most likely breaks some malloc internals. That's why printf crashes when it has any buffering. – Art Sep 16 '13 at 13:46
  • @Art: you're right, I corrected the question ... why `printf` can actually work whereas the region should be writable only. – Aif Sep 16 '13 at 13:46