From 2c0c21744fe50b5ab7e4c1262bdca20b2ee2b201 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 28 May 2015 22:02:31 +0200 Subject: [PATCH] helper: Drop setuid and use user namespaces --- Makefile.am | 10 ---- configure.ac | 14 ----- xdg-app-helper.c | 135 +++++++++++++++++++++++++---------------------- 3 files changed, 71 insertions(+), 88 deletions(-) diff --git a/Makefile.am b/Makefile.am index e71ec3ab..d7c9206e 100644 --- a/Makefile.am +++ b/Makefile.am @@ -133,16 +133,6 @@ xdg_dbus_proxy_LDADD = $(BASE_LIBS) libglnx.la xdg_dbus_proxy_CFLAGS = $(BASE_CFLAGS) -I$(srcdir)/libglnx -install-exec-hook: -if PRIV_MODE_SETUID - $(SUDO_BIN) chown root $(DESTDIR)$(bindir)/xdg-app-helper - $(SUDO_BIN) chmod u+s $(DESTDIR)$(bindir)/xdg-app-helper -else -if PRIV_MODE_FILECAPS - $(SUDO_BIN) setcap cap_sys_admin+ep $(DESTDIR)$(bindir)/xdg-app-helper -endif -endif - completiondir = $(datadir)/bash-completion/completions completion_DATA = completion/xdg-app EXTRA_DIST += $(completion_DATA) diff --git a/configure.ac b/configure.ac index 00c8ae94..a991a94a 100644 --- a/configure.ac +++ b/configure.ac @@ -61,20 +61,6 @@ PKG_CHECK_MODULES(OSTREE, [libgsystem >= 2015.1 ostree-1 >= 2015.3]) AC_SUBST(OSTREE_CFLAGS) AC_SUBST(OSTREE_LIBS) -AC_ARG_WITH(priv-mode, - AS_HELP_STRING([--with-priv-mode=setuid/caps/none], - [How to gain privileges]), - [], - [with_priv_mode="setuid"]) - -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]), - [SUDO_BIN="sudo"], [SUDO_BIN=""]) -AC_SUBST([SUDO_BIN]) - AC_ARG_ENABLE(documentation, AC_HELP_STRING([--enable-documentation], [Build documentation]),, enable_documentation=yes) diff --git a/xdg-app-helper.c b/xdg-app-helper.c index 8579186c..3455ce97 100644 --- a/xdg-app-helper.c +++ b/xdg-app-helper.c @@ -357,6 +357,7 @@ typedef enum { FILE_FLAGS_NON_FATAL = 1 << 0, FILE_FLAGS_IF_LAST_FAILED = 1 << 1, FILE_FLAGS_DEVICES = 1 << 2, + FILE_FLAGS_NOREMOUNT = 1 << 3, } file_flags_t; typedef struct { @@ -428,15 +429,15 @@ static const create_table_t create[] = { { FILE_TYPE_BIND_RO, "proc/bus", 0755, "proc/bus"}, { FILE_TYPE_DIR, "sys", 0755}, { FILE_TYPE_DIR, "sys/block", 0755}, - { FILE_TYPE_BIND_RO, "sys/block", 0755, "/sys/block"}, + { FILE_TYPE_BIND, "sys/block", 0755, "/sys/block", FILE_FLAGS_NOREMOUNT}, { FILE_TYPE_DIR, "sys/bus", 0755}, - { FILE_TYPE_BIND_RO, "sys/bus", 0755, "/sys/bus"}, + { FILE_TYPE_BIND, "sys/bus", 0755, "/sys/bus", FILE_FLAGS_NOREMOUNT}, { FILE_TYPE_DIR, "sys/class", 0755}, - { FILE_TYPE_BIND_RO, "sys/class", 0755, "/sys/class"}, + { FILE_TYPE_BIND, "sys/class", 0755, "/sys/class", FILE_FLAGS_NOREMOUNT}, { FILE_TYPE_DIR, "sys/dev", 0755}, - { FILE_TYPE_BIND_RO, "sys/dev", 0755, "/sys/dev"}, + { FILE_TYPE_BIND, "sys/dev", 0755, "/sys/dev", FILE_FLAGS_NOREMOUNT}, { FILE_TYPE_DIR, "sys/devices", 0755}, - { FILE_TYPE_BIND_RO, "sys/devices", 0755, "/sys/devices"}, + { FILE_TYPE_BIND, "sys/devices", 0755, "/sys/devices", FILE_FLAGS_NOREMOUNT}, { FILE_TYPE_DIR, "dev", 0755}, { FILE_TYPE_MOUNT, "dev"}, { FILE_TYPE_DIR, "dev/pts", 0755}, @@ -451,7 +452,6 @@ static const create_table_t create[] = { { FILE_TYPE_DEVICE, "dev/tty", 0666}, { FILE_TYPE_DIR, "dev/dri", 0755}, { FILE_TYPE_BIND_RO, "dev/dri", 0755, "/dev/dri", FILE_FLAGS_NON_FATAL|FILE_FLAGS_DEVICES, &allow_dri}, - { FILE_TYPE_REMOUNT, "dev", MS_RDONLY|MS_NOSUID|MS_NOEXEC}, }; /* warning: Don't create any actual files here, as we could potentially @@ -467,7 +467,7 @@ static const create_table_t create_post[] = { static const mount_table_t mount_table[] = { { "proc", "proc", "proc", NULL, MS_NOSUID|MS_NOEXEC|MS_NODEV }, { "tmpfs", "dev", "tmpfs", "mode=755", MS_NOSUID|MS_STRICTATIME }, - { "devpts", "dev/pts", "devpts","newinstance,ptmxmode=0666,mode=620,gid=5", MS_NOSUID|MS_NOEXEC }, + { "devpts", "dev/pts", "devpts","newinstance,ptmxmode=0666,mode=620", MS_NOSUID|MS_NOEXEC }, { "tmpfs", "dev/shm", "tmpfs", "mode=1777", MS_NOSUID|MS_NODEV|MS_STRICTATIME }, }; @@ -481,6 +481,7 @@ typedef enum { BIND_PRIVATE = (1<<1), BIND_DEVICES = (1<<2), BIND_RECURSIVE = (1<<3), + BIND_NOREMOUNT = (1<<4), } bind_option_t; typedef struct { @@ -688,6 +689,7 @@ bind_mount (const char *src, const char *dest, bind_option_t options) bool private = (options & BIND_PRIVATE) != 0; bool devices = (options & BIND_DEVICES) != 0; bool recursive = (options & BIND_RECURSIVE) != 0; + bool noremount = (options & BIND_NOREMOUNT) != 0; char **submounts; int i; @@ -701,7 +703,8 @@ bind_mount (const char *src, const char *dest, bind_option_t options) return 2; } - if (mount ("none", dest, + if (!noremount && + mount ("none", dest, NULL, MS_MGC_VAL|MS_BIND|MS_REMOUNT|(devices?0:MS_NODEV)|MS_NOSUID|(readonly?MS_RDONLY:0), NULL) != 0) return 3; @@ -880,11 +883,33 @@ copy_file (const char *src_path, const char *dst_path, mode_t mode) return res; } +static bool +write_file (const char *path, const char *content) +{ + int fd; + bool res; + int errsv; + + fd = open (path, O_RDWR | O_CLOEXEC, 0); + if (fd == -1) + return FALSE; + + res = TRUE; + if (content) + res = write_to_file (fd, content, strlen (content)); + + errsv = errno; + close (fd); + errno = errsv; + + return res; +} + static bool create_file (const char *path, mode_t mode, const char *content) { int fd; - int res; + bool res; int errsv; fd = creat (path, mode); @@ -999,7 +1024,9 @@ create_files (const create_table_t *create, int n_create, int ignore_shm, const if ((res = bind_mount (data, name, 0 | ((create[i].type == FILE_TYPE_BIND_RO) ? BIND_READONLY : 0) | - ((flags & FILE_FLAGS_DEVICES) ? BIND_DEVICES : 0)))) + ((flags & FILE_FLAGS_DEVICES) ? BIND_DEVICES : 0) | + ((flags & FILE_FLAGS_NOREMOUNT) ? BIND_NOREMOUNT : 0) + ))) { if (res > 1 || (flags & FILE_FLAGS_NON_FATAL) == 0) die_with_error ("mounting bindmount %s", name); @@ -1497,51 +1524,6 @@ do_init (int event_fd, pid_t initial_pid) return initial_exit_status; } -#define REQUIRED_CAPS (CAP_TO_MASK(CAP_SYS_ADMIN)) - -static void -acquire_caps (void) -{ - struct __user_cap_header_struct hdr; - struct __user_cap_data_struct data; - - if (getuid () != geteuid ()) - { - /* Tell kernel not clear capabilities when dropping root */ - if (prctl (PR_SET_KEEPCAPS, 1, 0, 0, 0) < 0) - die_with_error ("prctl(PR_SET_KEEPCAPS) failed"); - - /* Drop root uid, but retain the required permitted caps */ - if (setuid (getuid ()) < 0) - die_with_error ("unable to drop privs"); - } - - 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"); -} - -static void -drop_caps (void) -{ - struct __user_cap_header_struct hdr; - struct __user_cap_data_struct data; - - memset (&hdr, 0, sizeof(hdr)); - hdr.version = _LINUX_CAPABILITY_VERSION; - data.effective = 0; - data.permitted = 0; - data.inheritable = 0; - - if (capset (&hdr, &data) < 0) - die_with_error ("capset failed"); -} int main (int argc, char **argv) @@ -1574,15 +1556,13 @@ main (int argc, bool writable_app = FALSE; bool writable_exports = FALSE; char old_cwd[256]; + char *uid_map, *gid_map; int c, i; pid_t pid; int event_fd; int sync_fd = -1; char *endp; - /* Get the capabilities we need, drop root */ - acquire_caps (); - /* 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_CAPS) failed"); @@ -1768,7 +1748,7 @@ main (int argc, block_sigchild (); /* Block before we clone to avoid races */ - pid = raw_clone (SIGCHLD | CLONE_NEWNS | CLONE_NEWPID | + pid = raw_clone (SIGCHLD | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER | (network ? 0 : CLONE_NEWNET) | (ipc ? 0 : CLONE_NEWIPC), NULL); @@ -1777,12 +1757,27 @@ main (int argc, if (pid != 0) { - /* Drop all extra caps in the monitor child process */ - drop_caps (); monitor_child (event_fd); exit (0); /* Should not be reached, but better safe... */ } + /* 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); + + if (!write_file("/proc/self/setgroups", "deny\n")) + die_with_error ("error writing to setgroups"); + + 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); + old_umask = umask (0); /* Mark everything as slave, so that we still @@ -2002,9 +1997,6 @@ main (int argc, umask (old_umask); - /* Now we have everything we need CAP_SYS_ADMIN for, so drop it */ - drop_caps (); - if (chdir (old_cwd) < 0) { /* If the old cwd is not mapped, go to home */ @@ -2032,6 +2024,21 @@ main (int argc, free (tz_val); } + /* 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"); + + 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); + __debug__(("forking for child\n")); pid = fork ();