add initial version of ujail and utrace
authorJohn Crispin <blogic@openwrt.org>
Sat, 21 Mar 2015 10:47:01 +0000 (11:47 +0100)
committerJohn Crispin <blogic@openwrt.org>
Mon, 23 Mar 2015 07:09:26 +0000 (08:09 +0100)
Signed-off-by: John Crispin <blogic@openwrt.org>
14 files changed:
.gitignore
CMakeLists.txt
jail/elf.c [new file with mode: 0644]
jail/elf.h [new file with mode: 0644]
jail/jail.c [new file with mode: 0644]
jail/log.h [new file with mode: 0644]
jail/preload.c [new file with mode: 0644]
jail/seccomp-bpf.h [new file with mode: 0644]
jail/seccomp.c [new file with mode: 0644]
jail/seccomp.h [new file with mode: 0644]
make_syscall_h.sh [new file with mode: 0755]
preload.h [new file with mode: 0644]
trace/preload.c [new file with mode: 0644]
trace/trace.c [new file with mode: 0644]

index 4bd7b28b5db3836f3bb9e1915f2afb91b55f233c..9d80a7404d7096ac61f8465a0b586f2cc83da715 100644 (file)
@@ -6,5 +6,8 @@ init
 Makefile
 CMakeCache.txt
 CMakeFiles
+utrace
+ujail
+*.so
 *.cmake
 install_manifest.txt
index 98395f5c64212b43a0d058592feb7d78265bd002..26216cd44bdfb925ddd38177286f5d2927178079 100644 (file)
@@ -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 (file)
index 0000000..c198599
--- /dev/null
@@ -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 (file)
index 0000000..3ae311e
--- /dev/null
@@ -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 (file)
index 0000000..08d5ee1
--- /dev/null
@@ -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 (file)
index 0000000..f8590b3
--- /dev/null
@@ -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 (file)
index 0000000..97ac44d
--- /dev/null
@@ -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 (file)
index 0000000..1cc2908
--- /dev/null
@@ -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 (file)
index 0000000..de01fc6
--- /dev/null
@@ -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 (file)
index 0000000..6c585ad
--- /dev/null
@@ -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 (executable)
index 0000000..57333fd
--- /dev/null
@@ -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 (file)
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 (file)
index 0000000..99dd906
--- /dev/null
@@ -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 (file)
index 0000000..c6f32d7
--- /dev/null
@@ -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;
+}