具有mprotect的PROT_READ和PROT_WRITE的行为

我一直在尝试使用mprotect首先阅读,然后写作。

这是我的代码

 #include  #include  #include  #include  #include  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读取值时不会使程序崩溃。

有人可以解释这部分吗?

您正在观察两件事:

  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)。 x86中的MMU不支持可写但不可读的页面。 这样设定

     mprotect(a, pagesize, PROT_WRITE) 

    什么也没做。 而

     mprotect(a, pagesize, PROT_READ) 

    删除了写入priveledges并按预期获得SIGSEGV。

此外,虽然这似乎不是问题,但您应该使用-O0编译代码或将a设置为volatile int *以避免任何编译器优化。

大多数操作系统和/或cpu体系结构在可写时会自动生成可读的内容,因此PROT_WRITE通常也意味着PROT_READ 。 如果不使其可读,就不可能制作可写的东西。 可以推测原因,或者不值得在MMU和缓存中创建额外的可读性位,或者在某些早期架构中,您实际上需要在写入之前将MMU读入缓存,所以自动制作不可读的内容会使其无法写入。

此外, printf可能会尝试从内存中分配您使用mprotect损坏的mprotect 。 当您更改其保护时,您希望从libc分配整页,否则您将更改您不完全拥有的页面的保护,并且libc不希望它受到保护。 在使用PROT_READ MacOS测试时,会发生这种情况。 printf分配一些内部结构,尝试访问它们并在只读时崩溃。