mmap导致堆栈损坏,涉及内核?

时间:2014-06-28 20:48:02

标签: c linux glibc mmap

我们正在使用此代码获取段错误:

#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>

#define CHUNKSIZE 4096

int main(int argc, char **argv) {
    printf("Hallo!\n"); // does not segfault without this line

    void* first_chunk = mmap(NULL, CHUNKSIZE, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
    void* next_chunk_addr = (void*) ((char*)first_chunk + CHUNKSIZE);
    mmap(next_chunk_addr, CHUNKSIZE, PROT_NONE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, 0, 0);

    printf("Bumm!\n"); // segfaults
}

即使第二个mmap-call的地址无效,我相信我应该得到一个MAP_FAILED而不是一个损坏的堆栈。

GDB给了我这个:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7a94104 in _IO_file_xsputn () from /lib/x86_64-linux-gnu/libc.so.6
(gdb) bt
#0  0x00007ffff7a94104 in _IO_file_xsputn () from /lib/x86_64-linux-gnu/libc.so.6
#1  0x00007ffff7a8ad79 in puts () from /lib/x86_64-linux-gnu/libc.so.6
#2  0x0000000000400591 in main (argc=1, argv=0x7fffffffdf28) at test.cpp:14
(gdb) x/i $rip
=> 0x7ffff7a94104 <_IO_file_xsputn+324>:    mov    %dl,(%r8,%rax,1)

为什么他试图从0x7ffff7ff8000读取,这与他应该打印的内容无关?

在另一台机器上,我们使用相关代码获得了此堆栈跟踪:

Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7b0985a in mmap64 () at ../sysdeps/unix/syscall-template.S:81
81  ../sysdeps/unix/syscall-template.S: No such file or directory.

这可能与内核方面有关吗?

这发生在使用gcc和clang的三个不同Linux系统上。在OS X下没有任何事情发生。

1 个答案:

答案 0 :(得分:4)

内核的行为完全符合预期。请注意MAP_FIXED上的mmap(2) documentation中的这句话:

  

如果addrlen指定的内存区域与任何现有映射的页面重叠,则现有映射的重叠部分将被丢弃。

如果你执行了strace(1)程序,你会发现确实发生了这种情况:

...
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0710755000
write(1, "Hallo!\n", 7)                 = 7
mmap(NULL, 4096, PROT_NONE, MAP_SHARED|MAP_ANONYMOUS, 0, 0) = 0x7f0710754000
mmap(0x7f0710755000, 4096, PROT_NONE, MAP_SHARED|MAP_FIXED|MAP_ANONYMOUS, 0, 0) = 0x7f0710755000
--- SIGSEGV (Segmentation fault) @ 0 (0) ---
+++ killed by SIGSEGV (core dumped) +++

printf()的第一次调用(正如您所看到的那样被编译器优化为对puts()的调用)会为malloc()分配一些内存(因为stdout被缓冲了),调用mmap()。然后,程序调用{​​{1}}并在mmap(NULL)分配的内存之前立即获取页面。第二次调用printf在已分配的页面之上分配一个新页面,将其清零并破坏mmap()的内部数据结构。随后对malloc(实际上printf())的调用在它尝试附加到它认为已分配的内存缓冲区时访问这些已损坏的数据结构时会崩溃。