在使用 glibc 时,我尝试使用sbrk使用负参数来减少数据段,并发现了一个最奇怪的行为。
我先是malloc
,然后free
是它,然后用 减少数据段sbrk
,然后malloc
再次使用与第一个相同的大小。
问题是,如果malloc
大小(两个malloc
s 大小相同)足够小(32k 或 8 个 4k 页),那么一切正常。但是当我增加一点malloc
- free
-malloc
大小(到九个4k 页)然后我得到核心转储。更奇怪的是,当我提高malloc
大小以mmap
超过阈值(128k)时,我得到了调整中止行为。
C代码:
#define _GNU_SOURCE 1
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
// set MMAP_ALLOC_SIZE to 8 4k-pages it will work,
// set it to 9 4k-pages it raises a 'segmentation fault (core dumped)'
// set it to 33 4k-pages it raises a 'break adjusted to free malloc space' and 'abort (core dumped)'
#define MMAP_ALLOC_SIZE (33 * 4096)
#define PRINT_MEM { \
struct mallinfo mi; \
mi = mallinfo(); \
printf("ptr %p\n", ptr); \
printf("brk(0) %p\n", sbrk(0)); \
printf("heap %d bytes\n", mi.arena); \
printf("mmap %d bytes\n\n", mi.hblkhd); \
}
int main(int argc, char *argv[])
{
void *ptr;
ptr = NULL; PRINT_MEM
printf("1) will malloc > MMAP_THRESHOLD (128 KiB) ...\n");
ptr = malloc(MMAP_ALLOC_SIZE); PRINT_MEM
printf("2) will free malloc ...\n");
free(ptr); PRINT_MEM
printf("3) will reduce brk ...\n");
ptr = sbrk(-100000); PRINT_MEM
printf("4) will malloc > MMAP_THRESHOLD (128 KiB) ... \n");
ptr = malloc(MMAP_ALLOC_SIZE); PRINT_MEM
printf("5) completion.\n"); // never happens if MMAP_ALLOC_SIZE is > 8 4k-pages
return 0;
}
编译:
gcc -Wall testbrk.c -o testbrk
这给出了成功的输出MMAP_ALLOC_SIZE (8 * 4096)
:
ptr (nil)
brk(0) 0xf46000
heap 0 bytes
mmap 0 bytes
1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr 0xf25670
brk(0) 0xf46000
heap 135168 bytes
mmap 0 bytes
2) will free malloc ...
ptr 0xf25670
brk(0) 0xf46000
heap 135168 bytes
mmap 0 bytes
3) will reduce brk ...
ptr 0xf46000
brk(0) 0xf2d960
heap 135168 bytes
mmap 0 bytes
4) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr 0xf25670
brk(0) 0xf2d960
heap 135168 bytes
mmap 0 bytes
5) completion.
的以下中止输出MMAP_ALLOC_SIZE (9 * 4096)
:
ptr (nil)
brk(0) 0x1b7f000
heap 0 bytes
mmap 0 bytes
1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr 0x1b5e670
brk(0) 0x1b7f000
heap 135168 bytes
mmap 0 bytes
2) will free malloc ...
ptr 0x1b5e670
brk(0) 0x1b7f000
heap 135168 bytes
mmap 0 bytes
3) will reduce brk ...
ptr 0x1b7f000
brk(0) 0x1b66960
heap 135168 bytes
mmap 0 bytes
4) will malloc > MMAP_THRESHOLD (128 KiB) ...
Segmentation fault (core dumped)
以及以下的调整中止输出MMAP_ALLOC_SIZE (33 * 4096)
:
ptr (nil)
brk(0) 0x1093000
heap 0 bytes
mmap 0 bytes
1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr 0x7fdd1c7f6010
brk(0) 0x1093000
heap 135168 bytes
mmap 139264 bytes
2) will free malloc ...
ptr 0x7fdd1c7f6010
brk(0) 0x1093000
heap 135168 bytes
mmap 0 bytes
3) will reduce brk ...
ptr 0x1093000
brk(0) 0x107a960
heap 135168 bytes
mmap 0 bytes
4) will malloc > MMAP_THRESHOLD (128 KiB) ...
break adjusted to free malloc space
Aborted (core dumped)
因此,sbrk
减少调用可以正常工作,但malloc
即使仍有足够的内存可用,后续调用也会引发核心转储。
我做错了什么或者这是数据段调整大小的限制?
编辑:除了使用malloc_trim()
我在下面发布的代码解决方案外,非常欢迎接受的答案,还有一些关于这个问题的重要信息,从聊天中恢复:
首先,man
页面说避免使用sbrk
,但 glibc 手册没有。
malloc.c
来自 glibc 确实包含关于sbrk
可能使用有符号整数调用的评论 - 因此是负整数 - 内存参数减少值,并且确实对调用brk
和sbrk
与malloc
. 的工作malloc
并非对 不敏感sbrk
,它们不是来自“不同级别”的编码,它们应该和谐地协同工作,至少在代码注释方面是这样。另外,我的第一个测试用例运行良好,这意味着它sbrk
本身不是一个可以使用的问题malloc
,但只是在某些特定情况下不处理。
最后,重要的是有人可以破坏 glibc 分配,这可能是一个安全漏洞。例如,黑客可以使用被另一层间接访问的 glibc 的某个实例来调用sbrk
以导致库崩溃。我不是安全专家,但鉴于 glibc 在全球范围内有如此多的不同用途,原则上某些恶意程序员可能会利用此sbrk
崩溃来访问未受保护的系统。不确定,但肯定应该由 glibc 开发人员进行调查。
我敢肯定这不是一个无聊的问题。