You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

vfork后execve出现异常行为,是否属于GCC编译器Bug?

确认GCC编译器Bug:vfork()相关栈内存异常问题

问题概述

当前代码出现的内存异常疑似GCC编译器Bug,已尽可能排除周边代码的未定义行为,但无法简化为完全独立的复现案例——推测栈上存在大量变量是复现的必要条件。

该Bug的触发仅依赖函数名为vfork(),和vfork()的实际系统调用行为无关:即使修改vfork()汇编存根中的系统调用号,改用fork()系统调用,问题依然存在。

故障与正常代码对比

故障代码

pid_t pid;
memcpy(buf, "60000", 6);
volatile int execve_error = 0;
int status;
if ((pid = vfork()) == 0) {
    /* Compiler bug? moving this inside vfork child causes memory corruption */
    const char *argv[13] = { "/bin/false", "-md", namebuf,
    "-k", skelbufbase, "-s", shell,
        "-u", buf, "-g", create_group, username, NULL };
    execve_error = execve(argv[0], argv, NULL);
    _exit(0);
} else if (pid < 0) {
    writeblock(2, "can't fork\n", 11);
    r = pid;
    goto cleanup4;
}
wait4(pid, &status, 0, NULL);
if (execve_error) { r = execve_error; goto cleanup4; } /* execve failed */

正常运行代码(子进程内添加writeblock调用)

pid_t pid;
memcpy(buf, "60000", 6);
volatile int execve_error = 0;
int status;
if ((pid = vfork()) == 0) {
    /* Compiler bug? moving this inside vfork child causes memory corruption */
    const char *argv[13] = { "/bin/false", "-md", namebuf,
        "-k", skelbufbase, "-s", shell,
        "-u", buf, "-g", create_group, username, NULL };
    writeblock(2, "exec()\n", 7);
    execve_error = execve(argv[0], argv, NULL);
    _exit(0);
} else if (pid < 0) {
    writeblock(2, "can't fork\n", 11);
    r = pid;
    goto cleanup4;
}
wait4(pid, &status, 0, NULL);
if (execve_error) { r = execve_error; goto cleanup4; } /* execve failed */

正常运行代码(argv数组移到vfork调用外)

pid_t pid;
memcpy(buf, "60000", 6);
volatile int execve_error = 0;
int status;
/* Compiler bug? moving this inside vfork child causes memory corruption */
const char *argv[13] = { "/bin/false", "-md", namebuf,
    "-k", skelbufbase, "-s", shell,
    "-u", buf, "-g", create_group, username, NULL };
if ((pid = vfork()) == 0) {
    execve_error = execve(argv[0], argv, NULL);
    _exit(0);
} else if (pid < 0) {
    writeblock(2, "can't fork\n", 11);
    r = pid;
    goto cleanup4;
}
wait4(pid, &status, 0, NULL);
if (execve_error) { r = execve_error; goto cleanup4; } /* execve failed */

关键现象分析

  • 让代码正常运行的操作违背常规逻辑:writeblock()仅在部分写入时循环调用write(),但在子进程内添加该调用即可修复问题;移除stderr上的writeblock()调用,代码会再次故障
  • 故障发生在execve()执行前,并非仅在vfork()第二次返回时出现
  • strace调试显示:argv数组中的argv[0]被破坏(指向未映射内存),但传递给execve()的第一个参数是正确的
  • 将argv数组的创建移至vfork()调用外也可修复问题,且vfork()或setjmp()环境下声明固定大小数组并非被禁止的操作

近似最小复现案例(Almost-MVCE)

