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