unshare user namespace后fork映射uid调用execvp失败问题咨询
代码核心问题分析
- 野指针内存访问错误:
map_id函数中char *buf未申请内存就直接调用sprintf写入,属于未定义行为,会直接导致程序崩溃、执行无输出。 - unshare与fork的顺序逻辑错误:当前代码先让父进程执行
unshare进入新的user namespace,再fork子进程,父子进程都处于无外部权限的新命名空间中,不符合UID/GID映射的写入权限要求。正确逻辑应该是先fork,子进程独立创建新user namespace,父进程在初始命名空间中完成映射写入。 - UID来源错误:在子进程内部调用
getuid()获取待映射的UID,此时子进程处于未完成映射的新命名空间中,获取到的UID是默认的65534(nobody),并非宿主机的真实UID,映射规则完全无效。 - write返回值判断写反:
write调用成功时会返回正数字节数,现有代码的判断条件if(write(...))会在写入成功时触发报错,直接退出程序。 - 未处理setgroups限制与GID映射:Linux 3.19及以上版本中,非特权用户创建user namespace后,写入
gid_map前必须先向/proc/[pid]/setgroups写入deny,否则映射会失败;仅映射UID不映射GID也会导致id命令输出异常。 - open参数不规范:
open(file, 1)使用魔数替代宏定义,可读性差,应替换为O_WRONLY。
修复后可运行代码
#include <sched.h> #include <cstdio> #include <cstring> #include <cerrno> #include <stdlib.h> #include <unistd.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> // 父子进程同步用的管道 int sync_pipe[2]; void write_map_file(pid_t child_pid, const char *map_file, const char *content) { char file_path[100]; snprintf(file_path, sizeof(file_path), "/proc/%d/%s", child_pid, map_file); int fd = open(file_path, O_WRONLY); if (fd < 0) { perror("open map file failed"); exit(1); } ssize_t written = write(fd, content, strlen(content)); if (written < 0) { perror("write map file failed"); exit(1); } close(fd); } void child_proc(char *command, char **args) { char sync_buf; // 子进程新建user namespace if (unshare(CLONE_NEWUSER) != 0) { perror("unshare CLONE_NEWUSER failed"); exit(1); } // 通知父进程可以写映射了 write(sync_pipe[1], "x", 1); // 等待父进程写完映射 read(sync_pipe[0], &sync_buf, 1); // 执行目标命令 execvp(command, args); perror("execvp failed"); exit(1); } void parent_proc(pid_t child_pid) { char sync_buf; // 等待子进程完成unshare read(sync_pipe[0], &sync_buf, 1); int real_uid = getuid(); int real_gid = getgid(); char map_content[100]; // 先禁用setgroups write_map_file(child_pid, "setgroups", "deny"); // 写UID映射:命名空间内的0对应外部的real_uid,长度1 snprintf(map_content, sizeof(map_content), "0 %d 1", real_uid); write_map_file(child_pid, "uid_map", map_content); // 写GID映射 snprintf(map_content, sizeof(map_content), "0 %d 1", real_gid); write_map_file(child_pid, "gid_map", map_content); // 通知子进程映射完成,可以执行命令 write(sync_pipe[1], "x", 1); // 等待子进程退出 int status; waitpid(child_pid, &status, 0); } int main(int argc, char **argv) { if (argc < 2) { fprintf(stderr, "Usage: %s <command> [args...]\n", argv[0]); return 1; } // 初始化同步管道 if (pipe(sync_pipe) != 0) { perror("pipe failed"); return 1; } pid_t pid = fork(); if (pid < 0) { perror("fork failed"); return 1; } else if (pid == 0) { child_proc(argv[1], &argv[1]); } else { parent_proc(pid); } return 0; }
运行验证
编译运行命令:
g++ test.cpp && ./a.out id
正常输出会包含uid=0(root) gid=0(root),符合预期的用户映射效果。
内容的提问来源于stack exchange,提问作者rachid chami
相关产品推荐
相关产品推荐

