From b33c333bcb88557ad23a9bc5be0d619d537984e9 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Tue, 22 Mar 2022 17:12:33 +0000 Subject: Add an option to disable nested user namespaces by setting limit to 1 Some use-cases of bubblewrap want to ensure that the subprocess can't further re-arrange the filesystem namespace, or do other more complex namespace modification. For example, Flatpak wants to prevent sandboxed processes from altering their /proc/$pid/root/.flatpak-info, so that /.flatpak-info can safely be used as an indicator that a process is part of a Flatpak app. This approach was suggested by lukts30 on containers/bubblewrap#452. The sysctl-controlled maximum numbers of namespaces are themselves namespaced, so we can disable nested user namespaces by setting the limit to 1 and then entering a new, nested user namespace. The resulting process loses its privileges in the namespace where the limit was set to 1, so it is unable to move the limit back up. Co-authored-by: Alexander Larsson Signed-off-by: Simon McVittie --- bubblewrap.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 48 insertions(+), 6 deletions(-) (limited to 'bubblewrap.c') diff --git a/bubblewrap.c b/bubblewrap.c index eece9d8..bcfbe9d 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -73,6 +73,7 @@ static const char *opt_file_label = NULL; static bool opt_as_pid_1; const char *opt_chdir_path = NULL; +bool opt_disable_userns = FALSE; bool opt_unshare_user = FALSE; bool opt_unshare_user_try = FALSE; bool opt_unshare_pid = FALSE; @@ -311,6 +312,7 @@ usage (int ecode, FILE *out) " --unshare-cgroup-try Create new cgroup namespace if possible else continue by skipping it\n" " --userns FD Use this user namespace (cannot combine with --unshare-user)\n" " --userns2 FD After setup switch to this user namespace, only useful with --userns\n" + " --disable-userns Disable further use of user namespaces inside sandbox\n" " --pidns FD Use this pid namespace (as parent namespace if using --unshare-pid)\n" " --uid UID Custom uid in the sandbox (requires --unshare-user or --userns)\n" " --gid GID Custom gid in the sandbox (requires --unshare-user or --userns)\n" @@ -1777,6 +1779,10 @@ parse_args_recurse (int *argcp, argv++; argc--; } + else if (strcmp (arg, "--disable-userns") == 0) + { + opt_disable_userns = TRUE; + } else if (strcmp (arg, "--remount-ro") == 0) { if (argc < 2) @@ -2677,6 +2683,12 @@ main (int argc, if (opt_userns_fd != -1 && opt_unshare_user_try) die ("--userns not compatible --unshare-user-try"); + if (opt_disable_userns && !opt_unshare_user) + die ("--disable-userns requires --unshare-user"); + + if (opt_disable_userns && opt_userns_block_fd != -1) + die ("--disable-userns is not compatible with --userns-block-fd"); + /* Technically using setns() is probably safe even in the privileged * case, because we got passed in a file descriptor to the * namespace, and that can only be gotten if you have ptrace @@ -3155,13 +3167,34 @@ main (int argc, if (opt_userns2_fd > 0 && setns (opt_userns2_fd, CLONE_NEWUSER) != 0) die_with_error ("Setting userns2 failed"); - if (opt_unshare_user && - (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid) && - opt_userns_block_fd == -1) + if (opt_unshare_user && opt_userns_block_fd == -1 && + (ns_uid != opt_sandbox_uid || ns_gid != opt_sandbox_gid || + opt_disable_userns)) { - /* Now that devpts is mounted and we've no need for mount - permissions we can create a new userspace and map our uid - 1:1 */ + /* Here we create a second level userns inside the first one. This is + used for one or more of these reasons: + + * The 1st level namespace has a different uid/gid than the + requested due to requirements of beeing root in the first + level due for mounting devpts (opt_needs_devpts). + + * To disable user namespaces we set max_user_namespaces and then + create the second namespace so that the sandbox cannot undo this + change. + */ + + if (opt_disable_userns) + { + cleanup_fd int sysctl_fd = -1; + + sysctl_fd = openat (proc_fd, "sys/user/max_user_namespaces", O_WRONLY); + + if (sysctl_fd < 0) + die_with_error ("cannot open /proc/sys/user/max_user_namespaces"); + + if (write_to_fd (sysctl_fd, "1", 1) < 0) + die_with_error ("sysctl user.max_user_namespaces = 1"); + } if (unshare (CLONE_NEWUSER)) die_with_error ("unshare user ns"); @@ -3169,6 +3202,15 @@ main (int argc, /* We're in a new user namespace, we got back the bounding set, clear it again */ drop_cap_bounding_set (FALSE); + if (opt_disable_userns) + { + /* Verify that we can't make a new userns again */ + res = unshare (CLONE_NEWUSER); + + if (res == 0) + die ("unable to disable creation of new user namespaces"); + } + write_uid_gid_map (opt_sandbox_uid, ns_uid, opt_sandbox_gid, ns_gid, -1, FALSE, FALSE); -- cgit v1.2.1