mirror of
https://passt.top/passt
synced 2024-12-23 14:15:23 +00:00
66a95e331e
Pass to seccomp.sh a list of additional syscalls valgrind needs as EXTRA_SYSCALLS in a new 'valgrind' make target, and add corresponding support in seccomp.sh itself. In test setup functions, start passt with valgrind, but not for performance tests. Add tests checking that valgrind exits without errors after all the other tests in the group are done. Signed-off-by: Stefano Brivio <sbrivio@redhat.com>
251 lines
6.3 KiB
Bash
Executable File
251 lines
6.3 KiB
Bash
Executable File
#!/bin/sh -eu
|
|
#
|
|
# SPDX-License-Identifier: AGPL-3.0-or-later
|
|
#
|
|
# PASST - Plug A Simple Socket Transport
|
|
# for qemu/UNIX domain socket mode
|
|
#
|
|
# PASTA - Pack A Subtle Tap Abstraction
|
|
# for network namespace/tap device mode
|
|
#
|
|
# seccomp.sh - Build seccomp profiles from "#syscalls[:PROFILE]" code comments
|
|
#
|
|
# Copyright (c) 2021 Red Hat GmbH
|
|
# Author: Stefano Brivio <sbrivio@redhat.com>
|
|
|
|
TMP="$(mktemp)"
|
|
OUT="seccomp.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"
|
|
|
|
# Prefix for each profile: check that 'arch' in seccomp_data is matching
|
|
PRE='
|
|
struct sock_filter filter_@PROFILE@[] = {
|
|
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
|
|
(offsetof(struct seccomp_data, arch))),
|
|
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, PASST_AUDIT_ARCH, 0, @KILL@),
|
|
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_KILL_PROCESS),
|
|
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
|
|
};
|
|
'
|
|
|
|
# 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
|
|
# $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
|
|
}
|
|
|
|
# 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))
|
|
|
|
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}"
|
|
|
|
__distance=$(( __statements_calls / 2 ))
|
|
__level_nodes=1
|
|
__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
|
|
__ll=$(( __level_nodes - 1 ))
|
|
__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
|
|
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}"
|
|
__profiles="$(sed -n 's/[\t ]*\*[\t ]*#syscalls:\([^ ]*\).*/\1/p' *.[ch] | sort -u)"
|
|
for __p in ${__profiles}; do
|
|
__calls="$(sed -n 's/[\t ]*\*[\t ]*#syscalls\(:'"${__p}"'\|\)[\t ]\{1,\}\(.*\)/\2/p' *.[ch])"
|
|
__calls="${__calls} ${EXTRA_SYSCALLS:-}"
|
|
__calls="$(filter ${__calls})"
|
|
echo "seccomp profile ${__p} allows: ${__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
|