为了避免对程序的stack clash攻击,we试图将堆栈大小的限制设置为setrlimit(RLIMIT_STACK)大约2 MB。
这个限制对于我们程序自身的内部需求来说是很好的,但是我们随后注意到,在某些具有这个新限制的系统上,试图exec()外部程序的尝试开始失败。我们使用下面的测试程序研究的一个系统似乎具有超过4mib的exec()'d程序的最小堆栈大小。
我的问题是,我们如何知道给定系统上堆栈大小的安全最小值,以便exec()不会失败?
我们不想在我们当前测试的所有系统都停止失败之前就提出这个问题,因为这可能会导致将来的失败,因为程序被移植到具有更高最低要求的新系统类型。
下面的C测试程序是根据system()编写的,但较低级别的症状是execl()系统调用失败。根据您测试的主机操作系统,当您给被叫程序太小的堆栈空间而无法启动时,您可能会在被叫程序中得到errno == E2BIG或segfault。
构建方式:

$ CFLAGS="-std=c99 -D_POSIX_C_SOURCE=200809" make stacklim

这个问题与“To check the E2BIG error condition in exec”有着千丝万缕的联系,但我们的实际问题不同:我们对设置这个限制所导致的潜在可移植性问题感兴趣。
#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 = %lu\n", 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()之前由语言库分配内存
参考:http://man7.org/linux/man-pages/man2/execve.2.html

关于c - 我们如何知道用exec()启动的程序所需的最小堆栈大小?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/44727636/

10-11 20:56
查看更多