【问题标题】:Behaviour of PROT_READ and PROT_WRITE with mprotect使用 mprotect 的 PROT_READ 和 PROT_WRITE 的行为
【发布时间】:2013-09-20 15:52:49
【问题描述】:

我一直在尝试使用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 读取值时不会使程序崩溃。

有人能解释一下这部分吗?

【问题讨论】:

  • 您为什么希望PROT_WRITE 崩溃?
  • 因为只有PROT_WRITE 标志,内存应该是不可读的AFAIK。如果你想要 rw 访问,你需要PROT_WRITE | PROT_READ flag
  • @Mali 是的,如果他期望在读取第一个 printf 的参数时崩溃,而不是用*a = 24 覆盖值时,这将是一个问题。无论如何,我试图在我的回答中涵盖所有这些。

标签: c mprotect


【解决方案1】:

您正在观察两件事:

  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 * 以避免任何编译器优化。

【讨论】:

  • 1.如果不是来自堆,mmap 的内存来自哪里? 2. 我想通了,但我没有找到任何来源,也没有提示。这就是我想解决的问题!还是谢谢。
  • mmap 上的内存来自 VM 中的空闲区域。从某种意义上说,堆由用户空间 malloc 库(它反过来调用 mmap)管理并允许以字节块分配,而 VM 由内核管理并且只允许以块分配页数。
  • 2.我刚刚经历了x86上的MMU设计:访问位不是设计为rwx,而是设计为“无访问”、“写保护”和“可执行”。无法在硬件上物理设置页面“写入但不读取”。
  • 不仅仅是 x86。我曾在 5-6 种不同的 cpu 架构上使用过 MMU,但它们都没有读保护。浪费硅不是一个有用的功能,特别是因为您需要一些非常复杂的逻辑来确保通过 MMU 读取到缓存中而不允许从缓存中读取,同时还实现逻辑以防止通过 MMU 进行未缓存的读取.这不值得。缓存标签中的位很昂贵,PTE 中的位很昂贵,只写内存不是很有用,所以(几乎?)没有人这样做。
  • 仅供参考,我在10.11.6 上收到Bus error: 10。其他人可以报告此解决方案是否仍然适用于他们,还是真的过时了?
【解决方案2】:

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

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

【讨论】:

  • printf/stdout 是行缓冲的,所以他只要在每个输出的末尾设置一个换行符就可以了。仍然打印到stderr 可能是一个更好的主意。
  • 我也是这么想的。但 MacOS 似乎并不同意。第一次调用后崩溃时,MacOS 上的 printf 不会刷新。
  • 一旦我通过mmap 分配页面,我的 OS X 10.7 就会执行此操作。即使我将其更改为stderr,它仍然会在posix_memalign/mprotect(PROT_READ) 上第一次打印之前崩溃。
  • 使用 posix_memalign 为我工作。堆页在 MacOS 上并不特别。它在标准输出上使用 stderr 和 _IONBF 正确打印内容,但由于某种原因不能正确打印 _IOLBF。很奇怪。
  • 哦。我知道了。保护该页面很可能会破坏一些 malloc 内部结构。这就是 printf 在有任何缓冲时会崩溃的原因。
猜你喜欢
  • 1970-01-01
  • 2011-10-21
  • 2017-09-29
  • 2020-04-05
  • 2012-03-05
  • 2020-09-13
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
相关资源
最近更新 更多