From 58fb6d3f93bb8fffcefb7608d0a54bab35000622 Mon Sep 17 00:00:00 2001 From: Mikhail Kulko Date: Sun, 18 Jun 2023 03:09:15 +0600 Subject: [PATCH 1/2] implement --compat and flip some defaults Signed-off-by: Mikhail Kulko --- bubblewrap.c | 84 +++++++++++++++++++++++++++++++++++++----- completions/bash/bwrap | 3 ++ completions/zsh/_bwrap | 3 ++ 3 files changed, 81 insertions(+), 9 deletions(-) diff --git a/bubblewrap.c b/bubblewrap.c index de063058..fc68f952 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -59,6 +59,8 @@ * 2^64 - 2^12. */ #define MAX_TMPFS_BYTES ((size_t) (SIZE_MAX >> 1)) +#define LATEST_COMPAT_LEVEL 1 + /* Globals to avoid having to use getuid(), since the uid/gid changes during runtime */ static uid_t real_uid; static gid_t real_gid; @@ -75,6 +77,7 @@ static bool opt_as_pid_1; static const char *opt_chdir_path = NULL; static bool opt_assert_userns_disabled = FALSE; static bool opt_disable_userns = FALSE; +static bool opt_disable_userns_set = FALSE; static bool opt_unshare_user = FALSE; static bool opt_unshare_user_try = FALSE; static bool opt_unshare_pid = FALSE; @@ -85,6 +88,7 @@ static bool opt_unshare_cgroup = FALSE; static bool opt_unshare_cgroup_try = FALSE; static bool opt_needs_devpts = FALSE; static bool opt_new_session = FALSE; +static bool opt_new_session_set = FALSE; static bool opt_die_with_parent = FALSE; static uid_t opt_sandbox_uid = -1; static gid_t opt_sandbox_gid = -1; @@ -99,6 +103,7 @@ static char *opt_args_data = NULL; /* owned */ static int opt_userns_fd = -1; static int opt_userns2_fd = -1; static int opt_pidns_fd = -1; +static int opt_compat_level = 0; static int next_perms = -1; static size_t next_size_arg = 0; @@ -308,6 +313,7 @@ usage (int ecode, FILE *out) fprintf (out, " --help Print this help\n" " --version Print version\n" + " --compat Set compatability level (negative value means latest)\n" " --args FD Parse NUL-separated args from FD\n" " --unshare-all Unshare every namespace we support by default\n" " --share-net Retain the network namespace (can only combine with --unshare-all)\n" @@ -321,7 +327,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" + "%s" /* --(disable/allow)-userns */ " --assert-userns-disabled Fail unless further use of user namespace inside sandbox is disabled\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" @@ -357,15 +363,20 @@ usage (int ecode, FILE *out) " --userns-block-fd FD Block on FD until the user namespace is ready\n" " --info-fd FD Write information about the running container to FD\n" " --json-status-fd FD Write container status to FD as multiple JSON documents\n" - " --new-session Create a new terminal session\n" + "%s" /* -(-no)-new-session */ " --die-with-parent Kills with SIGKILL child process (COMMAND) when bwrap or bwrap's parent dies.\n" " --as-pid-1 Do not install a reaper process with PID=1\n" " --cap-add CAP Add cap CAP when running as privileged user\n" " --cap-drop CAP Drop cap CAP when running as privileged user\n" " --perms OCTAL Set permissions of next argument (--bind-data, --file, etc.)\n" " --size BYTES Set size of next argument (only for --tmpfs)\n" - " --chmod OCTAL PATH Change permissions of PATH (must already exist)\n" - ); + " --chmod OCTAL PATH Change permissions of PATH (must already exist)\n", + opt_compat_level == 0 ? + " --disable-userns Disable further use of user namespaces inside sandbox\n" : + " --allow-userns Allow further use of user namespaces inside sandbox\n", + opt_compat_level == 0 ? + " --new-session Create a new terminal session\n" : + " --no-new-session Don't create a new terminal session\n"); exit (ecode); } @@ -1652,18 +1663,43 @@ parse_args_recurse (int *argcp, if (*total_parsed_argc_p > MAX_ARGS) die ("Exceeded maximum number of arguments %u", MAX_ARGS); + bool print_help = FALSE; while (argc > 0) { const char *arg = argv[0]; if (strcmp (arg, "--help") == 0) { - usage (EXIT_SUCCESS, stdout); + /* Defer printing help as it now varies depending on the compat level. */ + print_help = TRUE; } else if (strcmp (arg, "--version") == 0) { print_version_and_exit (); } + else if (strcmp (arg, "--compat") == 0) + { + int the_compat_level; + char *endptr; + + if (argc < 2) + die ("--compat takes an argument"); + + the_compat_level = strtol (argv[1], &endptr, 10); + if (argv[1][0] == 0 || endptr[0] != 0) + die ("Invalid compat level: %s", argv[1]); + + if (the_compat_level > LATEST_COMPAT_LEVEL) + die ("Compat level %d is not suported by this version (latest supported is %d)", the_compat_level, LATEST_COMPAT_LEVEL); + + if (the_compat_level < 0) + the_compat_level = LATEST_COMPAT_LEVEL; + + opt_compat_level = the_compat_level; + + argv += 1; + argc -= 1; + } else if (strcmp (arg, "--args") == 0) { int the_fd; @@ -1789,13 +1825,20 @@ parse_args_recurse (int *argcp, argv++; argc--; } - else if (strcmp (arg, "--disable-userns") == 0) + else if (opt_compat_level == 0 && strcmp (arg, "--disable-userns") == 0) { opt_disable_userns = TRUE; + opt_disable_userns_set = TRUE; } else if (strcmp (arg, "--assert-userns-disabled") == 0) { opt_assert_userns_disabled = TRUE; + opt_disable_userns_set = TRUE; + } + else if (opt_compat_level > 0 && strcmp (arg, "--allow-userns") == 0) + { + opt_disable_userns = FALSE; + opt_disable_userns_set = TRUE; } else if (strcmp (arg, "--remount-ro") == 0) { @@ -1980,7 +2023,10 @@ parse_args_recurse (int *argcp, if (next_perms >= 0) op->perms = next_perms; else - op->perms = 0666; + if (opt_compat_level == 0) + op->perms = 0666; + else + op->perms = 0644; next_perms = -1; argv += 2; @@ -2176,7 +2222,12 @@ parse_args_recurse (int *argcp, die ("--seccomp cannot be combined with --add-seccomp-fd"); if (opt_seccomp_fd != -1) - warn_only_last_option ("--seccomp"); + { + if (opt_compat_level == 0) + warn_only_last_option ("--seccomp"); + else + die ("More than one --seccomp options specified, use --add-seccomp-fd instead."); + } the_fd = strtol (argv[1], &endptr, 10); if (argv[1][0] == 0 || endptr[0] != 0 || the_fd < 0) @@ -2349,9 +2400,15 @@ parse_args_recurse (int *argcp, argv += 1; argc -= 1; } - else if (strcmp (arg, "--new-session") == 0) + else if (opt_compat_level == 0 && strcmp (arg, "--new-session") == 0) { opt_new_session = TRUE; + opt_new_session_set = TRUE; + } + else if (opt_compat_level > 0 && strcmp (arg, "--no-new-session") == 0) + { + opt_new_session = FALSE; + opt_new_session_set = TRUE; } else if (strcmp (arg, "--die-with-parent") == 0) { @@ -2526,6 +2583,15 @@ parse_args_recurse (int *argcp, argc--; } + if (print_help) + usage (EXIT_SUCCESS, stdout); + + if (opt_compat_level > 0 && opt_unshare_user && !opt_disable_userns_set) + opt_disable_userns = TRUE; + + if (opt_compat_level > 0 && !opt_new_session_set) + opt_new_session = TRUE; + *argcp = argc; *argvp = argv; } diff --git a/completions/bash/bwrap b/completions/bash/bwrap index ca18d896..c9d44e20 100644 --- a/completions/bash/bwrap +++ b/completions/bash/bwrap @@ -9,12 +9,14 @@ _bwrap() { # Please keep sorted in LC_ALL=C order local boolean_options=" + --allow-userns --as-pid-1 --assert-userns-disabled --clearenv --disable-userns --help --new-session + --no-new-session --unshare-all --unshare-cgroup --unshare-cgroup-try @@ -39,6 +41,7 @@ _bwrap() { --cap-drop --chdir --chmod + --compat --dev --dev-bind --die-with-parent diff --git a/completions/zsh/_bwrap b/completions/zsh/_bwrap index a2e2caf3..5c079a11 100644 --- a/completions/zsh/_bwrap +++ b/completions/zsh/_bwrap @@ -27,6 +27,7 @@ _bwrap_args=( # Please sort alphabetically (in LC_ALL=C order) by option name '--add-seccomp-fd[Load and use seccomp rules from FD]: :_guard "[0-9]#" "file descriptor to read seccomp rules from"' + '--allow-userns[Allow further use of user namespaces inside sandbox]' '--assert-userns-disabled[Fail unless further use of user namespace inside sandbox is disabled]' '--args[Parse NUL-separated args from FD]: :_guard "[0-9]#" "file descriptor with NUL-separated arguments"' '--as-pid-1[Do not install a reaper process with PID=1]' @@ -38,6 +39,7 @@ _bwrap_args=( '--chdir[Change directory to DIR]:working directory for sandbox: _files -/' '--chmod[Set permissions]: :_guard "[0-7]#" "permissions in octal":path to set permissions:_files' '--clearenv[Unset all environment variables]' + '--compat[Set compatability level (negative value means latest)]:compatability level:' '--dev-bind-try[Equal to --dev-bind but ignores non-existent SRC]:source:_files:destination:_files' '--dev-bind[Bind mount the host path SRC on DEST, allowing device access]:source:_files:destination:_files' '--dev[Mount new dev on DEST]:mount point for /dev:_files -/' @@ -53,6 +55,7 @@ _bwrap_args=( '--lock-file[Take a lock on DEST while sandbox is running]:lock file:_files' '--mqueue[Mount new mqueue on DEST]:mount point for mqueue:_files -/' '--new-session[Create a new terminal session]' + "--no-new-session[Don't create a new terminal session]" '--perms[Set permissions for next action argument]: :_guard "[0-7]#" "permissions in octal": :->after_perms' '--pidns[Use this user namespace (as parent namespace if using --unshare-pid)]: :' '--proc[Mount new procfs on DEST]:mount point for procfs:_files -/' From 64ca7d1d80e0535b4faff60d46f0918b94b6718f Mon Sep 17 00:00:00 2001 From: Mikhail Kulko Date: Sun, 18 Jun 2023 03:27:29 +0600 Subject: [PATCH 2/2] document changes in bwrap.xml Signed-off-by: Mikhail Kulko --- bwrap.xml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/bwrap.xml b/bwrap.xml index 9d770ac0..e9b59076 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -84,6 +84,10 @@ Print version + + + Set compatability level (negative value means latest) + @@ -145,6 +149,15 @@ After setting up the new namespace, switch into the specified namespace. For this to work the specified namespace must be a descendant of the user namespace used for the setup, so this is only useful in combination with --userns. This is useful because sometimes bubblewrap itself creates nested user namespaces (to work around some kernel issues) and --userns2 can be used to enter these. + + + + Allow the process in the sandbox to create further user namespaces, + so that it can rearrange the filesystem namespace or do other more + complex namespace modification. + This option is only available in compatability level 1 or later. + + @@ -157,6 +170,7 @@ in the outer namespace. This option requires , and doesn't work in the setuid version of bubblewrap. + This option is not available in compatability level 1 or later. @@ -455,12 +469,29 @@ ignore members and objects that they do not understand. + + + + Don't create a new terminal session for the sandbox (don't call + setsid()). This doesn't disconnect the sandbox from the controlling + terminal which means the sandbox can for instance inject input into + the terminal. This option is only available in compatability level 1 + or later. + + Note: In a general sandbox, if you use --no-new-session, it is + recommended to use seccomp to disallow the TIOCSTI ioctl, otherwise + the application can feed keyboard input to the terminal + which can e.g. lead to out-of-sandbox command execution + (see CVE-2017-5226). + + Create a new terminal session for the sandbox (calls setsid()). This disconnects the sandbox from the controlling terminal which means the sandbox can't for instance inject input into the terminal. + This option is not available in compatability level 1 or later. Note: In a general sandbox, if you don't use --new-session, it is recommended to use seccomp to disallow the TIOCSTI ioctl, otherwise