C语言 PROT_READ的行为,PROT_WRITE与mprotect



我一直试图用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 的值时不会使程序崩溃。

有人可以解释这部分吗?

您正在观察两件事:

  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)
    

    删除了写入权限,您可以按预期获得 SIGSEGV。

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

大多数操作系统和/或 CPU 架构在可写时会自动使某些内容可读,因此PROT_WRITE通常也意味着PROT_READ。如果不使其可读,就不可能使某些内容可写。原因可以推测,要么不值得在 MMU 和缓存中增加一个可读性位,要么就像在一些早期的体系结构上一样,您实际上需要通过 MMU 读取到缓存中才能写入,因此使某些内容不可读会自动使其不可写。

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

最新更新