summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-09-24 17:01:42 +0000
committerLars Wirzenius <lars.wirzenius@codethink.co.uk>2013-09-24 17:01:42 +0000
commitd25cc110f69e6e71a95b4ac532dcfc5423d4a16b (patch)
treed731aaa7579a46a30880eef3e36647797ad6515f
parentd234ccf15a897024bb004d0f3a9a2f3b8d0976e6 (diff)
parent5d23708442b16138b800a4e4e9daf20eda50ba46 (diff)
downloadlinux-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.am33
-rw-r--r--Makefile-stub.am1
-rw-r--r--Makefile-user-chroot.am4
-rw-r--r--Makefile.am6
-rw-r--r--NEWS6
-rw-r--r--README43
-rwxr-xr-xautogen.sh5
-rw-r--r--configure.ac11
-rw-r--r--doc/linux-user-chroot.8103
-rw-r--r--src/linux-user-chroot.c108
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)
diff --git a/NEWS b/NEWS
new file mode 100644
index 0000000..7e5921f
--- /dev/null
+++ b/NEWS
@@ -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.
diff --git a/README b/README
index bab4b9b..a107280 100644
--- a/README
+++ b/README
@@ -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
-
diff --git a/autogen.sh b/autogen.sh
index 6035bc0..4674ca8 100755
--- a/autogen.sh
+++ b/autogen.sh
@@ -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");
}