Skip to content

Commit

Permalink
chore(proctree): add process tree e2e tests
Browse files Browse the repository at this point in the history
Create an end-to-end test for the process tree data source.
This in fact test the process tree functionality as a whole.
  • Loading branch information
AlonZivony committed Sep 20, 2023
1 parent 7098db0 commit ce696ac
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ env:
# network tests
NETTESTS: "IPv4 IPv6 TCP UDP ICMP ICMPv6 DNS HTTP"
# instrumentation tests
INSTTESTS: "VFS_WRITE FILE_MODIFICATION SECURITY_INODE_RENAME BPF_ATTACH CONTAINERS_DATA_SOURCE"
INSTTESTS: "VFS_WRITE FILE_MODIFICATION SECURITY_INODE_RENAME BPF_ATTACH CONTAINERS_DATA_SOURCE PROCESS_TREE_DATA_SOURCE"
jobs:
#
# CODE VERIFICATION
Expand Down
304 changes: 304 additions & 0 deletions tests/e2e-inst-signatures/e2e-proctree_data_source.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
package main

import (
"fmt"
"reflect"
"time"

"github.com/aquasecurity/tracee/pkg/utils"
"github.com/aquasecurity/tracee/signatures/helpers"
"github.com/aquasecurity/tracee/types/datasource"
"github.com/aquasecurity/tracee/types/detect"
"github.com/aquasecurity/tracee/types/protocol"
"github.com/aquasecurity/tracee/types/trace"
)

const (
bashPath = "/usr/bin/bash"
sleepPath = "/usr/bin/sleep"
lsPath = "/usr/bin/ls"
)

type e2eProcessTreeDataSource struct {
cb detect.SignatureHandler
processTreeDS detect.DataSource
}

func (sig *e2eProcessTreeDataSource) Init(ctx detect.SignatureContext) error {
sig.cb = ctx.Callback
processTreeDataSource, ok := ctx.GetDataSource("tracee", "process_tree")
if !ok {
return fmt.Errorf("process tree data source not registered")
}
if processTreeDataSource.Version() > 1 {
return fmt.Errorf("process tree data source version not supported, please update this signature")
}
sig.processTreeDS = processTreeDataSource
return nil
}

func (sig *e2eProcessTreeDataSource) GetMetadata() (detect.SignatureMetadata, error) {
return detect.SignatureMetadata{
ID: "PROCESS_TREE_DATA_SOURCE",
EventName: "PROCESS_TREE_DATA_SOURCE",
Version: "0.1.0",
Name: "Process Tree Data Source Test",
Description: "Instrumentation events E2E Tests: Process Tree Data Source Test",
Tags: []string{"e2e", "instrumentation"},
}, nil
}

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

func (sig *e2eProcessTreeDataSource) 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 "sched_process_exec":
err := sig.checkThread(&eventObj)
if err != nil {
return err
}

err = sig.checkProcess(&eventObj)
if err != nil {
return err
}

err = sig.checkLineage(&eventObj)
if err != nil {
return err
}

m, _ := sig.GetMetadata()

sig.cb(detect.Finding{
SigMetadata: m,
Event: event,
Data: map[string]interface{}{},
})
}

return nil
}

