summaryrefslogtreecommitdiffstats
path: root/jail
diff options
context:
space:
mode:
authorJohn Crispin <blogic@openwrt.org>2015-03-21 11:47:01 +0100
committerJohn Crispin <blogic@openwrt.org>2015-03-23 08:09:26 +0100
commitdfcfcca7baf2b22d8dac1a724bdb7dd9d52f4c05 (patch)
tree3a816421a7ad85c27bc9a174d73cf8fda7b776c8 /jail
parent0cf744c720c9ed01c2dae25f338d4e96b9db95e3 (diff)
downloadunitd-dfcfcca7baf2b22d8dac1a724bdb7dd9d52f4c05.tar
unitd-dfcfcca7baf2b22d8dac1a724bdb7dd9d52f4c05.zip
add initial version of ujail and utrace
Signed-off-by: John Crispin <blogic@openwrt.org>
Diffstat (limited to 'jail')
-rw-r--r--jail/elf.c369
-rw-r--r--jail/elf.h38
-rw-r--r--jail/jail.c492
-rw-r--r--jail/log.h26
-rw-r--r--jail/preload.c86
-rw-r--r--jail/seccomp-bpf.h77
-rw-r--r--jail/seccomp.c142
-rw-r--r--jail/seccomp.h19
8 files changed, 1249 insertions, 0 deletions
diff --git a/jail/elf.c b/jail/elf.c
new file mode 100644
index 0000000..c198599
--- /dev/null
+++ b/jail/elf.c
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <values.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <glob.h>
+#include <elf.h>
+
+#include <libubox/avl.h>
+#include <libubox/avl-cmp.h>
+#include <libubox/utils.h>
+#include <libubox/list.h>
+#include <libubox/uloop.h>
+
+#include "elf.h"
+
+struct avl_tree libraries;
+static LIST_HEAD(library_paths);
+
+void alloc_library_path(const char *path)
+{
+ struct library_path *p;
+ char *_path;
+
+ p = calloc_a(sizeof(*p),
+ &_path, strlen(path) + 1);
+ if (!p)
+ return;
+
+ p->path = strcpy(_path, path);
+
+ list_add_tail(&p->list, &library_paths);
+ DEBUG("adding ld.so path %s\n", path);
+}
+
+static void alloc_library(const char *path, const char *name)
+{
+ struct library *l;
+ char *_name, *_path;
+
+ l = calloc_a(sizeof(*l),
+ &_path, strlen(path) + 1,
+ &_name, strlen(name) + 1);
+ if (!l)
+ return;
+
+ l->avl.key = l->name = strcpy(_name, name);
+ l->path = strcpy(_path, path);
+
+ avl_insert(&libraries, &l->avl);
+ DEBUG("adding library %s/%s\n", path, name);
+}
+
+static int elf_open(char **dir, char *file)
+{
+ struct library_path *p;
+ char path[256];
+ int fd = -1;
+
+ *dir = NULL;
+
+ list_for_each_entry(p, &library_paths, list) {
+ if (strlen(p->path))
+ snprintf(path, sizeof(path), "%s/%s", p->path, file);
+ else
+ strncpy(path, file, sizeof(path));
+ fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ *dir = p->path;
+ break;
+ }
+ }
+
+ if (fd == -1)
+ fd = open(file, O_RDONLY);
+
+ return fd;
+}
+
+char* find_lib(char *file)
+{
+ struct library *l;
+ static char path[256];
+ const char *p;
+
+ l = avl_find_element(&libraries, file, l, avl);
+ if (!l)
+ return NULL;
+
+ p = l->path;
+ if (strstr(p, "local"))
+ p = "/lib";
+
+ snprintf(path, sizeof(path), "%s/%s", p, file);
+
+ return path;
+}
+
+static int elf64_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr)
+{
+ Elf64_Ehdr *e;
+ Elf64_Phdr *ph;
+ int i;
+
+ e = (Elf64_Ehdr *) map;
+ ph = (Elf64_Phdr *) (map + e->e_phoff);
+
+ for (i = 0; i < e->e_phnum; i++) {
+ if (ph[i].p_type == type) {
+ *offset = ph[i].p_offset;
+ if (size)
+ *size = ph[i].p_filesz;
+ if (vaddr)
+ *vaddr = ph[i].p_vaddr;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int elf32_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr)
+{
+ Elf32_Ehdr *e;
+ Elf32_Phdr *ph;
+ int i;
+
+ e = (Elf32_Ehdr *) map;
+ ph = (Elf32_Phdr *) (map + e->e_phoff);
+
+ for (i = 0; i < e->e_phnum; i++) {
+ if (ph[i].p_type == type) {
+ *offset = ph[i].p_offset;
+ if (size)
+ *size = ph[i].p_filesz;
+ if (vaddr)
+ *vaddr = ph[i].p_vaddr;
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int elf_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr)
+{
+ int clazz = map[EI_CLASS];
+
+ if (clazz == ELFCLASS32)
+ return elf32_find_section(map, type, offset, size, vaddr);
+ else if (clazz == ELFCLASS64)
+ return elf64_find_section(map, type, offset, size, vaddr);
+
+ ERROR("unknown elf format %d\n", clazz);
+
+ return -1;
+}
+
+static int elf32_scan_dynamic(char *map, int dyn_offset, int dyn_size, int load_offset)
+{
+ Elf32_Dyn *dynamic = (Elf32_Dyn *) (map + dyn_offset);
+ char *strtab = NULL;
+
+ while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) {
+ Elf32_Dyn *curr = dynamic;
+
+ dynamic++;
+ if (curr->d_tag != DT_STRTAB)
+ continue;
+
+ strtab = map + (curr->d_un.d_val - load_offset);
+ break;
+ }
+
+ if (!strtab)
+ return -1;
+
+ dynamic = (Elf32_Dyn *) (map + dyn_offset);
+ while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) {
+ Elf32_Dyn *curr = dynamic;
+
+ dynamic++;
+ if (curr->d_tag != DT_NEEDED)
+ continue;
+
+ if (elf_load_deps(&strtab[curr->d_un.d_val]))
+ return -1;
+ }
+
+ return 0;
+}
+
+static int elf64_scan_dynamic(char *map, int dyn_offset, int dyn_size, int load_offset)
+{
+ Elf64_Dyn *dynamic = (Elf64_Dyn *) (map + dyn_offset);
+ char *strtab = NULL;
+
+ while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) {
+ Elf64_Dyn *curr = dynamic;
+
+ dynamic++;
+ if (curr->d_tag != DT_STRTAB)
+ continue;
+
+ strtab = map + (curr->d_un.d_val - load_offset);
+ break;
+ }
+
+ if (!strtab)
+ return -1;
+
+ dynamic = (Elf64_Dyn *) (map + dyn_offset);
+ while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) {
+ Elf64_Dyn *curr = dynamic;
+
+ dynamic++;
+ if (curr->d_tag != DT_NEEDED)
+ continue;
+
+ if (elf_load_deps(&strtab[curr->d_un.d_val]))
+ return -1;
+ }
+
+ return 0;
+}
+
+int elf_load_deps(char *library)
+{
+ unsigned int dyn_offset, dyn_size;
+ unsigned int load_offset, load_vaddr;
+ struct stat s;
+ char *map = NULL, *dir = NULL;
+ int clazz, fd, ret = -1;
+
+ if (avl_find(&libraries, library))
+ return 0;
+
+ fd = elf_open(&dir, library);
+
+ if (fd < 0) {
+ ERROR("failed to open %s\n", library);
+ return -1;
+ }
+
+ if (fstat(fd, &s) == -1) {
+ ERROR("failed to stat %s\n", library);
+ ret = -1;
+ goto err_out;
+ }
+
+ map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (map == MAP_FAILED) {
+ ERROR("failed to mmap %s\n", library);
+ ret = -1;
+ goto err_out;
+ }
+
+ if (elf_find_section(map, PT_LOAD, &load_offset, NULL, &load_vaddr)) {
+ ERROR("failed to load the .load section from %s\n", library);
+ ret = -1;
+ goto err_out;
+ }
+
+ if (elf_find_section(map, PT_DYNAMIC, &dyn_offset, &dyn_size, NULL)) {
+ ERROR("failed to load the .dynamic section from %s\n", library);
+ ret = -1;
+ goto err_out;
+ }
+
+ if (dir) {
+ alloc_library(dir, library);
+ } else {
+ char *elf = strdup(library);
+
+ alloc_library(dirname(elf), basename(library));
+ free(elf);
+ }
+ clazz = map[EI_CLASS];
+
+ if (clazz == ELFCLASS32)
+ ret = elf32_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset);
+ else if (clazz == ELFCLASS64)
+ ret = elf64_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset);
+
+err_out:
+ if (map)
+ munmap(map, s.st_size);
+ close(fd);
+
+ return ret;
+}
+
+void load_ldso_conf(const char *conf)
+{
+ FILE* fp = fopen(conf, "r");
+ char line[256];
+
+ if (!fp) {
+ DEBUG("failed to open %s\n", conf);
+ return;
+ }
+
+ while (!feof(fp)) {
+ int len;
+
+ if (!fgets(line, 256, fp))
+ break;
+ len = strlen(line);
+ if (len < 2)
+ continue;
+ if (*line == '#')
+ continue;
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+ if (!strncmp(line, "include ", 8)) {
+ char *sep = strstr(line, " ");
+ glob_t gl;
+ int i;
+
+ if (!sep)
+ continue;;
+ while (*sep == ' ')
+ sep++;
+ if (glob(sep, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl)) {
+ ERROR("glob failed on %s\n", sep);
+ continue;
+ }
+ for (i = 0; i < gl.gl_pathc; i++)
+ load_ldso_conf(gl.gl_pathv[i]);
+ globfree(&gl);
+ } else {
+ struct stat s;
+
+ if (stat(line, &s))
+ continue;
+ alloc_library_path(line);
+ }
+ }
+
+ fclose(fp);
+}
diff --git a/jail/elf.h b/jail/elf.h
new file mode 100644
index 0000000..3ae311e
--- /dev/null
+++ b/jail/elf.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#ifndef _ELF_H__
+#include <libubox/avl.h>
+#include <libubox/avl-cmp.h>
+
+#include "log.h"
+
+struct library {
+ struct avl_node avl;
+ char *name;
+ char *path;
+};
+
+struct library_path {
+ struct list_head list;
+ char *path;
+};
+
+extern struct avl_tree libraries;
+
+extern void alloc_library_path(const char *path);
+extern char* find_lib(char *file);
+extern int elf_load_deps(char *library);
+extern void load_ldso_conf(const char *conf);
+
+#endif
diff --git a/jail/jail.c b/jail/jail.c
new file mode 100644
index 0000000..08d5ee1
--- /dev/null
+++ b/jail/jail.c
@@ -0,0 +1,492 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <sys/syscall.h>
+#include <sys/mman.h>
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/mount.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+
+#include <stdlib.h>
+#include <unistd.h>
+#include <values.h>
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <syslog.h>
+#include <libgen.h>
+#include <glob.h>
+#include <elf.h>
+#include <sched.h>
+
+#include "elf.h"
+
+#include <libubox/utils.h>
+#include <libubox/list.h>
+#include <libubox/uloop.h>
+
+#define STACK_SIZE (1024 * 1024)
+#define OPT_ARGS "P:S:n:r:w:psuld"
+
+struct extra {
+ struct list_head list;
+
+ const char *path;
+ const char *name;
+ int readonly;
+};
+
+static LIST_HEAD(extras);
+
+extern int pivot_root(const char *new_root, const char *put_old);
+
+int debug = 0;
+
+static char child_stack[STACK_SIZE];
+
+static int mkdir_p(char *dir, mode_t mask)
+{
+ char *l = strrchr(dir, '/');
+ int ret;
+
+ if (!l)
+ return 0;
+
+ *l = '\0';
+
+ if (mkdir_p(dir, mask))
+ return -1;
+
+ *l = '/';
+
+ ret = mkdir(dir, mask);
+ if (ret && errno == EEXIST)
+ return 0;
+
+ if (ret)
+ ERROR("mkdir failed on %s: %s\n", dir, strerror(errno));
+
+ return ret;
+}
+
+static int mount_bind(const char *root, const char *path, const char *name, int readonly, int error)
+{
+ const char *p = path;
+ struct stat s;
+ char old[256];
+ char new[256];
+ int fd;
+
+ if (strstr(p, "local"))
+ p = "/lib";
+
+ snprintf(old, sizeof(old), "%s/%s", path, name);
+ snprintf(new, sizeof(new), "%s%s", root, p);
+
+ mkdir_p(new, 0755);
+
+ snprintf(new, sizeof(new), "%s%s/%s", root, p, name);
+
+ if (stat(old, &s)) {
+ ERROR("%s does not exist\n", old);
+ return error;
+ }
+
+ if (S_ISDIR(s.st_mode)) {
+ mkdir_p(new, 0755);
+ } else {
+ fd = creat(new, 0644);
+ if (fd == -1) {
+ ERROR("failed to create %s: %s\n", new, strerror(errno));
+ return -1;
+ }
+ close(fd);
+ }
+
+ if (mount(old, new, NULL, MS_BIND, NULL)) {
+ ERROR("failed to mount -B %s %s: %s\n", old, new, strerror(errno));
+ return -1;
+ }
+
+ if (readonly && mount(old, new, NULL, MS_BIND | MS_REMOUNT | MS_RDONLY, NULL)) {
+ ERROR("failed to remount ro %s: %s\n", new, strerror(errno));
+ return -1;
+ }
+
+ DEBUG("mount -B %s %s\n", old, new);
+
+ return 0;
+}
+
+static int build_jail(const char *path)
+{
+ struct library *l;
+ struct extra *m;
+ int ret = 0;
+
+ mkdir(path, 0755);
+
+ if (mount("tmpfs", path, "tmpfs", MS_NOATIME, "mode=0744")) {
+ 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;
+}
+
+static void _umount(const char *root, const char *path)
+{
+ char *buf = NULL;
+
+ 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);
+ }
+}
+
+static int stop_jail(const char *root)
+{
+ struct library *l;
+ struct extra *m;
+
+ avl_for_each_element(&libraries, l, avl) {
+ char path[256];
+ char *p = l->path;
+
+ if (strstr(p, "local"))
+ p = "/lib";
+
+ snprintf(path, sizeof(path), "%s%s/%s", root, p, l->name);
+ DEBUG("umount %s\n", path);
+ umount(path);
+ }
+
+ 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);
+ }
+
+ _umount(root, "/proc");
+ _umount(root, "/sys");
+
+ DEBUG("umount %s\n", root);
+ umount(root);
+ rmdir(root);
+
+ return 0;
+}
+
+#define MAX_ENVP 8
+static char** build_envp(const char *seccomp, int debug)
+{
+ static char *envp[MAX_ENVP];
+ static char preload_var[64];
+ static char seccomp_var[64];
+ static char debug_var[] = "LD_DEBUG=all";
+ char *preload_lib = find_lib("libpreload-seccomp.so");
+ int count = 0;
+
+ if (seccomp && !preload_lib) {
+ ERROR("failed to add preload-lib to env\n");
+ return NULL;
+ }
+ if (seccomp) {
+ snprintf(seccomp_var, sizeof(seccomp_var), "SECCOMP_FILE=%s", seccomp);
+ envp[count++] = seccomp_var;
+ snprintf(preload_var, sizeof(preload_var), "LD_PRELOAD=%s", preload_lib);
+ envp[count++] = preload_var;
+ }
+ if (debug)
+ 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)
+{
+ fprintf(stderr, "jail <options> -D <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;
+}
+
+struct uloop_process child_process = {
+ .cb = child_process_handler,
+};
+
+static int spawn_child(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;
+
+ 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 's':
+ sysfs = 1;
+ break;
+ case 'n':
+ sethostname(optarg, strlen(optarg));
+ break;
+ }
+ }
+
+ asprintf(&mpoint, "%s/old", path);
+ 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 (sysfs) {
+ mkdir("/sys", 0755);
+ mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0);
+ }
+ mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0);
+
+ uloop_init();
+
+ 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);
+ }
+
+ return 0;
+}
+
+static int namespace_running = 1;
+
+static void namespace_process_handler(struct uloop_process *c, int ret)
+{
+ INFO("namespace (%d) exited: %d\n", c->pid, ret);
+ uloop_end();
+ namespace_running = 0;
+}
+
+struct uloop_process namespace_process = {
+ .cb = namespace_process_handler,
+};
+
+static void spawn_namespace(const char *path, int argc, char **argv)
+{
+ char *dir = get_current_dir_name();
+
+ uloop_init();
+ chdir(path);
+ namespace_process.pid = clone(spawn_child,
+ child_stack + STACK_SIZE,
+ CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, argv);
+
+ if (namespace_process.pid != -1) {
+ chdir(dir);
+ 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;
+
+ if (*name != '/') {
+ ERROR("%s is not an absolute path\n", name);
+ return;
+ }
+
+ f = calloc(1, sizeof(struct extra));
+
+ f->name = basename(name);
+ f->path = dirname(strdup(name));
+ f->readonly = readonly;
+
+ list_add_tail(&f->list, &extras);
+}
+
+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";
+
+ if (uid) {
+ ERROR("not root, aborting: %s\n", strerror(errno));
+ return -1;
+ }
+
+ umask(022);
+
+ while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) {
+ switch (ch) {
+ case 'd':
+ debug = 1;
+ break;
+ case 'P':
+ path = optarg;
+ break;
+ case 'n':
+ name = optarg;
+ break;
+ case 'S':
+ case 'r':
+ add_extra(optarg, 1);
+ break;
+ case 'w':
+ add_extra(optarg, 0);
+ break;
+ case 'u':
+ add_extra(ubus, 0);
+ break;
+ case 'l':
+ add_extra(log, 0);
+ break;
+ }
+ }
+
+ if (argc - optind < 1)
+ return usage();
+
+ if (!path && asprintf(&path, "/tmp/%s", basename(argv[optind])) == -1) {
+ ERROR("failed to set root path\n: %s", strerror(errno));
+ return -1;
+ }
+
+ if (!stat(path, &s)) {
+ ERROR("%s already exists: %s\n", path, strerror(errno));
+ return -1;
+ }
+
+ if (name)
+ prctl(PR_SET_NAME, 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");
+
+ if (elf_load_deps(argv[optind])) {
+ ERROR("failed to load dependencies\n");
+ return -1;
+ }
+
+ if (elf_load_deps("libpreload-seccomp.so")) {
+ ERROR("failed to load libpreload-seccomp.so\n");
+ return -1;
+ }
+
+ ret = build_jail(path);
+
+ if (!ret)
+ spawn_namespace(path, argc, argv);
+ else
+ ERROR("failed to build jail\n");
+
+ stop_jail(path);
+
+ return ret;
+}
diff --git a/jail/log.h b/jail/log.h
new file mode 100644
index 0000000..f8590b3
--- /dev/null
+++ b/jail/log.h
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+extern int debug;
+
+#define INFO(fmt, ...) do { \
+ printf("jail: "fmt, ## __VA_ARGS__); \
+ } while (0)
+#define ERROR(fmt, ...) do { \
+ syslog(LOG_ERR, "jail: "fmt, ## __VA_ARGS__); \
+ fprintf(stderr,"jail: "fmt, ## __VA_ARGS__); \
+ } while (0)
+#define DEBUG(fmt, ...) do { \
+ if (debug) printf("jail: "fmt, ## __VA_ARGS__); \
+ } while (0)
+
diff --git a/jail/preload.c b/jail/preload.c
new file mode 100644
index 0000000..97ac44d
--- /dev/null
+++ b/jail/preload.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <syslog.h>
+
+#include "seccomp.h"
+#include "../preload.h"
+
+static main_t __main__;
+
+static int __preload_main__(int argc, char **argv, char **envp)
+{
+ uid_t uid = getuid();
+ char *env_file = getenv("SECCOMP_FILE");
+
+ if (uid) {
+ INFO("preload-seccomp: %s: not root, cannot install seccomp filter\n", *argv);
+ return -1;
+ }
+
+ if (install_syscall_filter(*argv, env_file))
+ return -1;
+
+ unsetenv("LD_PRELOAD");
+ unsetenv("SECCOMP_FILE");
+
+ return (*__main__)(argc, argv, envp);
+}
+
+int __libc_start_main(main_t main,
+ int argc,
+ char **argv,
+ ElfW(auxv_t) *auxvec,
+ __typeof (main) init,
+ void (*fini) (void),
+ void (*rtld_fini) (void),
+ void *stack_end)
+{
+ start_main_t __start_main__;
+
+ __start_main__ = dlsym(RTLD_NEXT, "__libc_start_main");
+ if (!__start_main__)
+ INFO("failed to find __libc_start_main %s\n", dlerror());
+
+ __main__ = main;
+
+ return (*__start_main__)(__preload_main__, argc, argv, auxvec,
+ init, fini, rtld_fini, stack_end);
+}
+
+void __uClibc_main(main_t main,
+ int argc,
+ char **argv,
+ void (*app_init)(void),
+ void (*app_fini)(void),
+ void (*rtld_fini)(void),
+ void *stack_end attribute_unused)
+{
+ uClibc_main __start_main__;
+
+ __start_main__ = dlsym(RTLD_NEXT, "__uClibc_main");
+ if (!__start_main__)
+ INFO("failed to find __uClibc_main %s\n", dlerror());
+
+ __main__ = main;
+
+ return (*__start_main__)(__preload_main__, argc, argv,
+ app_init, app_fini, rtld_fini, stack_end);
+}
diff --git a/jail/seccomp-bpf.h b/jail/seccomp-bpf.h
new file mode 100644
index 0000000..1cc2908
--- /dev/null
+++ b/jail/seccomp-bpf.h
@@ -0,0 +1,77 @@
+/*
+ * seccomp example for x86 (32-bit and 64-bit) with BPF macros
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Authors:
+ * Will Drewry <wad@chromium.org>
+ * Kees Cook <keescook@chromium.org>
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#ifndef _SECCOMP_BPF_H_
+#define _SECCOMP_BPF_H_
+
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <signal.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/prctl.h>
+#ifndef PR_SET_NO_NEW_PRIVS
+# define PR_SET_NO_NEW_PRIVS 38
+#endif
+
+#include <linux/unistd.h>
+#include <linux/audit.h>
+#include <linux/filter.h>
+
+#ifdef HAVE_LINUX_SECCOMP_H
+# include <linux/seccomp.h>
+#endif
+
+#ifndef SECCOMP_MODE_FILTER
+#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */
+#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */
+#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */
+#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */
+#define SECCOMP_RET_LOG 0x00070000U
+#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */
+#define SECCOMP_RET_ERROR(x) (SECCOMP_RET_ERRNO | ((x) & 0x0000ffffU))
+#define SECCOMP_RET_LOGGER(x) (SECCOMP_RET_LOG | ((x) & 0x0000ffffU))
+
+struct seccomp_data {
+ int nr;
+ __u32 arch;
+ __u64 instruction_pointer;
+ __u64 args[6];
+};
+#endif
+
+#ifndef SYS_SECCOMP
+# define SYS_SECCOMP 1
+#endif
+
+#define syscall_nr (offsetof(struct seccomp_data, nr))
+#define arch_nr (offsetof(struct seccomp_data, arch))
+
+#if defined(__i386__)
+# define REG_SYSCALL REG_EAX
+# define ARCH_NR AUDIT_ARCH_I386
+#elif defined(__x86_64__)
+# define REG_SYSCALL REG_RAX
+# define ARCH_NR AUDIT_ARCH_X86_64
+#elif defined(__mips__)
+# define REG_SYSCALL regs[2]
+# define ARCH_NR AUDIT_ARCH_MIPSEL
+#else
+# warning "Platform does not support seccomp filter yet"
+# define REG_SYSCALL 0
+# define ARCH_NR 0
+#endif
+
+#endif /* _SECCOMP_BPF_H_ */
diff --git a/jail/seccomp.c b/jail/seccomp.c
new file mode 100644
index 0000000..de01fc6
--- /dev/null
+++ b/jail/seccomp.c
@@ -0,0 +1,142 @@
+/*
+ * seccomp example with syscall reporting
+ *
+ * Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * Authors:
+ * Kees Cook <keescook@chromium.org>
+ * Will Drewry <wad@chromium.org>
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#define _GNU_SOURCE 1
+#include <stdio.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <syslog.h>
+
+#include <libubox/utils.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+
+#include "seccomp-bpf.h"
+#include "seccomp.h"
+#include "../syscall-names.h"
+
+static int max_syscall = ARRAY_SIZE(syscall_names);
+
+static int find_syscall(const char *name)
+{
+ int i;
+
+ for (i = 0; i < max_syscall; i++)
+ if (syscall_names[i] && !strcmp(syscall_names[i], name))
+ return i;
+
+ return -1;
+}
+
+static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k)
+{
+ filter->code = code;
+ filter->jt = jt;
+ filter->jf = jf;
+ filter->k = k;
+}
+
+int install_syscall_filter(const char *argv, const char *file)
+{
+ enum {
+ SECCOMP_WHITELIST,
+ SECCOMP_POLICY,
+ __SECCOMP_MAX
+ };
+ static const struct blobmsg_policy policy[__SECCOMP_MAX] = {
+ [SECCOMP_WHITELIST] = { .name = "whitelist", .type = BLOBMSG_TYPE_ARRAY },
+ [SECCOMP_POLICY] = { .name = "policy", .type = BLOBMSG_TYPE_INT32 },
+ };
+ struct blob_buf b = { 0 };
+ struct blob_attr *tb[__SECCOMP_MAX];
+ struct blob_attr *cur;
+ int rem;
+
+ struct sock_filter *filter;
+ struct sock_fprog prog = { 0 };
+ int sz = 5, idx = 0, default_policy = 0;
+
+ INFO("%s: setting up syscall filter\n", argv);
+
+ blob_buf_init(&b, 0);
+ if (!blobmsg_add_json_from_file(&b, file)) {
+ INFO("%s: failed to load %s\n", argv, file);
+ return -1;
+ }
+
+ blobmsg_parse(policy, __SECCOMP_MAX, tb, blob_data(b.head), blob_len(b.head));
+ if (!tb[SECCOMP_WHITELIST]) {
+ INFO("%s: %s is missing the syscall table\n", argv, file);
+ return -1;
+ }
+
+ if (tb[SECCOMP_POLICY])
+ default_policy = blobmsg_get_u32(tb[SECCOMP_POLICY]);
+
+ blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem)
+ sz += 2;
+
+ filter = calloc(sz, sizeof(struct sock_filter));
+ if (!filter) {
+ INFO("failed to allocate filter memory\n");
+ return -1;
+ }
+
+ /* validate arch */
+ set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr);
+ set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 1, 0, ARCH_NR);
+ set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL);
+
+ /* get syscall */
+ set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr);
+
+ blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem) {
+ char *name = blobmsg_get_string(cur);
+ int nr;
+
+ if (!name) {
+ INFO("%s: invalid syscall name\n", argv);
+ continue;
+ }
+
+ nr = find_syscall(name);
+ if (nr == -1) {
+ INFO("%s: unknown syscall %s\n", argv, name);
+ continue;
+ }
+
+ /* add whitelist */
+ set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 1, nr);
+ set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW);
+ }
+
+ if (default_policy)
+ /* return -1 and set errno */
+ set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_LOGGER(default_policy));
+ else
+ /* kill the process */
+ set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL);
+
+ if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+ INFO("%s: prctl(PR_SET_NO_NEW_PRIVS) failed: %s\n", argv, strerror(errno));
+ return errno;
+ }
+
+ prog.len = (unsigned short) idx + 1;
+ prog.filter = filter;
+
+ if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) {
+ INFO("%s: prctl(PR_SET_SECCOMP) failed: %s\n", argv, strerror(errno));
+ return errno;
+ }
+ return 0;
+}
diff --git a/jail/seccomp.h b/jail/seccomp.h
new file mode 100644
index 0000000..6c585ad
--- /dev/null
+++ b/jail/seccomp.h
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2015 John Crispin <blogic@openwrt.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License version 2.1
+ * as published by the Free Software Foundation
+ *
+ * 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.
+ */
+
+#define INFO(fmt, ...) do { \
+ syslog(0,"perload-jail: "fmt, ## __VA_ARGS__); \
+ fprintf(stderr,"perload-jail: "fmt, ## __VA_ARGS__); \
+ } while (0)
+
+int install_syscall_filter(const char *argv, const char *file);