Skip to content

Commit

Permalink
feat(dependencies): add probes nodes and make more generic
Browse files Browse the repository at this point in the history
Add the option to track probes dependencies as node in the manager.
With this addition, the API was changed to support many node types.
The watchers interface was made more generic, and Actions were introduced.
The only supported Action currently available is node addition cancellation.
  • Loading branch information
AlonZivony committed Jun 10, 2024
1 parent 32b2aab commit 273ac35
Show file tree
Hide file tree
Showing 8 changed files with 643 additions and 172 deletions.
84 changes: 50 additions & 34 deletions pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -180,30 +180,30 @@ func (t *Tracee) addDependenciesToStateRecursive(eventNode *dependencies.EventNo
eventID := eventNode.GetID()
for _, dependencyEventID := range eventNode.GetDependencies().GetIDs() {
t.addDependencyEventToState(dependencyEventID, []events.ID{eventID})
dependencyNode, ok := t.eventsDependencies.GetEvent(dependencyEventID)
if ok {
dependencyNode, err := t.eventsDependencies.GetEvent(dependencyEventID)
if err == nil {
t.addDependenciesToStateRecursive(dependencyNode)
}
}
}

func (t *Tracee) chooseEvent(eventID events.ID, chosenState events.EventState) {
func (t *Tracee) selectEvent(eventID events.ID, chosenState events.EventState) {
t.addEventState(eventID, chosenState)
t.eventsDependencies.SelectEvent(eventID)
eventNode, ok := t.eventsDependencies.GetEvent(eventID)
if !ok {
logger.Errorw("Event is missing from dependency right after being selected")
eventNode, err := t.eventsDependencies.SelectEvent(eventID)
if err != nil {
logger.Errorw("Event selection failed",
"event", events.Core.GetDefinitionByID(eventID).GetName())
return
}
t.addDependenciesToStateRecursive(eventNode)
}

// addDependencyEventToState adds to tracee's state an event that is a dependency of other events.
// The difference from chosen events is that it doesn't affect its eviction.
func (t *Tracee) addDependencyEventToState(evtID events.ID, dependantEvts []events.ID) {
func (t *Tracee) addDependencyEventToState(evtID events.ID, dependentEvts []events.ID) {
newState := events.EventState{}
for _, dependantEvent := range dependantEvts {
newState.Submit |= t.eventsState[dependantEvent].Submit
for _, dependentEvent := range dependentEvts {
newState.Submit |= t.eventsState[dependentEvent].Submit
}
t.addEventState(evtID, newState)
if events.Core.GetDefinitionByID(evtID).IsSignature() {
Expand All @@ -212,7 +212,7 @@ func (t *Tracee) addDependencyEventToState(evtID events.ID, dependantEvts []even
}

func (t *Tracee) removeEventFromState(evtID events.ID) {
logger.Debugw("Cancel event", "event", events.Core.GetDefinitionByID(evtID).GetName())
logger.Debugw("Remove event from state", "event", events.Core.GetDefinitionByID(evtID).GetName())
delete(t.eventsState, evtID)
delete(t.eventSignatures, evtID)
}
Expand Down Expand Up @@ -246,13 +246,29 @@ func New(cfg config.Config) (*Tracee, error) {
requiredKsyms: []string{},
}

// TODO: As dynamic event addition or removal becomes a thing, we should subscribe all the watchers
// before selecting them. There is no reason to select the event in the New function anyhow.
t.eventsDependencies.SubscribeAdd(
func(node *dependencies.EventNode) {
t.addDependencyEventToState(node.GetID(), node.GetDependants())
dependencies.EventNodeType,
func(node interface{}) []dependencies.Action {
eventNode, ok := node.(*dependencies.EventNode)
if !ok {
logger.Errorw("Got node from type not requested")
return nil
}
t.addDependencyEventToState(eventNode.GetID(), eventNode.GetDependents())
return nil
})
t.eventsDependencies.SubscribeRemove(
func(node *dependencies.EventNode) {
t.removeEventFromState(node.GetID())
dependencies.EventNodeType,
func(node interface{}) []dependencies.Action {
eventNode, ok := node.(*dependencies.EventNode)
if !ok {
logger.Errorw("Got node from type not requested")
return nil
}
t.removeEventFromState(eventNode.GetID())
return nil
})

// Initialize capabilities rings soon
Expand All @@ -265,32 +281,32 @@ func New(cfg config.Config) (*Tracee, error) {

// Initialize events state with mandatory events (TODO: review this need for sched exec)

t.chooseEvent(events.SchedProcessFork, events.EventState{})
t.chooseEvent(events.SchedProcessExec, events.EventState{})
t.chooseEvent(events.SchedProcessExit, events.EventState{})
t.selectEvent(events.SchedProcessFork, events.EventState{})
t.selectEvent(events.SchedProcessExec, events.EventState{})
t.selectEvent(events.SchedProcessExit, events.EventState{})

// Control Plane Events

t.chooseEvent(events.SignalCgroupMkdir, policy.AlwaysSubmit)
t.chooseEvent(events.SignalCgroupRmdir, policy.AlwaysSubmit)
t.selectEvent(events.SignalCgroupMkdir, policy.AlwaysSubmit)
t.selectEvent(events.SignalCgroupRmdir, policy.AlwaysSubmit)

// Control Plane Process Tree Events

pipeEvts := func() {
t.chooseEvent(events.SchedProcessFork, policy.AlwaysSubmit)
t.chooseEvent(events.SchedProcessExec, policy.AlwaysSubmit)
t.chooseEvent(events.SchedProcessExit, policy.AlwaysSubmit)
t.selectEvent(events.SchedProcessFork, policy.AlwaysSubmit)
t.selectEvent(events.SchedProcessExec, policy.AlwaysSubmit)
t.selectEvent(events.SchedProcessExit, policy.AlwaysSubmit)
}
signalEvts := func() {
t.chooseEvent(events.SignalSchedProcessFork, policy.AlwaysSubmit)
t.chooseEvent(events.SignalSchedProcessExec, policy.AlwaysSubmit)
t.chooseEvent(events.SignalSchedProcessExit, policy.AlwaysSubmit)
t.selectEvent(events.SignalSchedProcessFork, policy.AlwaysSubmit)
t.selectEvent(events.SignalSchedProcessExec, policy.AlwaysSubmit)
t.selectEvent(events.SignalSchedProcessExit, policy.AlwaysSubmit)
}

// DNS Cache events

if t.config.DNSCacheConfig.Enable {
t.chooseEvent(events.NetPacketDNS, policy.AlwaysSubmit)
t.selectEvent(events.NetPacketDNS, policy.AlwaysSubmit)
}

switch t.config.ProcTree.Source {
Expand All @@ -306,7 +322,7 @@ func New(cfg config.Config) (*Tracee, error) {
// Pseudo events added by capture (if enabled by the user)

for eventID, eCfg := range GetCaptureEventsList(cfg) {
t.chooseEvent(eventID, eCfg)
t.selectEvent(eventID, eCfg)
}

// Events chosen by the user
Expand All @@ -323,7 +339,7 @@ func New(cfg config.Config) (*Tracee, error) {
}
utils.SetBit(&submit, uint(p.ID))
utils.SetBit(&emit, uint(p.ID))
t.chooseEvent(e, events.EventState{Submit: submit, Emit: emit})
t.selectEvent(e, events.EventState{Submit: submit, Emit: emit})

policyManager.EnableRule(p.ID, e)
}
Expand All @@ -337,8 +353,8 @@ func New(cfg config.Config) (*Tracee, error) {
if !events.Core.IsDefined(id) {
return t, errfmt.Errorf("event %d is not defined", id)
}
depsNode, ok := t.eventsDependencies.GetEvent(id)
if ok {
depsNode, err := t.eventsDependencies.GetEvent(id)
if err == nil {
deps := depsNode.GetDependencies()
evtCaps := deps.GetCapabilities()
err = caps.BaseRingAdd(evtCaps.GetBase()...)
Expand Down Expand Up @@ -899,9 +915,9 @@ func (t *Tracee) initKsymTableRequiredSyms() error {
return errfmt.Errorf("event %d is not defined", id)
}

depsNode, ok := t.eventsDependencies.GetEvent(id)
if !ok {
logger.Warnw("failed to extract required ksymbols from event", "event_id", id)
depsNode, err := t.eventsDependencies.GetEvent(id)
if err != nil {
logger.Warnw("failed to extract required ksymbols from event", "event_id", id, "error", err)
continue
}
// Add directly dependant symbols
Expand Down
12 changes: 12 additions & 0 deletions pkg/events/definition_dependencies.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ type Probe struct {
required bool // tracee fails if probe can't be attached
}

func NewProbe(handle probes.Handle, required bool) Probe {
return Probe{handle: handle, required: required}
}

func (p Probe) GetHandle() probes.Handle {
return p.handle
}
Expand All @@ -95,6 +99,10 @@ type KSymbol struct {
required bool // tracee fails if symbol is not found
}

func NewKSymbol(symbol string, required bool) KSymbol {
return KSymbol{symbol: symbol, required: required}
}

func (ks KSymbol) GetSymbolName() string {
return ks.symbol
}
Expand All @@ -110,6 +118,10 @@ type Capabilities struct {
ebpf []cap.Value // effective when using eBPF
}

func NewCapabilities(base []cap.Value, ebpf []cap.Value) Capabilities {
return Capabilities{base: base, ebpf: ebpf}
}

func (c Capabilities) GetBase() []cap.Value {
if c.base == nil {
return []cap.Value{}
Expand Down
35 changes: 35 additions & 0 deletions pkg/events/dependencies/actions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package dependencies

// Action is a struct representing a request by a watcher function to interact with the tree.
//
// Actions can perform various tasks, including but not limited to modifying the tree.
// Utilizing Actions ensures that operations are executed in the proper order, avoiding
// potential bugs related to operation sequencing. All interactions with the tree which
// might modify the tree should be carried out through Actions, rather than directly
// within a watcher's scope.
type Action interface{}

// CancelNodeAddAction cancels the process of adding a node to the manager.
//
// This method will:
// 1. Cancel the addition of the specified node.
// 2. Cancel the addition of all dependent nodes.
// 3. Remove any dependencies that are no longer referenced by other nodes.
//
// The overall effect is similar to calling RemoveEvent directly on the manager,
// but with additional safeguards and order of operations to ensure proper cleanup
// and consistency within the system.
//
// Note:
// - This action does not prevent other watchers from being notified.
// - When the node addition is cancelled, event removal watchers will be invoked to allow for cleanup operations.
//
// It is recommended to use CancelNodeAddAction instead of directly calling RemoveEvent
// to ensure that the cancellation and cleanup processes are handled in the correct order.
type CancelNodeAddAction struct {
Reason error
}

func NewCancelNodeAddAction(reason error) *CancelNodeAddAction {
return &CancelNodeAddAction{Reason: reason}
}
34 changes: 34 additions & 0 deletions pkg/events/dependencies/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package dependencies

import (
"errors"
"fmt"
"strings"
)

// ErrNodeAddCancelled is the error produced when cancelling a node add to the manager
// using the CancelNodeAddAction Action.
type ErrNodeAddCancelled struct {
Reasons []error
}

func NewErrNodeAddCancelled(reasons []error) *ErrNodeAddCancelled {
return &ErrNodeAddCancelled{Reasons: reasons}
}

func (cancelErr *ErrNodeAddCancelled) Error() string {
var errorsStrings []string
for _, err := range cancelErr.Reasons {
errorsStrings = append(errorsStrings, err.Error())
}
return fmt.Sprintf("node add was cancelled, reasons: \"%s\"", strings.Join(errorsStrings, "\", \""))
}

func (cancelErr *ErrNodeAddCancelled) AddReason(reason error) {
cancelErr.Reasons = append(cancelErr.Reasons, reason)
}

var (
ErrNodeType = errors.New("unsupported node type")
ErrNodeNotFound = errors.New("node not found")
)
28 changes: 14 additions & 14 deletions pkg/events/dependencies/event.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ type EventNode struct {
id events.ID
explicitlySelected bool
dependencies events.Dependencies
// There won't be more than a couple of dependants, so a slice is better for
// There won't be more than a couple of dependents, so a slice is better for
// both performance and supporting efficient thread-safe operation in the future
dependants []events.ID
dependents []events.ID
}

func newDependenciesNode(id events.ID, dependencies events.Dependencies, chosenDirectly bool) *EventNode {
return &EventNode{
id: id,
explicitlySelected: chosenDirectly,
dependencies: dependencies,
dependants: make([]events.ID, 0),
dependents: make([]events.ID, 0),
}
}

Expand All @@ -34,13 +34,13 @@ func (en *EventNode) GetDependencies() events.Dependencies {
return en.dependencies
}

func (en *EventNode) GetDependants() []events.ID {
return slices.Clone[[]events.ID](en.dependants)
func (en *EventNode) GetDependents() []events.ID {
return slices.Clone[[]events.ID](en.dependents)
}

func (en *EventNode) IsDependencyOf(dependant events.ID) bool {
for _, d := range en.dependants {
if d == dependant {
func (en *EventNode) IsDependencyOf(dependent events.ID) bool {
for _, d := range en.dependents {
if d == dependent {
return true
}
}
Expand All @@ -59,14 +59,14 @@ func (en *EventNode) unmarkAsExplicitlySelected() {
en.explicitlySelected = false
}

func (en *EventNode) addDependant(dependant events.ID) {
en.dependants = append(en.dependants, dependant)
func (en *EventNode) addDependent(dependent events.ID) {
en.dependents = append(en.dependents, dependent)
}

func (en *EventNode) removeDependant(dependant events.ID) {
for i, d := range en.dependants {
if d == dependant {
en.dependants = append(en.dependants[:i], en.dependants[i+1:]...)
func (en *EventNode) removeDependent(dependent events.ID) {
for i, d := range en.dependents {
if d == dependent {
en.dependents = append(en.dependents[:i], en.dependents[i+1:]...)
break
}
}
Expand Down
Loading

0 comments on commit 273ac35

Please sign in to comment.