From c0669ba8a0b1581dc2de0858106c220aef717373 Mon Sep 17 00:00:00 2001 From: Ashish Naware Date: Sun, 23 Feb 2025 23:01:14 -0800 Subject: [PATCH] chore: added verifier tests Signed-off-by: Ashish Naware --- Makefile | 2 +- contrib/verify/verify.sh | 123 ----------------------------- contrib/verify/verify_test.go | 143 ++++++++++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 124 deletions(-) delete mode 100755 contrib/verify/verify.sh create mode 100644 contrib/verify/verify_test.go diff --git a/Makefile b/Makefile index 9bbb5f9e7d4..c26f31c99f4 100644 --- a/Makefile +++ b/Makefile @@ -277,7 +277,7 @@ bpf-test: .PHONY: verify verify: tetragon-bpf ## Verify BPF programs. - sudo contrib/verify/verify.sh bpf/objs + sudo TETRAGONDIR=$(CURDIR)/bpf/objs $(shell which go) test contrib/verify/verify_test.go -v -d .PHONY: alignchecker alignchecker: ## Run alignchecker. diff --git a/contrib/verify/verify.sh b/contrib/verify/verify.sh deleted file mode 100755 index b87be0556ee..00000000000 --- a/contrib/verify/verify.sh +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env bash -# -# Script to verify all Tetragon programs by loading them with bpftool. -# -set -u -shopt -s nullglob - -RED="\033[31m" -BLUEUNDER="\033[34;4m" -GREEN="\033[32m" -YELLOW="\033[33m" -NOCOLOR="\033[0m" -TETRAGONDIR=/var/lib/tetragon -DEBUG=0 -KERNEL=$(uname -r | cut -d. -f1,2) # retrieves major.minor, e.g. 5.19 - -usage() { - echo "usage: verify.sh [-d] [TETRAGONDIR]" - echo "-d to run bpftool with -d and dump number of processed instructions" - exit 1 -} - -if [ $# -ge 1 ]; then - if [ "$1" == "-d" ]; then - DEBUG=1 - shift 1 - fi - - if [ $# -ge 1 ]; then - [ -d $1 ] || usage - TETRAGONDIR=$1 - fi -fi - -PINDIR=/sys/fs/bpf/tetragon-verify -mkdir -p "$PINDIR" -cleanup() { - rm -rf "$PINDIR" -} -trap cleanup EXIT - -STATUS=0 - -for obj in "$TETRAGONDIR"/*.o; do - B=$(basename "$obj") - - # Alignchecker is not a bpf program, so ignore it - if [[ "$B" == bpf_alignchecker* ]]; then - continue - fi - - # Globals is just for testing, so ignore it - if [[ "$B" == bpf_globals* ]]; then - continue - fi - - # Generic tracepoint needs more complex userspace logic to load, so ignore it - if [[ "$B" == bpf_generic_tracepoint* ]]; then - continue - fi - - # Multi kprobe support is still not widely around, skip the object - if [[ "$B" == bpf_multi_* ]]; then - continue - fi - - # Skip v6.1 objects check for kernel < 6.1 - if [[ "$B" == *61.o && $(echo "$KERNEL < 6.1" | bc) == 1 ]]; then - continue - fi - - # Skip v5.11 objects check for kernel < 5.11 - if [[ "$B" == *511.o && $(echo "$KERNEL < 5.11" | bc) == 1 ]]; then - continue - fi - - # Skip bpf_loader for kernel < 5.19 - if [[ "$B" == bpf_loader* && $(echo "$KERNEL < 5.19" | bc) == 1 ]]; then - continue - fi - - # Generic LSM BPF needs more complex userspace logic to load, so ignore it - if [[ "$B" == bpf_generic_lsm* ]]; then - continue - fi - - # Check if bpf_override_return is available - if [[ "$B" == bpf_generic_kprobe* || "$B" == bpf_enforcer* ]]; then - if ! bpftool feature probe | grep -q "bpf_override_return"; then - echo -e "${YELLOW}bpf_override_return not available, skipping $B ...${NOCOLOR}\n" - continue - fi - fi - - echo -e -n "Verifying $BLUEUNDER$obj$NOCOLOR... " - OUT="/tmp/tetragon-verify-$B" - - FLAGS="" - [ "$DEBUG" -eq 1 ] && FLAGS="-d" - bpftool help 2>&1 | grep -q -- "--legacy" && FLAGS="$FLAGS --legacy" - - bpftool $FLAGS prog loadall "$obj" "$PINDIR" &> "$OUT" - if [ $? -ne 0 ]; then - echo -e "${RED}Failed!${NOCOLOR}" - awk '/^func/ { in_func=1 } /^;/ { if (in_func) { print $0; print "..."; exit } }' < "$OUT" - tail -20 "$OUT" - echo "Full output left in $OUT" - STATUS=1 - else - echo -e "${GREEN}OK${NOCOLOR}" - awk ' -/^func/ { in_func=1 } -/^;/ { if (in_func) { print " " $0; in_func=0; } } -/^verification time/ { verify_lines=3 } -/^/ { if (verify_lines) { verify_lines -= 1; print " " $0 } }' < "$OUT" - rm "$OUT" - fi - echo - rm -rf "$PINDIR" -done - -exit "$STATUS" - diff --git a/contrib/verify/verify_test.go b/contrib/verify/verify_test.go new file mode 100644 index 00000000000..b6b81e503e5 --- /dev/null +++ b/contrib/verify/verify_test.go @@ -0,0 +1,143 @@ +// SPDX-License-Identifier: Apache-2.0 +// Copyright Authors of Tetragon +package verify + +import ( + "flag" + "fmt" + "log" + "os" + "path/filepath" + "strconv" + "strings" + "testing" + + "github.com/cilium/ebpf" + "github.com/cilium/ebpf/asm" + "github.com/cilium/ebpf/features" + "github.com/stretchr/testify/require" + "golang.org/x/sys/unix" +) + +const ( + TETRAGONDIR = "/var/lib/tetragon" +) + +var ( + DEBUG = flag.Bool("d", false, "debug") +) + +func TestVerifyTetragonPrograms(t *testing.T) { + + kernelVersion, err := strconv.ParseFloat(getKernelVersion(), 64) + if err != nil { + log.Fatalf("error: %v", err) + } + + tetragonDir := os.Getenv("TETRAGONDIR") + if tetragonDir == "" { + tetragonDir = TETRAGONDIR + } + + files, err := os.ReadDir(tetragonDir) + if err != nil { + log.Fatalf("error: %v", err) + } + + for _, file := range files { + fileName := file.Name() + if file.IsDir() || filepath.Ext(fileName) != ".o" { + continue + } + + // Alignchecker is not a bpf program, so ignore it + if strings.HasPrefix(fileName, "bpf_alignchecker") { + continue + } + + // Globals is just for testing, so ignore it + if strings.HasPrefix(fileName, "bpf_alignchecker") { + continue + } + + // Generic tracepoint needs more complex userspace logic to load, so ignore it + if strings.HasPrefix(fileName, "bpf_generic_tracepoint") { + continue + } + + // Multi kprobe support is still not widely around, skip the object + if strings.HasPrefix(fileName, "bpf_multi_") { + continue + } + + // Skip v6.1 objects check for kernel < 6.1 + if strings.HasSuffix(fileName, "61.o") && kernelVersion < 6.1 { + continue + } + + // Skip v5.11 objects check for kernel < 5.11 + if strings.HasSuffix(fileName, "511.o") && kernelVersion < 5.11 { + continue + } + + // Skip bpf_loader for kernel < 5.19 + if strings.HasPrefix(fileName, "bpf_loader") && kernelVersion < 5.19 { + continue + } + + // Generic LSM BPF needs more complex userspace logic to load, so ignore it + if strings.HasPrefix(fileName, "bpf_generic_lsm") { + continue + } + + // Check if bpf_override_return is available + if strings.HasPrefix(fileName, "bpf_generic_kprobe") || strings.HasPrefix(fileName, "bpf_enforcer") { + if err := features.HaveProgramHelper(ebpf.Kprobe, asm.FnOverrideReturn); err != nil { + continue + } + } + + spec, err := ebpf.LoadCollectionSpec(tetragonDir + "/" + fileName) + require.NoError(t, err, "failed to parse elf file into collection spec") + + if *DEBUG { + for _, progSpec := range spec.Programs { + fmt.Printf("%s\n", progSpec.Instructions.String()) + } + } + + coll, err := ebpf.NewCollection(spec) + require.NoError(t, err, "failed to load resources into the kernel") + + defer coll.Close() + + for _, prog := range coll.Programs { + require.NotEqual(t, -1, prog.FD()) + prog.Close() + } + } + +} + +func getKernelVersion() string { + var uts unix.Utsname + err := unix.Uname(&uts) + if err != nil { + log.Fatalf("error: %v", err) + } + + var release []byte + for _, c := range uts.Release { + if c == 0 { + break + } + release = append(release, byte(c)) + } + + version := strings.Split(string(release), ".") + if len(version) < 2 { + log.Fatalf("error: unexpected kernel version format") + } + + return strings.Join(version[:2], ".") +}