Skip to content

Commit

Permalink
cmd/tracee-ebpf: dynamicly load capabilities according to requirements
Browse files Browse the repository at this point in the history
Added dynamic building of capabilities set according to events used and capture flags
  • Loading branch information
AlonZivony authored and rafaeldtinoco committed Jun 6, 2022
1 parent bf0600a commit 6b0cad4
Show file tree
Hide file tree
Showing 7 changed files with 379 additions and 103 deletions.
110 changes: 110 additions & 0 deletions cmd/tracee-ebpf/capabilities.go
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
}
146 changes: 146 additions & 0 deletions cmd/tracee-ebpf/capabilities_test.go
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)
})
}
})
}
}
50 changes: 1 addition & 49 deletions cmd/tracee-ebpf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,10 @@ import (
"github.com/aquasecurity/libbpfgo/helpers"
embed "github.com/aquasecurity/tracee"
"github.com/aquasecurity/tracee/cmd/tracee-ebpf/internal/flags"
"github.com/aquasecurity/tracee/pkg/capabilities"
tracee "github.com/aquasecurity/tracee/pkg/ebpf"
"github.com/aquasecurity/tracee/pkg/metrics"
"github.com/aquasecurity/tracee/types/trace"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syndtr/gocapability/capability"
cli "github.com/urfave/cli/v2"
)

Expand Down Expand Up @@ -145,8 +143,7 @@ func main() {
cfg.Output = &output

// environment capabilities

err = ensureCapabilities(OSInfo)
err = ensureCapabilities(OSInfo, &cfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -503,51 +500,6 @@ func checkCommandIsHelp(s []string) bool {
return false
}

const bpfCapabilitiesMinKernelVersion = "5.8"

// ensureCapabilities makes sure the program has just the required capabilities to run
func ensureCapabilities(OSInfo *helpers.OSInfo) error {
selfCap, err := capabilities.Self()
if err != nil {
return err
}

// Build the set of capabilities required to run
rCaps := []capability.Cap{
capability.CAP_IPC_LOCK,
capability.CAP_SYS_PTRACE,
capability.CAP_SYS_RESOURCE,
capability.CAP_NET_ADMIN,
}
if OSInfo.CompareOSBaseKernelRelease(bpfCapabilitiesMinKernelVersion) <= 0 {
bpfCaps := []capability.Cap{
capability.CAP_BPF,
capability.CAP_PERFMON,
}
if err1 := capabilities.CheckRequired(selfCap, bpfCaps); err1 != nil {
bpfCaps = []capability.Cap{
capability.CAP_SYS_ADMIN,
}
if err2 := capabilities.CheckRequired(selfCap, bpfCaps); err2 != nil {
return fmt.Errorf("missing capabilites required for eBPF program loading - either CAP_BPF + CAP_PERFMON or CAP_SYS_ADMIN")
}
}
rCaps = append(rCaps, bpfCaps...)
} else {
rCaps = append(rCaps, []capability.Cap{
capability.CAP_SYS_ADMIN,
}...)
}

if err = capabilities.CheckRequired(selfCap, rCaps); err != nil {
return err
}
if err = capabilities.DropUnrequired(selfCap, rCaps); err != nil {
return err
}
return nil
}

func getFormattedEventParams(eventID int32) string {
eventParams := tracee.EventsDefinitions[eventID].Params
var verboseEventParams string
Expand Down
4 changes: 1 addition & 3 deletions pkg/capabilities/capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,7 @@ func TestDropUnrequired(t *testing.T) {
var setCaps []capability.Cap
fc := fakeCapability{
set: func(capType capability.CapType, caps ...capability.Cap) {
for _, c := range caps {
setCaps = append(setCaps, c)
}
setCaps = append(setCaps, caps...)
},
apply: func(kind capability.CapType) error {
for _, reqCap := range requiredCaps {
Expand Down
37 changes: 37 additions & 0 deletions pkg/ebpf/events_capabilities.go
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)
}
}
Loading

0 comments on commit 6b0cad4

Please sign in to comment.