summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore3
-rw-r--r--CMakeLists.txt33
-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
-rwxr-xr-xmake_syscall_h.sh18
-rw-r--r--preload.h56
-rw-r--r--trace/preload.c80
-rw-r--r--trace/trace.c222
14 files changed, 1661 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index 4bd7b28..9d80a74 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,5 +6,8 @@ init
Makefile
CMakeCache.txt
CMakeFiles
+utrace
+ujail
+*.so
*.cmake
install_manifest.txt
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 98395f5..26216cd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -54,3 +54,36 @@ ADD_EXECUTABLE(askfirst utils/askfirst.c)
INSTALL(TARGETS askfirst
RUNTIME DESTINATION sbin
)
+
+ADD_CUSTOM_COMMAND(
+ OUTPUT syscall-names.h
+ COMMAND ./make_syscall_h.sh ${CMAKE_C_COMPILER} > ./syscall-names.h
+ DEPENDS ./make_syscall_h.sh
+)
+ADD_CUSTOM_TARGET(headers DEPENDS syscall-names.h)
+
+ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c)
+TARGET_LINK_LIBRARIES(ujail ubox)
+INSTALL(TARGETS ujail
+ RUNTIME DESTINATION sbin
+)
+
+ADD_LIBRARY(preload-seccomp SHARED jail/preload.c jail/seccomp.c)
+TARGET_LINK_LIBRARIES(preload-seccomp dl ubox blobmsg_json)
+INSTALL(TARGETS preload-seccomp
+ LIBRARY DESTINATION lib
+)
+ADD_DEPENDENCIES(preload-seccomp headers)
+
+ADD_EXECUTABLE(utrace trace/trace.c)
+TARGET_LINK_LIBRARIES(utrace ubox ${json} blobmsg_json)
+INSTALL(TARGETS utrace
+ RUNTIME DESTINATION sbin
+)
+ADD_DEPENDENCIES(utrace headers)
+
+ADD_LIBRARY(preload-trace SHARED trace/preload.c)
+TARGET_LINK_LIBRARIES(preload-trace dl)
+INSTALL(TARGETS preload-trace
+ LIBRARY DESTINATION lib
+)
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);
diff --git a/make_syscall_h.sh b/make_syscall_h.sh
new file mode 100755
index 0000000..57333fd
--- /dev/null
+++ b/make_syscall_h.sh
@@ -0,0 +1,18 @@
+#!/bin/sh
+# syscall reporting example for seccomp
+#
+# Copyright (c) 2012 The Chromium OS Authors <chromium-os-dev@chromium.org>
+# Authors:
+# 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.
+
+CC=$1
+[ -n "$TARGET_CC_NOCACHE" ] && CC=$TARGET_CC_NOCACHE
+
+echo "#include <asm/unistd.h>"
+echo "static const char *syscall_names[] = {"
+echo "#include <sys/syscall.h>" | ${CC} -E -dM - | grep '^#define __NR_' | \
+ LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([ ()+0-9NR_Linux]+)(.*)/ [\2] = "\1",/p'
+echo "};"
diff --git a/preload.h b/preload.h
new file mode 100644
index 0000000..5e663ac
--- /dev/null
+++ b/preload.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+#include <link.h>
+
+#ifndef __unbounded
+#define __unbounded
+#endif
+
+#ifndef attribute_unused
+#define attribute_unused __attribute__ ((unused))
+#endif
+typedef int (*main_t)(int, char **, char **);
+
+typedef int (*start_main_t)(main_t main, int, char *__unbounded *__unbounded,
+ ElfW(auxv_t) *,
+ __typeof (main),
+ void (*fini) (void),
+ void (*rtld_fini) (void),
+ void *__unbounded stack_end);
+
+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);
+
+
+typedef 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);
+
+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);
diff --git a/trace/preload.c b/trace/preload.c
new file mode 100644
index 0000000..99dd906
--- /dev/null
+++ b/trace/preload.c
@@ -0,0 +1,80 @@
+/*
+ * 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/ptrace.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <dlfcn.h>
+
+#include "../preload.h"
+
+#define ERROR(fmt, ...) do { \
+ fprintf(stderr,"perload-jail: "fmt, ## __VA_ARGS__); \
+ } while (0)
+
+static main_t __main__;
+
+static int __preload_main__(int argc, char **argv, char **envp)
+{
+ unsetenv("LD_PRELOAD");
+ ptrace(PTRACE_TRACEME);
+ kill(getpid(), SIGSTOP);
+
+ 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__)
+ ERROR("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__)
+ ERROR("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/trace/trace.c b/trace/trace.c
new file mode 100644
index 0000000..c6f32d7
--- /dev/null
+++ b/trace/trace.c
@@ -0,0 +1,222 @@
+/*
+ * 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 <stddef.h>
+#include <sys/ptrace.h>
+#include <sys/user.h>
+#include <sys/wait.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <syslog.h>
+
+#include <libubox/uloop.h>
+#include <libubox/blobmsg.h>
+#include <libubox/blobmsg_json.h>
+
+#include "../syscall-names.h"
+
+#define _offsetof(a, b) __builtin_offsetof(a,b)
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+#ifdef __amd64__
+#define reg_syscall_nr _offsetof(struct user, regs.orig_rax)
+#elif defined(__i386__)
+#define reg_syscall_nr _offsetof(struct user, regs.orig_eax)
+#elif defined(__mips)
+# ifndef EF_REG2
+# define EF_REG2 8
+# endif
+#define reg_syscall_nr (EF_REG2 / 4)
+#else
+#error tracing is not supported on this architecture
+#endif
+
+#define INFO(fmt, ...) do { \
+ fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \
+} while (0)
+
+#define ERROR(fmt, ...) do { \
+ syslog(0, "utrace: "fmt, ## __VA_ARGS__); \
+ fprintf(stderr, "utrace: "fmt, ## __VA_ARGS__); \
+} while (0)
+
+static struct uloop_process tracer;
+static int *syscall_count;
+static struct blob_buf b;
+static int syscall_max;
+static int in_syscall;
+static int debug;
+
+static int max_syscall = ARRAY_SIZE(syscall_names);
+
+static void set_syscall(const char *name, int val)
+{
+ int i;
+
+ for (i = 0; i < max_syscall; i++)
+ if (syscall_names[i] && !strcmp(syscall_names[i], name)) {
+ syscall_count[i] = val;
+ return;
+ }
+}
+
+static void print_syscalls(int policy, const char *json)
+{
+ void *c;
+ int i;
+
+ set_syscall("rt_sigaction", 1);
+ set_syscall("sigreturn", 1);
+ set_syscall("rt_sigreturn", 1);
+ set_syscall("exit_group", 1);
+ set_syscall("exit", 1);
+
+ blob_buf_init(&b, 0);
+ c = blobmsg_open_array(&b, "whitelist");
+
+ for (i = 0; i < ARRAY_SIZE(syscall_names); i++) {
+ if (!syscall_count[i])
+ continue;
+ if (syscall_names[i]) {
+ if (debug)
+ printf("syscall %d (%s) was called %d times\n",
+ i, syscall_names[i], syscall_count[i]);
+ blobmsg_add_string(&b, NULL, syscall_names[i]);
+ } else {
+ ERROR("no name found for syscall(%d)\n", i);
+ }
+ }
+ blobmsg_close_array(&b, c);
+ blobmsg_add_u32(&b, "policy", policy);
+ if (json) {
+ FILE *fp = fopen(json, "w");
+ if (fp) {
+ fprintf(fp, "%s", blobmsg_format_json_indent(b.head, true, 0));
+ fclose(fp);
+ INFO("saving syscall trace to %s\n", json);
+ } else {
+ ERROR("failed to open %s\n", json);
+ }
+ } else {
+ printf("%s\n",
+ blobmsg_format_json_indent(b.head, true, 0));
+ }
+
+}
+
+static void tracer_cb(struct uloop_process *c, int ret)
+{
+ if (WIFSTOPPED(ret) && WSTOPSIG(ret) & 0x80) {
+ if (!in_syscall) {
+ int syscall = ptrace(PTRACE_PEEKUSER, c->pid, reg_syscall_nr);
+
+ if (syscall < syscall_max) {
+ syscall_count[syscall]++;
+ if (debug)
+ fprintf(stderr, "%s()\n", syscall_names[syscall]);
+ } else if (debug) {
+ fprintf(stderr, "syscal(%d)\n", syscall);
+ }
+ }
+ in_syscall = !in_syscall;
+ } else if (WIFEXITED(ret)) {
+ uloop_end();
+ return;
+ }
+ ptrace(PTRACE_SYSCALL, c->pid, 0, 0);
+ uloop_process_add(&tracer);
+}
+
+int main(int argc, char **argv, char **envp)
+{
+ char *json = NULL;
+ int status, ch, policy = EPERM;
+ pid_t child;
+
+ while ((ch = getopt(argc, argv, "f:p:")) != -1) {
+ switch (ch) {
+ case 'f':
+ json = optarg;
+ break;
+ case 'p':
+ policy = atoi(optarg);
+ break;
+ }
+ }
+
+ argc -= optind;
+ argv += optind;
+
+ if (!argc)
+ return -1;
+
+ if (getenv("TRACE_DEBUG"))
+ debug = 1;
+ unsetenv("TRACE_DEBUG");
+
+ child = fork();
+
+ if (child == 0) {
+ char **_argv = calloc(argc + 1, sizeof(char *));
+ char **_envp;
+ char preload[] = "LD_PRELOAD=/lib/libpreload-trace.so";
+ int envc = 1;
+ int ret;
+
+ memcpy(_argv, argv, argc * sizeof(char *));
+
+ while (envp[envc++])
+ ;
+
+ _envp = calloc(envc, sizeof(char *));
+ memcpy(&_envp[1], _envp, envc * sizeof(char *));
+ *envp = preload;
+
+ ret = execve(_argv[0], _argv, envp);
+ ERROR("failed to exec %s: %s\n", _argv[0], strerror(errno));
+ return ret;
+ }
+
+ if (child < 0)
+ return -1;
+
+ syscall_max = ARRAY_SIZE(syscall_names);
+ syscall_count = calloc(syscall_max, sizeof(int));
+ waitpid(child, &status, 0);
+ if (!WIFSTOPPED(status)) {
+ ERROR("failed to start %s\n", *argv);
+ return -1;
+ }
+
+ ptrace(PTRACE_SETOPTIONS, child, 0, PTRACE_O_TRACESYSGOOD);
+ ptrace(PTRACE_SYSCALL, child, 0, 0);
+
+ uloop_init();
+ tracer.pid = child;
+ tracer.cb = tracer_cb;
+ uloop_process_add(&tracer);
+ uloop_run();
+ uloop_done();
+
+ if (!json)
+ asprintf(&json, "/tmp/%s.%u.json", basename(*argv), child);
+
+ print_syscalls(policy, json);
+
+ return 0;
+}