Skip to content

Commit

Permalink
Drop unrequired capabilities upon startup
Browse files Browse the repository at this point in the history
  • Loading branch information
AlonZivony authored and rafaeldtinoco committed Jun 6, 2022
1 parent 36a9acc commit bf0600a
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 10 deletions.
61 changes: 52 additions & 9 deletions cmd/tracee-ebpf/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@ func main() {
}
cfg.Output = &output

// environment capabilities

err = ensureCapabilities(OSInfo)
if err != nil {
return err
}

// kernel lockdown check
lockdown, err := helpers.Lockdown()
if err == nil && lockdown == helpers.CONFIDENTIALITY {
Expand All @@ -153,15 +160,6 @@ func main() {
fmt.Fprintf(os.Stdout, "OSInfo: Security Lockdown is '%v'\n", lockdown)
}

// environment capabilities
selfCap, err := capabilities.Self()
if err != nil {
return err
}
if err = capabilities.CheckRequired(selfCap, []capability.Cap{capability.CAP_IPC_LOCK, capability.CAP_SYS_ADMIN}); err != nil {
return err
}

enabled, err := helpers.FtraceEnabled()
if err != nil {
return err
Expand Down Expand Up @@ -505,6 +503,51 @@ 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
21 changes: 21 additions & 0 deletions cmd/tracee-rules/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ import (
"strings"
"syscall"

"github.com/aquasecurity/tracee/pkg/capabilities"
"github.com/aquasecurity/tracee/pkg/rules/engine"
"github.com/aquasecurity/tracee/pkg/rules/metrics"
"github.com/aquasecurity/tracee/types/detect"
"github.com/open-policy-agent/opa/compile"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/syndtr/gocapability/capability"
"github.com/urfave/cli/v2"
)

Expand All @@ -32,6 +34,10 @@ func main() {
Name: "tracee-rules",
Usage: "A rule engine for Runtime Security",
Action: func(c *cli.Context) error {
err := dropCapabilities()
if err != nil {
return err
}

if c.NumFlags() == 0 {
cli.ShowAppHelp(c)
Expand Down Expand Up @@ -267,3 +273,18 @@ func sigHandler() chan bool {
}()
return done
}

// dropCapabilities drop all capabilities from the process
// The function also tries to drop the capabilities bounding set, but it won't work if CAP_SETPCAP is not available.
func dropCapabilities() error {
selfCaps, err := capabilities.Self()
if err != nil {
return err
}

err = capabilities.DropUnrequired(selfCaps, []capability.Cap{})
if err != nil {
return err
}
return nil
}
3 changes: 3 additions & 0 deletions docs/install/prerequisites.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ capabilities:
* Load and Attach eBPF programs:
1. `CAP_BPF`+`CAP_PERFMON` for recent kernels (>=5.8)
2. or `CAP_SYS_ADMIN` for older kernels
* `CAP_SYS_PTRACE` (to collect information about processes upon startup)
* `CAP_NET_ADMIN` (to use tc for packets capture)
* `CAP_SETPCAP` (if given - used to reduce bounding set capabilities)
* `CAP_SYSLOG` (to access kernel symbols through /proc/kallsyms)
* On some environments (e.g. Ubuntu) `CAP_IPC_LOCK` might be required as well.

Expand Down
14 changes: 14 additions & 0 deletions pkg/capabilities/capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,17 @@ func Self() (capability.Capabilities, error) {
}
return selfCap, nil
}

// DropUnrequired requires that all capabilities are already set or available in permitted set.
// The function also tries to drop the capabilities bounding set, but it won't work if CAP_SETPCAP is not available.
func DropUnrequired(selfCaps capability.Capabilities, reqCaps []capability.Cap) error {
selfCaps.Clear(capability.CAPS)
for _, rc := range reqCaps {
selfCaps.Set(capability.CAPS, rc)
}
err := selfCaps.Apply(capability.CAPS | capability.BOUNDS)
if err != nil {
return fmt.Errorf("couldn't drop capabilities: %v", err)
}
return nil
}
62 changes: 61 additions & 1 deletion pkg/capabilities/capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ type fakeCapability struct {

newpid2 func(int) (capability.Capabilities, error)
get func(capability.CapType, capability.Cap) bool
set func(capability.CapType, ...capability.Cap)
apply func(capability.CapType) error
clear func(capability.CapType)
load func() error
}

Expand All @@ -24,6 +27,25 @@ func (f fakeCapability) Get(which capability.CapType, what capability.Cap) bool
return true
}

func (f fakeCapability) Set(which capability.CapType, caps ...capability.Cap) {
if f.set != nil {
f.set(which, caps...)
}
}

func (f fakeCapability) Apply(kind capability.CapType) error {
if f.apply != nil {
return f.apply(kind)
}
return nil
}

func (f fakeCapability) Clear(kind capability.CapType) {
if f.clear != nil {
f.clear(kind)
}
}

func (f fakeCapability) Load() error {
if f.load != nil {
return f.load()
Expand All @@ -40,7 +62,7 @@ func (f fakeCapability) NewPid2(pid int) (capability.Capabilities, error) {

func TestCheckRequiredCapabilities(t *testing.T) {
t.Run("happy path", func(t *testing.T) {
require.NoError(t, CheckRequired(fakeCapability{}, []capability.Cap{capability.CAP_SYS_ADMIN, capability.CAP_IPC_LOCK}))
require.NoError(t, CheckRequired(fakeCapability{}, []capability.Cap{capability.CAP_SYS_ADMIN, capability.CAP_IPC_LOCK, capability.CAP_SYS_PTRACE}))
})

t.Run("missing CAP_SYS_ADMIN", func(t *testing.T) {
Expand Down Expand Up @@ -106,3 +128,41 @@ func TestLoadSelfCapabilities(t *testing.T) {
require.Nil(t, sc)
})
}

func TestDropUnrequired(t *testing.T) {
requiredCaps := []capability.Cap{capability.CAP_SYS_ADMIN, capability.CAP_IPC_LOCK, capability.CAP_SYS_PTRACE, capability.CAP_SYS_RESOURCE}
t.Run("happy path", func(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)
}
},
apply: func(kind capability.CapType) error {
for _, reqCap := range requiredCaps {
isFound := false
for _, setCap := range setCaps {
if reqCap == setCap {
isFound = true
break
}
}
if isFound == false {
return fmt.Errorf("capability %s was not set but was required", reqCap)
}
}
return nil
},
}
require.NoError(t, DropUnrequired(fc, requiredCaps))
})
t.Run("apply error invoked", func(t *testing.T) {
fc := fakeCapability{
apply: func(kind capability.CapType) error {
return fmt.Errorf("error in apply")
},
}
require.Error(t, DropUnrequired(fc, requiredCaps))
})
}

0 comments on commit bf0600a

Please sign in to comment.