// checkThread check that all the information from the event matches the information in
// the process tree
func (sig *e2eProcessTreeDataSource) checkThread(eventObj *trace.Event) error {
threadHash := utils.HashTaskID(uint32(eventObj.ThreadID), uint64(eventObj.ThreadStartTime))
threadQueryAnswer, err := sig.processTreeDS.Get(
datasource.ThreadKey{
Hash: threadHash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
})
if err != nil {
return fmt.Errorf("failed to find thread in data source: %v", err)
}

threadInfo, ok := threadQueryAnswer["thread_info"].(datasource.ThreadInfo)
if !ok {
return fmt.Errorf("failed to extract ThreadInfo from data")
}

// Check IDs
if threadInfo.Tid != eventObj.HostThreadID {
return fmt.Errorf("thread info TID in data source (%d) did not match TID from event (%d)",
threadInfo.Tid, eventObj.HostThreadID)
}

if threadInfo.NsTid != eventObj.ThreadID {
return fmt.Errorf("thread info NS TID in data source (%d) did not match NS TID from event (%d)",
threadInfo.NsTid, eventObj.ThreadID)
}

if threadInfo.Pid != eventObj.HostProcessID {
return fmt.Errorf("thread info PID in data source (%d) did not match PID from event (%d)",
threadInfo.Pid, eventObj.HostProcessID)
}

// Check thread information
if threadInfo.Name != eventObj.ProcessName {
return fmt.Errorf("thread info thread name in data source (%s) did not match known name from event (%s)",
threadInfo.Name, eventObj.ProcessName)
}
return nil
}

// checkProcess check that the information of the process in this point of time (after
// execution) which we can know from the exec event matches the information of the tree
// (which should process the information before it get to this signature)
func (sig *e2eProcessTreeDataSource) checkProcess(eventObj *trace.Event) error {
procHash, err := sig.getProcessHash(eventObj)
if err != nil {
return err
}
procQueryAnswer, err := sig.processTreeDS.Get(
datasource.ProcKey{
Hash: procHash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
})
if err != nil {
return fmt.Errorf("failed to find process in data source: %v", err)
}

processInfo, ok := procQueryAnswer["process_info"].(datasource.ProcessInfo)
if !ok {
return fmt.Errorf("failed to extract ProcessInfo from data")
}

// Check IDs
if processInfo.Pid != eventObj.HostProcessID {
return fmt.Errorf("process info PID in data source (%d) did not match PID from event (%d)",
processInfo.Pid, eventObj.HostProcessID)
}

if processInfo.NsPid != eventObj.ProcessID {
return fmt.Errorf("process info NS PID in data source (%d) did not match NS PID from event (%d)",
processInfo.NsPid, eventObj.ProcessID)
}

if processInfo.Ppid != eventObj.HostParentProcessID {
return fmt.Errorf("process info PPID in data source (%d) did not match PPID from event (%d)",
processInfo.Ppid, eventObj.HostParentProcessID)
}

threadExist := false
for tid := range processInfo.ThreadsIds {
if tid == eventObj.HostThreadID {
threadExist = true
break
}
}
if !threadExist {
return fmt.Errorf("process info existing threads (%v) doesn't record current thread (%d)",
processInfo.ThreadsIds, eventObj.HostThreadID)
}

// Check execution information
pathname, err := helpers.GetTraceeStringArgumentByName(*eventObj, "pathname")
if err != nil {
return err
}

if pathname != lsPath {
return nil
}

if processInfo.ExecutionBinary.Path != pathname {
return fmt.Errorf("process info execution binary in data source (%s) did not match known info from event (%s)",
processInfo.ExecutionBinary.Path, pathname)
}
return nil
}

