-
Notifications
You must be signed in to change notification settings - Fork 242
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add an option to disable nested user namespaces by setting limit to 1 #488
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
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 #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 <alexl@redhat.com> Signed-off-by: Simon McVittie <smcv@collabora.com>
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,20 +3167,50 @@ 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"); | ||
Comment on lines
+3201
to
+3202
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note that this has to be exactly 1. If it was 0, we wouldn't be able to |
||
} | ||
|
||
if (unshare (CLONE_NEWUSER)) | ||
die_with_error ("unshare user ns"); | ||
|
||
/* 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Alex's version had this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I did that because previously in that case we would not hit this codepath so I would avoid doing what we didn't do before. But we just set up a new user namespace, so we have to do this here. So, your change is correct. |
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that as written here, we can get into this block even if we're setuid root. Please check that this isn't a security vulnerability?
I think it's OK, because we could already have entered this block anyway (for
--unshare-user --sandbox-uid=42
) even with a setuid bwrap, and the only thing we're doing here is reducing themax_user_namespaces
in a new namespace, never increasing it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see how this is ever a problem. We only ever hit this if --unshare-user is set, which means at this point we did CLONE_USERNS. So, even if we had privileged capabilities on the host we now don't have them in the user namespace:
I.e. even in the setuid case we have at this point the same privileges as the non-setuid case.