/** * seccomp_add_filter - adds a filter for an unfiltered syscall * @filters: filters object to add a filter/action to * @syscall_nr: system call number to add a filter for * @filter_string: the filter string to apply * * Returns 0 on success and non-zero otherwise. */ static int seccomp_add_filter(struct seccomp_filters *filters, int syscall_nr, char *filter_string) { struct event_filter *filter; int ret = 0; if (!strcmp(SECCOMP_FILTER_ALLOW, filter_string)) { set_seccomp_filter(filters, syscall_nr, SECCOMP_ACTION_ALLOW, NULL); goto out; } /* ftrace events are not aware of CONFIG_COMPAT system calls and will * use the incorrect argument metadata if enabled. */ ret = -ENOSYS; if (filters->flags.compat) goto out; ret = 0; filter = alloc_event_filter(syscall_nr, filter_string); if (IS_ERR(filter)) { ret = PTR_ERR(filter); goto out; } /* Always add to the last slot available since additions are * are only done one at a time. */ set_seccomp_filter(filters, syscall_nr, filters->count - 1, filter); out: return ret; }
/** * seccomp_extend_filter - appends more text to a syscall_nr's filter * @filters: unattached filter object to operate on * @syscall_nr: syscall number to update filters for * @filter_string: string to append to the existing filter * * The new string will be &&'d to the original filter string to ensure that it * always matches the existing predicates or less: * (old_filter) && @filter_string * A new seccomp_filters instance is returned on success and a ERR_PTR on * failure. */ static int seccomp_extend_filter(struct seccomp_filters *filters, int syscall_nr, char *filter_string) { struct event_filter *filter; uint16_t id = seccomp_filter_id(filters, syscall_nr); char *merged = NULL; int ret = -EINVAL, expected; /* No extending with a "1". */ if (!strcmp(SECCOMP_FILTER_ALLOW, filter_string)) goto out; /* ftrace events are not aware of CONFIG_COMPAT system calls and will * use the incorrect argument metadata if enabled. */ ret = -ENOSYS; if (filters->flags.compat) goto out; filter = seccomp_dynamic_filter(filters, id); ret = -ENOENT; if (!filter) goto out; merged = kzalloc(SECCOMP_MAX_FILTER_LENGTH + 1, GFP_KERNEL); ret = -ENOMEM; if (!merged) goto out; /* Encapsulate the filter strings in parentheses to isolate operator * precedence behavior. */ expected = snprintf(merged, SECCOMP_MAX_FILTER_LENGTH, "(%s) && (%s)", get_filter_string(filter), filter_string); ret = -E2BIG; if (expected >= SECCOMP_MAX_FILTER_LENGTH || expected < 0) goto out; /* Free the old filter */ free_event_filter(filter); set_seccomp_filter(filters, syscall_nr, id, NULL); /* Replace it */ filter = alloc_event_filter(syscall_nr, merged); if (IS_ERR(filter)) { ret = PTR_ERR(filter); goto out; } set_seccomp_filter(filters, syscall_nr, id, filter); ret = 0; out: kfree(merged); return ret; }
/** * seccomp_filters_copy - copies filters from src to dst. * * @dst: seccomp_filters to populate. * @src: table to read from. * @skip: specifies an entry, by system call, to skip. * * Returns non-zero on failure. * Both the source and the destination should have no simultaneous * writers, and dst should be exclusive to the caller. * If @skip is < 0, it is ignored. */ static int seccomp_filters_copy(struct seccomp_filters *dst, const struct seccomp_filters *src, int skip) { int id = 0, ret = 0, nr; memcpy(&dst->flags, &src->flags, sizeof(src->flags)); memcpy(dst->syscalls, src->syscalls, sizeof(dst->syscalls)); if (!src->count) goto done; for (nr = 0; nr < NR_syscalls; ++nr) { struct event_filter *filter; char *str; uint16_t src_id = seccomp_filter_id(src, nr); if (nr == skip) { set_seccomp_filter(dst, nr, SECCOMP_ACTION_DENY, NULL); continue; } if (!seccomp_filter_dynamic(src_id)) continue; if (id >= dst->count) { ret = -EINVAL; goto done; } str = get_filter_string(seccomp_dynamic_filter(src, src_id)); filter = alloc_event_filter(nr, str); if (IS_ERR(filter)) { ret = PTR_ERR(filter); goto done; } set_seccomp_filter(dst, nr, id, filter); id++; } done: return ret; }
void API minijail_enter(const struct minijail *j) { if (j->flags.pids) die("tried to enter a pid-namespaced jail;" "try minijail_run()?"); if (j->flags.usergroups && !j->user) die("usergroup inheritance without username"); /* * We can't recover from failures if we've dropped privileges partially, * so we don't even try. If any of our operations fail, we abort() the * entire process. */ if (j->flags.vfs && unshare(CLONE_NEWNS)) pdie("unshare(vfs)"); if (j->flags.net && unshare(CLONE_NEWNET)) pdie("unshare(net)"); if (j->flags.chroot && enter_chroot(j)) pdie("chroot"); if (j->flags.chroot && j->flags.mount_tmp && mount_tmp()) pdie("mount_tmp"); if (j->flags.readonly && remount_readonly(j)) pdie("remount"); if (j->flags.caps) { /* * POSIX capabilities are a bit tricky. If we drop our * capability to change uids, our attempt to use setuid() * below will fail. Hang on to root caps across setuid(), then * lock securebits. */ if (prctl(PR_SET_KEEPCAPS, 1)) pdie("prctl(PR_SET_KEEPCAPS)"); if (prctl (PR_SET_SECUREBITS, SECURE_ALL_BITS | SECURE_ALL_LOCKS)) pdie("prctl(PR_SET_SECUREBITS)"); } /* * If we're setting no_new_privs, we can drop privileges * before setting seccomp filter. This way filter policies * don't need to allow privilege-dropping syscalls. */ if (j->flags.no_new_privs) { drop_ugid(j); if (j->flags.caps) drop_caps(j); set_seccomp_filter(j); } else { /* * If we're not setting no_new_privs, * we need to set seccomp filter *before* dropping privileges. * WARNING: this means that filter policies *must* allow * setgroups()/setresgid()/setresuid() for dropping root and * capget()/capset()/prctl() for dropping caps. */ set_seccomp_filter(j); drop_ugid(j); if (j->flags.caps) drop_caps(j); } /* * seccomp has to come last since it cuts off all the other * privilege-dropping syscalls :) */ if (j->flags.seccomp && prctl(PR_SET_SECCOMP, 1)) pdie("prctl(PR_SET_SECCOMP)"); }
noinline int do_action(const char* arg) { // Prefixes. if (!strncmp(arg, "wait-", strlen("wait-"))) { char buf[1]; TEMP_FAILURE_RETRY(read(STDIN_FILENO, buf, sizeof(buf))); return do_action(arg + strlen("wait-")); } else if (!strncmp(arg, "exhaustfd-", strlen("exhaustfd-"))) { errno = 0; while (errno != EMFILE) { open("/dev/null", O_RDONLY); } return do_action(arg + strlen("exhaustfd-")); } else if (!strncmp(arg, "thread-", strlen("thread-"))) { return do_action_on_thread(arg + strlen("thread-")); } // Actions. if (!strcasecmp(arg, "SIGSEGV-non-null")) { sigsegv_non_null(); } else if (!strcasecmp(arg, "smash-stack")) { volatile int len = 128; return smash_stack(&len); } else if (!strcasecmp(arg, "stack-overflow")) { overflow_stack(nullptr); } else if (!strcasecmp(arg, "nostack")) { crashnostack(); } else if (!strcasecmp(arg, "exit")) { exit(1); } else if (!strcasecmp(arg, "crash") || !strcmp(arg, "SIGSEGV")) { return crash(42); } else if (!strcasecmp(arg, "abort")) { maybe_abort(); } else if (!strcasecmp(arg, "assert")) { __assert("some_file.c", 123, "false"); } else if (!strcasecmp(arg, "assert2")) { __assert2("some_file.c", 123, "some_function", "false"); } else if (!strcasecmp(arg, "fortify")) { char buf[10]; __read_chk(-1, buf, 32, 10); while (true) pause(); } else if (!strcasecmp(arg, "LOG(FATAL)")) { LOG(FATAL) << "hello " << 123; } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL")) { LOG_ALWAYS_FATAL("hello %s", "world"); } else if (!strcasecmp(arg, "LOG_ALWAYS_FATAL_IF")) { LOG_ALWAYS_FATAL_IF(true, "hello %s", "world"); } else if (!strcasecmp(arg, "SIGFPE")) { raise(SIGFPE); return EXIT_SUCCESS; } else if (!strcasecmp(arg, "SIGTRAP")) { raise(SIGTRAP); return EXIT_SUCCESS; } else if (!strcasecmp(arg, "fprintf-NULL")) { fprintf_null(); } else if (!strcasecmp(arg, "readdir-NULL")) { readdir_null(); } else if (!strcasecmp(arg, "strlen-NULL")) { return strlen_null(); } else if (!strcasecmp(arg, "pthread_join-NULL")) { return pthread_join(0, nullptr); } else if (!strcasecmp(arg, "heap-usage")) { abuse_heap(); } else if (!strcasecmp(arg, "leak")) { leak(); } else if (!strcasecmp(arg, "SIGSEGV-unmapped")) { char* map = reinterpret_cast<char*>(mmap(nullptr, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0)); munmap(map, sizeof(int)); map[0] = '8'; } else if (!strcasecmp(arg, "seccomp")) { set_seccomp_filter(); syscall(99999); #if defined(__arm__) } else if (!strcasecmp(arg, "kuser_helper_version")) { return __kuser_helper_version; } else if (!strcasecmp(arg, "kuser_get_tls")) { return !__kuser_get_tls(); } else if (!strcasecmp(arg, "kuser_cmpxchg")) { return __kuser_cmpxchg(0, 0, 0); } else if (!strcasecmp(arg, "kuser_memory_barrier")) { __kuser_dmb(); } else if (!strcasecmp(arg, "kuser_cmpxchg64")) { return __kuser_cmpxchg64(0, 0, 0); #endif } else if (!strcasecmp(arg, "no_new_privs")) { if (prctl(PR_SET_NO_NEW_PRIVS, 1) != 0) { fprintf(stderr, "prctl(PR_SET_NO_NEW_PRIVS, 1) failed: %s\n", strerror(errno)); return EXIT_SUCCESS; } abort(); } else { return usage(); } fprintf(stderr, "%s: exiting normally!\n", getprogname()); return EXIT_SUCCESS; }