// checkLineage check that the lineage from the tree matches the information we know from the
//
// // bash script that ran.
func (sig *e2eProcessTreeDataSource) checkLineage(eventObj *trace.Event) error {
// For the check we need only the parent and grandparent (which are created by the test
// script)
maxDepth := 2
procHash, err := sig.getProcessHash(eventObj)
if err != nil {
return err
}
lineageQueryAnswer, err := sig.processTreeDS.Get(
datasource.LineageKey{
Hash: procHash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
MaxDepth: maxDepth,
})
if err != nil {
return fmt.Errorf("failed to find lineage in data source: %v", err)
}

// We want to check things from the lineage against the process query
procQueryAnswer, err := sig.processTreeDS.Get(
datasource.ProcKey{
Hash: procHash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
})
if err != nil {
return fmt.Errorf("failed to find process in data source: %v", err)
}

processInfo, ok := procQueryAnswer["process_info"].(datasource.ProcessInfo)
if !ok {
return fmt.Errorf("failed to extract ProcessInfo from data")
}

lineageInfo, ok := lineageQueryAnswer["process_lineage"].(datasource.ProcessLineage)
if !ok {
return fmt.Errorf("failed to extract ProcessLineage from data")
}
expectedLineageLen := maxDepth + 1 // We expect to get all requested ancestors, and the process itself
if len(lineageInfo) != expectedLineageLen {
return fmt.Errorf("missing some ancestors. Expected legacy of %d processes, got %d", expectedLineageLen, len(lineageInfo))
}
proc := lineageInfo[0]
if !reflect.DeepEqual(proc, processInfo) {
return fmt.Errorf("process in lineage doesn't match process from direct query")
}

parent := lineageInfo[1]
if parent.ExecutionBinary.Path != bashPath {
return fmt.Errorf("parent process binary path in data source lineage (%s) doesn't match expected (%s)", parent.ExecutionBinary.Path, bashPath)
}

grandParent := lineageInfo[2]
if grandParent.ExecutionBinary.Path != bashPath {
return fmt.Errorf("grand parent process binary path in data source lineage (%s) doesn't match expected (%s)", grandParent.ExecutionBinary.Path, bashPath)
}

// Check grandparent info now, which should have changed upon execution of sleep (the
// information in the lineage matches the information upon execution of the parent)
grandParentProcQueryAnswer, err := sig.processTreeDS.Get(
datasource.ProcKey{
Hash: grandParent.Hash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
})
if err != nil {
return fmt.Errorf("failed to find process in data source: %v", err)
}

grandParentCurrentInfo, ok := grandParentProcQueryAnswer["process_info"].(datasource.ProcessInfo)
if !ok {
return fmt.Errorf("failed to extract ProcessInfo from data of grand parent query")
}

if grandParentCurrentInfo.ExecutionBinary.Path != sleepPath {
return fmt.Errorf("grand parent process binary path in data source (%s) doesn't match expected (%s)", grandParentCurrentInfo.ExecutionBinary.Path, sleepPath)
}
return nil
}

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

func (sig *e2eProcessTreeDataSource) Close() {}

func (sig *e2eProcessTreeDataSource) getProcessHash(eventObj *trace.Event) (uint32, error) {
threadHash := utils.HashTaskID(uint32(eventObj.ThreadID), uint64(eventObj.ThreadStartTime))
threadQueryAnswer, err := sig.processTreeDS.Get(
datasource.ThreadKey{
Hash: threadHash,
Time: time.Unix(0, int64(eventObj.Timestamp)),
})
if err != nil {
return 0, fmt.Errorf("failed to find thread in data source: %v", err)
}

threadInfo, ok := threadQueryAnswer["thread_info"].(datasource.ThreadInfo)
if !ok {
return 0, fmt.Errorf("failed to extract ThreadInfo from data")
}
return threadInfo.ProcessHash, nil
}
1 change: 1 addition & 0 deletions tests/e2e-inst-signatures/export.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ var ExportedSignatures = []detect.Signature{
&e2eSecurityInodeRename{},
&e2eContainersDataSource{},
&e2eBpfAttach{},
&e2eProcessTreeDataSource{},
}
10 changes: 10 additions & 0 deletions tests/e2e-inst-signatures/scripts/proctree_data_source.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
#!/usr/bin/bash

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

bash -c "bash -c \"bash -c 'sleep 2; ls' & sleep 10\" & exec sleep 10" > /dev/null &
sleep 10
1 change: 1 addition & 0 deletions tests/e2e-inst-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ for TEST in $TESTS; do
--log file:$SCRIPT_TMP_DIR/tracee-log-$$ \
--signatures-dir $SIG_DIR \
--scope comm=echo,mv,ls,tracee \
-- proctree \
--events signatures &

# wait tracee-ebpf to be started (30 sec most)
Expand Down

0 comments on commit ce696ac

Please sign in to comment.