7

我一直在尝试先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.

有人可以解释这部分吗?

4

2 回答 2

11

您正在观察两件事:

  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或设置avolatile int *避免任何编译器优化。

于 2013-09-16T13:31:37.400 回答
2

大多数操作系统和/或 cpu 架构在可写时会自动使某些内容可读,因此PROT_WRITE通常PROT_READ也是如此。在不使其可读的情况下,根本不可能使某些东西可写。可以推测原因,要么不值得在 MMU 和缓存中增加额外的可读性位,要么就像在某些早期架构上一样,您实际上需要先将 MMU 读入缓存,然后才能写入,因此,使某些东西不可读会自动使其不可写。

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

于 2013-09-16T13:32:03.763 回答