Browse Source

add initial version of ujail and utrace

Signed-off-by: John Crispin <blogic@openwrt.org>
John Crispin 9 years ago
parent
commit
dfcfcca7ba
14 changed files with 1661 additions and 0 deletions
  1. 3 0
      .gitignore
  2. 33 0
      CMakeLists.txt
  3. 369 0
      jail/elf.c
  4. 38 0
      jail/elf.h
  5. 492 0
      jail/jail.c
  6. 26 0
      jail/log.h
  7. 86 0
      jail/preload.c
  8. 77 0
      jail/seccomp-bpf.h
  9. 142 0
      jail/seccomp.c
  10. 19 0
      jail/seccomp.h
  11. 18 0
      make_syscall_h.sh
  12. 56 0
      preload.h
  13. 80 0
      trace/preload.c
  14. 222 0
      trace/trace.c

+ 3 - 0
.gitignore

@@ -6,5 +6,8 @@ init
 Makefile
 CMakeCache.txt
 CMakeFiles
+utrace
+ujail
+*.so
 *.cmake
 install_manifest.txt

+ 33 - 0
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
+)

+ 369 - 0
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);
+}

+ 38 - 0
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

+ 492 - 0
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;
+}

+ 26 - 0
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)
+

+ 86 - 0
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);
+}

+ 77 - 0
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_ */

+ 142 - 0
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;
+}

+ 19 - 0
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);

+ 18 - 0
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 "};"

+ 56 - 0
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);

+ 80 - 0
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);
+}

+ 222 - 0
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;
+}