/* SPDX-License-Identifier: GPL-2.0-or-later * Copyright 2023 Red Hat GmbH * Author: Alice Frosi */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "testutil.h" #include "common/common.h" struct seccomp_notif req; int notifyfd; struct args_target *at; int pipefd[2]; pid_t pid; char path[PATH_MAX] = "/tmp/test-seitan"; struct gluten gluten; char stderr_buff[BUFSIZ]; char stdout_buff[BUFSIZ]; #define logfn(name) \ void name(const char *format, ...) { \ va_list args; \ \ va_start(args, format); \ (void)vfprintf(stderr, format, args); \ va_end(args); \ if (format[strlen(format)] != '\n') \ fprintf(stderr, "\n"); \ } logfn(debug) int install_single_syscall(long nr) { /* filter a single syscall for the tests */ struct sock_filter filter[] = { BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, arch))), BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, SEITAN_AUDIT_ARCH, 0, 3), BPF_STMT(BPF_LD | BPF_W | BPF_ABS, (offsetof(struct seccomp_data, nr))), BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, nr, 0, 1), BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW), }; return install_filter( filter, (unsigned short)(sizeof(filter) / sizeof(filter[0]))); } int install_notification_filter(struct args_target *at) { return install_single_syscall(at->nr); } int target() { int buf = 0; if (at->install_filter(at) < 0) { return -1; } at->ret = syscall(at->nr, at->targs[0], at->targs[1], at->targs[2], at->targs[3], at->targs[4], at->targs[5]); at->err = errno; if (at->open_path) { if ((at->fd = open(path, O_CREAT | O_RDONLY)) < 0) { perror("open"); return -1; } } if (at->check_fd) read(pipefd[0], &buf, 1); close(pipefd[0]); write(pipefd[1], &buf, 1); close(pipefd[1]); exit(0); } pid_t do_clone(int (*fn)(void *), void *arg, int flags) { char stack[STACK_SIZE]; pid_t child; child = clone(fn, stack + sizeof(stack), flags, arg); if (child == -1) { perror("clone"); return -1; } return child; } int create_test_fd() { return open("/tmp", O_RDWR | O_TMPFILE); } int get_fd_notifier(pid_t pid) { char path[PATH_MAX + 1]; int pidfd, notifier; int fd = -1; snprintf(path, sizeof(path), "/proc/%d/fd", pid); while (fd < 0) { sleep(2); fd = find_fd_seccomp_notifier(path); } ck_assert_int_ge(fd, 0); pidfd = syscall(SYS_pidfd_open, pid, 0); ck_assert_msg(pidfd >= 0, strerror(errno)); sleep(1); notifier = syscall(SYS_pidfd_getfd, pidfd, fd, 0); ck_assert_msg(notifier >= 0, strerror(errno)); return notifier; } void target_exit() { int status; waitpid(-1, &status, WUNTRACED | WNOHANG); if (WEXITSTATUS(status) != 0) { fprintf(stderr, "target process exited with an error\n"); exit(-1); } } void check_target_fd(int pid, int fd) { int buf = 0; ck_assert(has_fd(pid, fd)); write(pipefd[1], &buf, 1); close(pipefd[1]); } bool has_fd(int pid, int fd) { char path[PATH_MAX + 1]; snprintf(path, sizeof(path), "/proc/%d/fd/%d", pid, fd); return access(path, F_OK) == 0; } void check_target_result(long ret, int err, bool ignore_ret) { int buf; read(pipefd[0], &buf, 1); if (!ignore_ret) ck_assert_msg(at->ret == ret, "expect return value %ld to be equal to %ld", at->ret, ret); ck_assert_int_eq(at->err, err); ck_assert_int_eq(close(pipefd[0]), 0); } void check_target_result_nonegative() { int buf; read(pipefd[0], &buf, 1); ck_assert_msg(at->ret > -1, "expect return value %ld to be greater then -1", at->ret); ck_assert_int_eq(at->err, 0); ck_assert_int_eq(close(pipefd[0]), 0); } void continue_target() { struct seccomp_notif_resp resp; int ret; ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id); ck_assert_msg(ret == 0, strerror(errno)); resp.id = req.id; resp.flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; resp.error = 0; resp.val = 0; ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_SEND, &resp); ck_assert_msg(ret == 0, strerror(errno)); } void mock_syscall_target() { struct seccomp_notif_resp resp; int ret; ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_ID_VALID, &req.id); ck_assert_msg(ret == 0, strerror(errno)); resp.id = req.id; resp.flags = 0; resp.error = 0; resp.val = 0; ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_SEND, &resp); ck_assert_msg(ret == 0, strerror(errno)); } void set_args_no_check(struct args_target *at) { for (unsigned int i = 0; i < 6; i++) at->bpf_fields[i].cmp = NO_CHECK; } static int set_ns_flags(bool ns[], int flags) { unsigned int i; for (i = 0; i < NS_NUM; i++) { if (!ns[i]) continue; switch (i) { case NS_CGROUP: flags |= CLONE_NEWCGROUP; break; case NS_IPC: flags |= CLONE_NEWIPC; break; case NS_NET: flags |= CLONE_NEWNET; break; case NS_MOUNT: flags |= CLONE_NEWNS; break; case NS_PID: flags |= CLONE_NEWPID; break; case NS_USER: flags |= CLONE_NEWUSER; break; case NS_UTS: flags |= CLONE_NEWUTS; break; case NS_TIME: fprintf(stderr, "option NS_TIME not suppoted by clone\n"); break; default: fprintf(stderr, "unrecognized option %d\n", i); } } return flags; } void setup() { int ret; signal(SIGCHLD, target_exit); ck_assert_int_ne(pipe(pipefd), -1); if (at->target == NULL) at->target = target; pid = do_clone(at->target, at->tclone, set_ns_flags(at->ns, SIGCHLD)); ck_assert_int_ge(pid, 0); /* Use write pipe to sync the target for checking the existance of the fd */ if (!at->check_fd) ck_assert_int_ne(close(pipefd[1]), -1); notifyfd = get_fd_notifier(pid); memset(&req, 0, sizeof(req)); ret = ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_RECV, &req); ck_assert_msg(ret == 0, strerror(errno)); ck_assert_msg((req.data).nr == at->nr, "filter syscall nr: %d", (req.data).nr); } void teardown() { if (at != NULL) munmap(at, sizeof(struct args_target)); unlink(path); } void ck_stderr() { setbuf(stderr, stderr_buff); } void ck_stdout() { setbuf(stdout, stdout_buff); } void ck_error_msg(char *s) { ck_assert_msg(strstr(stderr_buff, s) != NULL, "err=\"%s\" doesn't contain \"%s\" ", stderr_buff, s); } int read_filter(struct sock_filter filter[], char *file) { int fd, n; fd = open(file, O_CLOEXEC | O_RDONLY); ck_assert_int_ge(fd, 0); n = read(fd, filter, sizeof(struct sock_filter) * MAX_FILTER); ck_assert_int_ge(n, 0); close(fd); return n / sizeof(struct sock_filter); }