From bcaf3a56b6e31aa3724250d182ea823f0e06c092 Mon Sep 17 00:00:00 2001 From: Patrick Steinhardt Date: Mon, 24 May 2021 12:40:17 +0200 Subject: [PATCH] bwrap: Implement ability to switch AppArmor profiles Bubblewrap is currently hard to use in combination with AppArmor profiles. The root cause of this is that it sets the NO_NEW_PRIVS flag quite early in the process, and if that flag is set then most AppArmor profile transitions are disallowed (except for unconfined -> confined and profile stacking). This makes it rather hard to have a central profile for bwrap acting as a portal with "normal" profiles. While this could be solved by granting the bwrap profile itself full permissions to everything on the system and then only use stacked transitions, this feels overly dangerous especially considering that bwrap is typically installed setuid. To fix this issue, this commit instead introduces the ability to explicitly transition to a specific target AppArmor profile. This allows us to perform the transition before we set the NO_NEW_PRIVS flag and thus make them both work together. There are two downsides to this: - NO_NEW_PRIVS must be set at a much later point in time, namely after the sandbox has been constructed and the new profile has been changed to. While we could switch to the profile at a much earlier point in time, this would require the target profile to grant permissions required to construct the sandbox. We cannot use AppArmor's "change_onexec" transition either: even if we set the transition up while NO_NEW_PRIVS is not yet effective, the profile switch on exec is going to be denied anyway. - In order to allow switching the profile, we need to have a writable "/proc". This is required such that we can effect the transition via a write to "/proc/self/attr/apparmor/current". Neither of these downsides should be a problem though: NO_NEW_PRIVS' main intent is to avoid granting new privileges on execve(2), which it still does given that execve(2) is the last step. And "/proc" being writable shouldn't matter much when a pid namespace is in use. Implement AppArmor profile switching via a new "--apparmor-profile" switch and document it. Signed-off-by: Patrick Steinhardt --- bubblewrap.c | 22 ++++++++++++++++++---- bwrap.xml | 7 +++++++ utils.c | 13 +++++++++++++ utils.h | 1 + 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/bubblewrap.c b/bubblewrap.c index 771e1eaf..17aaa8bd 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -62,6 +62,7 @@ static const char *host_tty_dev; static int proc_fd = -1; static const char *opt_exec_label = NULL; static const char *opt_file_label = NULL; +static const char *opt_apparmor_profile = NULL; static bool opt_as_pid_1; const char *opt_chdir_path = NULL; @@ -253,6 +254,7 @@ usage (int ecode, FILE *out) " --remount-ro DEST Remount DEST as readonly; does not recursively remount\n" " --exec-label LABEL Exec label for the sandbox\n" " --file-label LABEL File label for temporary sandbox content\n" + " --apparmor-profile PROFILE AppArmor profile for the sandbox\n" " --proc DEST Mount new procfs on DEST\n" " --dev DEST Mount new dev on DEST\n" " --tmpfs DEST Mount new tmpfs on DEST\n" @@ -1681,6 +1683,15 @@ parse_args_recurse (int *argcp, if (label_create_file (opt_file_label)) die_with_error ("--file-label setup failed"); + argv += 1; + argc -= 1; + } + else if (strcmp (arg, "--apparmor-profile") == 0) + { + if (argc < 2) + die ("--apparmor-profile takes an argument"); + opt_apparmor_profile = argv[1]; + argv += 1; argc -= 1; } @@ -2245,10 +2256,6 @@ main (int argc, /* Get the (optional) privileges we need */ acquire_privs (); - /* Never gain any more privs during exec */ - if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) - die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed"); - /* The initial code is run with high permissions (i.e. CAP_SYS_ADMIN), so take lots of care. */ @@ -2836,6 +2843,13 @@ main (int argc, if (label_exec (opt_exec_label) == -1) die_with_error ("label_exec %s", argv[0]); + if (apparmor_change_profile (opt_apparmor_profile) == -1) + die_with_error ("apparmor_change_profile %s", opt_apparmor_profile); + + /* Never gain any more privs during exec */ + if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) + die_with_error ("prctl(PR_SET_NO_NEW_PRIVS) failed"); + __debug__ (("forking for child\n")); if (!opt_as_pid_1 && (opt_unshare_pid || lock_files != NULL || opt_sync_fd != -1)) diff --git a/bwrap.xml b/bwrap.xml index e0e9c697..b77b6b58 100644 --- a/bwrap.xml +++ b/bwrap.xml @@ -283,6 +283,13 @@ the SELinux context for the sandbox content. + + + + AppArmor Profile from the sandbox. On an AppArmor system you can specify the + profile for the sandbox process(s). Requires writable procfs. + + diff --git a/utils.c b/utils.c index a99a8650..f1da18e3 100644 --- a/utils.c +++ b/utils.c @@ -824,3 +824,16 @@ label_exec (const char *exec_label) #endif return 0; } + +int apparmor_change_profile (const char *profile) +{ + cleanup_free char *data = NULL; + + if (!profile) + return 0; + data = strconcat ("changeprofile ", profile); + if (write_file_at (-1, "/proc/self/attr/apparmor/current", data) == -1) + return -1; + + return 0; +} diff --git a/utils.h b/utils.h index 8c4db619..a462cadb 100644 --- a/utils.h +++ b/utils.h @@ -122,6 +122,7 @@ char *label_mount (const char *opt, const char *mount_label); int label_exec (const char *exec_label); int label_create_file (const char *file_label); +int apparmor_change_profile (const char *profile); static inline void cleanup_freep (void *p)