forked from aquasecurity/tracee
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/tracee-ebpf: dynamicly load capabilities according to requirements
Added dynamic building of capabilities set according to events used and capture flags
- Loading branch information
1 parent
bf0600a
commit 6b0cad4
Showing
7 changed files
with
379 additions
and
103 deletions.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"github.com/aquasecurity/tracee/pkg/capabilities" | ||
tracee "github.com/aquasecurity/tracee/pkg/ebpf" | ||
"github.com/syndtr/gocapability/capability" | ||
) | ||
|
||
// IKernelVersionInfo is an interface to check kernel version | ||
type IKernelVersionInfo interface { | ||
// CompareOSBaseKernelRelease compare given kernel version to current one. | ||
// The return value is -1, 0 or 1 if given version is less, | ||
// equal or bigger, respectively, than running one. | ||
CompareOSBaseKernelRelease(string) int | ||
} | ||
|
||
const bpfCapabilitiesMinKernelVersion = "5.8" | ||
|
||
// ensureCapabilities makes sure program runs with required capabilities only | ||
func ensureCapabilities(OSInfo IKernelVersionInfo, cfg *tracee.Config) error { | ||
selfCap, err := capabilities.Self() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
rCaps, err := generateTraceeEbpfRequiredCapabilities(OSInfo, cfg, selfCap) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if err = capabilities.CheckRequired(selfCap, rCaps); err != nil { | ||
return err | ||
} | ||
if err = capabilities.DropUnrequired(selfCap, rCaps); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// Get all capabilities required to run tracee-ebpf for current run | ||
func generateTraceeEbpfRequiredCapabilities(OSInfo IKernelVersionInfo, cfg *tracee.Config, selfCap capability.Capabilities) ( | ||
[]capability.Cap, error) { | ||
rCaps, err := getCapabilitiesRequiredByEBPF(selfCap, OSInfo) | ||
if err != nil { | ||
return nil, err | ||
} | ||
rCaps = append(rCaps, getCapabilitiesRequiredByTraceeEvents(cfg)...) | ||
|
||
rCaps = removeDupCaps(rCaps) | ||
return rCaps, nil | ||
} | ||
|
||
func getCapabilitiesRequiredByTraceeEvents(cfg *tracee.Config) []capability.Cap { | ||
usedEvents := cfg.Filter.EventsToTrace | ||
for eventID := range tracee.CreateEssentialEventsList(cfg) { | ||
usedEvents = append(usedEvents, eventID) | ||
} | ||
for eventID := range tracee.GetCaptureEventsConfig(cfg) { | ||
usedEvents = append(usedEvents, eventID) | ||
} | ||
caps := tracee.GetCapabilitiesRequiredByEvents(usedEvents) | ||
|
||
return removeDupCaps(caps) | ||
} | ||
|
||
// Get all capabilities required for eBPF usage (including perf buffers maps management) | ||
func getCapabilitiesRequiredByEBPF(selfCap capability.Capabilities, OSInfo IKernelVersionInfo) ([]capability.Cap, error) { | ||
caps := []capability.Cap{ | ||
capability.CAP_IPC_LOCK, | ||
capability.CAP_SYS_RESOURCE, | ||
} | ||
var versCaps []capability.Cap | ||
if OSInfo.CompareOSBaseKernelRelease(bpfCapabilitiesMinKernelVersion) <= 0 { | ||
versCaps = []capability.Cap{ | ||
capability.CAP_BPF, | ||
capability.CAP_PERFMON, | ||
} | ||
if err1 := capabilities.CheckRequired(selfCap, versCaps); err1 != nil { | ||
versCaps = []capability.Cap{ | ||
capability.CAP_SYS_ADMIN, | ||
} | ||
if err2 := capabilities.CheckRequired(selfCap, versCaps); err2 != nil { | ||
return nil, fmt.Errorf("missing capabilites required for eBPF program loading - either CAP_BPF + CAP_PERFMON or CAP_SYS_ADMIN") | ||
} | ||
} | ||
} else { | ||
versCaps = []capability.Cap{ | ||
capability.CAP_SYS_ADMIN, | ||
} | ||
} | ||
caps = append(caps, versCaps...) | ||
return caps, nil | ||
} | ||
|
||
func removeDupCaps(dupCaps []capability.Cap) []capability.Cap { | ||
capsMap := make(map[capability.Cap]bool) | ||
for _, c := range dupCaps { | ||
capsMap[c] = true | ||
} | ||
caps := make([]capability.Cap, len(capsMap)) | ||
i := 0 | ||
for c := range capsMap { | ||
caps[i] = c | ||
i++ | ||
} | ||
|
||
return caps | ||
} |
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,146 @@ | ||
package main | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/aquasecurity/libbpfgo/helpers" | ||
tracee "github.com/aquasecurity/tracee/pkg/ebpf" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/syndtr/gocapability/capability" | ||
) | ||
|
||
type mockOSInfo struct { | ||
version string | ||
} | ||
|
||
func (mOSInfo mockOSInfo) CompareOSBaseKernelRelease(version string) int { | ||
return helpers.CompareKernelRelease(mOSInfo.version, version) | ||
} | ||
|
||
type mockCapabilities struct { | ||
missingCaps []capability.Cap | ||
} | ||
|
||
func (mockCaps *mockCapabilities) Get(which capability.CapType, what capability.Cap) bool { | ||
for _, mcap := range mockCaps.missingCaps { | ||
if what == mcap { | ||
return false | ||
} | ||
} | ||
return true | ||
} | ||
func (mockCaps *mockCapabilities) Empty(which capability.CapType) bool { return true } | ||
func (mockCaps *mockCapabilities) Full(which capability.CapType) bool { return true } | ||
func (mockCaps *mockCapabilities) Set(which capability.CapType, caps ...capability.Cap) {} | ||
func (mockCaps *mockCapabilities) Unset(which capability.CapType, caps ...capability.Cap) {} | ||
func (mockCaps *mockCapabilities) Fill(kind capability.CapType) {} | ||
func (mockCaps *mockCapabilities) Clear(kind capability.CapType) {} | ||
func (mockCaps *mockCapabilities) StringCap(which capability.CapType) string { return "" } | ||
func (mockCaps *mockCapabilities) String() string { return "" } | ||
func (mockCaps *mockCapabilities) Load() error { return nil } | ||
func (mockCaps *mockCapabilities) Apply(kind capability.CapType) error { return nil } | ||
|
||
func TestGenerateTraceeEbpfRequiredCapabilities(t *testing.T) { | ||
traceTestCases := []struct { | ||
name string | ||
chosenEvents []string | ||
ifaces []string | ||
expectedCapabilities []capability.Cap | ||
}{ | ||
{ | ||
name: "No events chosen", | ||
chosenEvents: []string{}, | ||
expectedCapabilities: []capability.Cap{}, | ||
}, | ||
{ | ||
name: "Net event chosen", | ||
chosenEvents: []string{"net_packet"}, | ||
ifaces: []string{"enp0s3"}, | ||
expectedCapabilities: []capability.Cap{capability.CAP_NET_ADMIN}, | ||
}, | ||
{ | ||
name: "Init namespaces event chosen", | ||
chosenEvents: []string{"init_namespaces"}, | ||
expectedCapabilities: []capability.Cap{capability.CAP_SYS_PTRACE}, | ||
}, | ||
} | ||
|
||
environmentTestCases := []struct { | ||
name string | ||
kernelVersion string | ||
missingCapabilities []capability.Cap | ||
expectedCapabilities []capability.Cap | ||
}{ | ||
{ | ||
name: "Version 4.19 with all capabilities", | ||
kernelVersion: "4.19.0", | ||
missingCapabilities: []capability.Cap{}, | ||
expectedCapabilities: []capability.Cap{ | ||
capability.CAP_IPC_LOCK, | ||
capability.CAP_SYS_RESOURCE, | ||
capability.CAP_SYS_ADMIN, | ||
}, | ||
}, | ||
{ | ||
name: "Version 5.17 with all capabilities", | ||
kernelVersion: "5.17.0", | ||
missingCapabilities: []capability.Cap{}, | ||
expectedCapabilities: []capability.Cap{ | ||
capability.CAP_IPC_LOCK, | ||
capability.CAP_SYS_RESOURCE, | ||
capability.CAP_BPF, | ||
capability.CAP_PERFMON, | ||
}, | ||
}, | ||
{ | ||
name: "Version 5.17 without CAP_BPF", | ||
kernelVersion: "5.17.0", | ||
missingCapabilities: []capability.Cap{capability.CAP_BPF}, | ||
expectedCapabilities: []capability.Cap{ | ||
capability.CAP_IPC_LOCK, | ||
capability.CAP_SYS_RESOURCE, | ||
capability.CAP_SYS_ADMIN, | ||
}, | ||
}, | ||
} | ||
|
||
eventsNameToID := make(map[string]int32, len(tracee.EventsDefinitions)) | ||
for id, event := range tracee.EventsDefinitions { | ||
eventsNameToID[event.Name] = id | ||
} | ||
|
||
for _, envTest := range environmentTestCases { | ||
t.Run(envTest.name, func(t *testing.T) { | ||
// Generate environment mockers | ||
osInfo := mockOSInfo{version: envTest.kernelVersion} | ||
caps := mockCapabilities{missingCaps: envTest.missingCapabilities} | ||
|
||
for _, traceTest := range traceTestCases { | ||
t.Run(traceTest.name, func(t *testing.T) { | ||
// Create configuration for given tracing | ||
var eventsToTrace []int32 | ||
for _, eventName := range traceTest.chosenEvents { | ||
eventsToTrace = append(eventsToTrace, eventsNameToID[eventName]) | ||
} | ||
cfg := tracee.Config{ | ||
Filter: &tracee.Filter{ | ||
EventsToTrace: eventsToTrace, | ||
NetFilter: &tracee.IfaceFilter{ | ||
InterfacesToTrace: traceTest.ifaces, | ||
}, | ||
}, | ||
Capture: &tracee.CaptureConfig{}, | ||
Debug: false, | ||
} | ||
|
||
neededCaps, err := generateTraceeEbpfRequiredCapabilities(osInfo, &cfg, &caps) | ||
require.NoError(t, err) | ||
expectedCaps := append(envTest.expectedCapabilities, traceTest.expectedCapabilities...) | ||
expectedCaps = removeDupCaps(expectedCaps) | ||
assert.ElementsMatch(t, expectedCaps, neededCaps) | ||
}) | ||
} | ||
}) | ||
} | ||
} |
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
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,37 @@ | ||
package ebpf | ||
|
||
import ( | ||
"github.com/syndtr/gocapability/capability" | ||
) | ||
|
||
func GetCapabilitiesRequiredByEvents(events []int32) []capability.Cap { | ||
reqCapabilities := make(map[capability.Cap]bool) | ||
for _, e := range events { | ||
addEventAndDependenciesCapabilities(e, reqCapabilities) | ||
} | ||
|
||
capList := make([]capability.Cap, len(reqCapabilities)) | ||
i := 0 | ||
for reqCap := range reqCapabilities { | ||
capList[i] = reqCap | ||
i++ | ||
} | ||
return capList | ||
} | ||
|
||
func addEventAndDependenciesCapabilities(event int32, reqCapabilities map[capability.Cap]bool) { | ||
eDef, ok := EventsDefinitions[event] | ||
if !ok { | ||
return | ||
} | ||
for _, reqCap := range eDef.Dependencies.capabilities { | ||
reqCapabilities[reqCap] = true | ||
} | ||
if len(eDef.Dependencies.ksymbols) > 0 { | ||
reqCapabilities[capability.CAP_SYSLOG] = true | ||
} | ||
|
||
for _, d := range eDef.Dependencies.events { | ||
addEventAndDependenciesCapabilities(d.eventID, reqCapabilities) | ||
} | ||
} |
Oops, something went wrong.