diff options
-rw-r--r-- | Makefile | 44 | ||||
-rw-r--r-- | README.md | 39 | ||||
-rw-r--r-- | build.c | 102 | ||||
-rwxr-xr-x | filter.sh | 265 | ||||
-rw-r--r-- | loader.c | 94 | ||||
-rw-r--r-- | qemu_filter | 10 | ||||
-rw-r--r-- | seitan.c | 215 | ||||
-rwxr-xr-x | transform.sh | 160 |
8 files changed, 929 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1555c5c --- /dev/null +++ b/Makefile @@ -0,0 +1,44 @@ +TARGET := $(shell $(CC) -dumpmachine) +# Get 'uname -m'-like architecture description for target +TARGET_ARCH := $(shell echo $(TARGET) | cut -f1 -d- | tr [A-Z] [a-z]) +TARGET_ARCH := $(shell echo $(TARGET_ARCH) | sed 's/powerpc/ppc/') + +AUDIT_ARCH := $(shell echo $(TARGET_ARCH) | tr [a-z] [A-Z] | sed 's/^ARM.*/ARM/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/I[456]86/I386/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPC64/PPC/') +AUDIT_ARCH := $(shell echo $(AUDIT_ARCH) | sed 's/PPCLE/PPC64LE/') + +CFLAGS += -DBUILD_TRANSFORM_OUT=\"t.out\" -DBUILD_BPF_OUT=\"bpf.out\" +CFLAGS += -DBUILD_IN=\"qemu_filter\" +CFLAGS += -DSEITAN_AUDIT_ARCH=AUDIT_ARCH_$(AUDIT_ARCH) +CFLAGS += -DBUILD_PROFILE=qemu_filter +CFLAGS += -Wall -Wextra -pedantic + +all: bpf.out t.out seitan-loader seitan + +bpf.out: qemu_filter build + ./build + +t.out: qemu_filter build + ./build + +build: build.c filter.h numbers.h transform.h + $(CC) $(CFLAGS) -o build build.c + +seitan-loader: loader.c + $(CC) $(CFLAGS) -o seitan-loader loader.c + +seitan: seitan.c transform.h + $(CC) $(CFLAGS) -o seitan seitan.c + +filter.h: qemu_filter + ./filter.sh qemu_filter + +numbers.h: qemu_filter + ./filter.sh qemu_filter + +transform.h: qemu_filter + ./transform.sh qemu_filter + +clean: + rm -f filter.h numbers.h transform.h t.out bpf.out build seitan-loader seitan diff --git a/README.md b/README.md new file mode 100644 index 0000000..048b30f --- /dev/null +++ b/README.md @@ -0,0 +1,39 @@ +<style> +.markdown-body { + display: block; + font-family: Roboto Mono, monospace; + font-weight: 200; + font-size: 13pt; + line-height: 1.5; +} + +div > ul { + float: left; +} +</style> + +<img src="/static/seitan.svg" alt="seitan diagram" + style="object-fit: contain; width: 70%; float: left"> + +* **build-filter** + * build BPF binary-search tree + +* **build-table** + * build transformation table + +* **seitan-loader** + * load BPF blob + * attach filter + * call blocking syscall + * on return, start binary + +* **seitan** + * load transformation table blob + * listen to netlink proc connector + * look for seitan-loader, once found: + * get seccomp notifier via pidfd_getfd() + * listen to it, new syscall: + * look up in transformation table + * load args from memory + * execute transformation, unblock, or block + * return, optionally injecting context @@ -0,0 +1,102 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* SEITAN - Syscall Expressive Interpreter, Transformer and Notifier + * + * build.c - Build BPF program and transformation table blobs + * + * Copyright (c) 2022 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> + +#include <linux/audit.h> +#include <linux/filter.h> +#include <linux/seccomp.h> + +struct syscall_numbers { + char name[1024]; + long number; +}; + +enum transform { + NONE, + FD1_UNIX, + FDRET_SRC, + DEV_CHECK, +}; + +#include "filter.h" +#include "numbers.h" + +struct table { + enum transform type; + long number; + + char arg[6][1024]; +}; + +static struct table t[16]; + +int main(void) +{ + struct table *tp = t; + char buf[BUFSIZ]; + FILE *fp; + int fd; + + fd = open(BUILD_BPF_OUT, O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, + S_IRUSR | S_IWUSR); + write(fd, BUILD_PROFILE, sizeof(BUILD_PROFILE)); + close(fd); + + fp = fopen(BUILD_IN, "r"); + while (fgets(buf, BUFSIZ, fp)) { + char name[1024]; + char type[1024]; + unsigned i; + + if (*buf == '\n' || *buf == '#') + continue; + if (sscanf(buf, "%s %s " /* syscall, type */ + "%s %s %s %s %s %s", name, type, + tp->arg[0], tp->arg[1], tp->arg[2], + tp->arg[3], tp->arg[4], tp->arg[5]) < 3) + continue; + + for (i = 0; i < sizeof(numbers) / sizeof(numbers[0]); i++) { + if (!strcmp(name, numbers[i].name)) + break; + } + + if (i == sizeof(numbers)) + continue; + + if (!strcmp(type, "fd1_unix")) + tp->type = 1; + else if (!strcmp(type, "fdret_src")) + tp->type = 2; + else if (!strcmp(type, "dev_check")) + tp->type = 3; + else + continue; + + tp->number = numbers[i].number; + + tp++; + } + fclose(fp); + + fd = open(BUILD_TRANSFORM_OUT, + O_WRONLY | O_CREAT | O_TRUNC | O_CLOEXEC, S_IRUSR | S_IWUSR); + + write(fd, t, sizeof(t)); + close(fd); + + return 0; +} diff --git a/filter.sh b/filter.sh new file mode 100755 index 0000000..b3b85a2 --- /dev/null +++ b/filter.sh @@ -0,0 +1,265 @@ +#!/bin/sh -eu +# +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# SEITAN - Syscall Expressive Interpreter, Transformer and Notifier +# +# filter.sh - Build binary-search tree BPF program with SECCOMP_RET_USER_NOTIF +# +# Copyright (c) 2022 Red Hat GmbH +# Author: Stefano Brivio <sbrivio@redhat.com> + +TMP="$(mktemp)" +IN="${@}" +OUT="filter.h" +OUT_NUMBERS="numbers.h" + +HEADER="/* This file was automatically generated by $(basename ${0}) */ + +#ifndef AUDIT_ARCH_PPC64LE +#define AUDIT_ARCH_PPC64LE (AUDIT_ARCH_PPC64 | __AUDIT_ARCH_LE) +#endif" + +HEADER_NUMBERS="/* This file was automatically generated by $(basename ${0}) */ +struct syscall_numbers numbers[] = {" + +FOOTER_NUMBERS="};" + +# Prefix for each profile: check that 'arch' in seccomp_data is matching +PRE=' +struct sock_filter @PROFILE@[] = { + /* cppcheck-suppress badBitmaskCheck */ + 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, @KILL@), + /* cppcheck-suppress badBitmaskCheck */ + BPF_STMT(BPF_LD | BPF_W | BPF_ABS, + (offsetof(struct seccomp_data, nr))), + +' + +# Suffix for each profile: return actions +POST=' BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW), + BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_USER_NOTIF), +}; +' + +# Syscall, @NR@: number, @ALLOW@: offset to RET_ALLOW, @NAME@: syscall name +CALL=' BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, @NR@, @ALLOW@, 0), /* @NAME@ */' + +# Binary search tree node or leaf, @NR@: value, @R@: right jump, @L@: left jump +BST=' BPF_JUMP(BPF_JMP | BPF_JGE | BPF_K, @NR@, @R@, @L@),' + +# cleanup() - Remove temporary file if it exists +cleanup() { + rm -f "${TMP}" +} +#trap "cleanup" EXIT + +# sub() - Substitute in-place file line with processed template line +# $1: Line number +# $2: Template name (variable name) +# $@: Replacement for @KEY@ in the form KEY:value +sub() { + IFS= + __line_no="${1}" + __template="$(eval printf '%s' "\${${2}}")" + shift; shift + + sed -i "${__line_no}s#.*#${__template}#" "${TMP}" + + IFS=' ' + for __def in ${@}; do + __key="@${__def%%:*}@" + __value="${__def#*:}" + sed -i "${__line_no}s/${__key}/${__value}/" "${TMP}" + done + unset IFS +} + +# finish() - Finalise header file from temporary files with prefix and suffix +# $1: Variable name of prefix +# $@: Replacements for prefix variable +finish() { + IFS= + __out="$(eval printf '%s' "\${${1}}")" + shift + + IFS=' ' + for __def in ${@}; do + __key="@${__def%%:*}@" + __value="${__def#*:}" + __out="$(printf '%s' "${__out}" | sed "s#${__key}#${__value}#")" + done + + printf '%s\n' "${__out}" >> "${OUT}" + cat "${TMP}" >> "${OUT}" + rm "${TMP}" + printf '%s' "${POST}" >> "${OUT}" + unset IFS +} + +# log2() - Binary logarithm +# $1: Operand +log2() { + __x=-1 + __y=${1} + while [ ${__y} -gt 0 ]; do : $((__y >>= 1)); __x=$((__x + 1)); done + echo ${__x} +} + +# syscall_nr - Get syscall number from compiler, also note in numbers.h +# $1: Name of syscall +syscall_nr() { + __in="$(printf "#include <asm-generic/unistd.h>\n#include <sys/syscall.h>\n__NR_%s" ${1})" + __out="$(echo "${__in}" | cc -E -xc - -o - | tail -1)" + [ "${__out}" = "__NR_$1" ] && return 1 + + # Output might be in the form "(x + y)" (seen on armv6l, armv7l) + __out="$(eval echo $((${__out})))" + echo "${__out}" + + printf "\t{ \"%s\",\t\t%i },\n" "${1}" "${__out}" >> "${OUT_NUMBERS}" +} + +filter() { + __filtered= + for __c in ${@}; do + __arch_match=0 + case ${__c} in + *:*) + case ${__c} in + $(uname -m):*) + __arch_match=1 + __c=${__c##*:} + ;; + esac + ;; + *) + __arch_match=1 + ;; + esac + [ ${__arch_match} -eq 0 ] && continue + + IFS='| ' + __found=0 + for __name in ${__c}; do + syscall_nr "${__name}" >/dev/null && __found=1 && break + done + unset IFS + + if [ ${__found} -eq 0 ]; then + echo + echo "Warning: no syscall number for ${__c}" >&2 + echo " none of these syscalls will be allowed" >&2 + continue + fi + + __filtered="${__filtered} ${__name}" + done + + echo "${__filtered}" | tr ' ' '\n' | sort -u +} + +# gen_profile() - Build struct sock_filter for a single profile +# $1: Profile name +# $@: Names of allowed system calls, amount padded to next power of two +gen_profile() { + __profile="${1}" + shift + + __statements_calls=${#} + __bst_levels=$(log2 $(( __statements_calls / 4 )) ) + __statements_bst=$(( __statements_calls / 4 - 1 )) + __statements=$((__statements_calls + __statements_bst)) + + [ ${__bst_levels} -eq 0 ] && __statements_bst=0 + for __i in $(seq 1 ${__statements_bst} ); do + echo -1 >> "${TMP}" + done + + for __i in $(seq 1 ${__statements_calls} ); do + __syscall_name="$(eval echo \${${__i}})" + if ! syscall_nr ${__syscall_name} >> "${TMP}"; then + echo "Cannot get syscall number for ${__syscall_name}" + exit 1 + fi + eval __syscall_nr_$(tail -1 "${TMP}")="${__syscall_name}" + done + sort -go "${TMP}" "${TMP}" + + __level_nodes=1 + __distance=$(( __statements_calls / 2 )) + __ll=0 + __line=1 + for __level in $(seq 1 $(( __bst_levels - 1 )) ); do + # Nodes + __cmp_pos=${__distance} + + for __node in $(seq 1 ${__level_nodes}); do + __cmp_line=$(( __statements_bst + __cmp_pos )) + __lr=$(( __ll + 1 )) + __nr="$(sed -n ${__cmp_line}p "${TMP}")" + + sub ${__line} BST "NR:${__nr}" "L:${__ll}" "R:${__lr}" + + __ll=${__lr} + __line=$(( __line + 1 )) + __cmp_pos=$(( __cmp_pos + __distance * 2 )) + done + + __distance=$(( __distance / 2 )) + __level_nodes=$(( __level_nodes * 2 )) + done + + # Leaves + if [ ${__bst_levels} -eq 0 ]; then + __ll=0 + else + __ll=$(( __level_nodes - 1 )) + fi + __lr=$(( __ll + __distance - 1 )) + __cmp_pos=${__distance} + + for __leaf in $(seq 1 ${__level_nodes}); do + __cmp_line=$(( __statements_bst + __cmp_pos )) + __nr="$(sed -n ${__cmp_line}p "${TMP}")" + sub ${__line} BST "NR:${__nr}" "L:${__ll}" "R:${__lr}" + + __ll=$(( __lr + __distance - 1 )) + __lr=$(( __ll + __distance)) + __line=$(( __line + 1 )) + __cmp_pos=$(( __cmp_pos + __distance * 2 )) + done + + # Calls + [ ${__bst_levels} -eq 0 ] && __statements_bst=$((__statements_bst + 1)) + for __i in $(seq $(( __statements_bst + 1 )) ${__statements}); do + __nr="$(sed -n ${__i}p "${TMP}")" + eval __name="\${__syscall_nr_${__nr}}" + __allow=$(( __statements - __i + 1)) + sub ${__i} CALL "NR:${__nr}" "NAME:${__name}" "ALLOW:${__allow}" + done + finish PRE "PROFILE:${__profile}" "KILL:$(( __statements + 1))" +} + +printf '%s\n' "${HEADER}" > "${OUT}" +printf '%s\n' "${HEADER_NUMBERS}" > "${OUT_NUMBERS}" +__profiles="${IN}" +for __p in ${__profiles}; do + __calls="$(sed -n 's/^\([^# \t]\{1,\}\).*/\1/p' "${__p}")" + __calls="$(filter ${__calls})" + echo "seccomp profile ${__p} handles: ${__calls}" | tr '\n' ' ' | fmt -t + + # Pad here to keep gen_profile() "simple" + __count=0 + for __c in ${__calls}; do __count=$(( __count + 1 )); done + __padded=$(( 1 << (( $(log2 ${__count}) + 1 )) )) + for __i in $( seq ${__count} $(( __padded - 1 )) ); do + __calls="${__calls} read" + done + + gen_profile "${__p}" ${__calls} +done + +printf '%s\n' "${FOOTER_NUMBERS}" >> "${OUT_NUMBERS}" diff --git a/loader.c b/loader.c new file mode 100644 index 0000000..bd2530e --- /dev/null +++ b/loader.c @@ -0,0 +1,94 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later + +/* SEITAN - Syscall Expressive Interpreter, Transformer and Notifier + * + * loader.c - Load BPF program and execute binary + * + * Copyright (c) 2022 Red Hat GmbH + * Author: Stefano Brivio <sbrivio@redhat.com> + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/socket.h> + +#include <linux/audit.h> +#include <linux/filter.h> +#include <linux/seccomp.h> + +extern char **environ; + +static char *qemu_names[] = { + "kvm", + "qemu-kvm", +#ifdef ARCH + ( "qemu-system-" ARCH ), +#endif + "/usr/libexec/qemu-kvm", + NULL, +}; + +/** + * usage() - Print usage and exit + */ +void usage(void) +{ + fprintf(stderr, "Usage: seitan-loader [QEMU_ARG]...\n"); + fprintf(stderr, "\n"); + + exit(EXIT_FAILURE); +} + +static int seccomp(unsigned int operation, unsigned int flags, void *args) +{ + return syscall(__NR_seccomp, operation, flags, args); +} + +/** + * main() - Entry point + * @argc: Argument count + * @argv: qemu arguments + * + * Return: 0 once interrupted, non-zero on failure + */ +int main(int argc, char **argv) +{ + int fd = open("bpf.out", O_CLOEXEC | O_RDONLY); + struct sock_filter filter[1024]; + struct sock_fprog prog; + char **name; + size_t n; + + (void)argc; + + n = read(fd, filter, sizeof(filter)); + close(fd); + + prog.filter = filter; + prog.len = (unsigned short)(n / sizeof(filter[0])); + prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0); + fd = seccomp(SECCOMP_SET_MODE_FILTER, + SECCOMP_FILTER_FLAG_NEW_LISTENER, &prog); + + connect(0, NULL, 0); /* Wait for seitan to unblock this */ + + for (name = qemu_names; *name; name++) { + argv[0] = *name; + execvpe(*name, argv, environ); + if (errno != ENOENT) { + perror("execvpe"); + usage(); + } + } + + perror("execvpe"); + return EXIT_FAILURE; +} diff --git a/qemu_filter b/qemu_filter new file mode 100644 index 0000000..9ce4824 --- /dev/null +++ b/qemu_filter @@ -0,0 +1,10 @@ +# syscall type args + +# type fd1: fd first argument, sockaddr_un with path, check and replace path +connect fd1_unix /tmp/qemu-pr-helper.sock /tmp/qemu-pr-helper.sock + +# type fdret_src: source path first argument, check and replace, return fd +mount fdret_src /escalate_badly /etc + +# type dev_check: path first argument, check and replace, allow second argument only +ioctl dev_check /dev/tun/tap /dev/tun/tap diff --git a/seitan.c b/seitan.c new file mode 100644 index 0000000..bc98aed --- /dev/null +++ b/seitan.c @@ -0,0 +1,215 @@ +// 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 <sbrivio@redhat.com> + */ + +#define _GNU_SOURCE +#include <errno.h> +#include <stdio.h> +#include <stddef.h> +#include <stdlib.h> +#include <string.h> +#include <fcntl.h> +#include <unistd.h> +#include <limits.h> +#include <signal.h> +#include <sys/prctl.h> +#include <sys/syscall.h> +#include <sys/ioctl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <linux/netlink.h> +#include <linux/connector.h> +#include <linux/cn_proc.h> + +#include <linux/audit.h> +#include <linux/filter.h> +#include <linux/seccomp.h> + +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; + while (NLMSG_OK(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) + return -EAGAIN; + + 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-loader") || + !strcmp(exe, "/usr/bin/seitan-loader")) + return ev->event_data.exec.process_pid; + + if (nlh->nlmsg_type == NLMSG_DONE) + break; + + nlh = NLMSG_NEXT(nlh, n); + } + + 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; + int fd; + + fd = open("t.out", O_CLOEXEC | O_RDONLY); + read(fd, t, sizeof(t)); + close(fd); + + if (argc < 2) + while ((ret = event(s)) == -EAGAIN); + else + ret = atoi(argv[1]); + + 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); + } +} diff --git a/transform.sh b/transform.sh new file mode 100755 index 0000000..1e9eaeb --- /dev/null +++ b/transform.sh @@ -0,0 +1,160 @@ +#!/bin/sh -eu +# +# SPDX-License-Identifier: AGPL-3.0-or-later +# +# SEITAN - Syscall Expressive Interpreter, Transformer and Notifier +# +# transform.sh - Build syscall transformation table headers +# +# Copyright (c) 2022 Red Hat GmbH +# Author: Stefano Brivio <sbrivio@redhat.com> + +TMP="$(mktemp)" +IN="${@}" +OUT="transform.h" + +HEADER="/* This file was automatically generated by $(basename ${0}) */ + +struct table { + int type; + long number; + char arg[1024][6]; +};" + +# Prefix for each profile +PRE=' +struct table table_@PROFILE@[] = {' + +# Suffix for each profile +POST='}; +' + +# cleanup() - Remove temporary file if it exists +cleanup() { + rm -f "${TMP}" +} +trap "cleanup" EXIT + +# sub() - Substitute in-place file line with processed template line +# $1: Line number +# $2: Template name (variable name) +# $@: Replacement for @KEY@ in the form KEY:value +sub() { + IFS= + __line_no="${1}" + __template="$(eval printf '%s' "\${${2}}")" + shift; shift + + sed -i "${__line_no}s#.*#${__template}#" "${TMP}" + + IFS=' ' + for __def in ${@}; do + __key="@${__def%%:*}@" + __value="${__def#*:}" + sed -i "${__line_no}s/${__key}/${__value}/" "${TMP}" + done + unset IFS +} + +# finish() - Finalise header file from temporary files with prefix and suffix +# $1: Variable name of prefix +# $@: Replacements for prefix variable +finish() { + IFS= + __out="$(eval printf '%s' "\${${1}}")" + shift + + IFS=' ' + for __def in ${@}; do + __key="@${__def%%:*}@" + __value="${__def#*:}" + __out="$(printf '%s' "${__out}" | sed "s#${__key}#${__value}#")" + done + + printf '%s\n' "${__out}" >> "${OUT}" + cat "${TMP}" >> "${OUT}" + rm "${TMP}" + printf '%s' "${POST}" >> "${OUT}" + unset IFS +} + +# syscall_nr - Get syscall number from compiler +# $1: Name of syscall +syscall_nr() { + __in="$(printf "#include <asm-generic/unistd.h>\n#include <sys/syscall.h>\n__NR_%s" ${1})" + __out="$(echo "${__in}" | cc -E -xc - -o - | tail -1)" + [ "${__out}" = "__NR_$1" ] && return 1 + + # Output might be in the form "(x + y)" (seen on armv6l, armv7l) + __out="$(eval echo $((${__out})))" + echo "${__out}" +} + +filter() { + __filtered= + for __c in ${@}; do + __arch_match=0 + case ${__c} in + *:*) + case ${__c} in + $(uname -m):*) + __arch_match=1 + __c=${__c##*:} + ;; + esac + ;; + *) + __arch_match=1 + ;; + esac + [ ${__arch_match} -eq 0 ] && continue + + IFS='| ' + __found=0 + for __name in ${__c}; do + syscall_nr "${__name}" >/dev/null && __found=1 && break + done + unset IFS + + if [ ${__found} -eq 0 ]; then + echo + echo "Warning: no syscall number for ${__c}" >&2 + echo " none of these syscalls will be allowed" >&2 + continue + fi + + __filtered="${__filtered} ${__name}" + done + + echo "${__filtered}" | tr ' ' '\n' | sort -u +} + +printf '%s\n' "${HEADER}" > "${OUT}" +__profiles="${IN}" +for __p in ${__profiles}; do + IFS=' +' + for __l in $(grep "^[a-z].*" "${__p}" | tr -s '\t'); do + unset IFS + __syscall_token="$(echo "${__l}" | cut -f1)" + __type_token="$(echo "${__l}" | cut -f2)" + __arg1_token="$(echo "${__l}" | cut -f3)" + __arg2_token="$(echo "${__l}" | cut -f4)" + __arg3_token="$(echo "${__l}" | cut -f5)" + __arg4_token="$(echo "${__l}" | cut -f6)" + __arg5_token="$(echo "${__l}" | cut -f7)" + __arg6_token="$(echo "${__l}" | cut -f8)" + + __syscall_nr="$(syscall_nr "${__syscall_token}")" + __type_enum="$(echo ${__type_token} | tr [a-z] [A-Z])" + + printf "\t{ %i, %s, \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", \"%s\", },\n" \ + ${__syscall_nr} "${__type_enum}" \ + "${__arg1_token}" "${__arg2_token}" "${__arg3_token}" \ + "${__arg4_token}" "${__arg5_token}" "${__arg6_token}" \ + >> "${TMP}" + IFS=' +' + done + finish PRE "PROFILE:${__p}" +done |