unsafe/legacy 关于 brk/sbrk 是什么?
What's unsafe/legacy about brk/sbrk?
我在很多地方(musl 邮件列表、macOS 论坛等)听说 brk()
和 sbrk()
不安全。这些地方很多要么根本不解释,要么解释的很模糊。例如,this link 声明 "these functions are fundamentally broken",然后继续说 malloc
和 sbrk
子系统完全损坏,它们破坏了堆,等
我的问题是:为什么会这样?如果 malloc
的使用方式是分配一块 sbrk
足够大的内存来平息或大幅减少进一步分配的需要,那么 sbrk
和 brk
使用起来绝对安全吗?
这是我对 sbrk
和 brk
的实现:
sbrk
:
#include <unistd.h>
#include <stddef.h>
void *sbrk(intptr_t inc)
{
intptr_t curbrk = syscall(SYS_brk, NULL);
if( inc == 0 ) goto ret;
if( curbrk < 0 ) return (void *)-1;
curbrk((void *)(curbrk+inc));
ret:
return (void *)curbrk;
}
brk
:
#include <unistd.h>
intptr_t brk(void *ptr)
{
if( (void *)syscall(SYS_brk, ptr) != ptr )
return -1;
else
return 0;
}
现实在很大程度上取决于实施,但这里有一些要素:
不安全
brk
/sbrk
的发明是为了允许进程从系统请求更多内存,并在单个连续段中释放它。因此,它们被许多 malloc
和 free
实现使用。问题是,当它返回一个唯一的段时,当多个模块(同一进程的)直接使用它时,事情会出错。由于竞争条件,它在多线程进程中变得更糟。假设有 2 个线程要添加新内存。他们将使用 sbrk(0)
查看当前的顶部地址,看到相同的地址,使用 brk
或 sbrk
请求新内存,并且由于竞争条件,它们将使用相同的内存.
即使在单线程进程中,某些 malloc
和 free
实现也假设它们只被允许使用低级 s/brk
接口,并且任何其他代码都应该使用他们。在那种情况下,如果他们内部维护的中断段的图像不再是假定值,事情就会出错。他们应该猜测该段的某些部分 "reserved" 用于其他用途,可能会破坏释放任何内存的能力。
因此,用户代码不应直接使用 brk
/sbrk
,而应仅依赖 malloc
/free
。如果且仅当您正在编写标准库的实现,包括 malloc
/realloc
/calloc
/free
,您可以安全地使用 brk
/ sbrk
传统
在现代系统上,mmap
可以更好地使用虚拟内存管理。您可以根据需要使用任意数量的动态内存段,它们之间没有交互。因此,在现代系统上,除非您有使用 brk
/sbrk
的特定内存分配需求,否则您应该使用 mmap
.
便携性
brk
和 sbrk
的 FreeBSD 参考说明如下:
The brk() and sbrk() functions are legacy interfaces from before the
advent of modern virtual memory management.
及以后:
BUGS:
Mixing brk() or sbrk() with malloc(3), free(3), or similar functions will
result in non-portable program behavior.
我在很多地方(musl 邮件列表、macOS 论坛等)听说 brk()
和 sbrk()
不安全。这些地方很多要么根本不解释,要么解释的很模糊。例如,this link 声明 "these functions are fundamentally broken",然后继续说 malloc
和 sbrk
子系统完全损坏,它们破坏了堆,等
我的问题是:为什么会这样?如果 malloc
的使用方式是分配一块 sbrk
足够大的内存来平息或大幅减少进一步分配的需要,那么 sbrk
和 brk
使用起来绝对安全吗?
这是我对 sbrk
和 brk
的实现:
sbrk
:
#include <unistd.h>
#include <stddef.h>
void *sbrk(intptr_t inc)
{
intptr_t curbrk = syscall(SYS_brk, NULL);
if( inc == 0 ) goto ret;
if( curbrk < 0 ) return (void *)-1;
curbrk((void *)(curbrk+inc));
ret:
return (void *)curbrk;
}
brk
:
#include <unistd.h>
intptr_t brk(void *ptr)
{
if( (void *)syscall(SYS_brk, ptr) != ptr )
return -1;
else
return 0;
}
现实在很大程度上取决于实施,但这里有一些要素:
不安全
brk
/sbrk
的发明是为了允许进程从系统请求更多内存,并在单个连续段中释放它。因此,它们被许多 malloc
和 free
实现使用。问题是,当它返回一个唯一的段时,当多个模块(同一进程的)直接使用它时,事情会出错。由于竞争条件,它在多线程进程中变得更糟。假设有 2 个线程要添加新内存。他们将使用 sbrk(0)
查看当前的顶部地址,看到相同的地址,使用 brk
或 sbrk
请求新内存,并且由于竞争条件,它们将使用相同的内存.
即使在单线程进程中,某些 malloc
和 free
实现也假设它们只被允许使用低级 s/brk
接口,并且任何其他代码都应该使用他们。在那种情况下,如果他们内部维护的中断段的图像不再是假定值,事情就会出错。他们应该猜测该段的某些部分 "reserved" 用于其他用途,可能会破坏释放任何内存的能力。
因此,用户代码不应直接使用 brk
/sbrk
,而应仅依赖 malloc
/free
。如果且仅当您正在编写标准库的实现,包括 malloc
/realloc
/calloc
/free
,您可以安全地使用 brk
/ sbrk
传统
在现代系统上,mmap
可以更好地使用虚拟内存管理。您可以根据需要使用任意数量的动态内存段,它们之间没有交互。因此,在现代系统上,除非您有使用 brk
/sbrk
的特定内存分配需求,否则您应该使用 mmap
.
便携性
brk
和 sbrk
的 FreeBSD 参考说明如下:
The brk() and sbrk() functions are legacy interfaces from before the advent of modern virtual memory management.
及以后:
BUGS: Mixing brk() or sbrk() with malloc(3), free(3), or similar functions will result in non-portable program behavior.