Skip to content

Commit

Permalink
Merge branch 'jrs/new-process-context' into momar/new-process-resolve…
Browse files Browse the repository at this point in the history
…r-tests
mftoure committed Oct 3, 2024

Verified

This commit was signed with the committer’s verified signature.
mftoure Momar TOURÉ
2 parents a0faf5f + c9ae110 commit 01537e8
Showing 7 changed files with 348 additions and 114 deletions.
98 changes: 84 additions & 14 deletions pkg/security/process_list/activity_tree/activity_tree.go
Original file line number Diff line number Diff line change
@@ -10,9 +10,12 @@ package activitytree

import (
"github.com/DataDog/datadog-go/v5/statsd"
"golang.org/x/exp/slices"

processlist "github.com/DataDog/datadog-agent/pkg/security/process_list"
processresolver "github.com/DataDog/datadog-agent/pkg/security/process_list/process_resolver"
"github.com/DataDog/datadog-agent/pkg/security/secl/model"
activitytree "github.com/DataDog/datadog-agent/pkg/security/security_profile/activity_tree"
"github.com/DataDog/datadog-agent/pkg/security/utils"
)

@@ -28,8 +31,8 @@ type Stats struct {

// ActivityTree contains a process tree and its activities. This structure has no locks.
type ActivityTree struct {
Stats *Stats
// pathsReducer *PathsReducer
Stats *Stats
pathsReducer *activitytree.PathsReducer

differentiateArgs bool
DNSMatchMaxDepth int
@@ -40,28 +43,95 @@ type ActivityTree struct {
}

// NewActivityTree returns a new ActivityTree instance
func NewActivityTree( /* pathsReducer *PathsReducer */ ) *ActivityTree {
func NewActivityTree(pathsReducer *activitytree.PathsReducer, differentiateArgs bool, DNSMatchMaxDepth int) *ActivityTree {
return &ActivityTree{
// pathsReducer: pathsReducer,
Stats: &Stats{},
DNSNames: utils.NewStringKeys(nil),
SyscallsMask: make(map[int]int),
pathsReducer: pathsReducer,
Stats: &Stats{},
DNSNames: utils.NewStringKeys(nil),
SyscallsMask: make(map[int]int),
differentiateArgs: differentiateArgs,
DNSMatchMaxDepth: DNSMatchMaxDepth,
}
}

// IsValidRootNode evaluates if the provided process entry is allowed to become a root node of an Activity Dump
func IsValidRootNode(entry *model.Process) bool {
// TODO
return true
// /!\ for now, everything related to cache funcs are equal to the process resolver
// TODO: if it's still the case when starting to replace parts of original code, remove
// it from the interface and make it generic

type processKey struct {
pid uint32
nsid uint64
}

func (at *ActivityTree) Matches(p1, p2 *processlist.ExecNode) bool {
// TODO
type execKey struct {
pid uint32
nsid uint64
execTime int64
pathnameStr string
}

// GetProcessCacheKey returns the process unique identifier
func (at *ActivityTree) GetProcessCacheKey(process *model.Process) interface{} {
if process.Pid != 0 {
return processKey{pid: process.Pid, nsid: process.NSID}
}
return nil
}

// GetExecCacheKey returns the exec unique identifier
func (at *ActivityTree) GetExecCacheKey(process *model.Process) interface{} {
if process.Pid != 0 {
path := process.FileEvent.PathnameStr
if processresolver.IsBusybox(process.FileEvent.PathnameStr) {
path = process.Argv[0]
}
return execKey{
pid: process.Pid,
nsid: process.NSID,
execTime: process.ExecTime.UnixMicro(),
pathnameStr: path,
}
}
return nil
}

// GetParentProcessCacheKey returns the parent process unique identifier
func (at *ActivityTree) GetParentProcessCacheKey(event *model.Event) interface{} {
if event.ProcessContext.Pid != 1 && event.ProcessContext.PPid > 0 {
return processKey{pid: event.ProcessContext.PPid, nsid: event.ProcessContext.NSID}
}
return nil
}

// IsAValidRootNode evaluates if the provided process entry is allowed to become a root node of an Activity Dump
// nolint: all
func (at *ActivityTree) IsAValidRootNode(entry *model.Process) bool {
// TODO: check runc/containerid stuff
return true
}

// ExecMatches returns true if both exec matches
func (at *ActivityTree) ExecMatches(e1, e2 *processlist.ExecNode) bool {
if e1.FileEvent.PathnameStr == e2.FileEvent.PathnameStr {
if at.differentiateArgs {
return slices.Equal(e1.Process.Argv, e2.Process.Argv)
}
return true
}
return false
}

// ProcessMatches returns true if both process nodes matches
func (at *ActivityTree) ProcessMatches(p1, p2 *processlist.ProcessNode) bool {
if p1.CurrentExec == nil || p2.CurrentExec == nil {
return false
}
return at.ExecMatches(p1.CurrentExec, p2.CurrentExec)
}

// SendStats sends the tree statistics
// nolint: all
func (at *ActivityTree) SendStats(client statsd.ClientInterface) error {
// TODO
return nil
// return at.Stats.SendStats(client, at.treeType)
}
16 changes: 16 additions & 0 deletions pkg/security/process_list/activity_tree/activity_tree_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Unless explicitly stated otherwise all files in this repository are licensed
// under the Apache License Version 2.0.
// This product includes software developed at Datadog (https://www.datadoghq.com/).
// Copyright 2016-present Datadog, Inc.

//go:build linux

// Package activitytree holds activitytree related files
package activitytree

import "testing"

// nolint: all
func TestDummy(t *testing.T) {

}
26 changes: 21 additions & 5 deletions pkg/security/process_list/exec_node.go
Original file line number Diff line number Diff line change
@@ -11,15 +11,22 @@ package processlist
import (
"fmt"
"io"
"math/rand"
"sync"

"github.com/DataDog/datadog-agent/pkg/security/secl/model"
)

// ExecNode defines an exec
type ExecNode struct {
sync.Mutex
model.Process

// Key represents the key used to retrieve the exec from the cache
// if the owner is able to define a key we use it, otherwise we'll put
// a random generated uint64 cookie
Key interface{}

ProcessLink *ProcessNode

MatchedRules []*model.MatchedRule
@@ -32,18 +39,26 @@ type ExecNode struct {
// Syscalls []int32
}

// NewExecNode returns a new ExecNode instance
// NewEmptyExecNode returns a new empty ExecNode instance
func NewEmptyExecNode() *ExecNode {
// TODO: init maps
return &ExecNode{}
}

func NewExecNodeFromEvent(event *model.Event) *ExecNode {
// NewExecNodeFromEvent returns a new exec node from a given event, and if any, use
// the provided key to assign it (otherwise it will choose a random one)
func NewExecNodeFromEvent(event *model.Event, key interface{}) *ExecNode {
if key == nil {
key = rand.Uint64()
}
exec := NewEmptyExecNode()
exec.Process = event.ProcessContext.Process
exec.Key = key
return exec
}

// Insert will inserts the given event to the exec node, returns true if an entry were inserted
// nolint: all
func (e *ExecNode) Insert(event *model.Event, imageTag string) (newEntryAdded bool, err error) {
e.Lock()
defer e.Unlock()
@@ -52,7 +67,7 @@ func (e *ExecNode) Insert(event *model.Event, imageTag string) (newEntryAdded bo
return false, nil
}

// debug prints out recursively content of each node
// Debug prints out recursively content of each node
func (e *ExecNode) Debug(w io.Writer, prefix string) {
e.Lock()
defer e.Unlock()
@@ -71,8 +86,9 @@ func (e *ExecNode) Debug(w io.Writer, prefix string) {
// TODO: rest of events
}

// scrub args/envs
func (pl *ExecNode) Scrub() error {
// Scrub scrubs args and envs
// nolint: all
func (e *ExecNode) Scrub() error {
// TODO
return nil
}
198 changes: 136 additions & 62 deletions pkg/security/process_list/process_list.go

Large diffs are not rendered by default.

49 changes: 39 additions & 10 deletions pkg/security/process_list/process_node.go
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ package processlist
import (
"fmt"
"io"
"math/rand"
"sync"

"github.com/DataDog/datadog-agent/pkg/security/secl/model"
@@ -21,6 +22,11 @@ import (
type ProcessNode struct {
sync.Mutex

// represent the key used to retrieve the process from the cache
// if the owner is able to define a key we use it, otherwise we'll put
// a random generated uint64 cookie
Key interface{}

// mainly used by dump/profiles
ImageTags []string

@@ -43,37 +49,42 @@ type ProcessNode struct {
// == Fields used by process resolver:
// refCount?
// onRelase CB?
// (would be great if we finaly can get rid of it!)
// (would be great if we finally can get rid of it!)
UserData interface{}
}

func NewProcessExecNodeFromEvent(event *model.Event) *ProcessNode {
exec := NewExecNodeFromEvent(event)
// NewProcessExecNodeFromEvent returns a process node filled with an exec node corresponding to the given event
func NewProcessExecNodeFromEvent(event *model.Event, processKey, execKey interface{}) *ProcessNode {
if processKey == nil {
processKey = rand.Uint64()
}
exec := NewExecNodeFromEvent(event, execKey)
process := &ProcessNode{
Key: processKey,
CurrentExec: exec,
PossibleExecs: []*ExecNode{exec},
}
exec.ProcessLink = process
return process
}

// GetParent returns nil for the ActivityTree
// GetCurrentParent returns the current parent
func (pn *ProcessNode) GetCurrentParent() ProcessNodeIface {
pn.Lock()
defer pn.Unlock()

return pn.CurrentParent
}

// GetParent returns nil for the ActivityTree
// GetPossibleParents returns the possible parents
func (pn *ProcessNode) GetPossibleParents() []ProcessNodeIface {
pn.Lock()
defer pn.Unlock()

return pn.PossibleParents
}

// GetChildren returns the list of children from the ProcessNode
// GetChildren returns the list of children of the ProcessNode
func (pn *ProcessNode) GetChildren() *[]*ProcessNode {
pn.Lock()
defer pn.Unlock()
@@ -95,7 +106,7 @@ func (pn *ProcessNode) GetCurrentSiblings() *[]*ProcessNode {
return nil
}

// AppendChild appends a new root node in the ActivityTree
// AppendChild appends a new node in the process node
func (pn *ProcessNode) AppendChild(child *ProcessNode, currentParent bool) {
pn.Lock()
defer pn.Unlock()
@@ -107,7 +118,7 @@ func (pn *ProcessNode) AppendChild(child *ProcessNode, currentParent bool) {
}
}

// AppendChild appends a new root node in the ActivityTree
// AppendExec adds a new exec to the process node, and mark it as current if currentExec is specified
func (pn *ProcessNode) AppendExec(exec *ExecNode, currentExec bool) {
pn.Lock()
defer pn.Unlock()
@@ -120,7 +131,7 @@ func (pn *ProcessNode) AppendExec(exec *ExecNode, currentExec bool) {
}

// UnlinkChild unlinks a child from the children list
func (pn *ProcessNode) UnlinkChild(owner ProcessListOwner, child *ProcessNode) bool {
func (pn *ProcessNode) UnlinkChild(owner Owner, child *ProcessNode) bool {
pn.Lock()
defer pn.Unlock()

@@ -135,7 +146,25 @@ func (pn *ProcessNode) UnlinkChild(owner ProcessListOwner, child *ProcessNode) b
return removed
}

// debug prints out recursively content of each node
// Walk walks the process node and childs recursively
func (pn *ProcessNode) Walk(f func(node *ProcessNode) (stop bool)) (stop bool) {
pn.Lock()
defer pn.Unlock()

for _, child := range pn.Children {
stop = child.Walk(f)
if stop {
return stop
}
stop = f(child)
if stop {
return stop
}
}
return stop
}

// Debug prints out recursively content of each node
func (pn *ProcessNode) Debug(w io.Writer, prefix string) {
pn.Lock()
defer pn.Unlock()
65 changes: 47 additions & 18 deletions pkg/security/process_list/process_resolver/process_resolver.go
Original file line number Diff line number Diff line change
@@ -43,39 +43,68 @@ type processKey struct {
}

type execKey struct {
pid uint32
nsid uint64
execTime int64
pathnameStr string
}

// IsValidRootNode evaluates if the provided process entry is allowed to become a root node of an Activity Dump
func (at *ProcessResolver) IsAValidRootNode(entry *model.Process) bool {
// GetProcessCacheKey returns the process unique identifier
func (pr *ProcessResolver) GetProcessCacheKey(process *model.Process) interface{} {
if process.Pid != 0 {
return processKey{pid: process.Pid, nsid: process.NSID}
}
return nil
}

// GetExecCacheKey returns the exec unique identifier
func (pr *ProcessResolver) GetExecCacheKey(process *model.Process) interface{} {
if process.Pid != 0 {
path := process.FileEvent.PathnameStr
if IsBusybox(process.FileEvent.PathnameStr) {
path = process.Argv[0]
}
return execKey{
pid: process.Pid,
nsid: process.NSID,
execTime: process.ExecTime.UnixMicro(),
pathnameStr: path,
}
}
return nil
}

// GetParentProcessCacheKey returns the parent process unique identifier
func (pr *ProcessResolver) GetParentProcessCacheKey(event *model.Event) interface{} {
if event.ProcessContext.Pid != 1 && event.ProcessContext.PPid > 0 {
return processKey{pid: event.ProcessContext.PPid, nsid: event.ProcessContext.NSID}
}
return nil
}

// IsAValidRootNode evaluates if the provided process entry is allowed to become a root node of an Activity Dump
func (pr *ProcessResolver) IsAValidRootNode(entry *model.Process) bool {
return entry.Pid == 1
}

func (at *ProcessResolver) ExecMatches(e1, e2 *processlist.ExecNode) bool {
// ExecMatches returns true if both exec nodes matches
func (pr *ProcessResolver) ExecMatches(e1, e2 *processlist.ExecNode) bool {
return e1.FileEvent.PathnameStr == e2.FileEvent.PathnameStr
}

func (at *ProcessResolver) ProcessMatches(p1, p2 *processlist.ProcessNode) bool {
// ProcessMatches returns true if both process nodes matches
func (pr *ProcessResolver) ProcessMatches(p1, p2 *processlist.ProcessNode) bool {
return p1.CurrentExec.Pid == p2.CurrentExec.Pid && p1.CurrentExec.NSID == p2.CurrentExec.NSID
}

// SendStats sends the tree statistics
func (at *ProcessResolver) SendStats(client statsd.ClientInterface) error {
// nolint: all
func (pr *ProcessResolver) SendStats(client statsd.ClientInterface) error {
// TODO
return nil
}

func (at *ProcessResolver) GetProcessCacheKey(process *model.Process) interface{} {
return processKey{pid: process.Pid, nsid: process.NSID}
}

func (at *ProcessResolver) GetExecCacheKey(process *model.Process) interface{} {
return execKey{pathnameStr: process.FileEvent.PathnameStr}
}

func (at *ProcessResolver) GetParentProcessCacheKey(event *model.Event) interface{} {
if event.ProcessContext.Pid != 1 {
return processKey{pid: event.ProcessContext.PPid, nsid: event.ProcessContext.NSID}
}
return nil
// IsBusybox returns true if the pathname matches busybox
func IsBusybox(pathname string) bool {
return pathname == "/bin/busybox" || pathname == "/usr/bin/busybox"
}
Original file line number Diff line number Diff line change
@@ -155,11 +155,11 @@ func TestFork1st(t *testing.T) {

// parent
parent := newFakeExecEvent(0, 1, "/bin/parent")
new, err := processList.Insert(parent, true, "")
inserted, err := processList.Insert(parent, true, "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, true, new)
assert.Equal(t, true, inserted)
stats.AddProcess(1)
stats.ValidateCounters(t, processList)
if !isProcessAndExecPresent(processList, pc, parent) {
@@ -169,11 +169,11 @@ func TestFork1st(t *testing.T) {
// parent
// \ child
child := newFakeExecEvent(1, 2, "/bin/child")
new, err = processList.Insert(child, true, "")
inserted, err = processList.Insert(child, true, "")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, true, new)
assert.Equal(t, true, inserted)
stats.AddProcess(1)
stats.ValidateCounters(t, processList)
if checkParentality(processList, pc, parent, child) == false {
@@ -197,7 +197,7 @@ func TestFork1st(t *testing.T) {
}

// nothing
deleted, err = processList.DeleteProcess(pc.GetProcessCacheKey(&parent.ProcessContext.Process), "")
deleted, err = processList.DeleteCachedProcess(pc.GetProcessCacheKey(&parent.ProcessContext.Process), "")
assert.Equal(t, true, deleted)
stats.DeleteProcess(1)
stats.ValidateCounters(t, processList)

0 comments on commit 01537e8

Please sign in to comment.