aboutgitcodebugslistschat
path: root/isolation.c
diff options
context:
space:
mode:
Diffstat (limited to 'isolation.c')
-rw-r--r--isolation.c93
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