尝试使用 sbrk 减小数据段大小时在 glibc 中中止

Abort in glibc while trying to use sbrk to reduce the size of the data segment

在使用 glibc 时,我尝试使用负参数 sbrk 来减少数据段,但发现了一个最奇怪的行为。

我先malloc,然后free它,然后用sbrk减少数据段,然后再次malloc,大小与第一个相同。

问题是,如果 malloc 大小(malloc 大小相同)足够小(32k,或八个 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()的代码解决方案外,非常欢迎接受的答案,关于这个问题还有一些重要的事情要知道, 从 chat:

中恢复

首先,man 页面说避免使用 sbrk,但 glibc 手册没有。

来自 glibc 的

malloc.c 确实包含对 sbrk 的评论可能被调用有符号整数 - 因此负整数 - 内存参数减少的值,并且确实有关于 [=36 的调用的规定=] 和 sbrkmalloc 保持一致。 malloc 的工作对 sbrk 并非不敏感,它们并非来自“不同级别”的编码,它们应该和谐地协同工作,至少在代码注释中是这样。此外,我的第一个测试用例运行良好,这意味着 sbrk 本身不是与 malloc 一起使用的问题,但仅在某些特定情况下未被处理。

最后,重要的是有人可以破解 glibc 分配,这可能是一个安全漏洞。例如,黑客可以使用 glibc 的某些实例被另一层间接访问来调用 sbrk 以导致库崩溃。我不是安全专家,但鉴于 glibc 在地球上有如此多的不同用途,原则上可能是一些恶意程序员使用此 sbrk 崩溃来访问未受保护的系统。不确定,但确定应该由 glibc 开发人员进行调查。

我相信这不是一个无聊的问题。

glibc malloc 在内部使用 sbrk 是有据可查的。如果没有另外说明,它也可以使用通过 sbrk 获得的内存用于内部簿记目的。既没有记录也无法猜测此内部簿记数据的确切存储位置。因此,拿走 anymalloc 获得的内存(通过 sbrk 或其他方式)可以使此数据无效。

由此可见,带负参数的 sbrk 决不能用在同时使用 malloc 的程序中(当然还有任何可能使用 malloc 的库函数,例如printf)。 glibc 文档中可能应该包含这方面的声明,以使上述推理变得不必要。有一个声明警告一般不要使用 brksbrk

You will not normally use the functions in this section, because the functions described in Memory Allocation are easier to use. Those are interfaces to a GNU C Library memory allocator that uses the functions below itself. The functions below are simple interfaces to system calls.

如果您想在 glibc malloc arena 结束时释放未使用的内存,请使用 malloc_trim()(glibc 扩展,不是标准 C 或 POSIX 函数)。

正如接受的答案所建议的那样,malloc_trim 完美地完成了这项工作。将主要更改为:

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 malloc_trim  ...\n");
    int r = malloc_trim(0);             PRINT_MEM
    printf("r is %d\n", r);
    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;
}

MMAP_ALLOC_SIZE (8 * 4096) 的成功输出:

ptr    (nil)
brk(0) 0x4b3000
heap   0 bytes
mmap   0 bytes

1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr    0x492670
brk(0) 0x4b3000
heap   135168 bytes
mmap   0 bytes

2) will free malloc ...
ptr    0x492670
brk(0) 0x4b3000
heap   135168 bytes
mmap   0 bytes

3) will reduce malloc_trim  ...
ptr    0x492670
brk(0) 0x493000
heap   4096 bytes
mmap   0 bytes

r is 1
4) will malloc > MMAP_THRESHOLD (128 KiB) ... 
ptr    0x492670
brk(0) 0x4bb000
heap   167936 bytes
mmap   0 bytes

5) completion.

MMAP_ALLOC_SIZE(9 * 4096)的成功输出如下:

ptr    (nil)
brk(0) 0xe30000
heap   0 bytes
mmap   0 bytes

1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr    0xe0f670
brk(0) 0xe30000
heap   135168 bytes
mmap   0 bytes

2) will free malloc ...
ptr    0xe0f670
brk(0) 0xe30000
heap   135168 bytes
mmap   0 bytes

3) will reduce malloc_trim  ...
ptr    0xe0f670
brk(0) 0xe10000
heap   4096 bytes
mmap   0 bytes

r is 1
4) will malloc > MMAP_THRESHOLD (128 KiB) ... 
ptr    0xe0f670
brk(0) 0xe39000
heap   172032 bytes
mmap   0 bytes

5) completion.

以及 MMAP_ALLOC_SIZE (33 * 4096) 的以下成功输出:

ptr    (nil)
brk(0) 0xa5b000
heap   0 bytes
mmap   0 bytes
    
1) will malloc > MMAP_THRESHOLD (128 KiB) ...
ptr    0x7fd54f4c3010
brk(0) 0xa5b000
heap   135168 bytes
mmap   139264 bytes

2) will free malloc ...
ptr    0x7fd54f4c3010
brk(0) 0xa5b000
heap   135168 bytes
mmap   0 bytes

3) will reduce malloc_trim  ...
ptr    0x7fd54f4c3010
brk(0) 0xa3b000
heap   4096 bytes
mmap   0 bytes

r is 1
4) will malloc > MMAP_THRESHOLD (128 KiB) ... 
ptr    0xa3a670
brk(0) 0xa7c000
heap   270336 bytes
mmap   0 bytes

5) completion.

并且,问题已解决!

另请注意,mallinfo 的堆大小现在可以使用带有零参数的 malloc_trim 正确更新(据记载,在竞技场顶部留下 4k 页面),同时使用 sbrk 带有负参数确实会向下移动 brk(0),但不会影响 mallinfo 堆大小。