static int __attribute__((noinline)) mkuser(char *buf, char *namebuf, const char *username,
        const char *runas_group, const char *create_group,
        const char *create_home, const char *shell)
{
    int r;
    unsigned long uidrange[4096];
    memzero(uidrange, sizeof(uidrange));
    int h = open("uid.db", O_CREAT|O_RDWR,0600);
    if (h < 0) return -h;
    r = flock(h, LOCK_EX);
    if (r < 0) return -r;
    for(;;) {
        r = readblock(h, buf, 4096);
        if (r == -ENOSPC) break; /* Sneaky; depends on absolute block size being >= 4096 */
        if (r) return -r;
        /* Depends on uid.db being the 0 length file */
    }
    size_t basenamelen = strlen(create_home);
    memcpy(namebuf, create_home, basenamelen);
    namebuf[basenamelen] = '/';
    size_t usernamelen = strlen(username);
    memcpy(namebuf + basenamelen + 1, username, usernamelen + 1);
    r = fake_mkdir(namebuf, 0755);
    if (r && r != -EEXIST) return -r;
    memcpy(namebuf + basenamelen + usernamelen + 1, "/home", 6);
    r = fake_mkdir(namebuf, 0755);
    if (r && r != -EEXIST) goto cleanup1;
    memcpy(namebuf + basenamelen + usernamelen + 1, "/etc", 5);
    r = fake_mkdir(namebuf, 0755);
    if (r && r != -EEXIST) goto cleanup2;
    memcpy(namebuf + basenamelen + usernamelen + 5, "/skel", 6);
    char *skelbufbase;
    skelbufbase = namebuf + 1024 + 42 + 42 + 64;
    memcpy(skelbufbase, namebuf, basenamelen + usernamelen + 12);
    r = fake_mkdir(namebuf, 0755);
    if (r && r != -EEXIST) goto cleanup3;
    memcpy(namebuf + basenamelen + usernamelen + 1, "/home/", 6);
    memcpy(namebuf + basenamelen + usernamelen + 7, username, usernamelen + 1);
    for (unsigned ui = 0; ui < sizeof(uidrange)/sizeof(uidrange[0]); ui++)
        for (unsigned uj = 0; uj < 8 * sizeof(uidrange[0]); uj++)
        {
            if (uidrange[ui] & ((unsigned long)1 << uj)) continue;
            pid_t pid;
            memcpy(buf, "60000", 6);
            volatile int execve_error = 0;
            int status;
            if ((pid = vfork()) == 0) {
                /* Compiler bug? moving this inside vfork child causes memory corruption */
                const char *argv[13] = { "/bin/false", "-md", namebuf,
                    "-k", skelbufbase, "-s", shell,
                    "-u", buf, "-g", create_group, username, NULL };
                execve_error = execve(argv[0], argv, NULL);
                _exit(0);
            } else if (pid < 0) {
                writeblock(2, "can't fork\n", 11);
                r = pid;
                goto cleanup4;
            }
            wait4(pid, &status, 0, NULL);
            if (execve_error) { r = execve_error; goto cleanup4; } /* execve failed */
            switch ((status >> 8) & 255) {
                case 0: /* Success: record user in internal database */
                    writeblock(2, "false should fail\n", sizeof("false should fail"));
                    r = -EIO;
                    goto cleanup4;
                case 1: case 10: r = -EIO; goto cleanup4; /* can't update files */
                case 2: case 3: r = -EINVAL; goto cleanup4; /* bad argument */
                case 4: break; /* Try next number */
                case 6: return r = -EINVAL; goto cleanup4; /* group not found */
                case 9: return r = -EEXIST; goto cleanup4; /* username in use */
                case 12: return r = -EIO; goto cleanup4; /* can't create home dir */
                case 14: return r = -EIO; goto cleanup4; /* can't update selinux */
                default: return r = -ERANGE; goto cleanup4; /* should not happen */
            }
        }
    r = -ENOSPC;
cleanup4:
    memcpy(namebuf + basenamelen + usernamelen + 1, "/etc/skel", 10);
    fake_rmdir(namebuf);
cleanup3:
    memcpy(namebuf + basenamelen + usernamelen + 1, "/etc", 5);
    fake_rmdir(namebuf);
cleanup2:
    memcpy(namebuf + basenamelen + usernamelen + 1, "/home", 6);
    fake_rmdir(namebuf);
cleanup1:
    namebuf[basenamelen + usernamelen + 1] = 0;
    fake_rmdir(namebuf);
    return -r;
}

该案例经过多轮删减验证,栈内存的大小是复现问题的关键。

依赖实现

fake_mkdir与fake_rmdir实现

需通过.o文件提供,否则无法复现问题:

int fake_mkdir(const char *path, int mode) { return 0; }
int fake_rmdir(const char *path) { return 0; }

memzero实现

void memzero(void *buf, size_t n) { memset(buf, 0, n); }

execve返回值workaround

代码原本期望execve返回错误码而非更新errno,可改用以下方式:

execve(argv[0], argv, NULL);
execve_error = errno;

编译选项

gcc -Wl,--no-dynamic-linker -pie -nostdlib -nostartfiles -O3 -s -fpic -fno-asynchronous-unwind-tables -ffreestanding

内容的提问来源于stack exchange,提问作者Joshua

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 14:39:51