我一直试图先使用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
读取值时不会导致程序崩溃。
有人可以解释这部分吗?
答案 0 :(得分:9)
您正在观察两件事:
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");
这是对MMU的限制(在我的情况下为x86)。 x86中的MMU不支持可写但不可读的页面。因此设置
mprotect(a, pagesize, PROT_WRITE)
什么都不做。而
mprotect(a, pagesize, PROT_READ)
删除了写入的priveledges,你得到了一个预期的SIGSEGV。
此外,虽然这似乎不是问题,但您应该使用-O0
编译代码或将a
设置为volatile int *
以避免任何编译器优化。
答案 1 :(得分:1)
大多数操作系统和/或cpu架构在可写时会自动生成可读的内容,因此PROT_WRITE
通常也会隐含PROT_READ
。在不使其可读的情况下,根本不可能制作可写的东西。可以推测原因,或者不值得在MMU和缓存中创建额外的可读性位,或者在某些早期架构中,您实际上需要在写入之前将MMU读入缓存,所以自动制作不可读的内容会使其无法写入。
此外,printf
可能会尝试从您使用mprotect
损坏的内存中进行分配。您希望在更改其保护时从libc分配整页,否则您将更改您不完全拥有的页面的保护,并且libc不希望它受到保护。在使用PROT_READ
进行MacOS测试时,会发生这种情况。 printf
分配一些内部结构,尝试访问它们并在只读时崩溃。