diff --git a/pkg/ebpf/signature_engine.go b/pkg/ebpf/signature_engine.go index 2e08dc95d7f5..44dedb180293 100644 --- a/pkg/ebpf/signature_engine.go +++ b/pkg/ebpf/signature_engine.go @@ -6,6 +6,7 @@ import ( "github.com/aquasecurity/tracee/pkg/containers" "github.com/aquasecurity/tracee/pkg/events" "github.com/aquasecurity/tracee/pkg/logger" + "github.com/aquasecurity/tracee/pkg/proctree" "github.com/aquasecurity/tracee/pkg/signatures/engine" "github.com/aquasecurity/tracee/types/detect" "github.com/aquasecurity/tracee/types/protocol" @@ -119,7 +120,11 @@ func (t *Tracee) engineEvents(ctx context.Context, in <-chan *trace.Event) (<-ch // PrepareBuiltinDataSources returns a list of all data sources tracee makes available built-in func (t *Tracee) PrepareBuiltinDataSources() []detect.DataSource { - return []detect.DataSource{ + datasources := []detect.DataSource{ containers.NewDataSource(t.containers), } + if t.config.ProcTree.Enabled { + datasources = append(datasources, proctree.NewDataSource(t.processTree)) + } + return datasources } diff --git a/pkg/proctree/datasource.go b/pkg/proctree/datasource.go new file mode 100644 index 000000000000..480ca0eee7e3 --- /dev/null +++ b/pkg/proctree/datasource.go @@ -0,0 +1,198 @@ +package proctree + +import ( + "encoding/json" + "time" + + "github.com/aquasecurity/tracee/types/datasource" + "github.com/aquasecurity/tracee/types/detect" +) + +// DataSource is an implementation to detect.Datasource interface, enveloping the ProcessTree type. +// It exposes all the relevant information of the tree using the interface methods. +type DataSource struct { + procTree *ProcessTree +} + +func NewDataSource(processTree *ProcessTree) *DataSource { + return &DataSource{procTree: processTree} +} + +func (ptds *DataSource) Get(key interface{}) (map[string]interface{}, error) { + switch typedKey := key.(type) { + case datasource.ProcKey: + process, found := ptds.procTree.GetProcessByHash(typedKey.EntityId) + if !found { + return nil, detect.ErrDataNotFound + } + return map[string]interface{}{ + "process_info": ptds.exportProcessInfo(process, typedKey.Time), + }, nil + case datasource.ThreadKey: + thread, found := ptds.procTree.GetThreadByHash(typedKey.EntityId) + if !found { + return nil, detect.ErrDataNotFound + } + return map[string]interface{}{ + "thread_info": ptds.exportThreadInfo(thread, typedKey.Time), + }, nil + case datasource.LineageKey: + process, found := ptds.procTree.GetProcessByHash(typedKey.EntityId) + if !found { + return nil, detect.ErrDataNotFound + } + return map[string]interface{}{ + "process_lineage": ptds.exportProcessLineage(process, typedKey.Time, typedKey.MaxDepth), + }, nil + default: + return nil, detect.ErrKeyNotSupported + } +} + +func (ptds *DataSource) Keys() []string { + return []string{"datasource.ProcKey", "datasource.ThreadKey", "datasource.LineageKey"} +} + +func (ptds *DataSource) Schema() string { + schemaMap := map[string]string{ + "process_info": "datasource.ProcessInfo", + "thread_info": "datasource.ThreadInfo", + "process_lineage": "datasource.ProcessLineage", + } + schema, _ := json.Marshal(schemaMap) + return string(schema) +} + +func (ptds *DataSource) Version() uint { + return 1 // TODO: Change to semantic versioning +} + +func (ptds *DataSource) Namespace() string { + return "tracee" +} + +func (ptds *DataSource) ID() string { + return "process_tree" +} + +// exportProcessInfo return a representation of the given Process information relevant to given time +// as the expected datasource process structure. +func (ptds *DataSource) exportProcessInfo( + process *Process, + queryTime time.Time, +) datasource.ProcessInfo { + info := process.GetInfo() + executable := process.GetExecutable() + interpreter := process.GetInterpreter() + interp := process.GetInterp() + + existingChildren := make(map[int]uint32) + for _, childHash := range process.GetChildren() { + child, ok := ptds.procTree.GetProcessByHash(childHash) + if !ok { + continue + } + childInfo := child.GetInfo() + if childInfo.IsAliveAt(queryTime) { + existingChildren[childInfo.GetPid()] = childHash + } + } + + existingThreads := make(map[int]uint32) + for _, threadHash := range process.GetThreads() { + thread, ok := ptds.procTree.GetThreadByHash(threadHash) + if !ok { + continue + } + threadInfo := thread.GetInfo() + if threadInfo.IsAliveAt(queryTime) { + existingThreads[threadInfo.GetPid()] = threadHash + } + } + + infoFeed := info.GetFeedAt(queryTime) + + return datasource.ProcessInfo{ + EntityId: process.GetHash(), + Pid: infoFeed.Pid, + NsPid: infoFeed.NsPid, + Ppid: infoFeed.PPid, + ContainerId: "", // TODO: Add + Cmd: []string{}, // TODO: Add + ExecutionBinary: exportFileInfo(executable, queryTime), + Interpreter: exportFileInfo(interpreter, queryTime), + Interp: exportFileInfo(interp, queryTime), + StartTime: info.GetStartTime(), + ExecTime: time.Unix(0, 0), // TODO: Add + ExitTime: info.GetExitTime(), + ParentEntityId: process.GetParentHash(), + ThreadsIds: existingThreads, + ChildProcessesIds: existingChildren, + IsAlive: info.IsAliveAt(queryTime), + } +} + +// exportThreadInfo return a representation of the given Thread information relevant to given time +// as the expected datasource thread structure. +func (ptds *DataSource) exportThreadInfo( + thread *Thread, + queryTime time.Time, +) datasource.ThreadInfo { + info := thread.GetInfo() + infoFeed := info.GetFeedAt(queryTime) + return datasource.ThreadInfo{ + EntityId: thread.GetHash(), + Tid: infoFeed.Tid, + NsTid: infoFeed.NsTid, + Pid: infoFeed.Pid, + UserId: infoFeed.Uid, + GroupId: infoFeed.Gid, + StartTime: info.GetStartTime(), + ExitTime: info.GetExitTime(), + Name: infoFeed.Name, + IsAlive: info.IsAliveAt(queryTime), + } +} + +// exportProcessLineage return a representation of the given Process information and up to a given +// depth of its ancestors as the expected datasource process lineage structure. +// The information of each struct is relevant to the fork time of its dependent from it. +func (ptds *DataSource) exportProcessLineage( + process *Process, + queryTime time.Time, + maxDepth int, +) datasource.ProcessLineage { + lineage := datasource.ProcessLineage{ + ptds.exportProcessInfo(process, queryTime), + } + currentProcess := process + var found bool + var iterationQueryTime time.Time + for depth := 0; depth < maxDepth; depth++ { + // We don't want to get parents processes which are not part of the container. + if currentProcess.GetInfo().GetNsPid() == 1 { + break + } + iterationQueryTime = currentProcess.GetInfo().GetStartTime() + currentProcess, found = ptds.procTree.GetProcessByHash(currentProcess.GetParentHash()) + if !found { + break + } + lineage = append(lineage, ptds.exportProcessInfo(currentProcess, iterationQueryTime)) + } + return lineage +} + +// exportFileInfo return a representation of the given FileInfo information relevant to given time +// as the expected datasource file info structure. +func exportFileInfo(fileInfo *FileInfo, queryTime time.Time) datasource.FileInfo { + fileInfoFeed := fileInfo.GetFeedAt(queryTime) + return datasource.FileInfo{ + Path: fileInfoFeed.Path, + Hash: "", // TODO: Add + Inode: fileInfoFeed.Inode, + Device: fileInfoFeed.Dev, + Ctime: time.Unix(0, int64(fileInfoFeed.Ctime)), + Mode: fileInfoFeed.InodeMode, + } +} diff --git a/pkg/proctree/proctree_output.go b/pkg/proctree/proctree_output.go index 4aab1f80d82b..19bfd2fe51e9 100644 --- a/pkg/proctree/proctree_output.go +++ b/pkg/proctree/proctree_output.go @@ -28,7 +28,7 @@ func (pt *ProcessTree) String() string { if !ok { continue } - if child.info.HasExited() { // only running children + if !child.info.IsAlive() { // only running children continue } pid := fmt.Sprintf("%d", child.GetInfo().GetPid()) @@ -56,7 +56,7 @@ func (pt *ProcessTree) String() string { if !ok { continue } - if thread.info.HasExited() { // only running threads + if !thread.info.IsAlive() { // only running threads continue } tid := fmt.Sprintf("%d", thread.GetInfo().GetTid()) @@ -79,9 +79,9 @@ func (pt *ProcessTree) String() string { // Use tablewriter to print the tree in a table newTable := func() *tablewriter.Table { table := tablewriter.NewWriter(buffer) - table.SetHeader([]string{"Ppid", "Tid", "Pid", "Date", "Comm", "Children", "Threads"}) + table.SetHeader([]string{"Ppid", "Tid", "Pid", "EntityId", "Date", "Comm", "Children", "Threads"}) // If debug() is enabled: - // table.SetHeader([]string{"Ppid", "Tid", "Pid", "StartTime", "Hash", "CMD", "Children", "Threads"}) + // table.SetHeader([]string{"Ppid", "Tid", "Pid", "StartTime", "EntityId", "CMD", "Children", "Threads"}) table.SetAutoWrapText(false) table.SetRowLine(false) table.SetAutoFormatHeaders(true) @@ -101,7 +101,7 @@ func (pt *ProcessTree) String() string { if !ok { continue } - if process.info.HasExited() { + if !process.info.IsAlive() { continue // only running processes } diff --git a/pkg/proctree/taskinfo.go b/pkg/proctree/taskinfo.go index b44132112e0d..6dca7012b06c 100644 --- a/pkg/proctree/taskinfo.go +++ b/pkg/proctree/taskinfo.go @@ -394,9 +394,27 @@ func (ti *TaskInfo) GetExitTime() time.Time { return bootTime.Add(duration) } -// HasExited returns true if the task has exited. -func (ti *TaskInfo) HasExited() bool { +// IsAlive returns true if the task has exited. +func (ti *TaskInfo) IsAlive() bool { ti.mutex.RLock() defer ti.mutex.RUnlock() - return ti.exitTimeNS != 0 + return ti.exitTimeNS == 0 +} + +// IsAliveAt return whether the task is alive in the given time, either because it didn't start +// yet or it has exited. +func (ti *TaskInfo) IsAliveAt(targetTime time.Time) bool { + ti.mutex.RLock() + defer ti.mutex.RUnlock() + if ti.exitTimeNS != 0 { + if targetTime.After(utils.NsSinceBootTimeToTime(ti.exitTimeNS)) { + return false + } + } + // If start time is not initialized it will count as 0 ns, meaning it will be before any + // query time given. + if targetTime.Before(utils.NsSinceBootTimeToTime(ti.startTimeNS)) { + return false + } + return true }