From a074f5bc331845dcbce7ac0b3c8d51d567958c6a Mon Sep 17 00:00:00 2001
From: Adam Jackson <ajax@redhat.com>
Date: Thu, 8 Apr 2010 14:03:45 -0400
Subject: [PATCH] chns: Like chroot, but effective.

Largely functionally equivalent to chroot(1), but creates a new
filesystem namespace so the chroot'd process can't escape.  It's
possible to do this with unshare/mount/chroot in a shell script, but
it's a bit tricky to get right.

Signed-off-by: Adam Jackson <ajax@redhat.com>
---
 configure.ac          |   24 ++++++++++
 sys-utils/Makefile.am |    5 ++
 sys-utils/chns.8      |   61 ++++++++++++++++++++++++++
 sys-utils/chns.c      |  113 +++++++++++++++++++++++++++++++++++++++++++++++++
 4 files changed, 203 insertions(+), 0 deletions(-)
 create mode 100644 sys-utils/chns.8
 create mode 100644 sys-utils/chns.c

diff --git a/configure.ac b/configure.ac
index 121ac50..8045bd3 100644
--- a/configure.ac
+++ b/configure.ac
@@ -741,6 +741,30 @@ fi
 AM_CONDITIONAL(BUILD_SWITCH_ROOT, test "x$build_switch_root" = xyes)
 
 
