#define _GNU_SOURCE #include #include #include #include #include #include #include int main(int argc, char **argv) { if (argc < 1) { fprintf(stderr, "Usage: launcher [args...]\n"); return 1; } // Check if we are joining an existing namespace char *join_pid_str = getenv("WG_WRAP_JOIN_PID"); if (join_pid_str != NULL && strlen(join_pid_str) > 0) { int target_pid = atoi(join_pid_str); if (target_pid > 0) { char path[128]; int fd; // 1. Join User Namespace first snprintf(path, sizeof(path), "/proc/%d/ns/user", target_pid); fd = open(path, O_RDONLY); if (fd == -1) { perror("open target user namespace"); return 1; } if (setns(fd, CLONE_NEWUSER) == -1) { perror("setns CLONE_NEWUSER"); close(fd); return 1; } close(fd); // 2. Join Mount Namespace snprintf(path, sizeof(path), "/proc/%d/ns/mnt", target_pid); fd = open(path, O_RDONLY); if (fd == -1) { perror("open target mount namespace"); return 1; } if (setns(fd, CLONE_NEWNS) == -1) { perror("setns CLONE_NEWNS"); close(fd); return 1; } close(fd); // 3. Join Network Namespace snprintf(path, sizeof(path), "/proc/%d/ns/net", target_pid); fd = open(path, O_RDONLY); if (fd == -1) { perror("open target network namespace"); return 1; } if (setns(fd, CLONE_NEWNET) == -1) { perror("setns CLONE_NEWNET"); close(fd); return 1; } close(fd); // Execute the target command if (argv[0] == NULL) { fprintf(stderr, "No target binary provided in argv[0]\n"); return 1; } if (execv(argv[0], argv) == -1) { perror("execv failed"); return 1; } return 0; } } // 1. Capture host identities BEFORE unsharing uid_t current_uid = getuid(); gid_t current_gid = getgid(); // 2. Combined Unshare for User, Mount, and Network namespaces // We unshare Mount namespace (CLONE_NEWNS) to allow private /etc/resolv.conf setup // without contaminating the host filesystem. if (unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET) == -1) { perror("unshare(CLONE_NEWUSER | CLONE_NEWNS | CLONE_NEWNET)"); return 1; } char map[64]; // 3. Write UID map snprintf(map, sizeof(map), "0 %u 1\n", current_uid); int fd = open("/proc/self/uid_map", O_WRONLY); if (fd == -1) { perror("open uid_map"); return 1; } if (write(fd, map, strlen(map)) == -1) { perror("write uid_map"); close(fd); return 1; } close(fd); // 4. Disable setgroups fd = open("/proc/self/setgroups", O_WRONLY); if (fd != -1) { write(fd, "deny", 4); close(fd); } // 5. Write GID map snprintf(map, sizeof(map), "0 %u 1\n", current_gid); fd = open("/proc/self/gid_map", O_WRONLY); if (fd == -1) { perror("open gid_map"); return 1; } if (write(fd, map, strlen(map)) == -1) { perror("write gid_map"); close(fd); return 1; } close(fd); // 6. Execute the target command // In this architecture, the Go Bootstrap code passes the target binary // as the first element of the argv array. // Therefore, argv[0] is the path to the binary we want to execute. if (argv[0] == NULL) { fprintf(stderr, "No target binary provided in argv[0]\\n"); return 1; } // Prepare a new argv for the target command. // We want the target binary to see itself as argv[0]. // The current argv is [target_binary, arg1, arg2, ...]. // execv expects argv[0] to be the filename, and the rest as arguments. // This is already the case here, but let's be explicit. if (execv(argv[0], argv) == -1) { perror("execv failed"); return 1; } return 0; }