具有mprotect的PROT_READ和PROT_WRITE的行为

时间:2013-09-16 13:16:24

标签: c 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 = %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读取值时不会导致程序崩溃。

有人可以解释这部分吗?

2 个答案:

答案 0 :(得分:9)

您正在观察两件事:

  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。

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

答案 1 :(得分:1)

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

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