+AC_ARG_ENABLE([chns],
+  AS_HELP_STRING([--disable-chns], [do not build chns]),
+  [], enable_chns=check
+)
+if test "x$enable_chns" = xno; then
+  build_chns=no
+else
+  build_chns=yes
+  case $enable_chns:$linux_os in
+  yes:no) AC_MSG_ERROR([chns selected for non-linux system]);;
+  check:no) AC_MSG_WARN([non-linux system; do not build chns])
+            build_chns=no;;
+  esac
+  if test "x$build_chns" = xyes; then
+    case $enable_chns:$have_unshare in
+    yes:no) AC_MSG_ERROR([chns selected but unshare() function not found]);;
+    check:no) AC_MSG_WARN([unshare() function not found; do not build chns])
+              build_chns=no;;
+    esac
+  fi
+fi
+AM_CONDITIONAL(BUILD_CHNS, test "x$build_chns" = xyes)
+
+
 AC_ARG_ENABLE([pivot_root],
   AS_HELP_STRING([--disable-pivot_root], [do not build pivot_root]),
   [], enable_pivot_root=check
diff --git a/sys-utils/Makefile.am b/sys-utils/Makefile.am
index 76828cc..9578e3e 100644
--- a/sys-utils/Makefile.am
+++ b/sys-utils/Makefile.am
@@ -39,6 +39,11 @@ sbin_PROGRAMS += switch_root
 dist_man_MANS += switch_root.8
 endif
 
+if BUILD_CHNS
+sbin_PROGRAMS += chns
+dist_man_MANS += chns.8
+endif
+
 if BUILD_UNSHARE
 usrbin_exec_PROGRAMS += unshare
 dist_man_MANS += unshare.1
diff --git a/sys-utils/chns.8 b/sys-utils/chns.8
new file mode 100644
index 0000000..c0bdb10
--- /dev/null
+++ b/sys-utils/chns.8
@@ -0,0 +1,61 @@
+.\" Karel Zak <kzak@redhat.com>
+.TH CHNS 8 "APR 2010" "Linux"
+.SH NAME
+chns \- run a process in a new rooted namespace
+.SH SYNOPSIS
+.B chns
+.RB [ \-hV ]
+.LP
+.B chns
+.I newroot
+.I init
+.RI [ arg ...]
+.SH DESCRIPTION
+.B chns
+starts the 
+.I init
+process with the specifies arguments inside
+.I newroot.
+This is like the traditional
+.B chroot
+command, but
+.B chns
+creates a new filesystem namespace for the process.  It is not possible to escape
+the new root filesystem as it is with
+.B chroot
+nor do mount changes inside the new namespace appear in the parent (or vice versa).
+
+.SH OPTIONS
+.IP "\fB\-h, \-\-help\fP"
+show help and exit
+.IP "\fB\-V, \-\-version\fP"
+show version number and exit
+
+.SH RETURN VALUE
+.B chns
+returns 0 on success and 1 on failure.
+
+.SH NOTES
+As with
+.B chroot,
+the calling process should ensure open directory file descriptors are not
+inherited by the child process.  Otherwise, the child can escape the root
+with
+.B fchdir(3).
+
+.SH "SEE ALSO"
+.BR mount (8)
+.BR chroot (2)
+.BR init (8)
+.BR switch_root (8)
+.SH AUTHORS
+.nf
+.nf
+Adam Jackson <ajax@redhat.com>
+Peter Jones <pjones@redhat.com>
+Jeremy Katz <katzj@redhat.com>
+Karel Zak <kzak@redhat.com>
+.fi
+.SH AVAILABILITY
+The chns command is part of the util-linux-ng package and is available from
+ftp://ftp.kernel.org/pub/linux/utils/util-linux-ng/.
diff --git a/sys-utils/chns.c b/sys-utils/chns.c
new file mode 100644
index 0000000..0825e53
--- /dev/null
+++ b/sys-utils/chns.c
@@ -0,0 +1,113 @@
+/*
+ * chns.c - like chroot(1), but with teeth.
+ *
+ * Copyright 2002-2009 Red Hat, Inc.  All rights reserved.
+ *
+ * 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 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 this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * Authors:
+ *	Adam Jackson <ajax@redhat.com>
+ *
+ * Based on switchroot.c, which was written by:
+ *	Peter Jones <pjones@redhat.com>
+ *	Jeremy Katz <katzj@redhat.com>
+ */
+#include <sys/mount.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <err.h>
+#include <sched.h>
+
+#ifndef MS_MOVE
+#define MS_MOVE 8192
+#endif
+
+static int chns(const char *newroot)
+{
+	if (chdir(newroot)) {
+		warn("failed to change directory to %s", newroot);
+		return -1;
+	}
+
+	if (unshare(CLONE_NEWNS)) {
+		warn("failed to create mount namespace");
+		return -1;
+	}
+
+	if (mount(newroot, newroot, NULL, MS_BIND, NULL) < 0) {
+		warn("failed to bindmount %s", newroot);
+		return -1;
+	}
+
+	if (mount(newroot, "/", NULL, MS_MOVE, NULL) < 0) {
+		warn("failed to mount moving %s to /", newroot);
+		return -1;
+	}
+
+	if (chroot(newroot)) {
+		warn("failed to change root");
+		return -1;
+	}
+
+	return 0;
+}
+
+static void usage(FILE *output)
+{
+	fprintf(output, "usage: %s <newrootdir> <program> <args>\n",
+			program_invocation_short_name);
+	exit(output == stderr ? EXIT_FAILURE : EXIT_SUCCESS);
+}
+
+static void version(void)
+{
+	fprintf(stdout,  "%s from %s\n", program_invocation_short_name,
+			PACKAGE_STRING);
+	exit(EXIT_SUCCESS);
+}
+
+int main(int argc, char *argv[])
+{
+	char *newroot, *init, **initargs;
+
+	if (argv[1] && (!strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")))
+		usage(stdout);
+	if (argv[1] && (!strcmp(argv[1], "--version") || !strcmp(argv[1], "-V")))
+		version();
+	if (argc < 3)
+		usage(stderr);
+
+	newroot = argv[1];
+	init = argv[2];
+	initargs = &argv[2];
+
+	if (!*newroot || !*init)
+		usage(stderr);
+
+	if (chns(newroot))
+		errx(EXIT_FAILURE, "failed. Sorry.");
+
+	execv(init, initargs);
+	err(EXIT_FAILURE, "failed to execute %s", init);
+}
+
-- 
1.6.5.2

