Skip to content

Commit

Permalink
chore(feat): probe bpf helper improve
Browse files Browse the repository at this point in the history
- create type BPFFunc in libbpfgo;
- improve tests with permission;
- propage errors.
  • Loading branch information
rscampos committed Jul 11, 2024
1 parent 73f0dea commit c8be37c
Show file tree
Hide file tree
Showing 5 changed files with 837 additions and 32 deletions.
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ module github.com/aquasecurity/libbpfgo

go 1.21

require github.com/stretchr/testify v1.9.0
require (
github.com/stretchr/testify v1.9.0
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 // indirect
)
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70 h1:QnLPkuDWWbD5C+3DUA2IUXai5TK6w2zff+MAGccqdsw=
kernel.org/pub/linux/libs/security/libcap/cap v1.2.70/go.mod h1:/iBwcj9nbLejQitYvUm9caurITQ6WyNHibJk6Q9fiS4=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70 h1:HsB2G/rEQiYyo1bGoQqHZ/Bvd6x1rERQTNdPr1FyWjI=
kernel.org/pub/linux/libs/security/libcap/psx v1.2.70/go.mod h1:+l6Ee2F59XiJ2I6WR5ObpC1utCQJZ/VLsEbQCD8RG24=
180 changes: 154 additions & 26 deletions helpers_test.go
Original file line number Diff line number Diff line change
@@ -1,56 +1,184 @@
package libbpfgo

import (
"errors"
"fmt"
"syscall"
"testing"

"github.com/aquasecurity/libbpfgo/helpers"
"kernel.org/pub/linux/libs/security/libcap/cap"
)

func printCapabilities(capabilites *cap.Set) {
for capVal := cap.CHOWN; capVal < cap.MaxBits(); capVal++ {
okE, _ := capabilites.GetFlag(cap.Effective, capVal)
fmt.Println("E:", capVal, " ok:", okE)
okP, _ := capabilites.GetFlag(cap.Permitted, capVal)
fmt.Println("P:", capVal, " ok:", okP)
okI, _ := capabilites.GetFlag(cap.Inheritable, capVal)
fmt.Println("I:", capVal, " ok:", okI)
}
}

// Reset only effective capabilites
func resetEffectiveCapabilities() error {
// current capability
existing := cap.GetProc()

// Clear all effective capabilites
existing.ClearFlag(cap.Effective)

// set capability to current process
if err := existing.SetProc(); err != nil {

Check failure on line 32 in helpers_test.go

View workflow job for this annotation

GitHub Actions / Analyze Code

redundant if ...; err != nil check, just return error instead.
return err
}

return nil
}

// Enforce effective capabilites only
func enforceEffectiveCapabilities(newCap []string) error {
existing := cap.GetProc()

// create a new empty capabilities
enforce := cap.NewSet()

// copy all/only permitted flags to new cap
enforce.FillFlag(cap.Permitted, existing, cap.Permitted)

values := []cap.Value{}

for _, name := range newCap {
value, err := cap.FromName(name)
if err != nil {
return fmt.Errorf("error getting capability %q: %w", name, err)
}

values = append(values, value)
}

// only set the given effetive capabilities
err := enforce.SetFlag(cap.Effective, true, values...)
if err != nil {
return fmt.Errorf("error setting effective capabilities: %w", err)
}

err = enforce.SetProc()
if err != nil {
return fmt.Errorf("failed to drop capabilities: %q -> %q: %w", existing, enforce, err)
}

return nil
}

