diff options
Diffstat (limited to 'isolation.c')
| -rw-r--r-- | isolation.c | 93 |
1 files changed, 86 insertions, 7 deletions
diff --git a/isolation.c b/isolation.c index 024c26a..a00475f 100644 --- a/isolation.c +++ b/isolation.c @@ -311,6 +311,61 @@ static void enter_userns(const char *userns) } /** + * userns_holder() - Hold a clone()ed namespace open until killed + * @arg: Unused + * + * Return: this function never returns + */ +static int userns_holder(void *arg) +{ + sigset_t set; + + (void)arg; + /* If the parent dies with an error, so should we */ + if (prctl(PR_SET_PDEATHSIG, SIGKILL)) + die_perror("Couldn't set PR_SET_PDEATHSIG"); + + /* Wait until the parent kills us */ + sigemptyset(&set); + sigwaitinfo(&set, NULL); + + die("userns holder process wasn't killed"); +} + +/** + * create_userns() - Create a new userns to isolate ourselves + * @uid: Parent UID to map to 0 within the namespace + * @gid: Parent GID to map to 0 within the namespace + * + * Return: PID of the process holding the new userns + * + * Several things combine to make this more complicated than you'd expect. + * - We want to make the userns before we setuid() to nobody (or the userns + * would be owned by nobody) + * - We still want to setuid() _after_ we enter the userns, which means + * (parent-)nobody must be mapped within the userns + * - It's only possible to map the current user from within a userns, so we must + * create that mapping from the parent + * - Therefore we can't create the userns with unshare(2), but must create it + * by clone(2)ing a temporary holder process. + */ +static pid_t create_userns(uid_t uid, gid_t gid) +{ + char ns_fn_stack[NS_FN_STACK_SIZE] + __attribute__ ((aligned(__alignof__(max_align_t)))); + pid_t pid; + + pid = do_clone(userns_holder, ns_fn_stack, sizeof(ns_fn_stack), + CLONE_NEWUSER | SIGCHLD, NULL); + if (pid < 0) + die_perror("Unable to create user namespace"); + + make_ugid_map(pid, uid, gid); + + return pid; +} + +/** * isolate_user() - Switch to final UID/GID and move into userns * @c: Execution context * @uid: User ID to run as (in original userns) @@ -336,17 +391,41 @@ void isolate_user(const struct ctx *c, uid_t uid, gid_t gid, bool use_userns, die_perror("Can't drop supplementary groups"); } - if (setgid(gid) != 0) - die_perror("Can't set GID to %u", gid); + /* If we're going to use a pre-existing userns (either named, or with + * --netns-only the current one), we need to switch to drop root first. + * However, if we're going to create our own userns, we need to delay + * dropping root, because we don't our userns to be owned by nobody. + */ + if (*userns || !use_userns) { + if (setgid(gid) != 0) + die_perror("Can't set GID to %u", gid); - if (setuid(uid) != 0) - die_perror("Can't set UID to %u", uid); + if (setuid(uid) != 0) + die_perror("Can't set UID to %u", uid); + } if (*userns) { /* If given a userns, join it */ enter_userns(userns); - } else if (use_userns) { /* Create and join a new userns */ - if (unshare(CLONE_NEWUSER) != 0) - die_perror("Couldn't create user namespace"); + } else if (use_userns) { /* Otherwise create our own */ + pid_t holder_pid = create_userns(uid, gid); + char new_userns[PATH_MAX]; + + if (snprintf_check(new_userns, sizeof(new_userns), + "/proc/%u/ns/user", holder_pid)) + die_perror("Could not build userns path"); + + enter_userns(new_userns); + + /* Now that we occupy the ns, we can kill the temporary holder */ + if (kill(holder_pid, SIGKILL)) + die_perror("Could not kill userns temporary holder"); + + /* Switch to our final uid/gid, which are mapped to 0 in the userns */ + if (setgid(0) != 0) + die_perror("Can't set GID to 0 in userns"); + + if (setuid(0) != 0) + die_perror("Can't set UID to 0 in userns"); } /* Joining a new userns gives us full capabilities; drop the |
