【问题标题】:mmaping two consecutive pages映射两个连续的页面
【发布时间】:2010-10-10 09:05:39
【问题描述】:

我正在为我的 UTF8 操作库编写一个单元测试,如果函数进入缓冲区溢出,我希望我的测试能够发生段错误。所以我想出了在内存中mmap两个相邻页面的想法,第一个是PROT_READ | PROT_WRITE,第二个是 PROT_NONE。这样,如果发生任何溢出,就可以保证段错误。这是一个例子:


void *addr1, *addr2; /* these are the pages; mmap call left out for simplicity */
char *p = (char *) (addr1 + getpagesize() - 8);

utf8_encode(aUtf8String, p, 8); // this shouldn't segfault

问题是,当我映射第二页时,我的程序出现了段错误。这是一个重现问题的示例程序(GNU/Linux):

#include <stdio.h> #include <errno.h> #include <string.h> #include <stdlib.h> #include <sys/mman.h> void checkMap(void *p) { if(p == MAP_FAILED) { printf("error running mmap: %s\n", strerror(errno)); exit(1); } } int main(void) { void *addr1, *addr2; size_t pagesize; pagesize = getpagesize(); checkMap(addr1 = mmap(NULL, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0)); checkMap(addr2 = mmap(addr1 + pagesize, pagesize, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, -1, 0)); /* segfaults */ munmap(addr1, pagesize); munmap(addr2, pagesize); return 0; }

有趣的是,第一个 mmap() 之前的 printf() 语句会导致程序成功运行。有谁知道为什么 mmap 会出现段错误?如果使用 mmap() 无法实现我的目标,是否有人对我如何测试我的代码是否存在缓冲区溢出有任何其他建议?

【问题讨论】:

  • 您的代码非常适合我。唯一的问题是您没有为 getpagesize() 包含 unistd.h。编译器会猜测类型。这可能会导致 64 位系统出现问题,我认为如果它猜测 32 位整数。
  • 我也测试过,我得到了段错误,但不是在 main() 中。相反,它在退出时在 glibc 的代码中崩溃......真的很奇怪,这个。我在一台非常普通的 32 位机器上进行了测试。
  • 只是好奇,您在哪个内核版本/发行版上运行它?我刚刚在 64 位上尝试过(在#include unistd.h 之后),现在在 munmap 上失败了。
  • 作为一个简短的后续,它似乎只在 addr2 = addr1 + pagesize 的情况下崩溃。我尝试添加 2 * pagesize 和 -2 * pagesize,然后崩溃就消失了。很奇怪。
  • 放松——我也注意到了;只有当页面在内存中相邻时。

标签: c unit-testing unix memory-mapped-files


【解决方案1】:

您可以调用mprotect() 更改mmap() 映射的内存上的保护标志。这可能比尝试mmap() 具有不同保护的两个相邻页面更好的解决方案,因为这似乎是导致您的问题的原因。

(Linux 允许您在 any 页面上调用 mprotect(),但 POSIX 只允许已由 mmap() 分配的页面。)

这是Electric Fence 用来捕获缓冲区溢出的技巧之一。

【讨论】:

  • 太完美了!非常感谢!
  • 请注意,它最初对mmap 2 页也有效,然后MAP_FIXED 新的PROT_NONE 页在第二页的顶部。无效的只是在现有页面旁边选择一个地址并将其映射为MAP_FIXED;您无法知道它还没有被另一个映射占用(例如 libc 的一部分)。
【解决方案2】:

这与您的问题并不完全相关(除非您的最终目标确实是让您的测试正常工作),但是,恕我直言,依赖这种故障处理可能会产生误导(最明显的是,如果您针对多个平台,测试应该失败的地方只是由于某些未指定的行为而没有触发 seg-fault)。

也许另一种方法对您来说更有意义,例如,只需分配一个比必要更大的缓冲区,在其末尾放置一个标记,然后检查它是否已被覆盖?

正如其他人所提到的,如果您愿意设置更复杂的测试环境,那么电栅栏、valgrind 或其他工具的分析可能会更精细。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2023-03-24
    • 1970-01-01
    • 1970-01-01
    • 2021-10-28
    • 1970-01-01
    • 2011-03-01
    • 1970-01-01
    相关资源
    最近更新 更多