From 50b3de3728ad5cb1042e44af4d3f5bbfdc19b239 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Fri, 29 May 2015 10:46:10 +0200 Subject: [PATCH] helper: Optionally add back setuid support If you don't have userns support in your kernel you can use this. The future lies with userns though, so it is the default. --- Makefile.am | 11 ++++ configure.ac | 27 ++++++++++ xdg-app-helper.c | 128 ++++++++++++++++++++++++++++++++++++----------- 3 files changed, 138 insertions(+), 28 deletions(-) diff --git a/Makefile.am b/Makefile.am index d7c9206e..f593dd54 100644 --- a/Makefile.am +++ b/Makefile.am @@ -132,6 +132,17 @@ xdg_dbus_proxy_SOURCES = \ xdg_dbus_proxy_LDADD = $(BASE_LIBS) libglnx.la xdg_dbus_proxy_CFLAGS = $(BASE_CFLAGS) -I$(srcdir)/libglnx +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 +else +if PRIV_MODE_FILECAPS + $(SUDO_BIN) setcap cap_sys_admin+ep $(DESTDIR)$(bindir)/xdg-app-helper +endif +endif +endif completiondir = $(datadir)/bash-completion/completions completion_DATA = completion/xdg-app diff --git a/configure.ac b/configure.ac index a991a94a..b85b678a 100644 --- a/configure.ac +++ b/configure.ac @@ -61,6 +61,33 @@ PKG_CHECK_MODULES(OSTREE, [libgsystem >= 2015.1 ostree-1 >= 2015.3]) AC_SUBST(OSTREE_CFLAGS) AC_SUBST(OSTREE_LIBS) +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)]), + [], + [with_priv_mode="setuid"]) + +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)]), + [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 874642ff..5388bed4 100644 --- a/xdg-app-helper.c +++ b/xdg-app-helper.c @@ -1526,6 +1526,56 @@ 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 +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"); +} + +#endif + int main (int argc, char **argv) @@ -1558,13 +1608,17 @@ 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; +#ifdef DISABLE_USERNS + /* Get the 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) die_with_error ("prctl(PR_SET_NO_NEW_CAPS) failed"); @@ -1750,7 +1804,10 @@ main (int argc, block_sigchild (); /* Block before we clone to avoid races */ - pid = raw_clone (SIGCHLD | CLONE_NEWNS | CLONE_NEWPID | CLONE_NEWUSER | + pid = raw_clone (SIGCHLD | CLONE_NEWNS | CLONE_NEWPID | +#ifndef DISABLE_USERNS + CLONE_NEWUSER | +#endif (network ? 0 : CLONE_NEWNET) | (ipc ? 0 : CLONE_NEWIPC), NULL); @@ -1763,22 +1820,27 @@ main (int argc, 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); +#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); - if (!write_file("/proc/self/setgroups", "deny\n")) - die_with_error ("error writing to setgroups"); + 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); + 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 old_umask = umask (0); @@ -1999,6 +2061,11 @@ 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 (old_cwd) < 0) { /* If the old cwd is not mapped, go to home */ @@ -2026,20 +2093,25 @@ 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"); +#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 (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); + 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 __debug__(("forking for child\n"));