尝试使用 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 的调用的规定=] 和 sbrk
与 malloc
保持一致。 malloc
的工作对 sbrk
并非不敏感,它们并非来自“不同级别”的编码,它们应该和谐地协同工作,至少在代码注释中是这样。此外,我的第一个测试用例运行良好,这意味着 sbrk
本身不是与 malloc
一起使用的问题,但仅在某些特定情况下未被处理。
最后,重要的是有人可以破解 glibc 分配,这可能是一个安全漏洞。例如,黑客可以使用 glibc 的某些实例被另一层间接访问来调用 sbrk
以导致库崩溃。我不是安全专家,但鉴于 glibc 在地球上有如此多的不同用途,原则上可能是一些恶意程序员使用此 sbrk
崩溃来访问未受保护的系统。不确定,但确定应该由 glibc 开发人员进行调查。
我相信这不是一个无聊的问题。
glibc malloc
在内部使用 sbrk
是有据可查的。如果没有另外说明,它也可以使用通过 sbrk
获得的内存用于内部簿记目的。既没有记录也无法猜测此内部簿记数据的确切存储位置。因此,拿走 any 由 malloc
获得的内存(通过 sbrk
或其他方式)可以使此数据无效。
由此可见,带负参数的 sbrk
决不能用在同时使用 malloc
的程序中(当然还有任何可能使用 malloc
的库函数,例如printf
)。 glibc 文档中可能应该包含这方面的声明,以使上述推理变得不必要。有一个声明警告一般不要使用 brk
和 sbrk
:
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
堆大小。
在使用 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 手册没有。
malloc.c
确实包含对 sbrk
的评论可能被调用有符号整数 - 因此负整数 - 内存参数减少的值,并且确实有关于 [=36 的调用的规定=] 和 sbrk
与 malloc
保持一致。 malloc
的工作对 sbrk
并非不敏感,它们并非来自“不同级别”的编码,它们应该和谐地协同工作,至少在代码注释中是这样。此外,我的第一个测试用例运行良好,这意味着 sbrk
本身不是与 malloc
一起使用的问题,但仅在某些特定情况下未被处理。
最后,重要的是有人可以破解 glibc 分配,这可能是一个安全漏洞。例如,黑客可以使用 glibc 的某些实例被另一层间接访问来调用 sbrk
以导致库崩溃。我不是安全专家,但鉴于 glibc 在地球上有如此多的不同用途,原则上可能是一些恶意程序员使用此 sbrk
崩溃来访问未受保护的系统。不确定,但确定应该由 glibc 开发人员进行调查。
我相信这不是一个无聊的问题。
glibc malloc
在内部使用 sbrk
是有据可查的。如果没有另外说明,它也可以使用通过 sbrk
获得的内存用于内部簿记目的。既没有记录也无法猜测此内部簿记数据的确切存储位置。因此,拿走 any 由 malloc
获得的内存(通过 sbrk
或其他方式)可以使此数据无效。
由此可见,带负参数的 sbrk
决不能用在同时使用 malloc
的程序中(当然还有任何可能使用 malloc
的库函数,例如printf
)。 glibc 文档中可能应该包含这方面的声明,以使上述推理变得不必要。有一个声明警告一般不要使用 brk
和 sbrk
:
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
堆大小。