Skip to content

Commit

Permalink
feat(caps): base ebpf capabilities (#4178)
Browse files Browse the repository at this point in the history
Add the ability to add the ebpf capabilities to the base ring, instead
of allocating a specific ring. When the feature is enabled, requests for
the eBPF ring will seamlessly avoid a ring switch, and the callback will
be called as is.
  • Loading branch information
NDStrahilevitz authored Jul 9, 2024
1 parent e01f933 commit 5c09519
Show file tree
Hide file tree
Showing 5 changed files with 86 additions and 30 deletions.
6 changes: 5 additions & 1 deletion cmd/tracee-rules/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,11 @@ func main() {
// happens after Find() is called) in that case.

bypass := c.Bool("allcaps") || !isRoot()
err = capabilities.Initialize(bypass)
err = capabilities.Initialize(
capabilities.Config{
Bypass: bypass,
},
)
if err != nil {
return err
}
Expand Down
89 changes: 63 additions & 26 deletions pkg/capabilities/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,20 @@ const (
)

type Capabilities struct {
have *cap.Set
all map[cap.Value]map[RingType]bool
bypass bool
lock *sync.Mutex // big lock to guarantee all threads are on the same ring
have *cap.Set
all map[cap.Value]map[RingType]bool
bypass bool
baseEbpf bool
lock *sync.Mutex // big lock to guarantee all threads are on the same ring
}

type Config struct {
Bypass bool
BaseEbpf bool
}

// Initialize initializes the "caps" instance (singleton).
func Initialize(bypass bool) error {
func Initialize(cfg Config) error {
var err error

once.Do(func() {
Expand All @@ -44,20 +50,23 @@ func Initialize(bypass bool) error {
caps.lock.Lock()
defer caps.lock.Unlock()

err = caps.initialize(bypass)
err = caps.initialize(cfg)
})

return errfmt.WrapError(err)
}

// GetInstance returns current "caps" instance. It initializes capabilities if
// needed, bypassing the privilege dropping by default.
// needed, bypassing the privilege dropping by default, and not adding eBPF to the base.
func GetInstance() *Capabilities {
capsMutex.Lock()
defer capsMutex.Unlock()

if caps == nil {
err := Initialize(true)
err := Initialize(Config{
Bypass: true,
BaseEbpf: false,
})
if err != nil {
return nil
}
Expand All @@ -66,12 +75,14 @@ func GetInstance() *Capabilities {
return caps
}

func (c *Capabilities) initialize(bypass bool) error {
if bypass {
func (c *Capabilities) initialize(cfg Config) error {
if cfg.Bypass {
c.bypass = true
return nil
}

c.baseEbpf = cfg.BaseEbpf

c.all = make(map[cap.Value]map[RingType]bool)

for v := cap.Value(0); v < cap.MaxBits(); v++ {
Expand Down Expand Up @@ -99,10 +110,17 @@ func (c *Capabilities) initialize(bypass bool) error {

// Add eBPF related capabilities to eBPF ring

err = c.eBPFRingAdd(
cap.IPC_LOCK,
cap.SYS_RESOURCE,
)
if c.baseEbpf {
err = c.baseRingAdd(
cap.IPC_LOCK,
cap.SYS_RESOURCE,
)
} else {
err = c.eBPFRingAdd(
cap.IPC_LOCK,
cap.SYS_RESOURCE,
)
}
if err != nil {
logger.Fatalw("Adding initial capabilities to EBPF ring", "error", err)
}
Expand Down Expand Up @@ -130,22 +148,41 @@ func (c *Capabilities) initialize(bypass bool) error {
hasBPF, _ := c.have.GetFlag(cap.Permitted, cap.BPF)
if hasBPF {
if paranoid < 2 {
err = c.eBPFRingAdd(
cap.BPF,
cap.PERFMON,
)
if c.baseEbpf {
err = c.baseRingAdd(
cap.BPF,
cap.PERFMON,
)
} else {
err = c.eBPFRingAdd(
cap.BPF,
cap.PERFMON,
)
}
} else {
err = c.eBPFRingAdd(
cap.SYS_ADMIN,
)
if c.baseEbpf {
err = c.baseRingAdd(
cap.SYS_ADMIN,
)
} else {
err = c.eBPFRingAdd(
cap.SYS_ADMIN,
)
}
}
if err != nil {
logger.Fatalw("Adding eBPF capabilities to EBPF ring", "error", err)
}
} else {
err = c.eBPFRingAdd(
cap.SYS_ADMIN,
)
if c.baseEbpf {
err = c.baseRingAdd(
cap.SYS_ADMIN,
)
} else {
err = c.eBPFRingAdd(
cap.SYS_ADMIN,
)
}
if err != nil {
logger.Fatalw("Adding eBPF capabilities to EBPF ring", "error", err)
}
Expand Down Expand Up @@ -187,7 +224,7 @@ func (c *Capabilities) EBPF(cb func() error) error {
c.lock.Lock()
defer c.lock.Unlock()

if !c.bypass {
if !c.bypass && !c.baseEbpf {
err = c.apply(EBPF) // move to ring EBPF
if err != nil {
return errfmt.WrapError(err)
Expand All @@ -196,7 +233,7 @@ func (c *Capabilities) EBPF(cb func() error) error {

errCb := cb() // callback

if !c.bypass {
if !c.bypass && !c.baseEbpf {
err = c.apply(Base) // back to ring Base
if err != nil {
return errfmt.WrapError(err)
Expand Down
4 changes: 3 additions & 1 deletion pkg/capabilities/capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ func Test_Initialize_And_GetInstance_Concurrent(t *testing.T) {
go func() {
defer wg.Done()

err := Initialize(true)
err := Initialize(Config{
Bypass: true,
})
assert.NoError(t, err)
}()
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/containers/path_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,11 @@ func TestPathResolver_ResolveAbsolutePath(t *testing.T) {
testMntNS := 1
testFilePath := "/tmp/tmp.so"

err := capabilities.Initialize(true) // initialize capabilities
err := capabilities.Initialize(
capabilities.Config{
Bypass: true,
},
) // initialize capabilities
assert.NoError(t, err)

for _, testCase := range testCases {
Expand Down
11 changes: 10 additions & 1 deletion pkg/ebpf/tracee.go
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,16 @@ func New(cfg config.Config) (*Tracee, error) {

// Initialize capabilities rings soon

err = capabilities.Initialize(t.config.Capabilities.BypassCaps)
useBaseEbpf := func(cfg config.Config) bool {
return cfg.Output.StackAddresses
}

err = capabilities.Initialize(
capabilities.Config{
Bypass: t.config.Capabilities.BypassCaps,
BaseEbpf: useBaseEbpf(t.config),
},
)
if err != nil {
return t, errfmt.WrapError(err)
}
Expand Down

0 comments on commit 5c09519

Please sign in to comment.