From 36b8eb3ce55602bcf36199330e98f2e154225cf7 Mon Sep 17 00:00:00 2001 From: Stefano Brivio Date: Tue, 25 Oct 2022 15:19:55 +0200 Subject: seitan: Initial import Signed-off-by: Stefano Brivio --- filter.sh | 265 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 265 insertions(+) create mode 100755 filter.sh (limited to 'filter.sh') 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 + +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 \n#include \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}" -- cgit v1.2.3