func TestFuncSupportbyType(t *testing.T) {

Check failure on line 74 in helpers_test.go

View workflow job for this annotation

GitHub Actions / Analyze Code

extra empty line at the start of a block

tt := []struct {
progType BPFProgType
funcId helpers.BPFFunc
supported bool
progType BPFProgType
funcId BPFFunc
supported bool
capability []string
errMsg error
}{
// func available but not enough permission (permission denied)
{
progType: BPFProgTypeKprobe,
funcId: BPFFuncGetCurrentUidGid,
supported: false,
capability: []string{},
errMsg: syscall.EPERM,
},
// func available and enough permission
{
progType: BPFProgTypeKprobe,
funcId: BPFFuncGetCurrentUidGid,
supported: true,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: nil,
},
// func unavailable and enough permission
// When the function is unavailable, BPF returns "Invalid Argument".
// Therefore, ignore the error and proceed with validation.
{
progType: BPFProgTypeSkLookup,
funcId: BPFFuncGetCurrentCgroupId,
supported: false,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: syscall.EINVAL,
},
{
progType: BPFProgTypeKprobe,
funcId: helpers.BPFFuncMapLookupElem,
supported: true,
progType: BPFProgTypeSkLookup,
funcId: BPFFuncGetCurrentCgroupId,
supported: false,
capability: []string{},
errMsg: syscall.EPERM,
},
{
progType: BPFProgTypeKprobe,
funcId: helpers.BPFFuncLoop,
supported: true,
progType: BPFProgTypeKprobe,
funcId: BPFFuncKtimeGetNs,
supported: true,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: nil,
},
{
progType: BPFProgTypeKprobe,
funcId: helpers.BPFFuncKtimeGetNs,
supported: true,
progType: BPFProgTypeKprobe,
funcId: BPFFuncKtimeGetNs,
supported: true,
capability: []string{"cap_sys_admin"},
errMsg: nil,
},
{
progType: BPFProgTypeKprobe,
funcId: helpers.BPFFuncSysBpf,
supported: false,
progType: BPFProgTypeKprobe,
funcId: BPFFuncSysBpf,
supported: false,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: syscall.EINVAL,
},
// Not able to probe helpers for some types (even with permission)
// https://github.com/libbpf/libbpf/blob/c1a6c770c46c6e78ad6755bf596c23a4e6f6b216/src/libbpf_probes.c#L430-L441
{
progType: BPFProgTypeLsm,
funcId: helpers.BPFFuncGetCurrentCgroupId,
supported: false,
progType: BPFProgTypeLsm,
funcId: BPFFuncGetCurrentCgroupId,
supported: false,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: syscall.EOPNOTSUPP,
},
{
progType: BPFProgTypeSkLookup,
funcId: helpers.BPFFuncGetCurrentCgroupId,
supported: true,
progType: BPFProgTypeLsm,
funcId: BPFFuncGetCurrentCgroupId,
supported: false,
capability: []string{},
errMsg: syscall.EOPNOTSUPP,
},
{
progType: BPFProgTypeKprobe,
funcId: helpers.BPFFuncSockMapUpdate,
supported: false,
progType: BPFProgTypeKprobe,
funcId: BPFFuncSockMapUpdate,
supported: false,
capability: []string{"cap_bpf", "cap_perfmon"},
errMsg: syscall.EINVAL,
},
}

for _, tc := range tt {
support, _ := BPFHelperIsSupported(tc.progType, tc.funcId)
// reset all current effective capabilities
resetEffectiveCapabilities()

if tc.capability != nil {
enforceEffectiveCapabilities(tc.capability)
}

support, err := BPFHelperIsSupported(tc.progType, tc.funcId)

if tc.errMsg == nil {
if err != nil {
t.Errorf("expected no error, got %v", err)
}
} else {
if !errors.Is(err, tc.errMsg) {
t.Errorf("expected error %v, got %v", tc.errMsg, err)
}
}

// This may fail if the bpf helper support for a specific program changes in future.
if support != tc.supported {
t.Errorf("expected %v, got %v", tc.supported, support)
Expand Down
20 changes: 15 additions & 5 deletions libbpfgo.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package libbpfgo
/*
#cgo LDFLAGS: -lelf -lz
#include "libbpfgo.h"
static int get_errno() {
return errno;
}
*/
import "C"

import (
"fmt"
"syscall"

"github.com/aquasecurity/libbpfgo/helpers"
)

//
Expand Down Expand Up @@ -98,10 +100,18 @@ func BPFMapTypeIsSupported(mapType MapType) (bool, error) {
return supportedC == 1, nil
}

func BPFHelperIsSupported(progType BPFProgType, funcId helpers.BPFFunc) (bool, error) {
supportedC := C.libbpf_probe_bpf_helper(C.enum_bpf_prog_type(int(progType)), C.enum_bpf_func_id(int(funcId)), nil)
// Probe bpf helper func for a given program type
// Note: BPF returns "Invalid Argument" when helper function is unavailable, just ignore the error.
func BPFHelperIsSupported(progType BPFProgType, funcId BPFFunc) (bool, error) {
supportedC, errno := C.libbpf_probe_bpf_helper(C.enum_bpf_prog_type(int(progType)), C.enum_bpf_func_id(int(funcId)), nil)

if errno != nil {
return false, fmt.Errorf("bpf: operation failed for function `%s` with program type `%s`: %w", funcId, progType, errno)
}

// helper not supported
if supportedC < 0 {
return false, syscall.Errno(-supportedC)
return false, fmt.Errorf("lib: operation failed for function `%s` with program type `%s`: %w", funcId, progType, syscall.Errno(-supportedC))
}

return supportedC == 1, nil
Expand Down
Loading

0 comments on commit c8be37c

Please sign in to comment.