static void epoll_add(int epoll_fd, int fd, uint32_t events) { struct epoll_event event = { .data.fd = fd, .events = events }; check_posix(epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event), "epoll_ctl"); } static void copy_to_stdstream(int in_fd, int out_fd) { uint8_t buffer[BUFSIZ]; ssize_t n = read(in_fd, buffer, sizeof buffer); if (check_eagain(n, "read")) return; check_posix(write(out_fd, buffer, (size_t)n), "write"); }
static void do_trace(const struct signalfd_siginfo *si, bool *trace_init, FILE *learn) { int status; if (waitpid((pid_t)si->ssi_pid, &status, WNOHANG) != (pid_t)si->ssi_pid) errx(EXIT_FAILURE, "waitpid"); if (WIFEXITED(status) || WIFSIGNALED(status) || !WIFSTOPPED(status)) errx(EXIT_FAILURE, "unexpected ptrace event"); int inject_signal = 0; if (*trace_init) { int signal = WSTOPSIG(status); if (signal != SIGTRAP || !(status & PTRACE_EVENT_SECCOMP)) inject_signal = signal; else { errno = 0; #ifdef __x86_64__ long syscall = ptrace(PTRACE_PEEKUSER, si->ssi_pid, sizeof(long)*ORIG_RAX); #else long syscall = ptrace(PTRACE_PEEKUSER, si->ssi_pid, sizeof(long)*ORIG_EAX); #endif if (errno) err(EXIT_FAILURE, "ptrace"); char *name = seccomp_syscall_resolve_num_arch(SCMP_ARCH_NATIVE, (int)syscall); if (!name) errx(EXIT_FAILURE, "seccomp_syscall_resolve_num_arch"); rewind(learn); char line[SYSCALL_NAME_MAX]; while (fgets(line, sizeof line, learn)) { char *pos; if ((pos = strchr(line, '\n'))) *pos = '\0'; if (!strcmp(name, line)) { name = NULL; break; } } if (name) { fprintf(learn, "%s\n", name); free(name); } } } else { check_posix(ptrace(PTRACE_SETOPTIONS, si->ssi_pid, 0, PTRACE_O_TRACESECCOMP), "ptrace"); *trace_init = true; } check_posix(ptrace(PTRACE_CONT, si->ssi_pid, 0, inject_signal), "ptrace"); }
int main(int argc, char *argv[]) { if (argc < 2) return -1; int fd = open("/dev/uinput", O_WRONLY | O_NONBLOCK); check_posix(fd, "failed to open uinput"); check_posix(ioctl(fd, UI_SET_EVBIT, EV_KEY), "failed to set EV_KEY"); check_posix(ioctl(fd, UI_SET_KEYBIT, KEY_LEFTSHIFT), "failed to set UI_SET_KEYBIT"); for (int key = KEY_1; key <= KEY_SPACE; ++key) { check_posix(ioctl(fd, UI_SET_KEYBIT, key & KEYMASK), "failed to set UI_SET_KEYBIT"); } struct uinput_user_dev uidev = { .id.bustype = BUS_VIRTUAL, .id.vendor = 0x1, .id.product = 0x1, .id.version = 1 }; snprintf(uidev.name, UINPUT_MAX_NAME_SIZE, "Virtual Injector Keyboard"); check_posix(write(fd, &uidev, sizeof(uidev)), "failed to write uinput_user_dev struct"); check_posix(ioctl(fd, UI_DEV_CREATE), "failed to create uinput device"); usleep(500000); ev_inject_keypresses(fd, argv[1]); ioctl(fd, UI_DEV_DESTROY); close(fd); }
// Mark any extra file descriptors `CLOEXEC`. Only `stdin`, `stdout` and `stderr` are left open. static void prevent_leaked_file_descriptors() { DIR *dir = opendir("/proc/self/fd"); if (!dir) err(EXIT_FAILURE, "opendir"); struct dirent *dp; while ((dp = readdir(dir))) { char *end; int fd = (int)strtol(dp->d_name, &end, 10); if (*end == '\0' && fd > 2 && fd != dirfd(dir)) { check_posix(ioctl(fd, FIOCLEX), "ioctl"); } } closedir(dir); }
static void handle_signal(int sig_fd, sd_bus *connection, const char *unit_name, bool *trace_init, FILE *learn) { struct signalfd_siginfo si; ssize_t bytes_r = read(sig_fd, &si, sizeof(si)); check_posix(bytes_r, "read"); if (bytes_r != sizeof(si)) errx(EXIT_FAILURE, "read the wrong amount of bytes"); switch (si.ssi_signo) { case SIGHUP: case SIGINT: case SIGTERM: stop_scope_unit(connection, unit_name); errx(EXIT_FAILURE, "interrupted, stopping early"); } if (si.ssi_signo != SIGCHLD) errx(EXIT_FAILURE, "got an unexpected signal"); switch (si.ssi_code) { case CLD_EXITED: if (si.ssi_status) { warnx("application terminated with error code %d", si.ssi_status); } exit(si.ssi_status); case CLD_KILLED: case CLD_DUMPED: errx(EXIT_FAILURE, "application terminated abnormally with signal %d (%s)", si.ssi_status, strsignal(si.ssi_status)); case CLD_TRAPPED: do_trace(&si, trace_init, learn); case CLD_STOPPED: default: break; } }
static void mountx(const char *source, const char *target, const char *filesystemtype, unsigned long mountflags, const void *data) { check_posix(mount(source, target, filesystemtype, mountflags, data), "mounting %s as %s (%s) failed", source, target, filesystemtype); }
static char *join_path(const char *left, const char *right) { char *dst; check_posix(asprintf(&dst, "%s/%s", left, right), "asprintf"); return dst; }
int main(int argc, char **argv) { prevent_leaked_file_descriptors(); bool mount_proc = false; bool mount_dev = false; const char *username = "******"; const char *hostname = "playpen"; long timeout = 0; long memory_limit = 128; struct bind_list *binds = NULL, *binds_tail = NULL; char *devices = NULL; char *syscalls = NULL; const char *syscalls_file = NULL; const char *learn_name = NULL; static const struct option opts[] = { { "help", no_argument, 0, 'h' }, { "version", no_argument, 0, 'v' }, { "mount-proc", no_argument, 0, 'p' }, { "mount-dev", no_argument, 0, 0x100 }, { "bind", required_argument, 0, 'b' }, { "bind-rw", required_argument, 0, 'B' }, { "user", required_argument, 0, 'u' }, { "hostname", required_argument, 0, 'n' }, { "timeout", required_argument, 0, 't' }, { "memory-limit", required_argument, 0, 'm' }, { "devices", required_argument, 0, 'd' }, { "syscalls", required_argument, 0, 's' }, { "syscalls-file", required_argument, 0, 'S' }, { "learn", required_argument, 0, 'l' }, { 0, 0, 0, 0 } }; for (;;) { int opt = getopt_long(argc, argv, "hvpb:B:u:n:t:m:d:s:S:l:", opts, NULL); if (opt == -1) break; switch (opt) { case 'h': usage(stdout); case 'v': printf("%s %s\n", program_invocation_short_name, VERSION); return 0; case 'p': mount_proc = true; break; case 0x100: mount_dev = true; break; case 'b': case 'B': if (binds) { binds_tail->next = bind_list_alloc(optarg, opt == 'b'); binds_tail = binds_tail->next; } else { binds = binds_tail = bind_list_alloc(optarg, opt == 'b'); } break; case 'u': username = optarg; break; case 'n': hostname = optarg; break; case 't': timeout = strtolx_positive(optarg, "timeout"); break; case 'm': memory_limit = strtolx_positive(optarg, "memory limit"); break; case 'd': devices = optarg; break; case 's': syscalls = optarg; break; case 'S': syscalls_file = optarg; break; case 'l': learn_name = optarg; break; default: usage(stderr); } } if (argc - optind < 2) { usage(stderr); } const char *root = argv[optind]; optind++; scmp_filter_ctx ctx = seccomp_init(learn_name ? SCMP_ACT_TRACE(0) : SCMP_ACT_KILL); if (!ctx) errx(EXIT_FAILURE, "seccomp_init"); if (syscalls_file) { char name[SYSCALL_NAME_MAX]; FILE *file = fopen(syscalls_file, "r"); if (!file) err(EXIT_FAILURE, "failed to open syscalls file: %s", syscalls_file); while (fgets(name, sizeof name, file)) { char *pos; if ((pos = strchr(name, '\n'))) *pos = '\0'; check(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, get_syscall_nr(name), 0)); } fclose(file); } check(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, __NR_execve, 0)); if (syscalls) { for (char *s_ptr = syscalls, *saveptr; ; s_ptr = NULL) { const char *syscall = strtok_r(s_ptr, ",", &saveptr); if (!syscall) break; check(seccomp_rule_add(ctx, SCMP_ACT_ALLOW, get_syscall_nr(syscall), 0)); } } int epoll_fd = epoll_create1(EPOLL_CLOEXEC); check_posix(epoll_fd, "epoll_create1"); sigset_t mask; sigemptyset(&mask); sigaddset(&mask, SIGCHLD); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); check_posix(sigprocmask(SIG_BLOCK, &mask, NULL), "sigprocmask"); int sig_fd = signalfd(-1, &mask, SFD_CLOEXEC); check_posix(sig_fd, "signalfd"); epoll_add(epoll_fd, sig_fd, EPOLLIN); int pipe_in[2]; int pipe_out[2]; int pipe_err[2]; check_posix(pipe(pipe_in), "pipe"); check_posix(pipe(pipe_out), "pipe"); set_non_blocking(pipe_out[0]); check_posix(pipe(pipe_err), "pipe"); set_non_blocking(pipe_err[0]); int rc = epoll_ctl(epoll_fd, EPOLL_CTL_ADD, STDIN_FILENO, &(struct epoll_event){ .data.fd = STDIN_FILENO, .events = EPOLLIN });
static void set_non_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); check_posix(flags, "fcntl"); check_posix(fcntl(fd, F_SETFL, flags | O_NONBLOCK), "fcntl"); }