我们如何知道用exec()启动的程序所需的最小堆栈大小?
为了避免针对某个程序的堆栈冲突攻击,我们尝试使用setrlimit(RLIMIT_STACK)
将堆栈大小setrlimit(RLIMIT_STACK)
为大约2 MB。
这个限制对我们程序自身的内部需求来说很好,但是我们注意到exec()
外部程序的尝试在一些具有这个新限制的系统上开始失败。 我们使用下面的测试程序研究的一个系统似乎对exec()
的程序的最小堆栈大小超过4 MiB。
我的问题是,我们如何知道给定系统上堆栈大小的安全最小值,以便exec()
不会失败?
我们不想仅仅提出这个问题,直到事情停止在我们目前测试的所有系统上失败为止,因为随着程序被移植到具有更高的最低要求的更新系统类型,这很可能会导致未来失败。
下面的C测试程序是根据system()
编写的,但是较低级别的症状是execl()
系统调用中的失败。 根据您测试的主机操作系统,当您给被调用的程序启动太少的堆栈空间时,您在被调用的程序中得到errno == E2BIG
或segfault。
搭配:
$ CFLAGS="-std=c99 -D_POSIX_C_SOURCE=200809" make stacklim
这个问题与“在exec中检查E2BIG错误条件”切线相关,但我们的实际问题是不同的:我们对设置此限制导致的潜在可移植性问题感兴趣。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <unistd.h>
static enum try_code {
raise_minimum,
lower_maximum,
failed_to_launch
}
try_stack_limit(rlim_t try)
{
// Update the stack limit to the given value
struct rlimit x;
getrlimit(RLIMIT_STACK, &x);
static int first_time = 1;
if (first_time) {
first_time = 0;
printf("The default stack limit here is %llu bytes.n", x.rlim_cur);
}
x.rlim_cur = try;
setrlimit(RLIMIT_STACK, &x);
// Test the limit with a do-nothing shell launch
int status = system("exit");
switch (status) {
case 0: return lower_maximum;
case -1:
perror("Failed to start shell");
return failed_to_launch;
default:
if (WIFEXITED(status) && WEXITSTATUS(status) == 127) {
// system() couldn't run /bin/sh, so assume it was
// because we set the stack limit too low.
return raise_minimum;
}
else if (WIFSIGNALED(status)) {
fprintf(stderr, "system() failed with signal %s.n",
strsignal(WTERMSIG(status)));
return failed_to_launch;
}
else {
fprintf(stderr, "system() failed: %d.n", status);
return failed_to_launch;
}
}
}
int main(void)
{
extern char **environ;
size_t etot = 0;
for (size_t i = 0; environ[i]; ++i) {
etot += strlen(environ[i]) + 1;
}
printf("Environment size = %lun", etot + sizeof(char*));
size_t tries = 0;
rlim_t try = 1 * 1000 * 1000, min = 0, max = 0;
while (1) {
enum try_code code = try_stack_limit(try);
switch (code) {
case lower_maximum:
// Call succeded, so lower max and try a lower limit.
++tries;
max = try;
printf("Lowered max to %llu bytes.n", max);
try = min + ((max - min) / 2);
break;
case failed_to_launch:
if (tries == 0) {
// Our first try failed, so there may be a bug in
// the system() call. Stop immediately.
return 2;
}
// Else, consider it a failure of the new limit, and
// assume we need to limit it.
case raise_minimum:
// Call failed, so raise minimum and try a higher limit.
++tries;
min = try > min ? try : min;
rlim_t next = max ?
min + ((max - min) / 2) :
try * 2;
if (next == try) {
printf("Min stack size here for exec is %llu.n", max);
return 0;
}
else {
printf("Raising limit from %llu to %llu.n", try, next);
try = next;
}
break;
default:
return 1;
}
}
}
调用exec()时,调用进程的内存映射失效。 它被改为适应新的可执行文件。 堆栈内存,堆内存,全局数据和代码内存分配给新的可执行文件。 映射通常在链接时定义,并且在调用main()之前由语言库分配内存。
ref:http://man7.org/linux/man-pages/man2/execve.2.html
您的程序已成功启动,因此您的程序会依次隐式给出用于启动其他程序的正确堆栈大小:在程序启动期间,在设置新的下限之前获取当前限制:
struct rlimit g_default_stack_limit; /* put in global scope */
getrlimit(RLIMIT_STACK, &g_default_stack_limit);
struct rlimit our_stack_limit;
memcpy(&our_stack_limit, &g_default_stack_limit, sizeof(our_stack_limit));
our_stack_limit.rlim_cur = 2000000; /* some lower value */
setrlimit(RLIMIT_STACK, &our_stack_limit);
然后在启动外部程序之前恢复该初始值,并在子fork()
创建或程序的同步调用(例如,通过system()
)退出后重新应用新的限制:
struct rlimit our_stack_limit;
getrlimit(RLIMIT_STACK, &our_stack_limit);
setrlimit(RLIMIT_STACK, &g_default_stack_limit);
if (system(...) == 0) {
....
}
setrlimit(RLIMIT_STACK, &our_stack_limit);
该初始值可能是操作系统的默认值,或者可能是程序设置的调用程序的限制。 无论哪种方式,它几乎肯定是传递给您的程序调用依次编程的正确起始值。
链接地址: http://www.djcxy.com/p/80291.html上一篇: How can we know the minimum stack size needed by a program launched with exec()?