c-mmap就在变量地址后面



有任何方法可以在字符串文字之后立即调用mmap内存吗?我想简单地实现redzoning,这样我的程序就可以在字符串文字的null终止符后面写一个字符,然后检查它是否被覆盖,以防缓冲区溢出,我的方法是在字符串null终止符地址的正上方使用mmap内存。

我担心的是,在字符串文字之后可能会有一些重要的数据,我可能会用MAP_FIXED覆盖它,但是,MAP_FIXED不起作用——invalid argument

void *addr; /* the address of the memory after the end of buf */
char *mmap_addr; /* This is where we put the redzone characters */
struct redzone zone; /* our zone, to be modified */
char buf[MAX_BUF_SIZE] = "ABCDEFGHIG";
addr = (void *)&buf[MAX_BUF_SIZE] + 1; /* exactly after NULL terminator */
mmap_addr = mmap(addr, sizeof(zone), PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED, 0, 0);
if (mmap_addr == MAP_FAILED) /* ((void *) - 1) */
FOO("mmap: ");
// ....

在没有MAP_FIXED的情况下,bufmmap_addr之间的偏移是巨大的。如果我理解正确的话,MAP_FIXEDmmap正是addr所在的位置,而不进行优化。

有没有办法在字符串文字之后立即映射内存?

除非您在汇编程序中(至少在Linux/x86-64上)编码了一些东西,以适当地对齐该字符串文字(这样它的最后一个字节之后的下一个字节是页面对齐的)。

例如在C:中尝试

const char*literal = "literal in " __FILE__;
printf("here is '%s' at %p (line %d)n", 
literal, (void*)literal, __LINE__);

由于字符串文字可能有一个随机地址(根据ASLR),并且其末尾很可能与页面不对齐(4K字节)。

我制作了一个小型C程序e.c(包含上面的三行),将其编译为gcc -O -g e.c -o e,然后运行两次:

% ./e
here is 'literal in e.c' at 0x55fb00466022 (line 5)
% ./e
here is 'literal in e.c' at 0x558237e51022 (line 5)

参见mmap(2)和proc(5)。

mmap的故障原因有很好的记录。MAP_FIXED要求第一个addr参数精确地页面对齐(例如4K字节的倍数)和有效地址。另请参阅getpagesize(2)。

仅出于调试目的,请考虑添加:

char cmd[64];
snprintf(cmd, sizeof(cmd), "/bin/cat /proc/%d/maps", (int)getpid())
fflush(NULL);
system(cmd);

以了解您的虚拟地址空间的布局。

出于某种直觉,请在终端中运行/bin/cat /proc/self/maps

有没有办法在字符串文字之后立即映射内存?

NO,没有安全可靠的方法来mmap相对于对象的区域,无论是字符串文字、普通数组还是其他任何方法。除了问题中指出的严重风险外,

  • 内存可能已经在用于其他东西(例如堆栈)

  • 映射的基地址必须适当对齐(细节取决于体系结构),并且

  • 如果映射的区域与另一个内存映射(不一定是由代码显式创建的)重叠,那么现有映射的重叠部分将被丢弃。

就。。。

MAP_FIXED不工作-invalid argument

。。。问题可能是指定的地址没有正确对齐。假设所需的对齐在页面边界上(典型),这对于尝试mmap任意选择的地址来说是非常可能的结果。

根据mmap():的Linux手册页面上的注释

MAP_FIXED的唯一安全用途是addrlength指定的地址范围以前使用另一个映射保留

正如Eric在评论中解释的那样,这是一个数组,而不是字符串文字。如果在运行时程序中存在字符串文字,那么它将位于.rodata中,而不仅仅是用作源cdoe中数组的初始值设定项。

不,您不能在运行时在编译器放置本地变量的堆栈空间上玩虚拟内存技巧。你可以,但你会破坏一切,例如,用一个新的零页替换你的返回地址(以及堆栈内存页中的其他所有内容),或者将其作为mremap(MREMAP_MAYMOVE)的一部分取消映射。


但只是为了解释您在mmap:中看到的内容

mmap需要一个页面对齐的地址,并且您没有将地址四舍五入到下一页的开头。(对于(addr + 4095) & -4096ULL,转换为uintptr_t)。它还需要4k的倍数大小。否则,它将返回-EINVAL,如Linuxmmap(2)手册页中所述。(在本地使用man 2 mmap来查看它,而不是更通用的POSIX手册页)。

EINVAL我们不喜欢addr、长度或偏移量(例如,它们太大,或者没有在页面边界上对齐)。

当然,由于这是堆栈内存中的本地var,在典型情况下,它上面会有另一页堆栈。(例如,在主线程中,在具有少量env-var和/或命令行args的进程中。)

mmapaddr参数只是一个提示。如果该页面已经映射(在本例中,作为堆栈的一部分映射到匿名内存),它将选择一个完全不同的地址。除了MAP_FIXED_NOREPLACE,所以它用EEXISTMAP_FIXED退出,所以它映射了一个新的页面,替换了以前的页面!

使用mremapmprotect更改现有页面的内容,如果您的程序没有在该页面中保留任何其他内容。。。这里不会是这样。特定于Linux的mremap是对大型阵列的一个有趣的系统调用:您可以在不复制的情况下扩展它们,最坏的情况是以一些TLB无效为代价。但只在一个最初分配了mmap的数组上执行,这样您就可以将其指针作为char*int*或其他什么,而不是实际的alignas(4096) static char arr[32768]或其他什么。在它之后不会有一个免费页面(除非它是BSS中的最后一个东西),而且你不能用mremap(mremap_MAYMOVE)将它移动到其他地方。所有对它的引用仍然会将原始地址烘焙到他们的机器代码中,所以他们会进行segfault。

最新更新