// SPDX-License-Identifier: GPL-2.0-or-later /* SEITAN - Syscall Expressive Interpreter, Transformer and Notifier * * seitan.c - Wait for processes, listen for syscalls, handle them * * Copyright (c) 2022 Red Hat GmbH * Authors: Alice Frosi * Stefano Brivio */ #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 "common.h" #include "gluten.h" #include "operations.h" #include "util.h" #define EPOLL_EVENTS 8 /* Seitan options */ static struct option options[] = { { "input", required_argument, NULL, 'i' }, { "pid", optional_argument, NULL, 'p' }, { "socket", optional_argument, NULL, 's' }, { "socket-user", optional_argument, NULL, 'u' }, { "socket-group", optional_argument, NULL, 'g' }, }; struct arguments { char *input_file; char *socket; int pid; uid_t uid; gid_t gid; }; static void usage() { printf("seitan: monitor for processes on seccomp events and executor for actions\n" "Example: setain -pid -i \n" "Usage:\n" "\t-i, --input:\tAction input file\n" "\t-p, --pid:\tPid of process to monitor (cannot be used together with socket)\n" "\t-s, --socket:\tSocket to pass the seccomp notifier fd (cannot be used together with pid)\n" "\t-u, --socket-user:\t User to set for the socket (cannot be used together with pid)\n" "\t-g, --socket-group:\t Group to set for the socket (cannot be used together with pid)\n"); exit(EXIT_FAILURE); } static void parse(int argc, char **argv, struct arguments *arguments) { int option_index = 0; int oc; if (arguments == NULL) usage(); while ((oc = getopt_long(argc, argv, ":i:o:p:s:u:g:", options, &option_index)) != -1) { switch (oc) { case 'p': arguments->pid = atoi(optarg); break; case 'i': arguments->input_file = optarg; break; case 's': arguments->socket = optarg; break; case 'u': arguments->uid = atoi(optarg); break; case 'g': arguments->gid = atoi(optarg); break; default: usage(); } } if (arguments->input_file == NULL) { err("missing input file"); usage(); } if (arguments->socket != NULL && arguments->pid > 0) { err("the socket and pid options cannot be used together"); usage(); } if (arguments->socket == NULL && arguments->pid < 0) { err("select one of the options between socket and pid"); usage(); } } static int pidfd_send_signal(int pidfd, int sig, siginfo_t *info, unsigned int flags) { return syscall(__NR_pidfd_send_signal, pidfd, sig, info, flags); } static void unblock_eater(int pidfd) { if (pidfd_send_signal(pidfd, SIGCONT, NULL, 0) == -1) die(" pidfd_send_signal"); } static int create_socket(const char *path, uid_t uid, gid_t gid) { struct sockaddr_un addr; int ret, conn; int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) die(" error creating UNIX socket"); strcpy(addr.sun_path, path); addr.sun_family = AF_UNIX; ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr)); if (ret < 0) die(" bind"); ret = lchown(path, uid, gid); if (ret < 0) die(" failed to the permission on the socket"); ret = listen(fd, 1); if (ret < 0) die(" listen"); conn = accept(fd, NULL, NULL); if (conn < 0) die(" accept"); return conn; } static int recvfd(int sockfd) { struct msghdr msgh; struct iovec iov; int data, fd; ssize_t nr; union { char buf[CMSG_SPACE(sizeof(int))]; struct cmsghdr align; } controlMsg; struct cmsghdr *cmsgp; msgh.msg_name = NULL; msgh.msg_namelen = 0; msgh.msg_iov = &iov; msgh.msg_iovlen = 1; iov.iov_base = &data; iov.iov_len = sizeof(int); msgh.msg_control = controlMsg.buf; msgh.msg_controllen = sizeof(controlMsg.buf); nr = recvmsg(sockfd, &msgh, 0); if (nr == -1) die(" recvmsg"); cmsgp = CMSG_FIRSTHDR(&msgh); if (cmsgp == NULL || cmsgp->cmsg_len != CMSG_LEN(sizeof(int)) || cmsgp->cmsg_level != SOL_SOCKET || cmsgp->cmsg_type != SCM_RIGHTS) { errno = EINVAL; return -1; } memcpy(&fd, CMSG_DATA(cmsgp), sizeof(int)); return fd; } static void gluten_relocate(struct gluten *g) { unsigned i; for (i = 0; i < 256; i++) { void **p; switch (g->header.relocation[i].type) { case OFFSET_DATA: p = gluten_write_ptr(g, g->header.relocation[i]); *p = (intptr_t)*p + g->data; break; default: ; } } } int main(int argc, char **argv) { char req_b[BUFSIZ]; struct epoll_event ev, events[EPOLL_EVENTS]; struct seccomp_notif *req = (struct seccomp_notif *)req_b; struct arguments arguments = { 0 }; char path[PATH_MAX + 1]; int fd = -1, epollfd; int pidfd, notifier; struct gluten g; int notifierfd; int n, i; arguments.pid = -1; arguments.uid = getuid(); arguments.gid = getgid(); parse(argc, argv, &arguments); fd = open(arguments.input_file, O_CLOEXEC | O_RDONLY); if (read(fd, &g, sizeof(g)) != sizeof(g)) die("Failed to read gluten file"); close(fd); gluten_relocate(&g); if (arguments.pid > 0) { if ((pidfd = syscall(SYS_pidfd_open, arguments.pid, 0)) < 0) die(" pidfd_open"); snprintf(path, sizeof(path), "/proc/%d/fd", arguments.pid); if ((notifierfd = find_fd_seccomp_notifier(path)) < 0) die(" failed getting fd of the notifier"); if ((notifier = syscall(SYS_pidfd_getfd, pidfd, notifierfd, 0)) < 0) die(" pidfd_getfd"); /* Unblock seitan-loader */ unblock_eater(pidfd); } else if (arguments.socket != NULL) { unlink(arguments.socket); if ((fd = create_socket(arguments.socket, arguments.uid, arguments.gid)) < 0) die(" creating the socket"); if ((notifier = recvfd(fd)) < 0) die(" failed recieving the notifier fd"); } if ((epollfd = epoll_create1(0)) < 0) die(" epoll_create"); ev.events = EPOLLIN; ev.data.fd = notifier; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, notifier, &ev) == -1) die(" epoll_ctl: notifier"); loop: n = epoll_wait(epollfd, events, EPOLL_EVENTS, -1); if (n < 0) die("waiting for seccomp events: %s", strerror(errno)); for (i = 0; i < n; i++) { if (events[i].events & EPOLLHUP) { if (fd >= 0) unlink(arguments.socket); exit(EXIT_SUCCESS); } if (events[i].events & EPOLLERR) die("error on notifier"); } memset(req, 0, sizeof(*req)); if (ioctl(notifier, SECCOMP_IOCTL_NOTIF_RECV, req) < 0) die("receiving seccomp notification: %s", strerror(errno)); if (eval(&g, req, notifier)) err("an error occured during the evaluation"); goto loop; return 0; }