Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add event enable/disable #3466

Merged
merged 1 commit into from
Sep 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions pkg/ebpf/events_pipeline.go
Original file line number Diff line number Diff line change
Expand Up @@ -570,9 +570,9 @@ func (t *Tracee) sinkEvents(ctx context.Context, in <-chan *trace.Event) <-chan
continue // might happen during initialization (ctrl+c seg faults)
}

// Is the rule disabled for the matched policies?
if !t.policyManager.IsRuleEnabled(event.MatchedPoliciesUser, events.ID(event.EventID)) {
logger.Debugw("event dropped because matched rule disabled", "event", event.EventName)
// Is the event enabled for the policies or globally?
if !t.policyManager.IsEnabled(event.MatchedPoliciesUser, events.ID(event.EventID)) {
logger.Debugw("event dropped because it is not enabled", "event", event.EventName)
t.eventsPool.Put(event)
continue
}
Expand Down
102 changes: 92 additions & 10 deletions pkg/ebpf/policy_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,47 +10,129 @@ import (
// policyManager is a thread-safe struct that manages the enabled policies for each rule
type policyManager struct {
mutex sync.Mutex
rules map[events.ID]uint64
rules map[events.ID]*eventState
}

// eventState is a struct that holds the state of a given event
type eventState struct {
policyMask uint64
enabled bool
}

func newPolicyManager() *policyManager {
return &policyManager{
mutex: sync.Mutex{},
rules: make(map[events.ID]uint64),
rules: make(map[events.ID]*eventState),
}
}

// IsEnabled tests if a event, or a policy per event is enabled (in the future it will also check if a policy is enabled)
// TODO: add metrics about an event being enabled/disabled, or a policy being enabled/disabled?
func (pm *policyManager) IsEnabled(matchedPolicies uint64, ruleId events.ID) bool {
pm.mutex.Lock()
defer pm.mutex.Unlock()

if !pm.isEventEnabled(ruleId) {
return false
}

return pm.isRuleEnabled(matchedPolicies, ruleId)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should it be ruleId or eventId then?
Should it be isRuleEnabled or isEventEnabled?

Copy link
Contributor Author

@josedonizetti josedonizetti Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using ruleId where in the future it is a rule, although now it is type events.ID and I'm using eventId where it is an event, rule depends on passing policy information, event doesn't

And we have both concepts, Rules can be enabled/disabled, and Events can be enabled/disable, so we are exposing synchronized methods to deal with it (specially good to test both concepts), but in the pipeline we want to do a single test, get the mutex only one time, so we use IsEnabled which in the future should also cover the case of Policies enabled/disabled

}

// IsRuleEnabled returns true if a given event policy is enabled for a given rule
func (pm *policyManager) IsRuleEnabled(matchedPolicies uint64, ruleId events.ID) bool {
pm.mutex.Lock()
defer pm.mutex.Unlock()

policyMask, ok := pm.rules[ruleId]
return pm.isRuleEnabled(matchedPolicies, ruleId)
}

// not synchronized, use IsRuleEnabled instead
func (pm *policyManager) isRuleEnabled(matchedPolicies uint64, ruleId events.ID) bool {
geyslan marked this conversation as resolved.
Show resolved Hide resolved
state, ok := pm.rules[ruleId]
if !ok {
return false
}

return state.policyMask&matchedPolicies != 0
}

// IsEventEnabled returns true if a given event policy is enabled for a given rule
func (pm *policyManager) IsEventEnabled(evenId events.ID) bool {
pm.mutex.Lock()
defer pm.mutex.Unlock()

return pm.isEventEnabled(evenId)
}

// not synchronized, use IsEventEnabled instead
func (pm *policyManager) isEventEnabled(evenId events.ID) bool {
state, ok := pm.rules[evenId]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we use pm.rules once with eventId (typo: eventId) and above with ruleId - which one is correct?
Reminder that in the future rule id will be composed of event id and some index

Copy link
Contributor Author

@josedonizetti josedonizetti Sep 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm using ruleId where in the future it is a rule, although now it is type events.ID and I'm using eventId where it is an event, rule depends on passing policy information, event doesn't

if !ok {
return false
}

return policyMask&matchedPolicies != 0
return state.enabled
}

// EnableRule enables a rule for a given event policy
func (pm *policyManager) EnableRule(policyId int, ruleId events.ID) {
pm.mutex.Lock()
defer pm.mutex.Unlock()

policyMask := pm.rules[ruleId]
utils.SetBit(&policyMask, uint(policyId))
state, ok := pm.rules[ruleId]
if !ok {
// if you enabling/disabling a rule for an event that
// was not enabled/disabled yet, we assume the event should be enabled
state = &eventState{enabled: true}
}

utils.SetBit(&state.policyMask, uint(policyId))

pm.rules[ruleId] = policyMask
pm.rules[ruleId] = state
}

// DisableRule disables a rule for a given event policy
func (pm *policyManager) DisableRule(policyId int, ruleId events.ID) {
pm.mutex.Lock()
defer pm.mutex.Unlock()

policyMask := pm.rules[ruleId]
utils.ClearBit(&policyMask, uint(policyId))
state, ok := pm.rules[ruleId]
if !ok {
// if you enabling/disabling a rule for an event that
// was not enabled/disabled yet, we assume the event should be enabled
state = &eventState{enabled: true}
}

utils.ClearBit(&state.policyMask, uint(policyId))

pm.rules[ruleId] = state
}

// EnableEvent enables a given event
func (pm *policyManager) EnableEvent(eventId events.ID) {
pm.mutex.Lock()
defer pm.mutex.Unlock()

state, ok := pm.rules[eventId]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how will this code look like in the future when we will have rule ids different than event id?
We will not be able to use pm.rules map anymore, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably, but it is an internal structure we should be able to refactor it without affecting anything that is consuming it. Right? It is encapsulated.

if !ok {
pm.rules[eventId] = &eventState{enabled: true}
return
}

state.enabled = true
}

// DisableEvent disables a given event
func (pm *policyManager) DisableEvent(eventId events.ID) {
pm.mutex.Lock()
defer pm.mutex.Unlock()

state, ok := pm.rules[eventId]
if !ok {
pm.rules[eventId] = &eventState{enabled: false}
return
}

pm.rules[ruleId] = policyMask
state.enabled = false
}
156 changes: 153 additions & 3 deletions pkg/ebpf/policy_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (
"github.com/aquasecurity/tracee/pkg/policy"
)

func TestEventsManagerEnableRule(t *testing.T) {
func TestPolicyManagerEnableRule(t *testing.T) {
policyManager := newPolicyManager()

policy1Mached := uint64(0b10)
Expand All @@ -28,12 +28,13 @@ func TestEventsManagerEnableRule(t *testing.T) {
assert.True(t, policyManager.IsRuleEnabled(policy1And2Mached, events.SecurityBPF))

policyManager.EnableRule(2, events.SecurityBPF)

assert.True(t, policyManager.IsRuleEnabled(policy1Mached, events.SecurityBPF))
assert.True(t, policyManager.IsRuleEnabled(policy2Mached, events.SecurityBPF))
assert.True(t, policyManager.IsRuleEnabled(policy1And2Mached, events.SecurityBPF))
}

func TestEventsManagerDisableRule(t *testing.T) {
func TestPolicyManagerDisableRule(t *testing.T) {
policyManager := newPolicyManager()

policy1Mached := uint64(0b10)
Expand All @@ -53,7 +54,7 @@ func TestEventsManagerDisableRule(t *testing.T) {
assert.False(t, policyManager.IsRuleEnabled(policy1And2Mached, events.SecurityBPF))
}

func TestEventsManagerEnableAndDisableConcurrent(t *testing.T) {
func TestPolicyManagerEnableAndDisableRuleConcurrent(t *testing.T) {
eventsToEnable := []events.ID{
events.SecurityBPF,
events.SchedGetPriorityMax,
Expand Down Expand Up @@ -105,3 +106,152 @@ func TestEventsManagerEnableAndDisableConcurrent(t *testing.T) {
}
}
}

func TestPolicyManagerEnableEvent(t *testing.T) {
policyManager := newPolicyManager()

assert.False(t, policyManager.isEventEnabled(events.SecurityBPF))
assert.False(t, policyManager.isEventEnabled(events.SecurityFileOpen))
assert.False(t, policyManager.isEventEnabled(events.SecuritySocketAccept))

policyManager.EnableEvent(events.SecurityBPF)
policyManager.EnableEvent(events.SecurityFileOpen)
policyManager.EnableEvent(events.SecuritySocketAccept)

assert.True(t, policyManager.isEventEnabled(events.SecurityBPF))
assert.True(t, policyManager.isEventEnabled(events.SecurityFileOpen))
assert.True(t, policyManager.isEventEnabled(events.SecuritySocketAccept))
}

func TestPolicyManagerDisableEvent(t *testing.T) {
policyManager := newPolicyManager()

policyManager.EnableEvent(events.SecurityBPF)
policyManager.EnableEvent(events.SecurityFileOpen)
policyManager.EnableEvent(events.SecuritySocketAccept)

assert.True(t, policyManager.IsEventEnabled(events.SecurityBPF))
assert.True(t, policyManager.IsEventEnabled(events.SecurityFileOpen))
assert.True(t, policyManager.IsEventEnabled(events.SecuritySocketAccept))

policyManager.DisableEvent(events.SecurityBPF)
policyManager.DisableEvent(events.SecurityFileOpen)

assert.False(t, policyManager.IsEventEnabled(events.SecurityBPF))
assert.False(t, policyManager.IsEventEnabled(events.SecurityFileOpen))
assert.True(t, policyManager.IsEventEnabled(events.SecuritySocketAccept))
}

func TestPolicyManagerEnableAndDisableEventConcurrent(t *testing.T) {
eventsToEnable := []events.ID{
events.SecurityBPF,
events.SchedGetPriorityMax,
events.SchedProcessExec,
events.SchedProcessExit,
events.Ptrace,
}

eventsToDisable := []events.ID{
events.SecurityBPFMap,
events.Openat2,
events.SchedProcessFork,
events.MagicWrite,
events.FileModification,
}

policyManager := newPolicyManager()

// activate events
for _, e := range eventsToDisable {
policyManager.EnableEvent(e)
}

var wg sync.WaitGroup

wg.Add(1)
go func() {
for i := 0; i < policy.MaxPolicies; i++ {
for _, e := range eventsToEnable {
policyManager.EnableEvent(e)
}
}
wg.Done()
}()

wg.Add(1)
go func() {
for i := 0; i < policy.MaxPolicies; i++ {
for _, e := range eventsToDisable {
policyManager.DisableEvent(e)
}
}
wg.Done()
}()

wg.Wait()

for i := 0; i < policy.MaxPolicies; i++ {
for _, e := range eventsToEnable {
assert.True(t, policyManager.IsEventEnabled(e))
}
for _, e := range eventsToDisable {
assert.False(t, policyManager.IsEventEnabled(e))
}
}
}

func TestEnableRuleAlsoEnableEvent(t *testing.T) {
policyManager := newPolicyManager()

assert.False(t, policyManager.IsEventEnabled(events.SecurityBPF))

policyManager.EnableRule(1, events.SecurityBPF)

assert.True(t, policyManager.IsEventEnabled(events.SecurityBPF))
}

func TestDisableRuleAlsoEnableEvent(t *testing.T) {
policyManager := newPolicyManager()

assert.False(t, policyManager.IsEventEnabled(events.SecurityFileOpen))

policyManager.DisableRule(1, events.SecurityFileOpen)

assert.True(t, policyManager.IsEventEnabled(events.SecurityFileOpen))
}

func TestPolicyManagerIsEnabled(t *testing.T) {
policyManager := newPolicyManager()

policy1Mached := uint64(0b10)
policy2Mached := uint64(0b100)
policy1And2Mached := uint64(0b110)

assert.False(t, policyManager.IsEnabled(policy1Mached, events.SecurityBPF))
assert.False(t, policyManager.IsEnabled(policy2Mached, events.SecurityBPF))
assert.False(t, policyManager.IsEnabled(policy1And2Mached, events.SecurityBPF))

policyManager.EnableRule(1, events.SecurityBPF)

assert.True(t, policyManager.IsEnabled(policy1Mached, events.SecurityBPF))
assert.False(t, policyManager.IsEnabled(policy2Mached, events.SecurityBPF))
assert.True(t, policyManager.IsEnabled(policy1And2Mached, events.SecurityBPF))

policyManager.EnableRule(2, events.SecurityBPF)

assert.True(t, policyManager.IsEnabled(policy1Mached, events.SecurityBPF))
assert.True(t, policyManager.IsEnabled(policy2Mached, events.SecurityBPF))
assert.True(t, policyManager.IsEnabled(policy1And2Mached, events.SecurityBPF))

policyManager.DisableEvent(events.SecurityBPF)

assert.False(t, policyManager.IsEnabled(policy1Mached, events.SecurityBPF))
assert.False(t, policyManager.IsEnabled(policy2Mached, events.SecurityBPF))
assert.False(t, policyManager.IsEnabled(policy1And2Mached, events.SecurityBPF))

policyManager.EnableEvent(events.SecurityBPF)

assert.True(t, policyManager.IsEnabled(policy1Mached, events.SecurityBPF))
assert.True(t, policyManager.IsEnabled(policy2Mached, events.SecurityBPF))
assert.True(t, policyManager.IsEnabled(policy1And2Mached, events.SecurityBPF))
}
22 changes: 22 additions & 0 deletions pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -1773,6 +1773,28 @@ func (t *Tracee) Unsubscribe(s *streams.Stream) {
t.streamsManager.Unsubscribe(s)
}

func (t *Tracee) EnableEvent(eventName string) error {
id, found := events.Core.GetDefinitionIDByName(eventName)
if !found {
return errfmt.Errorf("error event not found: %s", eventName)
}

t.policyManager.EnableEvent(id)

return nil
}

func (t *Tracee) DisableEvent(eventName string) error {
id, found := events.Core.GetDefinitionIDByName(eventName)
if !found {
return errfmt.Errorf("error event not found: %s", eventName)
}

t.policyManager.DisableEvent(id)

return nil
}

// EnableRule enables a rule in the specified policies
func (t *Tracee) EnableRule(policyNames []string, ruleId string) error {
eventID, found := events.Core.GetDefinitionIDByName(ruleId)
Expand Down