From dfcfcca7baf2b22d8dac1a724bdb7dd9d52f4c05 Mon Sep 17 00:00:00 2001 From: John Crispin Date: Sat, 21 Mar 2015 11:47:01 +0100 Subject: [PATCH] add initial version of ujail and utrace Signed-off-by: John Crispin --- .gitignore | 3 + CMakeLists.txt | 33 +++ jail/elf.c | 369 ++++++++++++++++++++++++++++++++++ jail/elf.h | 38 ++++ jail/jail.c | 492 +++++++++++++++++++++++++++++++++++++++++++++ jail/log.h | 26 +++ jail/preload.c | 86 ++++++++ jail/seccomp-bpf.h | 77 +++++++ jail/seccomp.c | 142 +++++++++++++ jail/seccomp.h | 19 ++ make_syscall_h.sh | 18 ++ preload.h | 56 ++++++ trace/preload.c | 80 ++++++++ trace/trace.c | 222 ++++++++++++++++++++ 14 files changed, 1661 insertions(+) create mode 100644 jail/elf.c create mode 100644 jail/elf.h create mode 100644 jail/jail.c create mode 100644 jail/log.h create mode 100644 jail/preload.c create mode 100644 jail/seccomp-bpf.h create mode 100644 jail/seccomp.c create mode 100644 jail/seccomp.h create mode 100755 make_syscall_h.sh create mode 100644 preload.h create mode 100644 trace/preload.c create mode 100644 trace/trace.c diff --git a/.gitignore b/.gitignore index 4bd7b28..9d80a74 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,8 @@ init Makefile CMakeCache.txt CMakeFiles +utrace +ujail +*.so *.cmake install_manifest.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 98395f5..26216cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,3 +54,36 @@ ADD_EXECUTABLE(askfirst utils/askfirst.c) INSTALL(TARGETS askfirst RUNTIME DESTINATION sbin ) + +ADD_CUSTOM_COMMAND( + OUTPUT syscall-names.h + COMMAND ./make_syscall_h.sh ${CMAKE_C_COMPILER} > ./syscall-names.h + DEPENDS ./make_syscall_h.sh +) +ADD_CUSTOM_TARGET(headers DEPENDS syscall-names.h) + +ADD_EXECUTABLE(ujail jail/jail.c jail/elf.c) +TARGET_LINK_LIBRARIES(ujail ubox) +INSTALL(TARGETS ujail + RUNTIME DESTINATION sbin +) + +ADD_LIBRARY(preload-seccomp SHARED jail/preload.c jail/seccomp.c) +TARGET_LINK_LIBRARIES(preload-seccomp dl ubox blobmsg_json) +INSTALL(TARGETS preload-seccomp + LIBRARY DESTINATION lib +) +ADD_DEPENDENCIES(preload-seccomp headers) + +ADD_EXECUTABLE(utrace trace/trace.c) +TARGET_LINK_LIBRARIES(utrace ubox ${json} blobmsg_json) +INSTALL(TARGETS utrace + RUNTIME DESTINATION sbin +) +ADD_DEPENDENCIES(utrace headers) + +ADD_LIBRARY(preload-trace SHARED trace/preload.c) +TARGET_LINK_LIBRARIES(preload-trace dl) +INSTALL(TARGETS preload-trace + LIBRARY DESTINATION lib +) diff --git a/jail/elf.c b/jail/elf.c new file mode 100644 index 0000000..c198599 --- /dev/null +++ b/jail/elf.c @@ -0,0 +1,369 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "elf.h" + +struct avl_tree libraries; +static LIST_HEAD(library_paths); + +void alloc_library_path(const char *path) +{ + struct library_path *p; + char *_path; + + p = calloc_a(sizeof(*p), + &_path, strlen(path) + 1); + if (!p) + return; + + p->path = strcpy(_path, path); + + list_add_tail(&p->list, &library_paths); + DEBUG("adding ld.so path %s\n", path); +} + +static void alloc_library(const char *path, const char *name) +{ + struct library *l; + char *_name, *_path; + + l = calloc_a(sizeof(*l), + &_path, strlen(path) + 1, + &_name, strlen(name) + 1); + if (!l) + return; + + l->avl.key = l->name = strcpy(_name, name); + l->path = strcpy(_path, path); + + avl_insert(&libraries, &l->avl); + DEBUG("adding library %s/%s\n", path, name); +} + +static int elf_open(char **dir, char *file) +{ + struct library_path *p; + char path[256]; + int fd = -1; + + *dir = NULL; + + list_for_each_entry(p, &library_paths, list) { + if (strlen(p->path)) + snprintf(path, sizeof(path), "%s/%s", p->path, file); + else + strncpy(path, file, sizeof(path)); + fd = open(path, O_RDONLY); + if (fd >= 0) { + *dir = p->path; + break; + } + } + + if (fd == -1) + fd = open(file, O_RDONLY); + + return fd; +} + +char* find_lib(char *file) +{ + struct library *l; + static char path[256]; + const char *p; + + l = avl_find_element(&libraries, file, l, avl); + if (!l) + return NULL; + + p = l->path; + if (strstr(p, "local")) + p = "/lib"; + + snprintf(path, sizeof(path), "%s/%s", p, file); + + return path; +} + +static int elf64_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + Elf64_Ehdr *e; + Elf64_Phdr *ph; + int i; + + e = (Elf64_Ehdr *) map; + ph = (Elf64_Phdr *) (map + e->e_phoff); + + for (i = 0; i < e->e_phnum; i++) { + if (ph[i].p_type == type) { + *offset = ph[i].p_offset; + if (size) + *size = ph[i].p_filesz; + if (vaddr) + *vaddr = ph[i].p_vaddr; + return 0; + } + } + + return -1; +} + +static int elf32_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + Elf32_Ehdr *e; + Elf32_Phdr *ph; + int i; + + e = (Elf32_Ehdr *) map; + ph = (Elf32_Phdr *) (map + e->e_phoff); + + for (i = 0; i < e->e_phnum; i++) { + if (ph[i].p_type == type) { + *offset = ph[i].p_offset; + if (size) + *size = ph[i].p_filesz; + if (vaddr) + *vaddr = ph[i].p_vaddr; + return 0; + } + } + + return -1; +} + +static int elf_find_section(char *map, unsigned int type, unsigned int *offset, unsigned int *size, unsigned int *vaddr) +{ + int clazz = map[EI_CLASS]; + + if (clazz == ELFCLASS32) + return elf32_find_section(map, type, offset, size, vaddr); + else if (clazz == ELFCLASS64) + return elf64_find_section(map, type, offset, size, vaddr); + + ERROR("unknown elf format %d\n", clazz); + + return -1; +} + +static int elf32_scan_dynamic(char *map, int dyn_offset, int dyn_size, int load_offset) +{ + Elf32_Dyn *dynamic = (Elf32_Dyn *) (map + dyn_offset); + char *strtab = NULL; + + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf32_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_STRTAB) + continue; + + strtab = map + (curr->d_un.d_val - load_offset); + break; + } + + if (!strtab) + return -1; + + dynamic = (Elf32_Dyn *) (map + dyn_offset); + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf32_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_NEEDED) + continue; + + if (elf_load_deps(&strtab[curr->d_un.d_val])) + return -1; + } + + return 0; +} + +static int elf64_scan_dynamic(char *map, int dyn_offset, int dyn_size, int load_offset) +{ + Elf64_Dyn *dynamic = (Elf64_Dyn *) (map + dyn_offset); + char *strtab = NULL; + + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf64_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_STRTAB) + continue; + + strtab = map + (curr->d_un.d_val - load_offset); + break; + } + + if (!strtab) + return -1; + + dynamic = (Elf64_Dyn *) (map + dyn_offset); + while ((void *) dynamic < (void *) (map + dyn_offset + dyn_size)) { + Elf64_Dyn *curr = dynamic; + + dynamic++; + if (curr->d_tag != DT_NEEDED) + continue; + + if (elf_load_deps(&strtab[curr->d_un.d_val])) + return -1; + } + + return 0; +} + +int elf_load_deps(char *library) +{ + unsigned int dyn_offset, dyn_size; + unsigned int load_offset, load_vaddr; + struct stat s; + char *map = NULL, *dir = NULL; + int clazz, fd, ret = -1; + + if (avl_find(&libraries, library)) + return 0; + + fd = elf_open(&dir, library); + + if (fd < 0) { + ERROR("failed to open %s\n", library); + return -1; + } + + if (fstat(fd, &s) == -1) { + ERROR("failed to stat %s\n", library); + ret = -1; + goto err_out; + } + + map = mmap(NULL, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) { + ERROR("failed to mmap %s\n", library); + ret = -1; + goto err_out; + } + + if (elf_find_section(map, PT_LOAD, &load_offset, NULL, &load_vaddr)) { + ERROR("failed to load the .load section from %s\n", library); + ret = -1; + goto err_out; + } + + if (elf_find_section(map, PT_DYNAMIC, &dyn_offset, &dyn_size, NULL)) { + ERROR("failed to load the .dynamic section from %s\n", library); + ret = -1; + goto err_out; + } + + if (dir) { + alloc_library(dir, library); + } else { + char *elf = strdup(library); + + alloc_library(dirname(elf), basename(library)); + free(elf); + } + clazz = map[EI_CLASS]; + + if (clazz == ELFCLASS32) + ret = elf32_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset); + else if (clazz == ELFCLASS64) + ret = elf64_scan_dynamic(map, dyn_offset, dyn_size, load_vaddr - load_offset); + +err_out: + if (map) + munmap(map, s.st_size); + close(fd); + + return ret; +} + +void load_ldso_conf(const char *conf) +{ + FILE* fp = fopen(conf, "r"); + char line[256]; + + if (!fp) { + DEBUG("failed to open %s\n", conf); + return; + } + + while (!feof(fp)) { + int len; + + if (!fgets(line, 256, fp)) + break; + len = strlen(line); + if (len < 2) + continue; + if (*line == '#') + continue; + if (line[len - 1] == '\n') + line[len - 1] = '\0'; + if (!strncmp(line, "include ", 8)) { + char *sep = strstr(line, " "); + glob_t gl; + int i; + + if (!sep) + continue;; + while (*sep == ' ') + sep++; + if (glob(sep, GLOB_NOESCAPE | GLOB_MARK, NULL, &gl)) { + ERROR("glob failed on %s\n", sep); + continue; + } + for (i = 0; i < gl.gl_pathc; i++) + load_ldso_conf(gl.gl_pathv[i]); + globfree(&gl); + } else { + struct stat s; + + if (stat(line, &s)) + continue; + alloc_library_path(line); + } + } + + fclose(fp); +} diff --git a/jail/elf.h b/jail/elf.h new file mode 100644 index 0000000..3ae311e --- /dev/null +++ b/jail/elf.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include + +#include "log.h" + +struct library { + struct avl_node avl; + char *name; + char *path; +}; + +struct library_path { + struct list_head list; + char *path; +}; + +extern struct avl_tree libraries; + +extern void alloc_library_path(const char *path); +extern char* find_lib(char *file); +extern int elf_load_deps(char *library); +extern void load_ldso_conf(const char *conf); + +#endif diff --git a/jail/jail.c b/jail/jail.c new file mode 100644 index 0000000..08d5ee1 --- /dev/null +++ b/jail/jail.c @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "elf.h" + +#include +#include +#include + +#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 -D \n"); + fprintf(stderr, " -P \tpath where the jail will be staged\n"); + fprintf(stderr, " -S \tseccomp filter\n"); + fprintf(stderr, " -n \tthe name of the jail\n"); + fprintf(stderr, " -r \treadonly files that should be staged\n"); + fprintf(stderr, " -w \twriteable files that should be staged\n"); + fprintf(stderr, " -p\t\tjail has /proc\t\n"); + fprintf(stderr, " -s\t\tjail has /sys\t\n"); + fprintf(stderr, " -l\t\tjail has /dev/log\t\n"); + fprintf(stderr, " -u\t\tjail has a ubus socket\t\n"); + + return -1; +} + +static int child_running = 1; + +static void child_process_handler(struct uloop_process *c, int ret) +{ + INFO("child (%d) exited: %d\n", c->pid, ret); + uloop_end(); + child_running = 0; +} + +struct uloop_process child_process = { + .cb = child_process_handler, +}; + +static int spawn_child(void *arg) +{ + char *path = get_current_dir_name(); + int procfs = 0, sysfs = 0; + char *seccomp = NULL; + char **argv = arg; + int argc = 0, ch; + char *mpoint; + + while (argv[argc]) + argc++; + + optind = 0; + while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'S': + seccomp = optarg; + break; + case 'p': + procfs = 1; + break; + case 's': + sysfs = 1; + break; + case 'n': + sethostname(optarg, strlen(optarg)); + break; + } + } + + asprintf(&mpoint, "%s/old", path); + mkdir_p(mpoint, 0755); + if (pivot_root(path, mpoint) == -1) { + ERROR("pivot_root failed:%s\n", strerror(errno)); + return -1; + } + free(mpoint); + umount2("/old", MNT_DETACH); + rmdir("/old"); + if (procfs) { + mkdir("/proc", 0755); + mount("proc", "/proc", "proc", MS_NOATIME, 0); + } + if (sysfs) { + mkdir("/sys", 0755); + mount("sysfs", "/sys", "sysfs", MS_NOATIME, 0); + } + mount(NULL, "/", NULL, MS_RDONLY | MS_REMOUNT, 0); + + uloop_init(); + + child_process.pid = spawn(path, &argv[optind], seccomp); + uloop_process_add(&child_process); + uloop_run(); + uloop_done(); + if (child_running) { + kill(child_process.pid, SIGTERM); + waitpid(child_process.pid, NULL, 0); + } + + return 0; +} + +static int namespace_running = 1; + +static void namespace_process_handler(struct uloop_process *c, int ret) +{ + INFO("namespace (%d) exited: %d\n", c->pid, ret); + uloop_end(); + namespace_running = 0; +} + +struct uloop_process namespace_process = { + .cb = namespace_process_handler, +}; + +static void spawn_namespace(const char *path, int argc, char **argv) +{ + char *dir = get_current_dir_name(); + + uloop_init(); + chdir(path); + namespace_process.pid = clone(spawn_child, + child_stack + STACK_SIZE, + CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, argv); + + if (namespace_process.pid != -1) { + chdir(dir); + free(dir); + uloop_process_add(&namespace_process); + uloop_run(); + uloop_done(); + if (namespace_running) { + kill(namespace_process.pid, SIGTERM); + waitpid(namespace_process.pid, NULL, 0); + } + } else { + ERROR("failed to spawn namespace: %s\n", strerror(errno)); + } +} + +static void add_extra(char *name, int readonly) +{ + struct extra *f; + + if (*name != '/') { + ERROR("%s is not an absolute path\n", name); + return; + } + + f = calloc(1, sizeof(struct extra)); + + f->name = basename(name); + f->path = dirname(strdup(name)); + f->readonly = readonly; + + list_add_tail(&f->list, &extras); +} + +int main(int argc, char **argv) +{ + uid_t uid = getuid(); + const char *name = NULL; + char *path = NULL; + struct stat s; + int ch, ret; + char log[] = "/dev/log"; + char ubus[] = "/var/run/ubus.sock"; + + if (uid) { + ERROR("not root, aborting: %s\n", strerror(errno)); + return -1; + } + + umask(022); + + while ((ch = getopt(argc, argv, OPT_ARGS)) != -1) { + switch (ch) { + case 'd': + debug = 1; + break; + case 'P': + path = optarg; + break; + case 'n': + name = optarg; + break; + case 'S': + case 'r': + add_extra(optarg, 1); + break; + case 'w': + add_extra(optarg, 0); + break; + case 'u': + add_extra(ubus, 0); + break; + case 'l': + add_extra(log, 0); + break; + } + } + + if (argc - optind < 1) + return usage(); + + if (!path && asprintf(&path, "/tmp/%s", basename(argv[optind])) == -1) { + ERROR("failed to set root path\n: %s", strerror(errno)); + return -1; + } + + if (!stat(path, &s)) { + ERROR("%s already exists: %s\n", path, strerror(errno)); + return -1; + } + + if (name) + prctl(PR_SET_NAME, name, NULL, NULL, NULL); + + avl_init(&libraries, avl_strcmp, false, NULL); + alloc_library_path("/lib64"); + alloc_library_path("/lib"); + alloc_library_path("/usr/lib"); + load_ldso_conf("/etc/ld.so.conf"); + + if (elf_load_deps(argv[optind])) { + ERROR("failed to load dependencies\n"); + return -1; + } + + if (elf_load_deps("libpreload-seccomp.so")) { + ERROR("failed to load libpreload-seccomp.so\n"); + return -1; + } + + ret = build_jail(path); + + if (!ret) + spawn_namespace(path, argc, argv); + else + ERROR("failed to build jail\n"); + + stop_jail(path); + + return ret; +} diff --git a/jail/log.h b/jail/log.h new file mode 100644 index 0000000..f8590b3 --- /dev/null +++ b/jail/log.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2015 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +extern int debug; + +#define INFO(fmt, ...) do { \ + printf("jail: "fmt, ## __VA_ARGS__); \ + } while (0) +#define ERROR(fmt, ...) do { \ + syslog(LOG_ERR, "jail: "fmt, ## __VA_ARGS__); \ + fprintf(stderr,"jail: "fmt, ## __VA_ARGS__); \ + } while (0) +#define DEBUG(fmt, ...) do { \ + if (debug) printf("jail: "fmt, ## __VA_ARGS__); \ + } while (0) + diff --git a/jail/preload.c b/jail/preload.c new file mode 100644 index 0000000..97ac44d --- /dev/null +++ b/jail/preload.c @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include +#include +#include +#include +#include +#include + +#include "seccomp.h" +#include "../preload.h" + +static main_t __main__; + +static int __preload_main__(int argc, char **argv, char **envp) +{ + uid_t uid = getuid(); + char *env_file = getenv("SECCOMP_FILE"); + + if (uid) { + INFO("preload-seccomp: %s: not root, cannot install seccomp filter\n", *argv); + return -1; + } + + if (install_syscall_filter(*argv, env_file)) + return -1; + + unsetenv("LD_PRELOAD"); + unsetenv("SECCOMP_FILE"); + + return (*__main__)(argc, argv, envp); +} + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end) +{ + start_main_t __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__libc_start_main"); + if (!__start_main__) + INFO("failed to find __libc_start_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, auxvec, + init, fini, rtld_fini, stack_end); +} + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused) +{ + uClibc_main __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__uClibc_main"); + if (!__start_main__) + INFO("failed to find __uClibc_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, + app_init, app_fini, rtld_fini, stack_end); +} diff --git a/jail/seccomp-bpf.h b/jail/seccomp-bpf.h new file mode 100644 index 0000000..1cc2908 --- /dev/null +++ b/jail/seccomp-bpf.h @@ -0,0 +1,77 @@ +/* + * seccomp example for x86 (32-bit and 64-bit) with BPF macros + * + * Copyright (c) 2012 The Chromium OS Authors + * Authors: + * Will Drewry + * Kees Cook + * + * 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 +#include +#include +#include +#include +#include +#include + +#include +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 +#endif + +#include +#include +#include + +#ifdef HAVE_LINUX_SECCOMP_H +# include +#endif + +#ifndef SECCOMP_MODE_FILTER +#define SECCOMP_MODE_FILTER 2 /* uses user-supplied filter. */ +#define SECCOMP_RET_KILL 0x00000000U /* kill the task immediately */ +#define SECCOMP_RET_TRAP 0x00030000U /* disallow and force a SIGSYS */ +#define SECCOMP_RET_ERRNO 0x00050000U /* returns an errno */ +#define SECCOMP_RET_LOG 0x00070000U +#define SECCOMP_RET_ALLOW 0x7fff0000U /* allow */ +#define SECCOMP_RET_ERROR(x) (SECCOMP_RET_ERRNO | ((x) & 0x0000ffffU)) +#define SECCOMP_RET_LOGGER(x) (SECCOMP_RET_LOG | ((x) & 0x0000ffffU)) + +struct seccomp_data { + int nr; + __u32 arch; + __u64 instruction_pointer; + __u64 args[6]; +}; +#endif + +#ifndef SYS_SECCOMP +# define SYS_SECCOMP 1 +#endif + +#define syscall_nr (offsetof(struct seccomp_data, nr)) +#define arch_nr (offsetof(struct seccomp_data, arch)) + +#if defined(__i386__) +# define REG_SYSCALL REG_EAX +# define ARCH_NR AUDIT_ARCH_I386 +#elif defined(__x86_64__) +# define REG_SYSCALL REG_RAX +# define ARCH_NR AUDIT_ARCH_X86_64 +#elif defined(__mips__) +# define REG_SYSCALL regs[2] +# define ARCH_NR AUDIT_ARCH_MIPSEL +#else +# warning "Platform does not support seccomp filter yet" +# define REG_SYSCALL 0 +# define ARCH_NR 0 +#endif + +#endif /* _SECCOMP_BPF_H_ */ diff --git a/jail/seccomp.c b/jail/seccomp.c new file mode 100644 index 0000000..de01fc6 --- /dev/null +++ b/jail/seccomp.c @@ -0,0 +1,142 @@ +/* + * seccomp example with syscall reporting + * + * Copyright (c) 2012 The Chromium OS Authors + * Authors: + * Kees Cook + * Will Drewry + * + * 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 +#include +#include +#include +#include + +#include +#include +#include + +#include "seccomp-bpf.h" +#include "seccomp.h" +#include "../syscall-names.h" + +static int max_syscall = ARRAY_SIZE(syscall_names); + +static int find_syscall(const char *name) +{ + int i; + + for (i = 0; i < max_syscall; i++) + if (syscall_names[i] && !strcmp(syscall_names[i], name)) + return i; + + return -1; +} + +static void set_filter(struct sock_filter *filter, __u16 code, __u8 jt, __u8 jf, __u32 k) +{ + filter->code = code; + filter->jt = jt; + filter->jf = jf; + filter->k = k; +} + +int install_syscall_filter(const char *argv, const char *file) +{ + enum { + SECCOMP_WHITELIST, + SECCOMP_POLICY, + __SECCOMP_MAX + }; + static const struct blobmsg_policy policy[__SECCOMP_MAX] = { + [SECCOMP_WHITELIST] = { .name = "whitelist", .type = BLOBMSG_TYPE_ARRAY }, + [SECCOMP_POLICY] = { .name = "policy", .type = BLOBMSG_TYPE_INT32 }, + }; + struct blob_buf b = { 0 }; + struct blob_attr *tb[__SECCOMP_MAX]; + struct blob_attr *cur; + int rem; + + struct sock_filter *filter; + struct sock_fprog prog = { 0 }; + int sz = 5, idx = 0, default_policy = 0; + + INFO("%s: setting up syscall filter\n", argv); + + blob_buf_init(&b, 0); + if (!blobmsg_add_json_from_file(&b, file)) { + INFO("%s: failed to load %s\n", argv, file); + return -1; + } + + blobmsg_parse(policy, __SECCOMP_MAX, tb, blob_data(b.head), blob_len(b.head)); + if (!tb[SECCOMP_WHITELIST]) { + INFO("%s: %s is missing the syscall table\n", argv, file); + return -1; + } + + if (tb[SECCOMP_POLICY]) + default_policy = blobmsg_get_u32(tb[SECCOMP_POLICY]); + + blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem) + sz += 2; + + filter = calloc(sz, sizeof(struct sock_filter)); + if (!filter) { + INFO("failed to allocate filter memory\n"); + return -1; + } + + /* validate arch */ + set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, arch_nr); + set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 1, 0, ARCH_NR); + set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); + + /* get syscall */ + set_filter(&filter[idx++], BPF_LD + BPF_W + BPF_ABS, 0, 0, syscall_nr); + + blobmsg_for_each_attr(cur, tb[SECCOMP_WHITELIST], rem) { + char *name = blobmsg_get_string(cur); + int nr; + + if (!name) { + INFO("%s: invalid syscall name\n", argv); + continue; + } + + nr = find_syscall(name); + if (nr == -1) { + INFO("%s: unknown syscall %s\n", argv, name); + continue; + } + + /* add whitelist */ + set_filter(&filter[idx++], BPF_JMP + BPF_JEQ + BPF_K, 0, 1, nr); + set_filter(&filter[idx++], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_ALLOW); + } + + if (default_policy) + /* return -1 and set errno */ + set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_LOGGER(default_policy)); + else + /* kill the process */ + set_filter(&filter[idx], BPF_RET + BPF_K, 0, 0, SECCOMP_RET_KILL); + + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) { + INFO("%s: prctl(PR_SET_NO_NEW_PRIVS) failed: %s\n", argv, strerror(errno)); + return errno; + } + + prog.len = (unsigned short) idx + 1; + prog.filter = filter; + + if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog)) { + INFO("%s: prctl(PR_SET_SECCOMP) failed: %s\n", argv, strerror(errno)); + return errno; + } + return 0; +} diff --git a/jail/seccomp.h b/jail/seccomp.h new file mode 100644 index 0000000..6c585ad --- /dev/null +++ b/jail/seccomp.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2015 John Crispin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License version 2.1 + * as published by the Free Software Foundation + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define INFO(fmt, ...) do { \ + syslog(0,"perload-jail: "fmt, ## __VA_ARGS__); \ + fprintf(stderr,"perload-jail: "fmt, ## __VA_ARGS__); \ + } while (0) + +int install_syscall_filter(const char *argv, const char *file); diff --git a/make_syscall_h.sh b/make_syscall_h.sh new file mode 100755 index 0000000..57333fd --- /dev/null +++ b/make_syscall_h.sh @@ -0,0 +1,18 @@ +#!/bin/sh +# syscall reporting example for seccomp +# +# Copyright (c) 2012 The Chromium OS Authors +# Authors: +# Kees Cook +# +# 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 " +echo "static const char *syscall_names[] = {" +echo "#include " | ${CC} -E -dM - | grep '^#define __NR_' | \ + LC_ALL=C sed -r -n -e 's/^\#define[ \t]+__NR_([a-z0-9_]+)[ \t]+([ ()+0-9NR_Linux]+)(.*)/ [\2] = "\1",/p' +echo "};" diff --git a/preload.h b/preload.h new file mode 100644 index 0000000..5e663ac --- /dev/null +++ b/preload.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 + +#ifndef __unbounded +#define __unbounded +#endif + +#ifndef attribute_unused +#define attribute_unused __attribute__ ((unused)) +#endif +typedef int (*main_t)(int, char **, char **); + +typedef int (*start_main_t)(main_t main, int, char *__unbounded *__unbounded, + ElfW(auxv_t) *, + __typeof (main), + void (*fini) (void), + void (*rtld_fini) (void), + void *__unbounded stack_end); + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end); + + +typedef void (*uClibc_main)(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused); + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused); diff --git a/trace/preload.c b/trace/preload.c new file mode 100644 index 0000000..99dd906 --- /dev/null +++ b/trace/preload.c @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#include "../preload.h" + +#define ERROR(fmt, ...) do { \ + fprintf(stderr,"perload-jail: "fmt, ## __VA_ARGS__); \ + } while (0) + +static main_t __main__; + +static int __preload_main__(int argc, char **argv, char **envp) +{ + unsetenv("LD_PRELOAD"); + ptrace(PTRACE_TRACEME); + kill(getpid(), SIGSTOP); + + return (*__main__)(argc, argv, envp); +} + +int __libc_start_main(main_t main, + int argc, + char **argv, + ElfW(auxv_t) *auxvec, + __typeof (main) init, + void (*fini) (void), + void (*rtld_fini) (void), + void *stack_end) +{ + start_main_t __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__libc_start_main"); + if (!__start_main__) + ERROR("failed to find __libc_start_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, auxvec, + init, fini, rtld_fini, stack_end); +} + +void __uClibc_main(main_t main, + int argc, + char **argv, + void (*app_init)(void), + void (*app_fini)(void), + void (*rtld_fini)(void), + void *stack_end attribute_unused) +{ + uClibc_main __start_main__; + + __start_main__ = dlsym(RTLD_NEXT, "__uClibc_main"); + if (!__start_main__) + ERROR("failed to find __uClibc_main %s\n", dlerror()); + + __main__ = main; + + return (*__start_main__)(__preload_main__, argc, argv, + app_init, app_fini, rtld_fini, stack_end); +} diff --git a/trace/trace.c b/trace/trace.c new file mode 100644 index 0000000..c6f32d7 --- /dev/null +++ b/trace/trace.c @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2015 John Crispin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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; +} -- 2.25.1