Skip to content

Commit

Permalink
tests(event): add hooked_syscall instrumentation test
Browse files Browse the repository at this point in the history
  • Loading branch information
rafaeldtinoco committed Oct 27, 2023
1 parent f5f2d01 commit 3c8251d
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 24 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ env:
DNS
HTTP
INSTTESTS: >
HOOKED_SYSCALL
VFS_WRITE
FILE_MODIFICATION
SECURITY_INODE_RENAME
Expand Down
74 changes: 74 additions & 0 deletions tests/e2e-inst-signatures/e2e-hooked_syscall.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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 e2eHookedSyscall struct {
cb detect.SignatureHandler
}

func (sig *e2eHookedSyscall) Init(ctx detect.SignatureContext) error {
sig.cb = ctx.Callback
return nil
}

func (sig *e2eHookedSyscall) GetMetadata() (detect.SignatureMetadata, error) {
return detect.SignatureMetadata{
ID: "HOOKED_SYSCALL",
EventName: "HOOKED_SYSCALL",
Version: "0.1.0",
Name: "Hooked Syscall Test",
Description: "Instrumentation events E2E Tests: Hooked Syscall",
Tags: []string{"e2e", "instrumentation"},
}, nil
}

func (sig *e2eHookedSyscall) GetSelectedEvents() ([]detect.SignatureEventSelector, error) {
return []detect.SignatureEventSelector{
{Source: "tracee", Name: "hooked_syscall"},
}, nil
}

func (sig *e2eHookedSyscall) 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 "hooked_syscall":
syscall, err := helpers.GetTraceeStringArgumentByName(eventObj, "syscall")
if err != nil {
return err
}
owner, err := helpers.GetTraceeStringArgumentByName(eventObj, "owner")
if err != nil {
return err
}

if syscall == "uname" && owner == "hijack" {
m, _ := sig.GetMetadata()
sig.cb(
detect.Finding{
SigMetadata: m,
Event: event,
Data: map[string]interface{}{},
},
)
}
}

return nil
}

func (sig *e2eHookedSyscall) OnSignal(s detect.Signal) error {
return nil
}

func (sig *e2eHookedSyscall) Close() {}
1 change: 1 addition & 0 deletions tests/e2e-inst-signatures/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ var ExportedSignatures = []detect.Signature{
&e2eContainersDataSource{},
&e2eBpfAttach{},
&e2eProcessTreeDataSource{},
&e2eHookedSyscall{},
}
6 changes: 6 additions & 0 deletions tests/e2e-inst-signatures/scripts/hijack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*
!.gitignore
!Makefile
!hijack.c
!load.sh
!unload.sh
13 changes: 13 additions & 0 deletions tests/e2e-inst-signatures/scripts/hijack/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
obj-m += hijack.o

PWD := $(shell pwd)

KBUILD_CFLAGS += -g -Wall
KERNELDIR ?= /lib/modules/$(shell uname -r)/build

hijack.o:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

clean:
rm -f hijack.mod hijack.o hijack.mod.c hijack.mod.o hijack.ko
rm -f modules.order Module.symvers
56 changes: 56 additions & 0 deletions tests/e2e-inst-signatures/scripts/hijack/hijack.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <linux/init.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/utsname.h>

// Example of a kernel module hijacking a system call.

MODULE_LICENSE("GPL");

ulong table;
module_param(table, ulong, 0);

asmlinkage u64 (*orig_uname)(struct old_utsname *);

asmlinkage u64 hooked_uname(struct old_utsname *name)
{
printk(KERN_INFO "uname() intercepted!\n");
return orig_uname(name);
}

#define RO 0
#define RW 1

static int set_page(u64 addr, int flag)
{
u32 level;
pte_t *pte = lookup_address(addr, &level);

if (pte && pte_present(*pte))
pte->pte = flag ? pte->pte | _PAGE_RW : pte->pte & ~_PAGE_RW;

return 0;
}

static int __init hijack_init(void)
{
if (!table)
return -EINVAL;

set_page(table, RW);
orig_uname = (void *) ((u64 **) table)[__NR_uname];
((u64 **) table)[__NR_uname] = (u64 *) hooked_uname;
set_page(table, RO);

return 0;
}

static void __exit hijack_exit(void)
{
set_page(table, RW);
((u64 **) table)[__NR_uname] = (u64 *) orig_uname;
set_page(table, RO);
}

module_init(hijack_init);
module_exit(hijack_exit);
15 changes: 15 additions & 0 deletions tests/e2e-inst-signatures/scripts/hijack/load.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/bin/bash

if [[ $UID -ne 0 ]]; then
echo must be root
exit 1
fi

sudo lsmod | grep -q hijack && {
echo module already loaded
exit 0
}

address=$(cat /proc/kallsyms | grep -E " sys_call_table$" | cut -d' ' -f1)
arg="./hijack.ko table=0x$address"
modprobe $arg || insmod $arg
8 changes: 8 additions & 0 deletions tests/e2e-inst-signatures/scripts/hijack/unload.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

if [[ $UID -ne 0 ]]; then
echo must be root
exit 1
fi

rmmod hijack
15 changes: 15 additions & 0 deletions tests/e2e-inst-signatures/scripts/hooked_syscall.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/bash -e

exit_err() {
echo -n "ERROR: "
echo "$@"
exit 1
}

# Build and load module
dir="tests/e2e-inst-signatures/scripts/hijack"
cd $dir || exit_err "could not cd to $dir"
make && ./load.sh || exit_err "could not load module"

