diff options
author | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-09-24 17:01:42 +0000 |
---|---|---|
committer | Lars Wirzenius <lars.wirzenius@codethink.co.uk> | 2013-09-24 17:01:42 +0000 |
commit | d25cc110f69e6e71a95b4ac532dcfc5423d4a16b (patch) | |
tree | d731aaa7579a46a30880eef3e36647797ad6515f | |
parent | d234ccf15a897024bb004d0f3a9a2f3b8d0976e6 (diff) | |
parent | 5d23708442b16138b800a4e4e9daf20eda50ba46 (diff) | |
download | linux-user-chroot-d25cc110f69e6e71a95b4ac532dcfc5423d4a16b.tar.gz |
Merge branch 'baserock/larswirzenius/update-to-master' into baserock/morphbaserock/morph
Reviewed-by: Jonathan Maw
Reviewed-by: Daniel Silverstone
-rw-r--r-- | Makefile-docbook-man.am | 33 | ||||
-rw-r--r-- | Makefile-stub.am | 1 | ||||
-rw-r--r-- | Makefile-user-chroot.am | 4 | ||||
-rw-r--r-- | Makefile.am | 6 | ||||
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | README | 43 | ||||
-rwxr-xr-x | autogen.sh | 5 | ||||
-rw-r--r-- | configure.ac | 11 | ||||
-rw-r--r-- | doc/linux-user-chroot.8 | 103 | ||||
-rw-r--r-- | src/linux-user-chroot.c | 108 |
10 files changed, 249 insertions, 71 deletions
diff --git a/Makefile-docbook-man.am b/Makefile-docbook-man.am deleted file mode 100644 index 4bdb501..0000000 --- a/Makefile-docbook-man.am +++ /dev/null @@ -1,33 +0,0 @@ -# Docbook generation copied from systemd/Makefile.am -# -# Copyright 2010 Lennart Poettering -# -# systemd is free software; you can redistribute it and/or modify it -# under the terms of the GNU General Public License as published by -# the Free Software Foundation; either version 2 of the License, or -# (at your option) any later version. -# -# systemd is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with systemd; If not, see <http://www.gnu.org/licenses/>. - -XML_FILES = \ - ${patsubst %.1,%.xml,${patsubst %.3,%.xml,${patsubst %.5,%.xml,${patsubst %.7,%.xml,${patsubst %.8,%.xml,$(MANPAGES)}}}}} -EXTRA_DIST += $(XML_FILES) - -dist_man_MANS = $(MANPAGES) - -XSLTPROC_FLAGS = \ - --nonet \ - --param funcsynopsis.style "'ansi'" - -XSLTPROC_PROCESS_MAN = \ - $(AM_V_GEN)$(MKDIR_P) $(dir $@) && \ - $(XSLTPROC) -o $@ $(XSLTPROC_FLAGS) http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl $< - -doc/%.1: doc/%.xml - $(XSLTPROC_PROCESS_MAN) diff --git a/Makefile-stub.am b/Makefile-stub.am index fe4b88b..236aaaa 100644 --- a/Makefile-stub.am +++ b/Makefile-stub.am @@ -28,6 +28,7 @@ bin_PROGRAMS = sbin_PROGRAMS = bin_SCRIPTS = sbin_SCRIPTS = +dist_man_MANS = libexec_PROGRAMS = noinst_LTLIBRARIES = noinst_PROGRAMS = diff --git a/Makefile-user-chroot.am b/Makefile-user-chroot.am index c3801f5..32db975 100644 --- a/Makefile-user-chroot.am +++ b/Makefile-user-chroot.am @@ -28,3 +28,7 @@ endif linux_user_chroot_newnet_SOURCES = src/linux-user-chroot-newnet.c linux_user_chroot_newnet_CFLAGS = $(AM_CFLAGS) + +if BUILD_DOCUMENTATION +dist_man_MANS += doc/linux-user-chroot.8 +endif diff --git a/Makefile.am b/Makefile.am index af22885..e5279b2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -17,6 +17,6 @@ include Makefile-stub.am include Makefile-user-chroot.am -if HAVE_XSLTPROC -include Makefile-docbook-man.am -endif + +release-tag: + git tag -m "Release $(VERSION)" v$(VERSION) @@ -0,0 +1,6 @@ +2012.1 +------ + +This is the first release with a new version numbering scheme of +<YEAR.SERIAL>. No important code changes, but we now include a +contributed manual page from Lars Wirzenius. @@ -1,13 +1,23 @@ -Motivation ----------- +Summary +------- + +This tool allows regular (non-root) users to call chroot(2), create +Linux bind mounts, and use some Linux container features. It's +primarily intended for use by build systems. + +Project information +------------------- -It's really useful for build systems to be able to call chroot(2) as a -regular (non-root) user. +There's no web page yet; send patches to +Colin Walters <walters@verbum.org> -First, it ensures that the build isn't picking up files it shouldn't -be. This helps avoid the problem of "host contamination", where -e.g. we want libfoo.h from inside our root, not the one outside the -root. +Why is this useful? +------------------- + +For build systems, being inside a chroot ensures that the build isn't +picking up files it shouldn't be. This helps avoid the problem of +"host contamination", where e.g. we want libfoo.h from inside our +root, not the one outside the root. Second, it helps avoid the fragility inherent in having to set up a large set of environment variables pointing to our root (e.g. PATH, @@ -17,13 +27,27 @@ the same as it normally is (/bin:/usr/bin). Security -------- +**** IMPORTANT NOTE **** + +Installing this tool accessible to all users significantly increases +their ability to perform local, authenticated denial of service +attacks. The intended mitigation against this is to ensure the tool +is only executable by certain users. + +**** IMPORTANT NOTE **** + The historical reason Unix doesn't allow chroot(2) as non-root is because of setuid binaries. It's trivial to use chroot to create a hostile environment, then execute a setuid binary to subvert it. This tool closes that historical hole by simply disallowing privilege gain by execution of setuid binaries. It creates a "nosuid" bind -mount over "/". +mount over "/". This restriction is typically irrelevant for build +systems. + +However, this tool also allows creating bind mounts, which currently +have no resource controls. This is why this tool is not intended to +be installed by default. Abilities granted ----------------- @@ -75,4 +99,3 @@ This binary can be installed in two modes: 1) uwsr-xr-x root:root - Executable by everyone 2) uwsr-x--- root:somegroup - Executable only by somegroup - @@ -6,9 +6,8 @@ test -n "$srcdir" || srcdir=. olddir=`pwd` cd $srcdir -AUTORECONF=`which autoreconf` -if test -z $AUTORECONF; then - echo "*** No autoreconf found, please intall it ***" +if ! (autoreconf --version >/dev/null 2>&1); then + echo "*** No autoreconf found, please install it ***" exit 1 fi diff --git a/configure.ac b/configure.ac index da5863f..4910b32 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_PREREQ([2.63]) -AC_INIT([linux-user-chroot], [3], [walters@verbum.org]) +AC_INIT([linux-user-chroot], [2013.1], [walters@verbum.org]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) AC_CONFIG_AUX_DIR([build-aux]) @@ -8,6 +8,8 @@ AM_INIT_AUTOMAKE([1.11 -Wno-portability foreign no-define tar-ustar no-dist-gzip AM_MAINTAINER_MODE([enable]) AM_SILENT_RULES([yes]) +AC_SYS_LARGEFILE + AC_PROG_CC AM_PROG_CC_C_O @@ -26,8 +28,11 @@ AC_CHECK_HEADER([linux/securebits.h], [AC_DEFINE([HAVE_LINUX_SECUREBITS_H], [1], [Define to 1 if we have securebits.h])]) -AC_PATH_PROG([XSLTPROC], [xsltproc]) -AM_CONDITIONAL(HAVE_XSLTPROC, test x"$XSLTPROC" != x) +AC_ARG_ENABLE(documentation, + AC_HELP_STRING([--enable-documentation], + [build documentation]),, + enable_documentation=yes) +AM_CONDITIONAL(BUILD_DOCUMENTATION, test x$enable_documentation = xyes) AC_ARG_ENABLE(newnet-helper, AC_HELP_STRING([--enable-newnet-helper], diff --git a/doc/linux-user-chroot.8 b/doc/linux-user-chroot.8 new file mode 100644 index 0000000..f1cae55 --- /dev/null +++ b/doc/linux-user-chroot.8 @@ -0,0 +1,103 @@ +.\" Copyright 2012 Codethink Limited +.\" +.\" This program is free software; you can redistribute it and/or modify +.\" it under the terms of the GNU General Public License as published by +.\" the Free Software Foundation; either version 2 of the License, or +.\" (at your option) any later version. +.\" +.\" This program is distributed in the hope that it would be useful, +.\" but WITHOUT ANY WARRANTY; without even the implied warranty of +.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +.\" GNU General Public License for more details. +.\" +.\" You should have received a copy of the GNU General Public License +.\" along with this program; if not, write to the Free Software Foundation, +.\" Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +.\" +.TH LINUX-USER-CHROOT 8 +.SH NAME +linux\-user\-chroot \- safely allow normal users to chroot +.SH SYNOPSIS +.B linux\-user\-chroot +.RB [ --unshare-ipc ] +.RB [ --unshare-pid ] +.RB [ --unshare-net ] +.RB [ --mount-proc " \fIDIR\fR] +.RB [ --mount-readonly " \fIDIR\fR"] +.RB [ --mount-bind " \fISOURCE DEST\fR"] +.RB [ --chdir " \fIDIR\fR"] +.I ROOTDIR +.I PROGRAM +.IR ARGS... +.SH DESCRIPTION +.B linux\-user\-chroot +is a tool meant for building software in a clean environment. +The user needs to create a directory tree with the build dependencies needed, +and only those, +and then +.B linux\-user\-chroot +runs the actual build commands such that the commands only see the directory +tree. +This is useful for ensuring the build gets the right version of its build +dependencies, for example. +.PP +.B linux\-user\-chroot +works similary to +.BR chroot (8), +but does not require the caller to have root privileges. +It uses Linux containers to restrict the chroot to make this safe. +The command run inside the chroot is run as the calling user, not as root. +.PP +.B linux\-user\-chroot +executes a command, and sets the root directory for the command to the +directory specified by the user +.RI ( ROOTDIR ). +Additionally, it creates a "nosuid" bind mount over the root filesystem, +to prevent the build from gaining privileges using setuid binaries. +The command can further be restricted from accessing the network, +and it can be set up with new process ID and SysV IPC namespaces. +.SH OPTIONS +.TP +.BR \-\-unshare\-ipc +Create a new SysV IPC namespace for the command. +.TP +.BR \-\-unshare\-pid +Create a new process ID (PID) namespace for the command. +This prevents the command from seeing any other processes in the system, +except itself and the processes it itself creates. +.TP +.BR \-\-unshare\-net +Create a new, empty networking stack. +This prevents the command from using any networking, +including loopback. +.TP +.BI \-\-mount\-proc " DIR" +Mount the proc filesystem at +.IR DIR . +.TP +.BI \-\-mount\-readonly " DIR" +Make +.I DIR +be read-only for the command. +.TP +.BI \-\-mount\-bind " SOURCE DEST" +Add a bind mount while the command is executing. +.TP +.BI \-\-chdir " DIR" +After setting the new root directory for the command, +change the current working directory to be +.IR DIR . +.SH "EXIT STATUS" +The exit status is the exit status of the executed command, +or 1 if +.B linux\-user\-chroot +failed to execute the command. +.SH EXAMPLE +To build software in the real system, but without networking: +.IP +.nf +linux\-user\-chroot \-\-unshare\-net \-\-chdir "$(pwd)" +make clean all check +.fi +.SH "SEE ALSO" +.BR chroot (8). 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"); } |