summaryrefslogtreecommitdiff
path: root/src/linux-user-chroot.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/linux-user-chroot.c')
-rw-r--r--src/linux-user-chroot.c108
1 files changed, 89 insertions, 19 deletions
diff --git a/src/linux-user-chroot.c b/src/linux-user-chroot.c
index 66285e5..8b8700d 100644
--- a/src/linux-user-chroot.c
+++ b/src/linux-user-chroot.c
@@ -5,7 +5,8 @@
* "safely": I believe that this program, when deployed as setuid on a
* typical "distribution" such as RHEL or Debian, does not, even when
* used in combination with typical software installed on that
- * distribution, allow privilege escalation.
+ * distribution, allow privilege escalation. See the README for more
+ * details.
*
* Copyright 2011,2012 Colin Walters <walters@verbum.org>
*
@@ -24,16 +25,20 @@
* Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
+#include "config.h"
+
#define _GNU_SOURCE
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
+#include <errno.h>
#include <stdarg.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/prctl.h>
+#include <sys/fsuid.h>
#include <sys/mount.h>
#include <sys/syscall.h>
#include <sys/wait.h>
@@ -45,6 +50,20 @@
#define SECBIT_NOROOT_LOCKED (1 << 1)
#endif
+#ifndef PR_SET_NO_NEW_PRIVS
+#define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+/* Totally arbitrary; we're just trying to mitigate somewhat against
+ * DoS attacks. In practice uids can typically spawn multiple
+ * processes, so this isn't effective. What is needed is for the
+ * kernel to understand we're creating bind mounts on behalf of a
+ * given uid. Most likely this will happen if the kernel obsoletes
+ * this tool by allowing processes with PR_SET_NO_NEW_PRIVS to create
+ * private mounts or chroot.
+ */
+#define MAX_BIND_MOUNTS 1024
+
static void fatal (const char *message, ...) __attribute__ ((noreturn)) __attribute__ ((format (printf, 1, 2)));
static void fatal_errno (const char *message) __attribute__ ((noreturn));
@@ -102,6 +121,28 @@ reverse_mount_list (MountSpec *mount)
return prev;
}
+/**
+ * fsuid_chdir:
+ * @uid: User id we should use
+ * @path: Path string
+ *
+ * Like chdir() except we use the filesystem privileges of @uid.
+ */
+static int
+fsuid_chdir (uid_t uid,
+ const char *path)
+{
+ int errsv;
+ int ret;
+ /* Note we don't check errors here because we can't, basically */
+ (void) setfsuid (uid);
+ ret = chdir (path);
+ errsv = errno;
+ (void) setfsuid (0);
+ errno = errsv;
+ return ret;
+}
+
int
main (int argc,
char **argv)
@@ -114,7 +155,7 @@ main (int argc,
gid_t rgid, egid, sgid;
int after_mount_arg_index;
unsigned int n_mounts = 0;
- const unsigned int max_mounts = 50; /* Totally arbitrary... */
+ const unsigned int max_mounts = MAX_BIND_MOUNTS;
char **program_argv;
MountSpec *bind_mounts = NULL;
MountSpec *bind_mount_iter;
@@ -145,7 +186,17 @@ main (int argc,
fatal ("Too many mounts (maximum of %u)", n_mounts);
n_mounts++;
- if (strcmp (arg, "--mount-bind") == 0)
+ if (strcmp (arg, "--help") == 0)
+ {
+ printf ("%s\n", "See \"man linux-user-chroot\"");
+ exit (0);
+ }
+ else if (strcmp (arg, "--version") == 0)
+ {
+ printf ("%s\n", PACKAGE_STRING);
+ exit (0);
+ }
+ else if (strcmp (arg, "--mount-bind") == 0)
{
if ((argc - after_mount_arg_index) < 3)
fatal ("--mount-bind takes two arguments");
@@ -257,21 +308,25 @@ main (int argc,
clone_flags |= CLONE_NEWNET;
if ((child = syscall (__NR_clone, clone_flags, NULL)) < 0)
- perror ("clone");
+ fatal_errno ("clone");
if (child == 0)
{
/*
- * SECBIT_NOROOT helps close the main historical reason why only
- * uid 0 can chroot(2) - because unprivileged users can create
- * hard links to setuid binaries, and possibly confuse them into
- * looking at data (or loading libraries) that they don't
- * expect, and thus elevating privileges. With this, executing
- * a setuid program doesn't gain us any new Linux capabilities
- * (but it still changes uid). See below for where we create a
- * MS_NOSUID bind mount.
+ * First, we attempt to use PR_SET_NO_NEW_PRIVS, since it does
+ * exactly what we want - ensures the child can not gain any
+ * privileges, even attempting to execute setuid binaries.
+ *
+ * http://lwn.net/Articles/504879/
+ *
+ * If that's not available, we fall back to using SECBIT_NOROOT.
+ *
+ * Following the belt-and-suspenders model, we also make a
+ * MS_NOSUID bind mount below.
*/
- if (prctl (PR_SET_SECUREBITS,
+ if (prctl (PR_SET_NO_NEW_PRIVS, 1) < 0 && errno != EINVAL)
+ fatal_errno ("prctl (PR_SET_NO_NEW_PRIVS)");
+ else if (prctl (PR_SET_SECUREBITS,
SECBIT_NOROOT | SECBIT_NOROOT_LOCKED) < 0)
fatal_errno ("prctl (SECBIT_NOROOT)");
@@ -308,7 +363,9 @@ main (int argc,
}
else if (bind_mount_iter->type == MOUNT_SPEC_BIND)
{
- if (mount (bind_mount_iter->source, dest,
+ if (fsuid_chdir (ruid, bind_mount_iter->source) < 0)
+ fatal ("Couldn't chdir to bind mount source");
+ if (mount (".", dest,
NULL, MS_BIND | MS_PRIVATE, NULL) < 0)
fatal_errno ("mount (MS_BIND)");
}
@@ -322,13 +379,23 @@ main (int argc,
assert (0);
free (dest);
}
-
- /* Actually perform the chroot. */
- if (chroot (chroot_dir) < 0)
- fatal_errno ("chroot");
- if (chdir (chdir_target) < 0)
+
+ if (fsuid_chdir (ruid, chroot_dir) < 0)
fatal_errno ("chdir");
+ if (mount (".", ".", NULL, MS_BIND | MS_PRIVATE, NULL) < 0)
+ fatal_errno ("mount (MS_BIND)");
+
+ /* Only move if we're not actually just using / */
+ if (strcmp (chroot_dir, "/") != 0)
+ {
+ if (mount (chroot_dir, "/", NULL, MS_MOVE, NULL) < 0)
+ fatal_errno ("mount (MS_MOVE)");
+
+ if (chroot (".") < 0)
+ fatal_errno ("chroot");
+ }
+
/* Switch back to the uid of our invoking process. These calls are
* irrevocable - see setuid(2) */
if (setgid (rgid) < 0)
@@ -336,6 +403,9 @@ main (int argc,
if (setuid (ruid) < 0)
fatal_errno ("setuid");
+ if (chdir (chdir_target) < 0)
+ fatal_errno ("chdir");
+
if (execvp (program, program_argv) < 0)
fatal_errno ("execv");
}