-
Notifications
You must be signed in to change notification settings - Fork 412
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
chore(proctree): add process tree e2e tests
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
1 parent
7098db0
commit ce696ac
Showing
5 changed files
with
317 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters