diff mbox

[OpenWrt-Devel,procd,v2,4/5] ujail: reworks & cleanups

Message ID 1440543643-43546-5-git-send-email-champetier.etienne@gmail.com
State Superseded
Headers show

Commit Message

Etienne Champetier Aug. 25, 2015, 11 p.m. UTC
-use EXIT_SUCCESS/EXIT_FAILURE (not -1)
-parse every option in main, put them in opts struct
-add CLONE_NEWIPC to the clone() call (it's already compiled in openwrt kernel)
-return the exit status of the jailed process, or the num of the signal that killed it
-add missing options to usage()
-add a warning in usage() about ujail security
-debug option can now take an int as parameter (~debug level),
  with -d2 you now activate "LD_DEBUG=all" for exemple
-do not depend on libpreload-seccomp.so if -S is not present
-there is now only one ujail process instead of two

jail creation is now as follow:
1) create jail root dir (mkdir)
2) create new namespace (clone)
(in the parent wait for the child with uloop)
3) build the jail root fs (mount bind all the libs/bins ...),
pivot_root and mount special fs (procfs, sysfs) (build_jail_fs())
4) build envp (LD_PRELOAD the seccomp helper or ...)
5) drop capabilities (next patch)
6) execve the jailed bin
7) remove jail root dir (once child is dead)

there is no need to umount anything because we are already in a namespace

Todo:
-allow signals from the parent to the child

Feature request:
-when we add a file or dir, detect if it's an exec and add it's dependencies

Signed-off-by: Etienne CHAMPETIER <champetier.etienne@gmail.com>
---
 jail/jail.c | 391 ++++++++++++++++++++++++------------------------------------
 1 file changed, 156 insertions(+), 235 deletions(-)
diff mbox

Patch

diff --git a/jail/jail.c b/jail/jail.c
index 2bba292..487d18f 100644
--- a/jail/jail.c
+++ b/jail/jail.c
@@ -43,7 +43,17 @@ 
 #include <libubox/uloop.h>
 
 #define STACK_SIZE	(1024 * 1024)
