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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 10:21:02