[OpenWrt-Devel] [PATCH 2/2] [procd, RFC] ujail: reworks & cleanups
Etienne Champetier
champetier.etienne at gmail.com
Thu Aug 20 18:44:02 EDT 2015
2015-08-21 0:39 GMT+02:00 Etienne CHAMPETIER <champetier.etienne at gmail.com>:
> This is an RFC patch for ujail
>
> -use EXIT_SUCCESS/EXIT_FAILURE (not -1)
> -parse every options 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) coming soon: drop capabilities()
> 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 the namespace
>
> Todo:
> -add capabilities() support
> -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
>
forgot to say: run tested on openwrt CC ar71xx
>
> Signed-off-by: Etienne CHAMPETIER <champetier.etienne at gmail.com>
> ---
> jail/jail.c | 390
> ++++++++++++++++++++++++------------------------------------
> 1 file changed, 155 insertions(+), 235 deletions(-)
>
> diff --git a/jail/jail.c b/jail/jail.c
> index 2bba292..dd46c86 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;
> -}
> -
> -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;
> + 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");
> }
>
> -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,26 @@ 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;
> 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 +374,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;
> }
> --
> 1.9.1
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.infradead.org/pipermail/openwrt-devel/attachments/20150821/123aabea/attachment.htm>
-------------- next part --------------
_______________________________________________
openwrt-devel mailing list
openwrt-devel at lists.openwrt.org
https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
More information about the openwrt-devel
mailing list