# Unload module after 30 seconds
nohup sleep 30 > /dev/null 2>&1 && ./unload.sh &
80 changes: 56 additions & 24 deletions tests/e2e-inst-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@
# This test is executed by github workflows inside the action runners
#

ARCH=$(uname -m)

TRACEE_STARTUP_TIMEOUT=30
TRACEE_SHUTDOWN_TIMEOUT=30
TRACEE_RUN_TIMEOUT=60
SCRIPT_TMP_DIR=/tmp
TRACEE_TMP_DIR=/tmp/tracee

# Default test to run if no other is given
TESTS=${INSTTESTS:=VFS_WRITE}

info_exit() {
echo -n "INFO: "
echo "$@"
Expand All @@ -35,6 +40,8 @@ if [[ ! -d ./signatures ]]; then
error_exit "need to be in tracee root directory"
fi

rm -rf ${TRACEE_TMP_DIR:?}/* || error_exit "could not delete $TRACEE_TMP_DIR"

KERNEL=$(uname -r)
KERNEL_MAJ=$(echo "$KERNEL" | cut -d'.' -f1)

Expand All @@ -44,19 +51,15 @@ fi

SCRIPT_PATH="$(readlink -f "$0")"
SCRIPT_DIR="$(dirname "$SCRIPT_PATH")"
TESTS_DIR="$SCRIPT_DIR/e2e-inst-signatures/scripts"
SIG_DIR="$SCRIPT_DIR/../dist/e2e-inst-signatures"

# run CO-RE VFS_WRITE test only by default
TESTS=${INSTTESTS:=VFS_WRITE}

# startup needs
rm -rf ${TRACEE_TMP_DIR:?}/* || error_exit "could not delete $TRACEE_TMP_DIR"
git config --global --add safe.directory "*"

info
info "= ENVIRONMENT ================================================="
info
info "KERNEL: $(uname -r)"
info "KERNEL: ${KERNEL}"
info "CLANG: $(clang --version)"
info "GO: $(go version)"
info
Expand All @@ -67,20 +70,41 @@ set -e
make -j"$(nproc)" all
make e2e-inst-signatures
set +e

# Check if tracee was built correctly

if [[ ! -x ./dist/tracee ]]; then
error_exit "could not find tracee executable"
fi

# if any test has failed
anyerror=""

# run tests
# Run tests, one by one

for TEST in $TESTS; do

info
info "= TEST: $TEST =============================================="
info

# Some tests might need special setup (like running before tracee)

case $TEST in
HOOKED_SYSCALL)
if [[ $ARCH == "aarch64" ]]; then
info "skip hooked_syscall test in aarch64"
continue
fi
if [[ ! -d /lib/modules/${KERNEL}/build ]]; then
info "skip hooked_syscall test, no kernel headers"
continue
fi
"${TESTS_DIR}"/hooked_syscall.sh
;;
esac

# Run tracee

rm -f $SCRIPT_TMP_DIR/build-$$
rm -f $SCRIPT_TMP_DIR/tracee-log-$$

Expand All @@ -97,7 +121,8 @@ for TEST in $TESTS; do
--scope comm=echo,mv,ls,tracee,proctreetester \
--events "$TEST" &

# wait tracee-ebpf to be started (30 sec most)
# Wait tracee to start

times=0
timedout=0
while true; do
Expand All @@ -116,7 +141,8 @@ for TEST in $TESTS; do
fi
done

# tracee-ebpf could not start for some reason, check stderr
# Tracee failed to start

if [[ $timedout -eq 1 ]]; then
info
info "$TEST: FAILED. ERRORS:"
Expand All @@ -127,20 +153,30 @@ for TEST in $TESTS; do
continue
fi

# give some time for tracee to settle
# Allow tracee to start processing events

sleep 3

# run test scripts
timeout --preserve-status $TRACEE_RUN_TIMEOUT \
./tests/e2e-inst-signatures/scripts/"${TEST,,}".sh
# Run tests

case $TEST in
HOOKED_SYSCALL)
;;
*)
timeout --preserve-status $TRACEE_RUN_TIMEOUT "${TESTS_DIR}"/"${TEST,,}".sh
;;
esac

# So events can finish processing

# so event can be processed and detected
sleep 3

## cleanup at EXIT
# The cleanup happens at EXIT

logfile=$SCRIPT_TMP_DIR/tracee-log-$$

# Check if the test has failed or not

found=0
cat $SCRIPT_TMP_DIR/build-$$ | jq .eventName | grep -q "$TEST" && found=1
errors=$(cat $logfile | wc -l 2>/dev/null)
Expand All @@ -165,24 +201,20 @@ for TEST in $TESTS; do
rm -f $SCRIPT_TMP_DIR/build-$$
rm -f $SCRIPT_TMP_DIR/tracee-log-$$

# make sure we exit to start it again
# Make sure we exit tracee to start it again

pid_tracee=$(pidof tracee | cut -d' ' -f1)

kill -2 "$pid_tracee"

sleep $TRACEE_SHUTDOWN_TIMEOUT

# make sure tracee is exited with SIGKILL
kill -9 "$pid_tracee" >/dev/null 2>&1

# give a little break for OS noise to reduce
sleep 3

# cleanup leftovers
# Cleanup leftovers
rm -rf $TRACEE_TMP_DIR
done

# Print summary and exit with error if any test failed

info
if [[ $anyerror != "" ]]; then
info "ALL TESTS: FAILED: ${anyerror::-1}"
Expand Down

0 comments on commit 3c8251d

Please sign in to comment.