// SPDX-License-Identifier: AGPL-3.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 * Author: 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 static char doc[] = "Usage: seitan: setain -pid -i "; /* Seitan options */ static struct argp_option options[] = { { "input", 'i', "FILE", 0, "Action input file", 0 }, { "pid", 'p', "pid", 0, "Pid of process to monitor", 0 }, { 0 } }; struct arguments { char *input_file; int pid; }; static error_t parse_opt(int key, char *arg, struct argp_state *state) { struct arguments *arguments = state->input; switch (key) { case 'p': arguments->pid = atoi(arg); break; case 'i': arguments->input_file = arg; break; case ARGP_KEY_END: if (arguments->input_file == NULL) argp_error(state, "missing input file"); break; default: return ARGP_ERR_UNKNOWN; } return 0; } static struct argp argp = { .options = options, .parser = parse_opt, .args_doc = NULL, .doc = doc, .children = NULL, .help_filter = NULL, .argp_domain = NULL }; static int nl_init(void) { int s = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR); struct sockaddr_nl sa = { .nl_family = AF_NETLINK, .nl_groups = CN_IDX_PROC, .nl_pid = getpid(), }; struct req_t { struct nlmsghdr nlh; struct cn_msg cnm; enum proc_cn_mcast_op mop; } __attribute__ ((packed, aligned(NLMSG_ALIGNTO))) req = { .nlh.nlmsg_type = NLMSG_DONE, .nlh.nlmsg_pid = getpid(), .cnm.id.idx = CN_IDX_PROC, .cnm.id.val = CN_VAL_PROC, .cnm.len = sizeof(enum proc_cn_mcast_op), .mop = PROC_CN_MCAST_LISTEN, }; bind(s, (struct sockaddr *)&sa, sizeof(sa)); req.nlh.nlmsg_len = sizeof(req); send(s, &req, sizeof(req), 0); return s; } static int event(int s) { char path[PATH_MAX + 1], exe[PATH_MAX + 1]; struct proc_event *ev; struct nlmsghdr *nlh; struct cn_msg *cnh; char buf[BUFSIZ]; ssize_t n; if ((n = recv(s, &buf, sizeof(buf), 0)) <= 0) return -EIO; nlh = (struct nlmsghdr *)buf; for (; NLMSG_OK(nlh, n); nlh = NLMSG_NEXT(nlh, n)) { if (nlh->nlmsg_type == NLMSG_NOOP) continue; if ((nlh->nlmsg_type == NLMSG_ERROR) || (nlh->nlmsg_type == NLMSG_OVERRUN)) break; cnh = NLMSG_DATA(nlh); ev = (struct proc_event *)cnh->data; if (ev->what != PROC_EVENT_EXEC) continue; snprintf(path, PATH_MAX, "/proc/%i/exe", ev->event_data.exec.process_pid); readlink(path, exe, PATH_MAX); if (!strcmp(exe, "/usr/local/bin/seitan-eater") || !strcmp(exe, "/usr/bin/seitan-eater")) return ev->event_data.exec.process_pid; if (nlh->nlmsg_type == NLMSG_DONE) break; } return -EAGAIN; } enum transform { NONE, FD1_UNIX, FDRET_SRC, DEV_CHECK, }; struct table { enum transform type; long number; char arg[6][1024]; }; static struct table t[16]; int handle(struct seccomp_notif *req, int notifyfd) { char path[PATH_MAX + 1]; struct sockaddr_un s_un; int fd_unix; unsigned i; int mem; for (i = 0; i < sizeof(t) / sizeof(t[0]); i++) { if (t[i].number == req->data.nr) break; } if (i == sizeof(t) / sizeof(t[0])) /* Not found */ return 1; if (t[i].type != FD1_UNIX) /* Not implemented yet */ return 1; /* FD1_UNIX here */ snprintf(path, sizeof(path), "/proc/%i/mem", req->pid); fd_unix = req->data.args[0]; mem = open(path, O_RDONLY); lseek(mem, req->data.args[1], SEEK_SET); read(mem, &s_un, sizeof(s_un)); close(mem); if (!strcmp(s_un.sun_path, t[i].arg[0])) { int own_fd = socket(AF_UNIX, SOCK_STREAM, 0); struct seccomp_notif_addfd addfd = { .id = req->id, .flags = SECCOMP_ADDFD_FLAG_SEND | SECCOMP_ADDFD_FLAG_SETFD, .srcfd = own_fd, .newfd = fd_unix, }; connect(own_fd, &s_un, sizeof(s_un)); ioctl(notifyfd, SECCOMP_IOCTL_NOTIF_ADDFD, &addfd); return 0; } return 1; } int main(int argc, char **argv) { int s = nl_init(), ret, pidfd, notifier; char resp_b[BUFSIZ], req_b[BUFSIZ]; struct seccomp_notif_resp *resp = (struct seccomp_notif_resp *)resp_b; struct seccomp_notif *req = (struct seccomp_notif *)req_b; struct arguments arguments; int fd; arguments.pid = -1; argp_parse(&argp, argc, argv, 0, 0, &arguments); fd = open(arguments.input_file, O_CLOEXEC | O_RDONLY); read(fd, t, sizeof(t)); close(fd); if (arguments.pid < 0) while ((ret = event(s)) == -EAGAIN); else ret = arguments.pid; if (ret < 0) exit(EXIT_FAILURE); if ((pidfd = syscall(SYS_pidfd_open, ret, 0)) < 0) { perror("pidfd_open"); exit(EXIT_FAILURE); } sleep(1); if ((notifier = syscall(SYS_pidfd_getfd, pidfd, 3, 0)) < 0) { perror("pidfd_getfd"); exit(EXIT_FAILURE); } while (1) { /* TODO: Open syscall transformation table blob, actually handle * syscalls actions as parsed */ memset(req, 0, sizeof(*req)); ioctl(notifier, SECCOMP_IOCTL_NOTIF_RECV, req); if (!handle(req, notifier)) continue; resp->flags = SECCOMP_USER_NOTIF_FLAG_CONTINUE; resp->id = req->id; resp->error = 0; resp->val = 0; ioctl(notifier, SECCOMP_IOCTL_NOTIF_SEND, resp); } }