aboutgitcodelistschat:MatrixIRC
diff options
context:
space:
mode:
authorStefano Brivio <sbrivio@redhat.com>2022-10-25 15:19:55 +0200
committerStefano Brivio <sbrivio@redhat.com>2022-10-25 15:19:55 +0200
commit36b8eb3ce55602bcf36199330e98f2e154225cf7 (patch)
tree53935e784940eb07401aea7d85fbede6f5f3bafa
downloadseitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar.gz
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar.bz2
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar.lz
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar.xz
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.tar.zst
seitan-36b8eb3ce55602bcf36199330e98f2e154225cf7.zip
seitan: Initial import
Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
-rw-r--r--Makefile44
-rw-r--r--README.md39
-rw-r--r--build.c102
-rwxr-xr-xfilter.sh265
-rw-r--r--loader.c94
-rw-r--r--qemu_filter10
-rw-r--r--seitan.c215
-rwxr-xr-xtransform.sh160
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
diff --git a/build.c b/build.c
new file mode 100644
index 0000000..9695b5e
--- /dev/null
+++ b/build.c
@@ -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