<div dir="ltr"><br><div class="gmail_extra"><br><div class="gmail_quote">2015-08-21 0:39 GMT+02:00 Etienne CHAMPETIER <span dir="ltr"><<a href="mailto:champetier.etienne@gmail.com" target="_blank">champetier.etienne@gmail.com</a>></span>:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">This is an RFC patch for ujail<br>
<br>
-use EXIT_SUCCESS/EXIT_FAILURE (not -1)<br>
-parse every options in main, put them in opts struct<br>
-add CLONE_NEWIPC to the clone() call (it's already compiled in openwrt kernel)<br>
-return the exit status of the jailed process, or the num of the signal that killed it<br>
-add missing options to usage()<br>
-add a warning in usage() about ujail security<br>
-debug option can now take an int as parameter (~debug level),<br>
with -d2 you now activate "LD_DEBUG=all" for exemple<br>
-do not depend on libpreload-seccomp.so if -S is not present<br>
-there is now only one ujail process instead of two<br>
<br>
jail creation is now as follow:<br>
1) create jail root dir (mkdir)<br>
2) create new namespace (clone)<br>
(in the parent wait for the child with uloop)<br>
3) build the jail root fs (mount bind all the libs/bins ...),<br>
pivot_root and mount special fs (procfs, sysfs) (build_jail_fs())<br>
4) build envp (LD_PRELOAD the seccomp helper or ...)<br>
5) coming soon: drop capabilities()<br>
6) execve the jailed bin<br>
7) remove jail root dir (once child is dead)<br>
<br>
there is no need to umount anything because we are already in the namespace<br>
<br>
Todo:<br>
-add capabilities() support<br>
-allow signals from the parent to the child<br>
<br>
Feature request:<br>
-when we add a file or dir, detect if it's an exec and add it's dependencies<br></blockquote><div><br></div><div>forgot to say: run tested on openwrt CC ar71xx<br></div><div> </div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Signed-off-by: Etienne CHAMPETIER <<a href="mailto:champetier.etienne@gmail.com">champetier.etienne@gmail.com</a>><br>
---<br>
jail/jail.c | 390 ++++++++++++++++++++++++------------------------------------<br>
1 file changed, 155 insertions(+), 235 deletions(-)<br>
<br>
diff --git a/jail/jail.c b/jail/jail.c<br>
index 2bba292..dd46c86 100644<br>
--- a/jail/jail.c<br>
+++ b/jail/jail.c<br>
@@ -43,7 +43,17 @@<br>
#include <libubox/uloop.h><br>
<br>
#define STACK_SIZE (1024 * 1024)<br>
-#define OPT_ARGS "P:S:n:r:w:psuldo"<br>
+#define OPT_ARGS "P:S:n:r:w:d:psulo"<br>
+<br>
+static struct {<br>
+ char *path;<br>
+ char *name;<br>
+ char **jail_argv;<br>
+ char *seccomp;<br>
+ int procfs;<br>
+ int ronly;<br>
+ int sysfs;<br>
+} opts;<br>
<br>
struct extra {<br>
struct list_head list;<br>
@@ -125,7 +135,7 @@ static int mount_bind(const char *root, const char *path, const char *name, int<br>
return -1;<br>
}<br>
<br>
- if (readonly && mount(old, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) {<br>
+ if (readonly && mount(NULL, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) {<br>
ERROR("failed to remount ro %s: %s\n", new, strerror(errno));<br>
return -1;<br>
}<br>
@@ -135,80 +145,75 @@ static int mount_bind(const char *root, const char *path, const char *name, int<br>
return 0;<br>
}<br>
<br>
-static int build_jail(const char *path)<br>
+static int build_jail_fs()<br>
{<br>
struct library *l;<br>
struct extra *m;<br>
- int ret = 0;<br>
<br>
- mkdir(path, 0755);<br>
-<br>
- if (mount("tmpfs", path, "tmpfs", MS_NOATIME, "mode=0755")) {<br>
+ if (mount("tmpfs", opts.path, "tmpfs", MS_NOATIME, "mode=0755")) {<br>
ERROR("tmpfs mount failed %s\n", strerror(errno));<br>
return -1;<br>
}<br>
<br>
- avl_for_each_element(&libraries, l, avl)<br>
- if (mount_bind(path, l->path, l->name, 1, -1))<br>
- return -1;<br>
-<br>
- list_for_each_entry(m, &extras, list)<br>
- if (mount_bind(path, m->path, m->name, m->readonly, 0))<br>
- return -1;<br>
-<br>
- return ret;<br>
-}<br>
+ if (chdir(opts.path)) {<br>
+ ERROR("failed to chdir() in the jail root\n");<br>
+ return -1;<br>
+ }<br>
<br>
-static void _umount(const char *root, const char *path)<br>
-{<br>
- char *buf = NULL;<br>
+ avl_init(&libraries, avl_strcmp, false, NULL);<br>
+ alloc_library_path("/lib64");<br>
+ alloc_library_path("/lib");<br>
+ alloc_library_path("/usr/lib");<br>
+ load_ldso_conf("/etc/ld.so.conf");<br>
<br>
- if (asprintf(&buf, "%s%s", root, path) < 0) {<br>
- ERROR("failed to alloc umount buffer: %s\n", strerror(errno));<br>
- } else {<br>
- DEBUG("umount %s\n", buf);<br>
- umount(buf);<br>
- free(buf);<br>
+ if (elf_load_deps(*opts.jail_argv)) {<br>
+ ERROR("failed to load dependencies\n");<br>
+ return -1;<br>
}<br>
-}<br>
<br>
-static int stop_jail(const char *root)<br>
-{<br>
- struct library *l;<br>
- struct extra *m;<br>
+ if (opts.seccomp && elf_load_deps("libpreload-seccomp.so")) {<br>
+ ERROR("failed to load libpreload-seccomp.so\n");<br>
+ return -1;<br>
+ }<br>
<br>
- avl_for_each_element(&libraries, l, avl) {<br>
- char path[256];<br>
- char *p = l->path;<br>
+ avl_for_each_element(&libraries, l, avl)<br>
+ if (mount_bind(opts.path, l->path, l->name, 1, -1))<br>
+ return -1;<br>
<br>
- if (strstr(p, "local"))<br>
- p = "/lib";<br>
+ list_for_each_entry(m, &extras, list)<br>
+ if (mount_bind(opts.path, m->path, m->name, m->readonly, 0))<br>
+ return -1;<br>
<br>
- snprintf(path, sizeof(path), "%s%s/%s", root, p, l->name);<br>
- DEBUG("umount %s\n", path);<br>
- umount(path);<br>
+ char *mpoint;<br>
+ if (asprintf(&mpoint, "%s/old", opts.path) < 0) {<br>
+ ERROR("failed to alloc pivot path: %s\n", strerror(errno));<br>
+ return -1;<br>
}<br>
-<br>
- list_for_each_entry(m, &extras, list) {<br>
- char path[256];<br>
-<br>
- snprintf(path, sizeof(path), "%s%s/%s", root, m->path, m->name);<br>
- DEBUG("umount %s\n", path);<br>
- umount(path);<br>
+ mkdir_p(mpoint, 0755);<br>
+ if (pivot_root(opts.path, mpoint) == -1) {<br>
+ ERROR("pivot_root failed:%s\n", strerror(errno));<br>
+ free(mpoint);<br>
+ return -1;<br>
}<br>
-<br>
- _umount(root, "/proc");<br>
- _umount(root, "/sys");<br>
-<br>
- DEBUG("umount %s\n", root);<br>
- umount(root);<br>
- rmdir(root);<br>
+ free(mpoint);<br>
+ umount2("/old", MNT_DETACH);<br>
+ rmdir("/old");<br>
+ if (opts.procfs) {<br>
+ mkdir("/proc", 0755);<br>
+ mount("proc", "/proc", "proc", MS_NOATIME, 0);<br>
+ }<br>
+ if (opts.sysfs) {<br>
+ mkdir("/sys", 0755);<br>
+ mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0);<br>
+ }<br>
+ if (opts.ronly)<br>
+ mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0);<br>
<br>
return 0;<br>
}<br>
<br>
#define MAX_ENVP 8<br>
-static char** build_envp(const char *seccomp, int debug)<br>
+static char** build_envp(const char *seccomp)<br>
{<br>
static char *envp[MAX_ENVP];<br>
static char preload_var[64];<br>
@@ -227,177 +232,77 @@ static char** build_envp(const char *seccomp, int debug)<br>
snprintf(preload_var, sizeof(preload_var), "LD_PRELOAD=%s", preload_lib);<br>
envp[count++] = preload_var;<br>
}<br>
- if (debug)<br>
+ if (debug > 1)<br>
envp[count++] = debug_var;<br>
<br>
return envp;<br>
}<br>
<br>
-static int spawn(const char *path, char **argv, const char *seccomp)<br>
-{<br>
- pid_t pid = fork();<br>
-<br>
- if (pid < 0) {<br>
- ERROR("failed to spawn %s: %s\n", *argv, strerror(errno));<br>
- return -1;<br>
- } else if (!pid) {<br>
- char **envp = build_envp(seccomp, 0);<br>
-<br>
- INFO("spawning %s\n", *argv);<br>
- execve(*argv, argv, envp);<br>
- ERROR("failed to spawn child %s: %s\n", *argv, strerror(errno));<br>
- exit(-1);<br>
- }<br>
-<br>
- return pid;<br>
-}<br>
-<br>
-static int usage(void)<br>
+static void usage(void)<br>
{<br>
- fprintf(stderr, "jail <options> -D <binary> <params ...>\n");<br>
+ fprintf(stderr, "ujail <options> -- <binary> <params ...>\n");<br>
fprintf(stderr, " -P <path>\tpath where the jail will be staged\n");<br>
fprintf(stderr, " -S <file>\tseccomp filter\n");<br>
fprintf(stderr, " -n <name>\tthe name of the jail\n");<br>
fprintf(stderr, " -r <file>\treadonly files that should be staged\n");<br>
fprintf(stderr, " -w <file>\twriteable files that should be staged\n");<br>
- fprintf(stderr, " -p\t\tjail has /proc\t\n");<br>
- fprintf(stderr, " -s\t\tjail has /sys\t\n");<br>
- fprintf(stderr, " -l\t\tjail has /dev/log\t\n");<br>
- fprintf(stderr, " -u\t\tjail has a ubus socket\t\n");<br>
-<br>
- return -1;<br>
-}<br>
-<br>
-static int child_running = 1;<br>
-<br>
-static void child_process_handler(struct uloop_process *c, int ret)<br>
-{<br>
- INFO("child (%d) exited: %d\n", c->pid, ret);<br>
- uloop_end();<br>
- child_running = 0;<br>
+ fprintf(stderr, " -d <num>\tshow debug log (increase num to increase verbosity)\n");<br>
+ fprintf(stderr, " -p\t\tjail has /proc\n");<br>
+ fprintf(stderr, " -s\t\tjail has /sys\n");<br>
+ fprintf(stderr, " -l\t\tjail has /dev/log\n");<br>
+ fprintf(stderr, " -u\t\tjail has a ubus socket\n");<br>
+ fprintf(stderr, " -o\t\tremont jail root (/) read only\n");<br>
+ fprintf(stderr, "\nWarning: by default root inside the jail is the same\n\<br>
+and he has the same powers as root outside the jail,\n\<br>
+thus he can escape the jail and/or break stuff.\n\<br>
+Please use an appropriate seccomp filter (-S) to restrict his powers\n");<br>
}<br>
<br>
-struct uloop_process child_process = {<br>
- .cb = child_process_handler,<br>
-};<br>
-<br>
-static int spawn_child(void *arg)<br>
+static int spawn_jail(void *arg)<br>
{<br>
- char *path = get_current_dir_name();<br>
- int procfs = 0, sysfs = 0;<br>
- char *seccomp = NULL;<br>
- char **argv = arg;<br>
- int argc = 0, ch;<br>
- char *mpoint;<br>
- int ronly = 0;<br>
-<br>
- while (argv[argc])<br>
- argc++;<br>
-<br>
- optind = 0;<br>
- while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {<br>
- switch (ch) {<br>
- case 'd':<br>
- debug = 1;<br>
- break;<br>
- case 'S':<br>
- seccomp = optarg;<br>
- break;<br>
- case 'p':<br>
- procfs = 1;<br>
- break;<br>
- case 'o':<br>
- ronly = 1;<br>
- break;<br>
- case 's':<br>
- sysfs = 1;<br>
- break;<br>
- case 'n':<br>
- if (sethostname(optarg, strlen(optarg)))<br>
- ERROR("failed to sethostname: %s\n", strerror(errno));<br>
- break;<br>
- }<br>
+ if (<a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a> && sethostname(<a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a>, strlen(<a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a>))) {<br>
+ ERROR("failed to sethostname: %s\n", strerror(errno));<br>
}<br>
<br>
- if (asprintf(&mpoint, "%s/old", path) < 0) {<br>
- ERROR("failed to alloc pivot path: %s\n", strerror(errno));<br>
- return -1;<br>
- }<br>
- mkdir_p(mpoint, 0755);<br>
- if (pivot_root(path, mpoint) == -1) {<br>
- ERROR("pivot_root failed:%s\n", strerror(errno));<br>
- return -1;<br>
- }<br>
- free(mpoint);<br>
- umount2("/old", MNT_DETACH);<br>
- rmdir("/old");<br>
- if (procfs) {<br>
- mkdir("/proc", 0755);<br>
- mount("proc", "/proc", "proc", MS_NOATIME, 0);<br>
+ if (build_jail_fs()) {<br>
+ ERROR("failed to build jail fs");<br>
+ exit(EXIT_FAILURE);<br>
}<br>
- if (sysfs) {<br>
- mkdir("/sys", 0755);<br>
- mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0);<br>
- }<br>
- if (ronly)<br>
- mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0);<br>
<br>
- uloop_init();<br>
+ char **envp = build_envp(opts.seccomp);<br>
+ if (!envp)<br>
+ exit(EXIT_FAILURE);<br>
<br>
- child_process.pid = spawn(path, &argv[optind], seccomp);<br>
- uloop_process_add(&child_process);<br>
- uloop_run();<br>
- uloop_done();<br>
- if (child_running) {<br>
- kill(child_process.pid, SIGTERM);<br>
- waitpid(child_process.pid, NULL, 0);<br>
- }<br>
+ //TODO: drop capabilities() here<br>
+ //prctl(PR_CAPBSET_DROP, ..., 0, 0, 0);<br>
<br>
- return 0;<br>
+ INFO("exec-ing %s\n", *opts.jail_argv);<br>
+ execve(*opts.jail_argv, opts.jail_argv, envp);<br>
+ //we get there only if execve fails<br>
+ ERROR("failed to execve %s: %s\n", *opts.jail_argv, strerror(errno));<br>
+ exit(EXIT_FAILURE);<br>
}<br>
<br>
-static int namespace_running = 1;<br>
+static int jail_running = 1;<br>
+static int jail_return_code = 0;<br>
<br>
-static void namespace_process_handler(struct uloop_process *c, int ret)<br>
+static void jail_process_handler(struct uloop_process *c, int ret)<br>
{<br>
- INFO("namespace (%d) exited: %d\n", c->pid, ret);<br>
+ if (WIFEXITED(ret)) {<br>
+ jail_return_code = WEXITSTATUS(ret);<br>
+ INFO("jail (%d) exited with exit: %d\n", c->pid, jail_return_code);<br>
+ } else {<br>
+ jail_return_code = WTERMSIG(ret);<br>
+ INFO("jail (%d) exited with signal: %d\n", c->pid, jail_return_code);<br>
+ }<br>
+ jail_running = 0;<br>
uloop_end();<br>
- namespace_running = 0;<br>
}<br>
<br>
-struct uloop_process namespace_process = {<br>
- .cb = namespace_process_handler,<br>
+static struct uloop_process jail_process = {<br>
+ .cb = jail_process_handler,<br>
};<br>
<br>
-static void spawn_namespace(const char *path, int argc, char **argv)<br>
-{<br>
- char *dir = get_current_dir_name();<br>
-<br>
- uloop_init();<br>
- if (chdir(path)) {<br>
- ERROR("failed to chdir() into the jail\n");<br>
- return;<br>
- }<br>
- namespace_process.pid = clone(spawn_child,<br>
- child_stack + STACK_SIZE,<br>
- CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, argv);<br>
-<br>
- if (namespace_process.pid != -1) {<br>
- if (chdir(dir))<br>
- ERROR("failed to chdir() out of the jail\n");<br>
- free(dir);<br>
- uloop_process_add(&namespace_process);<br>
- uloop_run();<br>
- uloop_done();<br>
- if (namespace_running) {<br>
- kill(namespace_process.pid, SIGTERM);<br>
- waitpid(namespace_process.pid, NULL, 0);<br>
- }<br>
- } else {<br>
- ERROR("failed to spawn namespace: %s\n", strerror(errno));<br>
- }<br>
-}<br>
-<br>
static void add_extra(char *name, int readonly)<br>
{<br>
struct extra *f;<br>
@@ -419,16 +324,14 @@ static void add_extra(char *name, int readonly)<br>
int main(int argc, char **argv)<br>
{<br>
uid_t uid = getuid();<br>
- const char *name = NULL;<br>
- char *path = NULL;<br>
- struct stat s;<br>
- int ch, ret;<br>
char log[] = "/dev/log";<br>
char ubus[] = "/var/run/ubus.sock";<br>
+ int ret = EXIT_SUCCESS;<br>
+ int ch;<br>
<br>
if (uid) {<br>
ERROR("not root, aborting: %s\n", strerror(errno));<br>
- return -1;<br>
+ return EXIT_FAILURE;<br>
}<br>
<br>
umask(022);<br>
@@ -436,15 +339,26 @@ int main(int argc, char **argv)<br>
while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {<br>
switch (ch) {<br>
case 'd':<br>
- debug = 1;<br>
+ debug = atoi(optarg);<br>
+ break;<br>
+ case 'p':<br>
+ opts.procfs = 1;<br>
+ break;<br>
+ case 'o':<br>
+ opts.ronly = 1;<br>
+ break;<br>
+ case 's':<br>
+ opts.sysfs = 1;<br>
+ break;<br>
+ case 'S':<br>
+ opts.seccomp = optarg;<br>
break;<br>
case 'P':<br>
- path = optarg;<br>
+ opts.path = optarg;<br>
break;<br>
case 'n':<br>
- name = optarg;<br>
+ <a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a> = optarg;<br>
break;<br>
- case 'S':<br>
case 'r':<br>
add_extra(optarg, 1);<br>
break;<br>
@@ -460,46 +374,52 @@ int main(int argc, char **argv)<br>
}<br>
}<br>
<br>
- if (argc - optind < 1)<br>
- return usage();<br>
+ //no <binary> param found<br>
+ if (argc - optind < 1) {<br>
+ usage();<br>
+ return EXIT_FAILURE;<br>
+ }<br>
<br>
- if (!path && asprintf(&path, "/tmp/%s", basename(argv[optind])) == -1) {<br>
- ERROR("failed to set root path\n: %s", strerror(errno));<br>
- return -1;<br>
+ opts.jail_argv = &argv[optind];<br>
+<br>
+ if (!opts.path && asprintf(&opts.path, "/tmp/%s", basename(*opts.jail_argv)) == -1) {<br>
+ ERROR("failed to asprintf root path: %s\n", strerror(errno));<br>
+ return EXIT_FAILURE;<br>
}<br>
<br>
- if (!stat(path, &s)) {<br>
- ERROR("%s already exists: %s\n", path, strerror(errno));<br>
- return -1;<br>
+ if (mkdir(opts.path, 0755)) {<br>
+ ERROR("unable to create root path: %s (%s)\n", opts.path, strerror(errno));<br>
+ return EXIT_FAILURE;<br>
}<br>
<br>
- if (name)<br>
- prctl(PR_SET_NAME, name, NULL, NULL, NULL);<br>
+ if (<a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a>)<br>
+ prctl(PR_SET_NAME, <a href="http://opts.name" rel="noreferrer" target="_blank">opts.name</a>, NULL, NULL, NULL);<br>
<br>
- avl_init(&libraries, avl_strcmp, false, NULL);<br>
- alloc_library_path("/lib64");<br>
- alloc_library_path("/lib");<br>
- alloc_library_path("/usr/lib");<br>
- load_ldso_conf("/etc/ld.so.conf");<br>
+ uloop_init();<br>
+ jail_process.pid = clone(spawn_jail,<br>
+ child_stack + STACK_SIZE,<br>
+ CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | SIGCHLD, argv);<br>
<br>
- if (elf_load_deps(argv[optind])) {<br>
- ERROR("failed to load dependencies\n");<br>
- return -1;<br>
+ if (jail_process.pid != -1) {<br>
+ uloop_process_add(&jail_process);<br>
+ uloop_run();<br>
+ uloop_done();<br>
+ if (jail_running) {<br>
+ kill(jail_process.pid, SIGTERM);<br>
+ waitpid(jail_process.pid, NULL, 0);<br>
+ }<br>
+ } else {<br>
+ ERROR("failed to spawn namespace: %s\n", strerror(errno));<br>
+ ret = EXIT_FAILURE;<br>
}<br>
<br>
- if (elf_load_deps("libpreload-seccomp.so")) {<br>
- ERROR("failed to load libpreload-seccomp.so\n");<br>
- return -1;<br>
+ if (rmdir(opts.path)) {<br>
+ ERROR("Unable to remove root path: %s (%s)\n", opts.path, strerror(errno));<br>
+ ret = EXIT_FAILURE;<br>
}<br>
<br>
- ret = build_jail(path);<br>
-<br>
- if (!ret)<br>
- spawn_namespace(path, argc, argv);<br>
- else<br>
- ERROR("failed to build jail\n");<br>
-<br>
- stop_jail(path);<br>
+ if (ret)<br>
+ return ret;<br>
<br>
- return ret;<br>
+ return jail_return_code;<br>
}<br>
<span class="HOEnZb"><font color="#888888">--<br>
1.9.1<br>
<br>
</font></span></blockquote></div><br></div></div>