diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 2c7aab7913d3..6f6272b84e5d 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -65,6 +65,7 @@ env: VFS_WRITE FILE_MODIFICATION HOOKED_SYSCALL + FTRACE_HOOK SECURITY_INODE_RENAME BPF_ATTACH CONTAINERS_DATA_SOURCE diff --git a/tests/e2e-inst-signatures/e2e-ftrace_hook.go b/tests/e2e-inst-signatures/e2e-ftrace_hook.go new file mode 100644 index 000000000000..43c0d8444031 --- /dev/null +++ b/tests/e2e-inst-signatures/e2e-ftrace_hook.go @@ -0,0 +1,70 @@ +package main + +import ( + "fmt" + + "github.com/aquasecurity/tracee/signatures/helpers" + "github.com/aquasecurity/tracee/types/detect" + "github.com/aquasecurity/tracee/types/protocol" + "github.com/aquasecurity/tracee/types/trace" +) + +type e2eFtraceHook struct { + cb detect.SignatureHandler +} + +func (sig *e2eFtraceHook) Init(ctx detect.SignatureContext) error { + sig.cb = ctx.Callback + return nil +} + +func (sig *e2eFtraceHook) GetMetadata() (detect.SignatureMetadata, error) { + return detect.SignatureMetadata{ + ID: "FTRACE_HOOK", + EventName: "FTRACE_HOOK", + Version: "0.1.0", + Name: "ftrace_hook Test", + Description: "Instrumentation events E2E Tests: ftrace_hook", + Tags: []string{"e2e", "instrumentation"}, + }, nil +} + +func (sig *e2eFtraceHook) GetSelectedEvents() ([]detect.SignatureEventSelector, error) { + return []detect.SignatureEventSelector{ + {Source: "tracee", Name: "ftrace_hook"}, + }, nil +} + +func (sig *e2eFtraceHook) OnEvent(event protocol.Event) error { + eventObj, ok := event.Payload.(trace.Event) + if !ok { + return fmt.Errorf("failed to cast event's payload") + } + + switch eventObj.EventName { + case "ftrace_hook": + symbolName, err := helpers.GetTraceeStringArgumentByName(eventObj, "symbol") + if err != nil { + return err + } + + if symbolName != "commit_creds" { + return nil + } + + m, _ := sig.GetMetadata() + sig.cb(&detect.Finding{ + SigMetadata: m, + Event: event, + Data: map[string]interface{}{}, + }) + } + + return nil +} + +func (sig *e2eFtraceHook) OnSignal(s detect.Signal) error { + return nil +} + +func (sig *e2eFtraceHook) Close() {} diff --git a/tests/e2e-inst-signatures/export.go b/tests/e2e-inst-signatures/export.go index 2002befcba88..df459de39c5f 100644 --- a/tests/e2e-inst-signatures/export.go +++ b/tests/e2e-inst-signatures/export.go @@ -21,6 +21,7 @@ var ExportedSignatures = []detect.Signature{ &e2eWritableDatasourceSig{}, &e2eSecurityPathNotify{}, &e2eSetFsPwd{}, + &e2eFtraceHook{}, } var ExportedDataSources = []detect.DataSource{ diff --git a/tests/e2e-inst-signatures/scripts/ftrace_hook.sh b/tests/e2e-inst-signatures/scripts/ftrace_hook.sh new file mode 100755 index 000000000000..a243d1d97967 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/ftrace_hook.sh @@ -0,0 +1,23 @@ +#!/usr/bin/bash -e + +KERNEL_VERSION=$(uname -r) + +exit_err() { + echo -n "ERROR: " + echo "$@" + exit 1 +} + +. /etc/os-release + +# Build and load module +dir="tests/e2e-inst-signatures/scripts/hooker" +cd $dir || exit_err "could not cd to $dir" +make && ./load.sh || exit_err "could not load module" + +# Sleep a bit to allow module to load +sleep 5 +lsmod | grep hooker || exit_err "module not loaded" + +# Unload module after 30 seconds +nohup sleep 30 > /dev/null 2>&1 && ./unload.sh & diff --git a/tests/e2e-inst-signatures/scripts/hooker/Makefile b/tests/e2e-inst-signatures/scripts/hooker/Makefile new file mode 100644 index 000000000000..d37355532b64 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/hooker/Makefile @@ -0,0 +1,13 @@ +obj-m += hooker.o + +PWD := $(shell pwd) + +KBUILD_CFLAGS += -g -Wall +KERNELDIR ?= /lib/modules/$(shell uname -r)/build + +hooker.o: + make -C $(KERNELDIR) M=$(PWD) modules + +clean: + rm -f hooker.mod hooker.o hooker.mod.c hooker.mod.o hooker.ko + rm -f modules.order Module.symvers diff --git a/tests/e2e-inst-signatures/scripts/hooker/hooker.c b/tests/e2e-inst-signatures/scripts/hooker/hooker.c new file mode 100644 index 000000000000..f7f927925759 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/hooker/hooker.c @@ -0,0 +1,43 @@ +#include +#include +#include + + +MODULE_LICENSE("GPL"); + +static char *symbol = "commit_creds"; +module_param(symbol, charp, 0000); +MODULE_PARM_DESC(symbol, "The symbol to hook"); + +static struct kprobe kp; + +/* Handler for pre-kprobe (executed just before the probed instruction) */ +static int handler_pre(struct kprobe *p, struct pt_regs *regs) +{ + return 0; +} + +static int __init hooker_init(void) +{ + int ret; + + kp.symbol_name = symbol; + kp.pre_handler = handler_pre; + + ret = register_kprobe(&kp); + if (ret < 0) { + pr_err("register_kprobe failed, returned %d\n", ret); + return ret; + } + pr_info("Planted kprobe at %p\n", kp.addr); + return 0; +} + +static void __exit hooker_exit(void) +{ + unregister_kprobe(&kp); + pr_info("kprobe at %p unregistered\n", kp.addr); +} + +module_init(hooker_init); +module_exit(hooker_exit); diff --git a/tests/e2e-inst-signatures/scripts/hooker/load.sh b/tests/e2e-inst-signatures/scripts/hooker/load.sh new file mode 100755 index 000000000000..f77c40f41d46 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/hooker/load.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +if [[ $UID -ne 0 ]]; then + echo must be root + exit 1 +fi + +sudo lsmod | grep -q hooker && { + echo module already loaded + exit 0 +} + +insmod ./hooker.ko "commit_creds" diff --git a/tests/e2e-inst-signatures/scripts/hooker/unload.sh b/tests/e2e-inst-signatures/scripts/hooker/unload.sh new file mode 100755 index 000000000000..de5b8d6924a6 --- /dev/null +++ b/tests/e2e-inst-signatures/scripts/hooker/unload.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +if [[ $UID -ne 0 ]]; then + echo must be root + exit 1 +fi + +rmmod hooker diff --git a/tests/e2e-inst-test.sh b/tests/e2e-inst-test.sh index 6a81401a8abf..a2200f184227 100755 --- a/tests/e2e-inst-test.sh +++ b/tests/e2e-inst-test.sh @@ -112,6 +112,21 @@ for TEST in $TESTS; do fi "${TESTS_DIR}"/hooked_syscall.sh ;; + FTRACE_HOOK) + if [[ ! -d /lib/modules/${KERNEL}/build ]]; then + info "skip ftrace_hook test, no kernel headers" + continue + fi + if [[ "$KERNEL" == *"amzn"* ]]; then + info "skip ftrace_hook test in amazon linux" + continue + fi + if [[ $ARCH == "aarch64" ]]; then + info "skip ftrace_hook test in aarch64" + continue + fi + "${TESTS_DIR}"/ftrace_hook.sh + ;; esac # Run tracee @@ -177,6 +192,9 @@ for TEST in $TESTS; do # wait for tracee hooked event to be processed sleep 15 ;; + FTRACE_HOOK) + sleep 15 + ;; *) timeout --preserve-status $TRACEE_RUN_TIMEOUT "${TESTS_DIR}"/"${TEST,,}".sh ;;