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.
tingping/wmclass
Alexander Larsson 2016-01-28 09:43:14 +01:00
parent b8a4455e5c
commit 6349b3ffc1
4 changed files with 113 additions and 107 deletions

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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)]),