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
相关产品推荐
相关产品推荐

