From 6349b3ffc1ebd974a8c6f064fa05ca1a044aec2d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 28 Jan 2016 09:43:14 +0100 Subject: [PATCH] helper: Make user namespace support vs setuid a runtime, not build-time option We now check at runtime if we have raised privs, and only if not so do we try to use unprivileged user namespaces. This means you can build xdg-app however, and then setuid/setcap the binary however you want afterwards. --- app/xdg-app-builtins-enter.c | 41 ++++----- common/Makefile.am.inc | 2 - common/xdg-app-helper.c | 159 ++++++++++++++++++++--------------- configure.ac | 18 +--- 4 files changed, 113 insertions(+), 107 deletions(-) diff --git a/app/xdg-app-builtins-enter.c b/app/xdg-app-builtins-enter.c index 30f7f9c2..c751306a 100644 --- a/app/xdg-app-builtins-enter.c +++ b/app/xdg-app-builtins-enter.c @@ -40,7 +40,6 @@ static GOptionEntry options[] = { { NULL } }; -#ifndef DISABLE_USERNS static gboolean write_to_file (int fd, const char *content, ssize_t len) { @@ -81,7 +80,6 @@ write_file (const char *path, const char *content) return res; } -#endif static uid_t uid; static gid_t gid; @@ -89,28 +87,33 @@ static gid_t gid; static void child_setup (gpointer user_data) { -#ifndef DISABLE_USERNS g_autofree char *uid_map = NULL; g_autofree char *gid_map = NULL; - - /* Work around user namespace devpts issue by creating a new - userspace and map our uid like the helper does */ + uid_t ns_uid; + gid_t ns_gid; - if (unshare (CLONE_NEWUSER)) + ns_uid = getuid (); + ns_gid = getgid (); + + if (ns_uid != uid || ns_gid != gid) { - g_warning ("Can't unshare user namespace: %s", strerror (errno)); - return; + /* Work around user namespace devpts issue by creating a new + userspace and map our uid like the helper does */ + + if (unshare (CLONE_NEWUSER)) + { + g_warning ("Can't unshare user namespace: %s", strerror (errno)); + return; + } + + uid_map = g_strdup_printf ("%d %d 1\n", uid, ns_uid); + if (!write_file ("/proc/self/uid_map", uid_map)) + g_warning ("setting up uid map"); + + gid_map = g_strdup_printf ("%d %d 1\n", gid, ns_gid); + if (!write_file ("/proc/self/gid_map", gid_map)) + g_warning ("setting up gid map"); } - - uid_map = g_strdup_printf ("%d 0 1\n", uid); - if (!write_file ("/proc/self/uid_map", uid_map)) - g_warning ("setting up uid map"); - - gid_map = g_strdup_printf ("%d 0 1\n", gid); - if (!write_file ("/proc/self/gid_map", gid_map)) - g_warning ("setting up gid map"); - -#endif } gboolean diff --git a/common/Makefile.am.inc b/common/Makefile.am.inc index 8b806f9a..7ce7e840 100644 --- a/common/Makefile.am.inc +++ b/common/Makefile.am.inc @@ -55,7 +55,6 @@ xdg_app_helper_LDADD = $(LIBSECCOMP_LIBS) xdg_app_helper_CFLAGS = $(LIBSECCOMP_CFLAGS) install-exec-hook: -if DISABLE_USERNS if PRIV_MODE_SETUID $(SUDO_BIN) chown root $(DESTDIR)$(bindir)/xdg-app-helper $(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/xdg-app-helper @@ -64,4 +63,3 @@ if PRIV_MODE_FILECAPS $(SUDO_BIN) setcap cap_sys_admin+ep $(DESTDIR)$(bindir)/xdg-app-helper endif endif -endif diff --git a/common/xdg-app-helper.c b/common/xdg-app-helper.c index 10ae8802..b81179a7 100644 --- a/common/xdg-app-helper.c +++ b/common/xdg-app-helper.c @@ -73,6 +73,7 @@ typedef int bool; /* Globals to avoid having to use getuid(), since the uid/gid changes during runtime */ static uid_t uid; static gid_t gid; +static bool is_privileged; static void die_with_error (const char *format, ...) @@ -1193,7 +1194,6 @@ copy_file (const char *src_path, const char *dst_path, mode_t mode) return res; } -#ifndef DISABLE_USERNS static bool write_file (const char *path, const char *content) { @@ -1215,7 +1215,6 @@ write_file (const char *path, const char *content) return res; } -#endif static bool create_file (const char *path, mode_t mode, const char *content) @@ -1929,8 +1928,6 @@ do_init (int event_fd, pid_t initial_pid) return initial_exit_status; } -#ifdef DISABLE_USERNS - #define REQUIRED_CAPS (CAP_TO_MASK(CAP_SYS_ADMIN)) static void @@ -1939,6 +1936,16 @@ acquire_caps (void) struct __user_cap_header_struct hdr; struct __user_cap_data_struct data; + memset (&hdr, 0, sizeof(hdr)); + hdr.version = _LINUX_CAPABILITY_VERSION; + + if (capget (&hdr, &data) < 0) + die_with_error ("capget failed"); + + if (((data.effective & REQUIRED_CAPS) == REQUIRED_CAPS) && + ((data.permitted & REQUIRED_CAPS) == REQUIRED_CAPS)) + is_privileged = TRUE; + if (getuid () != geteuid ()) { /* Tell kernel not clear capabilities when dropping root */ @@ -1950,15 +1957,19 @@ acquire_caps (void) die_with_error ("unable to drop privs"); } - memset (&hdr, 0, sizeof(hdr)); - hdr.version = _LINUX_CAPABILITY_VERSION; + if (is_privileged) + { + memset (&hdr, 0, sizeof(hdr)); + hdr.version = _LINUX_CAPABILITY_VERSION; - /* Drop all non-require capabilities */ - data.effective = REQUIRED_CAPS; - data.permitted = REQUIRED_CAPS; - data.inheritable = 0; - if (capset (&hdr, &data) < 0) - die_with_error ("capset failed"); + /* Drop all non-require capabilities */ + data.effective = REQUIRED_CAPS; + data.permitted = REQUIRED_CAPS; + data.inheritable = 0; + if (capset (&hdr, &data) < 0) + die_with_error ("capset failed"); + } + /* Else, we try unprivileged user namespaces */ } static void @@ -1967,6 +1978,9 @@ drop_caps (void) struct __user_cap_header_struct hdr; struct __user_cap_data_struct data; + if (!is_privileged) + return; + memset (&hdr, 0, sizeof(hdr)); hdr.version = _LINUX_CAPABILITY_VERSION; data.effective = 0; @@ -1975,9 +1989,10 @@ drop_caps (void) if (capset (&hdr, &data) < 0) die_with_error ("capset failed"); -} -#endif + if (prctl (PR_SET_DUMPABLE, 1, 0, 0, 0) < 0) + die_with_error ("prctl(PR_SET_DUMPABLE) failed"); +} static char *arg_space; size_t arg_space_size; @@ -2037,17 +2052,19 @@ main (int argc, bool writable = FALSE; bool writable_app = FALSE; bool writable_exports = FALSE; + int clone_flags; char *old_cwd = NULL; int c, i; pid_t pid; int event_fd; int sync_fd = -1; char *endp; + char *uid_map, *gid_map; + uid_t ns_uid; + gid_t ns_gid; -#ifdef DISABLE_USERNS - /* Get the capabilities we need, drop root */ + /* Get the (optional) capabilities we need, drop root */ acquire_caps (); -#endif /* Never gain any more privs during exec */ if (prctl (PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) @@ -2249,23 +2266,26 @@ main (int argc, block_sigchild (); /* Block before we clone to avoid races */ - pid = raw_clone (SIGCHLD | CLONE_NEWNS | CLONE_NEWPID | -#ifndef DISABLE_USERNS - CLONE_NEWUSER | -#endif - (network ? 0 : CLONE_NEWNET) | - (ipc ? 0 : CLONE_NEWIPC), - NULL); + clone_flags = SIGCHLD | CLONE_NEWNS | CLONE_NEWPID; + if (!is_privileged) + clone_flags |= CLONE_NEWUSER; + if (!network) + clone_flags |= CLONE_NEWNET; + if (!ipc) + clone_flags |= CLONE_NEWIPC; + + pid = raw_clone (clone_flags, NULL); if (pid == -1) { -#ifndef DISABLE_USERNS - if (errno == EINVAL) - die ("Creating new namespace failed, likely because the kernel does not support user namespaces. Recompile xdg-app with --disable-userns, or switch to a kernel with user namespace support."); - else if (errno == EPERM) - die ("No permissions to creating new namespace, likely because the kernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'."); - else -#endif - die_with_error ("Creating new namespace failed"); + if (!is_privileged) + { + if (errno == EINVAL) + die ("Creating new namespace failed, likely because the kernel does not support user namespaces. Recompile xdg-app with --disable-userns, or switch to a kernel with user namespace support."); + else if (errno == EPERM) + die ("No permissions to creating new namespace, likely because the kernel does not allow non-privileged user namespaces. On e.g. debian this can be enabled with 'sysctl kernel.unprivileged_userns_clone=1'."); + } + + die_with_error ("Creating new namespace failed"); } if (pid != 0) @@ -2276,27 +2296,30 @@ main (int argc, exit (0); /* Should not be reached, but better safe... */ } -#ifndef DISABLE_USERNS - { - char *uid_map, *gid_map; - /* This is a bit hacky, but we need to first map the real uid/gid to - 0, otherwise we can't mount the devpts filesystem because root is - not mapped. Later we will create another child user namespace and - map back to the real uid */ - uid_map = strdup_printf ("0 %d 1\n", uid); - if (!write_file ("/proc/self/uid_map", uid_map)) - die_with_error ("setting up uid map"); - free (uid_map); + ns_uid = uid; + ns_gid = gid; + if (!is_privileged) + { + /* This is a bit hacky, but we need to first map the real uid/gid to + 0, otherwise we can't mount the devpts filesystem because root is + not mapped. Later we will create another child user namespace and + map back to the real uid */ + ns_uid = 0; + ns_gid = 0; - if (!write_file("/proc/self/setgroups", "deny\n")) - die_with_error ("error writing to setgroups"); + uid_map = strdup_printf ("%d %d 1\n", ns_uid, uid); + if (!write_file ("/proc/self/uid_map", uid_map)) + die_with_error ("setting up uid map"); + free (uid_map); - gid_map = strdup_printf ("0 %d 1\n", gid); - if (!write_file ("/proc/self/gid_map", gid_map)) - die_with_error ("setting up gid map"); - free (gid_map); - } -#endif + if (!write_file("/proc/self/setgroups", "deny\n")) + die_with_error ("error writing to setgroups"); + + gid_map = strdup_printf ("%d %d 1\n", ns_gid, gid); + if (!write_file ("/proc/self/gid_map", gid_map)) + die_with_error ("setting up gid map"); + free (gid_map); + } old_umask = umask (0); @@ -2541,10 +2564,8 @@ main (int argc, umask (old_umask); -#ifdef DISABLE_USERNS /* Now we have everything we need CAP_SYS_ADMIN for, so drop it */ drop_caps (); -#endif if (chdir_path) { @@ -2598,26 +2619,24 @@ main (int argc, { __debug__(("launch executable %s\n", args[0])); -#ifndef DISABLE_USERNS - { - char *uid_map, *gid_map; - /* Now that devpts is mounted we can create a new userspace and map - our uid 1:1 */ + if (ns_uid != uid || ns_gid != gid) + { + /* Now that devpts is mounted we can create a new userspace + and map our uid 1:1 */ - if (unshare (CLONE_NEWUSER)) - die_with_error ("unshare user ns"); + if (unshare (CLONE_NEWUSER)) + die_with_error ("unshare user ns"); - uid_map = strdup_printf ("%d 0 1\n", uid); - if (!write_file ("/proc/self/uid_map", uid_map)) - die_with_error ("setting up uid map"); - free (uid_map); + uid_map = strdup_printf ("%d 0 1\n", uid); + if (!write_file ("/proc/self/uid_map", uid_map)) + die_with_error ("setting up uid map"); + free (uid_map); - gid_map = strdup_printf ("%d 0 1\n", gid); - if (!write_file ("/proc/self/gid_map", gid_map)) - die_with_error ("setting up gid map"); - free (gid_map); - } -#endif + gid_map = strdup_printf ("%d 0 1\n", gid); + if (!write_file ("/proc/self/gid_map", gid_map)) + die_with_error ("setting up gid map"); + free (gid_map); + } __debug__(("setting up seccomp in child\n")); setup_seccomp (devel); diff --git a/configure.ac b/configure.ac index 4a8641e7..62c92937 100644 --- a/configure.ac +++ b/configure.ac @@ -116,28 +116,14 @@ if test "x$enable_seccomp" = "xyes"; then [Define if using seccomp]) fi - -AC_ARG_ENABLE([userns], - AC_HELP_STRING([--disable-userns], - [Disable User namespaces (requires setuid/setcaps)]), - [], - [enable_userns=yes]) - -AM_CONDITIONAL(DISABLE_USERNS, test "x$enable_userns" = "xno") -if test "x$enable_userns" = "xno"; then - AC_DEFINE([DISABLE_USERNS], [1], - [Define if not using user namespaces]) -fi - AC_ARG_WITH(priv-mode, AS_HELP_STRING([--with-priv-mode=setuid/caps/none], - [How to set privilege-raising during install (only needed if userns disabled)]), + [How to set privilege-raising during install (only needed if userns not working)]), [], - [with_priv_mode="setuid"]) + [with_priv_mode="none"]) AM_CONDITIONAL(PRIV_MODE_FILECAPS, test "x$with_priv_mode" = "xcaps") AM_CONDITIONAL(PRIV_MODE_SETUID, test "x$with_priv_mode" = "xsetuid") -AM_CONDITIONAL(PRIV_MODE_FILECAPS, test "x$with_priv_mode" = "xcaps") AC_ARG_ENABLE(sudo, AS_HELP_STRING([--enable-sudo],[Use sudo to set setuid flags on binaries during install (only needed if userns disabled)]),