【问题标题】:Why am I getting E2BIG from exec() when I'm accounting for the arguments and the environment?当我考虑参数和环境时,为什么我会从 exec() 获得 E2BIG?
【发布时间】:2017-10-23 19:23:03
【问题描述】:

我已经阅读了几页(如this one)关于如何在 Linux 上正确解释 ARG_MAX。据我所知,每个参数/环境变量占用一个指针的大小,再加上它的长度为 nul 终止的字符串本身。填充物可能还占用了一些额外的空间。然而,即使在给自己 2K 的额外空间之后,我在尝试 exec() 长命令行时仍然遇到 E2BIG。这是什么原因造成的?

我认为它可能是auxiliary vector,但即使添加它的大小(在我的情况下为 320 字节)也无济于事。我也远低于MAX_ARG_STRINGSMAX_ARG_STRLEN(见https://unix.stackexchange.com/a/120842/56202)。

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

extern char **environ;

size_t arg_size(const char *arg) {
        // Count the string as well as the argv/environ pointer to it
        size_t size = sizeof(arg);
        if (arg) {
                size += strlen(arg) + 1;
        }
        return size;
}

int main() {
        size_t arg_max = sysconf(_SC_ARG_MAX);
        arg_max -= 2048; // POSIX recommends 2048 bytes of additional headroom
        printf("arg_max: %zu\n", arg_max);

        size_t size = 320; // For the auxiliary vector

        for (char **envp = environ; *envp; ++envp) {
                size += arg_size(*envp);
        }
        size += arg_size(NULL);

        char *argv[100001] = {"true"};
        size += arg_size(argv[0]);
        size += arg_size(NULL);

        char *arg = "0123456789ABCDEF0123456789ABCDEF"
                    "0123456789ABCDEF0123456789ABCDEF";
        size_t each_size = arg_size(arg);
        size_t i;
        for (i = 1; i < 100000 && size + each_size < arg_max; ++i) {
                argv[i] = arg;
                size += each_size;
        }
        argv[i] = NULL;

        printf("size: %zu, argc: %zu\n", size, i);
        assert(size < arg_max);

        execvp(argv[0], argv);
        perror("execvp()");
        return EXIT_FAILURE;
}
$ gcc e2big.c -o e2big && ./e2big
arg_max: 2095104
size: 2095059, argc: 28640
execvp(): Argument list too long

为了完整起见,这是开启

$ uname -a
Linux superluminal 4.13.7-1-ARCH #1 SMP PREEMPT Sat Oct 14 20:13:26 CEST 2017 x86_64 GNU/Linux

【问题讨论】:

  • 这就是说,计算指针的大小是没有意义的。
  • @Jean-FrançoisFabre 确实如此,argv 数组本身就像它指向的数据一样占用空间。
  • 我认为他的意思可能是sizeof(arg) 是指针的大小,而不是它指向的数据的大小。
  • @WeatherVane ...但我两个都算?
  • @WeatherVane 不,我看到了。 OP 正在计算指针的大小 + 字符串的大小 + 1

标签: c linux exec


【解决方案1】:

想通了!首先,除了argv[]envp[] 之外,exec() 系统调用实现还出于某种原因复制了可执行文件的完整路径,这种方式不利于 E2BIG:

retval = copy_strings_kernel(1, &bprm->filename, bprm);
if (retval < 0)
        goto out;

bprm->exec = bprm->p;
retval = copy_strings(bprm->envc, envp, bprm);
if (retval < 0)
        goto out;

retval = copy_strings(bprm->argc, argv, bprm);
if (retval < 0)
        goto out;

https://github.com/torvalds/linux/blob/v4.13/fs/exec.c#L1775

其次,参数字符串一次复制一个页面,并且仅与整页粒度的限制进行比较:

unsigned long size = bprm->vma->vm_end - bprm->vma->vm_start;
unsigned long ptr_size, limit;
...
ptr_size = (bprm->argc + bprm->envc) * sizeof(void *);
if (ptr_size > ULONG_MAX - size)
        goto fail;
size += ptr_size;

https://github.com/torvalds/linux/blob/v4.13/fs/exec.c#L222

因此,我不仅要计算参数/环境字符串的总长度,还要在添加指针大小之前将其四舍五入到页面大小的倍数。或者,为简单起见,只需确保我至少留出一整页的空间。

【讨论】:

    【解决方案2】:
        char *argv[100000] = {"true"};
        ....
        for (i = 1; i < 100000 && size + each_size < arg_max; ++i) {
                argv[i] = arg;
                size += each_size;
        }
        argv[i] = NULL;
    

    欢迎来到 UB 世界。在循环终止时,i 是 100000,你不应该触摸argv[100000]

    纯属推测:argv 数组是在堆栈上分配的,只有编译器知道该一次性元素是如何被 printf 破坏的

    【讨论】:

    • 诚然是个错误,但循环在迭代 28640 的早期就中断了,所以这不是原因!
    • 确实可以。如果该 NULL 被覆盖(因为它可能是,因为 argv[100000] 是“借用内存”),argv 将不再在您认为的位置终止。
    • 不确定你的意思...当我运行这段代码时,size + each_size &lt; arg_max 条件是falsei == 28640,所以循环提前中断并且i 永远不会等于100000,因此永远不会访问 argv[100000]。无论如何,我修复了问题中的代码,因此没有越界访问的机会。
    猜你喜欢
    • 2017-03-19
    • 2021-03-04
    • 2011-12-27
    • 2020-02-18
    • 2011-12-09
    • 2019-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多