我一直试图用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 = %dn", *a);
*a = 24;
printf("a = %dn", *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
的值时不会使程序崩溃。
有人可以解释这部分吗?
您正在观察两件事:
-
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)
删除了写入权限,您可以按预期获得 SIGSEGV。
此外,尽管这里似乎不是问题,但您应该使用 -O0
编译代码或将a
设置为 volatile int *
以避免任何编译器优化。
大多数操作系统和/或 CPU 架构在可写时会自动使某些内容可读,因此PROT_WRITE
通常也意味着PROT_READ
。如果不使其可读,就不可能使某些内容可写。原因可以推测,要么不值得在 MMU 和缓存中增加一个可读性位,要么就像在一些早期的体系结构上一样,您实际上需要通过 MMU 读取到缓存中才能写入,因此使某些内容不可读会自动使其不可写。
此外,printf
很可能尝试从您因mprotect
损坏的内存中分配。当您更改libc的保护时,您希望从libc分配一个完整的页面,否则您将更改您不完全拥有的页面的保护,并且libc不希望它受到保护。在MacOS测试中,PROT_READ
就会发生这种情况。 printf
分配一些内部结构,尝试访问它们并在它们为只读时崩溃。