-#define OPT_ARGS	"P:S:n:r:w:psuldo"
+#define OPT_ARGS	"P:S:n:r:w:d:psulo"
+
+static struct {
+	char *path;
+	char *name;
+	char **jail_argv;
+	char *seccomp;
+	int procfs;
+	int ronly;
+	int sysfs;
+} opts;
 
 struct extra {
 	struct list_head list;
@@ -125,7 +135,7 @@  static int mount_bind(const char *root, const char *path, const char *name, int
 		return -1;
 	}
 
-	if (readonly && mount(old, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) {
+	if (readonly && mount(NULL, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) {
 		ERROR("failed to remount ro %s: %s\n", new, strerror(errno));
 		return -1;
 	}
@@ -135,80 +145,75 @@  static int mount_bind(const char *root, const char *path, const char *name, int
 	return 0;
 }
 
-static int build_jail(const char *path)
+static int build_jail_fs()
 {
 	struct library *l;
 	struct extra *m;
-	int ret = 0;
 
-	mkdir(path, 0755);
-
-	if (mount("tmpfs", path, "tmpfs", MS_NOATIME, "mode=0755")) {
+	if (mount("tmpfs", opts.path, "tmpfs", MS_NOATIME, "mode=0755")) {
 		ERROR("tmpfs mount failed %s\n", strerror(errno));
 		return -1;
 	}
 
-	avl_for_each_element(&libraries, l, avl)
-		if (mount_bind(path, l->path, l->name, 1, -1))
-			return -1;
-
-	list_for_each_entry(m, &extras, list)
-		if (mount_bind(path, m->path, m->name, m->readonly, 0))
-			return -1;
-
-	return ret;
-}
+	if (chdir(opts.path)) {
+		ERROR("failed to chdir() in the jail root\n");
+		return -1;
+	}
 
-static void _umount(const char *root, const char *path)
-{
-	char *buf = NULL;
+	avl_init(&libraries, avl_strcmp, false, NULL);
+	alloc_library_path("/lib64");
+	alloc_library_path("/lib");
+	alloc_library_path("/usr/lib");
+	load_ldso_conf("/etc/ld.so.conf");
 
-	if (asprintf(&buf, "%s%s", root, path) < 0) {
-		ERROR("failed to alloc umount buffer: %s\n", strerror(errno));
-	} else {
-		DEBUG("umount %s\n", buf);
-		umount(buf);
-		free(buf);
+	if (elf_load_deps(*opts.jail_argv)) {
+		ERROR("failed to load dependencies\n");
+		return -1;
 	}
-}
 
-static int stop_jail(const char *root)
-{
-	struct library *l;
-	struct extra *m;
+	if (opts.seccomp && elf_load_deps("libpreload-seccomp.so")) {
+		ERROR("failed to load libpreload-seccomp.so\n");
+		return -1;
+	}
 
-	avl_for_each_element(&libraries, l, avl) {
-		char path[256];
-		char *p = l->path;
+	avl_for_each_element(&libraries, l, avl)
+		if (mount_bind(opts.path, l->path, l->name, 1, -1))
+			return -1;
 
-		if (strstr(p, "local"))
-			p = "/lib";
+	list_for_each_entry(m, &extras, list)
+		if (mount_bind(opts.path, m->path, m->name, m->readonly, 0))
+			return -1;
 
-		snprintf(path, sizeof(path), "%s%s/%s", root, p, l->name);
-		DEBUG("umount %s\n", path);
-		umount(path);
+	char *mpoint;
+	if (asprintf(&mpoint, "%s/old", opts.path) < 0) {
+		ERROR("failed to alloc pivot path: %s\n", strerror(errno));
+		return -1;
 	}
-
-	list_for_each_entry(m, &extras, list) {
-		char path[256];
-
-		snprintf(path, sizeof(path), "%s%s/%s", root, m->path, m->name);
-		DEBUG("umount %s\n", path);
-		umount(path);
+	mkdir_p(mpoint, 0755);
+	if (pivot_root(opts.path, mpoint) == -1) {
+		ERROR("pivot_root failed:%s\n", strerror(errno));
+		free(mpoint);
+		return -1;
 	}
-
-	_umount(root, "/proc");
-	_umount(root, "/sys");
-
-	DEBUG("umount %s\n", root);
-	umount(root);
-	rmdir(root);
+	free(mpoint);
+	umount2("/old", MNT_DETACH);
+	rmdir("/old");
+	if (opts.procfs) {
+		mkdir("/proc", 0755);
+		mount("proc", "/proc", "proc", MS_NOATIME, 0);
+	}
+	if (opts.sysfs) {
+		mkdir("/sys", 0755);
+		mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0);
+	}
+	if (opts.ronly)
+		mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0);
 
 	return 0;
 }
 
 #define MAX_ENVP	8
-static char** build_envp(const char *seccomp, int debug)
+static char** build_envp(const char *seccomp)
 {
 	static char *envp[MAX_ENVP];
 	static char preload_var[64];
@@ -227,177 +232,77 @@  static char** build_envp(const char *seccomp, int debug)
 		snprintf(preload_var, sizeof(preload_var), "LD_PRELOAD=%s", preload_lib);
 		envp[count++] = preload_var;
 	}
-	if (debug)
+	if (debug > 1)
 		envp[count++] = debug_var;
 
 	return envp;
 }
 
-static int spawn(const char *path, char **argv, const char *seccomp)
-{
-	pid_t pid = fork();
-
-	if (pid < 0) {
-		ERROR("failed to spawn %s: %s\n", *argv, strerror(errno));
-		return -1;
-	} else if (!pid) {
-		char **envp = build_envp(seccomp, 0);
-
-		INFO("spawning %s\n", *argv);
-		execve(*argv, argv, envp);
-		ERROR("failed to spawn child %s: %s\n", *argv, strerror(errno));
-		exit(-1);
-	}
-
-	return pid;
-}
-
-static int usage(void)
+static void usage(void)
 {
-	fprintf(stderr, "jail <options> -D <binary> <params ...>\n");
+	fprintf(stderr, "ujail <options> -- <binary> <params ...>\n");
 	fprintf(stderr, "  -P <path>\tpath where the jail will be staged\n");
 	fprintf(stderr, "  -S <file>\tseccomp filter\n");
 	fprintf(stderr, "  -n <name>\tthe name of the jail\n");
 	fprintf(stderr, "  -r <file>\treadonly files that should be staged\n");
 	fprintf(stderr, "  -w <file>\twriteable files that should be staged\n");
-	fprintf(stderr, "  -p\t\tjail has /proc\t\n");
-	fprintf(stderr, "  -s\t\tjail has /sys\t\n");
-	fprintf(stderr, "  -l\t\tjail has /dev/log\t\n");
-	fprintf(stderr, "  -u\t\tjail has a ubus socket\t\n");
-
-	return -1;
+	fprintf(stderr, "  -d <num>\tshow debug log (increase num to increase verbosity)\n");
+	fprintf(stderr, "  -p\t\tjail has /proc\n");
+	fprintf(stderr, "  -s\t\tjail has /sys\n");
+	fprintf(stderr, "  -l\t\tjail has /dev/log\n");
+	fprintf(stderr, "  -u\t\tjail has a ubus socket\n");
+	fprintf(stderr, "  -o\t\tremont jail root (/) read only\n");
+	fprintf(stderr, "\nWarning: by default root inside the jail is the same\n\
+and he has the same powers as root outside the jail,\n\
+thus he can escape the jail and/or break stuff.\n\
+Please use an appropriate seccomp filter (-S) to restrict his powers\n");
 }
 
-static int child_running = 1;
-
-static void child_process_handler(struct uloop_process *c, int ret)
-{
-	INFO("child (%d) exited: %d\n", c->pid, ret);
-	uloop_end();
-	child_running = 0;
-}
-
-struct uloop_process child_process = {
-	.cb = child_process_handler,
-};
-
-static int spawn_child(void *arg)
+static int spawn_jail(void *arg)
 {
-	char *path = get_current_dir_name();
-	int procfs = 0, sysfs = 0;
-	char *seccomp = NULL;
-	char **argv = arg;
-	int argc = 0, ch;
-	char *mpoint;
-	int ronly = 0;
-
-	while (argv[argc])
-		argc++;
-
-	optind = 0;
-	while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {
-		switch (ch) {
-		case 'd':
-			debug = 1;
-			break;
-		case 'S':
-			seccomp = optarg;
-			break;
-		case 'p':
-			procfs = 1;
-			break;
-		case 'o':
-			ronly = 1;
-			break;
-		case 's':
-			sysfs = 1;
-			break;
-		case 'n':
-			if (sethostname(optarg, strlen(optarg)))
-				ERROR("failed to sethostname: %s\n", strerror(errno));
-			break;
-		}
+	if (opts.name && sethostname(opts.name, strlen(opts.name))) {
+		ERROR("failed to sethostname: %s\n", strerror(errno));
 	}
 
-	if (asprintf(&mpoint, "%s/old", path) < 0) {
-		ERROR("failed to alloc pivot path: %s\n", strerror(errno));
-		return -1;
-	}
-	mkdir_p(mpoint, 0755);
-	if (pivot_root(path, mpoint) == -1) {
-		ERROR("pivot_root failed:%s\n", strerror(errno));
-		return -1;
-	}
-	free(mpoint);
-	umount2("/old", MNT_DETACH);
-	rmdir("/old");
-	if (procfs) {
-		mkdir("/proc", 0755);
-		mount("proc", "/proc", "proc", MS_NOATIME, 0);
+	if (build_jail_fs()) {
+		ERROR("failed to build jail fs");
+		exit(EXIT_FAILURE);
 	}
-	if (sysfs) {
-		mkdir("/sys", 0755);
-		mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0);
-	}
-	if (ronly)
-		mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0);
 
-	uloop_init();
+	char **envp = build_envp(opts.seccomp);
+	if (!envp)
+		exit(EXIT_FAILURE);
 
-	child_process.pid = spawn(path, &argv[optind], seccomp);
-	uloop_process_add(&child_process);
-	uloop_run();
-	uloop_done();
-	if (child_running) {
-		kill(child_process.pid, SIGTERM);
-		waitpid(child_process.pid, NULL, 0);
-	}
+	//TODO: drop capabilities() here
+	//prctl(PR_CAPBSET_DROP, ..., 0, 0, 0);
 
-	return 0;
+	INFO("exec-ing %s\n", *opts.jail_argv);
+	execve(*opts.jail_argv, opts.jail_argv, envp);
+	//we get there only if execve fails
+	ERROR("failed to execve %s: %s\n", *opts.jail_argv, strerror(errno));
+	exit(EXIT_FAILURE);
 }
 
-static int namespace_running = 1;
+static int jail_running = 1;
+static int jail_return_code = 0;
 
-static void namespace_process_handler(struct uloop_process *c, int ret)
+static void jail_process_handler(struct uloop_process *c, int ret)
 {
-	INFO("namespace (%d) exited: %d\n", c->pid, ret);
+	if (WIFEXITED(ret)) {
+		jail_return_code = WEXITSTATUS(ret);
+		INFO("jail (%d) exited with exit: %d\n", c->pid, jail_return_code);
+	} else {
+		jail_return_code = WTERMSIG(ret);
+		INFO("jail (%d) exited with signal: %d\n", c->pid, jail_return_code);
+	}
+	jail_running = 0;
 	uloop_end();
-	namespace_running = 0;
 }
 
-struct uloop_process namespace_process = {
-	.cb = namespace_process_handler,
+static struct uloop_process jail_process = {
+	.cb = jail_process_handler,
 };
 
-static void spawn_namespace(const char *path, int argc, char **argv)
-{
-	char *dir = get_current_dir_name();
-
-	uloop_init();
-	if (chdir(path)) {
-		ERROR("failed to chdir() into the jail\n");
-		return;
-	}
-	namespace_process.pid = clone(spawn_child,
-			child_stack + STACK_SIZE,
-			CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, argv);
-
-	if (namespace_process.pid != -1) {
-		if (chdir(dir))
-			ERROR("failed to chdir() out of the jail\n");
-		free(dir);
-		uloop_process_add(&namespace_process);
-		uloop_run();
-		uloop_done();
-		if (namespace_running) {
-			kill(namespace_process.pid, SIGTERM);
-			waitpid(namespace_process.pid, NULL, 0);
-		}
-	} else {
-		ERROR("failed to spawn namespace: %s\n", strerror(errno));
-	}
-}
-
 static void add_extra(char *name, int readonly)
 {
 	struct extra *f;
@@ -419,16 +324,14 @@  static void add_extra(char *name, int readonly)
 int main(int argc, char **argv)
 {
 	uid_t uid = getuid();
-	const char *name = NULL;
-	char *path = NULL;
-	struct stat s;
-	int ch, ret;
 	char log[] = "/dev/log";
 	char ubus[] = "/var/run/ubus.sock";
+	int ret = EXIT_SUCCESS;
+	int ch;
 
 	if (uid) {
 		ERROR("not root, aborting: %s\n", strerror(errno));
-		return -1;
+		return EXIT_FAILURE;
 	}
 
 	umask(022);
@@ -436,15 +339,27 @@  int main(int argc, char **argv)
 	while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {
 		switch (ch) {
 		case 'd':
-			debug = 1;
+			debug = atoi(optarg);
+			break;
+		case 'p':
+			opts.procfs = 1;
+			break;
+		case 'o':
+			opts.ronly = 1;
+			break;
+		case 's':
+			opts.sysfs = 1;
+			break;
+		case 'S':
+			opts.seccomp = optarg;
+			add_extra(optarg, 1);
 			break;
 		case 'P':
-			path = optarg;
+			opts.path = optarg;
 			break;
 		case 'n':
-			name = optarg;
+			opts.name = optarg;
 			break;
-		case 'S':
 		case 'r':
 			add_extra(optarg, 1);
 			break;
@@ -460,46 +375,52 @@  int main(int argc, char **argv)
 		}
 	}
 
-	if (argc - optind < 1)
-		return usage();
+	//no <binary> param found
+	if (argc - optind < 1) {
+		usage();
+		return EXIT_FAILURE;
+	}
 
-	if (!path && asprintf(&path, "/tmp/%s", basename(argv[optind])) == -1) {
-		ERROR("failed to set root path\n: %s", strerror(errno));
-		return -1;
+	opts.jail_argv = &argv[optind];
+
+	if (!opts.path && asprintf(&opts.path, "/tmp/%s", basename(*opts.jail_argv)) == -1) {
+		ERROR("failed to asprintf root path: %s\n", strerror(errno));
+		return EXIT_FAILURE;
 	}
 
-	if (!stat(path, &s)) {
-		ERROR("%s already exists: %s\n", path, strerror(errno));
-		return -1;
+	if (mkdir(opts.path, 0755)) {
+		ERROR("unable to create root path: %s (%s)\n", opts.path, strerror(errno));
+		return EXIT_FAILURE;
 	}
 
-	if (name)
-		prctl(PR_SET_NAME, name, NULL, NULL, NULL);
+	if (opts.name)
+		prctl(PR_SET_NAME, opts.name, NULL, NULL, NULL);
 
-	avl_init(&libraries, avl_strcmp, false, NULL);
-	alloc_library_path("/lib64");
-	alloc_library_path("/lib");
-	alloc_library_path("/usr/lib");
-	load_ldso_conf("/etc/ld.so.conf");
+	uloop_init();
+	jail_process.pid = clone(spawn_jail,
+			child_stack + STACK_SIZE,
+			CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | SIGCHLD, argv);
 
-	if (elf_load_deps(argv[optind])) {
-		ERROR("failed to load dependencies\n");
-		return -1;
+	if (jail_process.pid != -1) {
+		uloop_process_add(&jail_process);
+		uloop_run();
+		uloop_done();
+		if (jail_running) {
+			kill(jail_process.pid, SIGTERM);
+			waitpid(jail_process.pid, NULL, 0);
+		}
+	} else {
+		ERROR("failed to spawn namespace: %s\n", strerror(errno));
+		ret = EXIT_FAILURE;
 	}
 
-	if (elf_load_deps("libpreload-seccomp.so")) {
-		ERROR("failed to load libpreload-seccomp.so\n");
-		return -1;
+	if (rmdir(opts.path)) {
+		ERROR("Unable to remove root path: %s (%s)\n", opts.path, strerror(errno));
+		ret = EXIT_FAILURE;
 	}
 
-	ret = build_jail(path);
-
-	if (!ret)
-		spawn_namespace(path, argc, argv);
-	else
-		ERROR("failed to build jail\n");
-
-	stop_jail(path);
+	if (ret)
+		return ret;
 
-	return ret;
+	return jail_return_code;
 }