-1

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

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

问题是,如果malloc大小(两个mallocs 大小相同)足够小(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可能使用有符号整数调用的评论 - 因此是负整​​数 - 内存参数减少值,并且确实对调用brksbrkmalloc. 的工作malloc并非对 不敏感sbrk,它们不是来自“不同级别”的编码,它们应该和谐地协同工作,至少在代码注释方面是这样。另外,我的第一个测试用例运行良好,这意味着它sbrk本身不是一个可以使用的问题malloc,但只是在某些特定情况下不处理。

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

我敢肯定这不是一个无聊的问题。

4

2 回答 2

5

有据可查的是 glibc 在内部malloc使用sbrk。如果没有另外声明,它也可以将获得的内存sbrk用于内部簿记目的。这个内部簿记数据的确切存储位置既没有记录也没有猜测。因此,删除通过(通过或其他方式)获得的任何内存都可以使该数据无效。mallocsbrk

因此,在也使用(当然还有可能使用的任何库函数,例如)sbrk的程序中,永远不应该使用负参数。glibc 文档中可能应该包含这样的声明,以使上述推理变得不必要。有一个声明警告不要使用和一般来说:mallocmallocprintfbrksbrk

您通常不会使用本节中的函数,因为内存分配中描述的函数更易于使用。这些是 GNU C 库内存分配器的接口,它使用下面的函数。下面的函数是系统调用的简单接口。

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

于 2022-01-09T20:29:56.843 回答
0

正如接受的答案所建议的那样, 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不会影响堆大小。

于 2022-01-10T01:26:54.517 回答