From 3b7798250a2d73fa47a9d87f0fc877a8e1025641 Mon Sep 17 00:00:00 2001 From: Djalal Harouni Date: Mon, 22 Jan 2024 13:51:50 +0100 Subject: [PATCH] test: fix test on old kernels Signed-off-by: Djalal Harouni --- pkg/reader/caps/caps.go | 49 +++++++++++++++++++++++++++++- pkg/reader/caps/caps_test.go | 7 +++++ pkg/sensors/tracing/kprobe_test.go | 30 +++++++++++++----- 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/pkg/reader/caps/caps.go b/pkg/reader/caps/caps.go index 35330408d20..382db8738b6 100644 --- a/pkg/reader/caps/caps.go +++ b/pkg/reader/caps/caps.go @@ -9,6 +9,7 @@ import ( "path/filepath" "strconv" "strings" + "sync" "github.com/cilium/tetragon/api/v1/tetragon" "github.com/cilium/tetragon/pkg/api/processapi" @@ -18,6 +19,30 @@ import ( "golang.org/x/sys/unix" ) +var ( + // Set default last capability based on upstream unix go library + cap_last_cap = int32(unix.CAP_LAST_CAP) + lastCapOnce sync.Once +) + +// capLastCap() Returns unix.CAP_LAST_CAP unless the kernel +// defines another last cap which is the case for old kernels. +func capLastCap() int32 { + lastCapOnce.Do(func() { + cap := uint64(0) + d, err := os.ReadFile(filepath.Join(option.Config.ProcFS, "/sys/kernel/cap_last_cap")) + if err == nil { + cap, err = strconv.ParseUint(string(d), 10, 64) + } + if err != nil { + logger.GetLogger().WithError(err).Warnf("Could not detect cap_last_cap, using default '%d' as cap_last_cap", cap_last_cap) + return + } + cap_last_cap = int32(cap) + }) + return cap_last_cap +} + func isCapValid(capInt int32) bool { if capInt >= 0 && capInt <= unix.CAP_LAST_CAP { return true @@ -26,6 +51,29 @@ func isCapValid(capInt int32) bool { return false } +// CapsAreSubset() Checks if "a" is a subset of "set" +// Rerturns true if all "a" capabilities are also in "set", otherwise +// false. +func CapsAreSubset(a uint64, set uint64) bool { + return (!((a & ^uint64(set)) != 0)) +} + +// capToMask() returns the mask of the corresponding u32 +func capToMask(cap int32) uint32 { + return uint32(1 << ((cap) & 31)) +} + +// GetCapsFullSet() Returns up to date (go unix library) full set. +func GetCapsFullSet() uint64 { + // Get last u32 bits + caps := uint64(capToMask(capLastCap()+1)-1) << 32 + // Get first u32 bits + caps |= uint64(^uint32(0)) + + return caps +} + +/* uapi/linux/capability.h */ func GetCapability(capInt int32) (string, error) { if !isCapValid(capInt) { return "", fmt.Errorf("invalid capability value %d", capInt) @@ -53,7 +101,6 @@ func GetCapabilitiesHex(capInt uint64) string { return fmt.Sprintf("%016x", capInt) } -/* uapi/linux/capability.h */ var capabilitiesString = map[uint64]string{ /* In a system with the [_POSIX_CHOWN_RESTRICTED] option defined, this overrides the restriction of changing file ownership and group diff --git a/pkg/reader/caps/caps_test.go b/pkg/reader/caps/caps_test.go index 1535d8ef1e6..c3ed8095d0c 100644 --- a/pkg/reader/caps/caps_test.go +++ b/pkg/reader/caps/caps_test.go @@ -42,3 +42,10 @@ func TestGetCapability(t *testing.T) { assert.Error(t, err) assert.Empty(t, str) } + +func TestCapsAreSubset(t *testing.T) { + assert.Equal(t, true, CapsAreSubset(0x000001ffffffffff, 0x000001ffffffffff)) + assert.Equal(t, true, CapsAreSubset(0x000001fffffffffe, 0x000001ffffffffff)) + assert.Equal(t, false, CapsAreSubset(0x000001ffffffffff, 0x000001fffffffffe)) + assert.Equal(t, true, CapsAreSubset(0x0, 0x0)) +} diff --git a/pkg/sensors/tracing/kprobe_test.go b/pkg/sensors/tracing/kprobe_test.go index 918485b12b1..2c2f3bb4f89 100644 --- a/pkg/sensors/tracing/kprobe_test.go +++ b/pkg/sensors/tracing/kprobe_test.go @@ -6104,6 +6104,24 @@ spec: createCrdFile(t, tracingPolicy) + fullSet := caps.GetCapsFullSet() + firstChange := fullSet&0xffffffff00000000 | uint64(0xffdfffff) // Removes CAP_SYS_ADMIN + secondChange := fullSet&0xffffffff00000000 | uint64(0xffdffffe) // removes CAP_SYS_ADMIN and CAP_CHOWN + + _, currentPermitted, currentEffective, _ := caps.GetPIDCaps(filepath.Join(option.Config.ProcFS, fmt.Sprint(os.Getpid()), "status")) + + if currentPermitted == 0 || currentPermitted != currentEffective { + t.Skip("Skipping test since current Permitted or Effective capabilities are zero or do not match") + } + + // Now we ensure at least that we have the fullset active + if caps.CapsAreSubset(fullSet, currentPermitted) == false || + caps.CapsAreSubset(fullSet, currentEffective) == false { + // fullSet is not set in currentPermitted let's check the old fullset of old kernels + t.Skipf("Skipping test since current Permitted or Effective capabilities are not a full capabilities set %s - %s", + caps.GetCapabilitiesHex(currentPermitted), caps.GetCapabilitiesHex(currentEffective)) + } + obs, err := observertesthelper.GetDefaultObserverWithFile(t, ctx, testConfigFile, tus.Conf().TetragonLib, observertesthelper.WithMyPid()) if err != nil { t.Fatalf("GetDefaultObserverWithFile error: %s", err) @@ -6113,21 +6131,17 @@ spec: testSetCaps := testutils.RepoRootPath("contrib/tester-progs/change-capabilities") - fullSet := "000001ffffffffff" - firstChange := "000001ffffdfffff" // removes CAP_SYS_ADMIN - secondChange := "000001ffffdffffe" // removes CAP_SYS_ADMIN and CAP_CHOWN - kpCheckers1 := ec.NewProcessKprobeChecker(""). WithMessage(sm.Full("Process changed its capabilities with capset system call")). WithFunctionName(sm.Full("security_capset")). WithArgs(ec.NewKprobeArgumentListMatcher(). WithValues( // effective caps - ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(firstChange)), + ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(caps.GetCapabilitiesHex(firstChange))), // inheritable ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(fmt.Sprintf("%016x", 0))), // permitted - ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(fullSet)), + ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(caps.GetCapabilitiesHex(fullSet))), )) kpCheckers2 := ec.NewProcessKprobeChecker(""). @@ -6136,11 +6150,11 @@ spec: WithArgs(ec.NewKprobeArgumentListMatcher(). WithValues( // effective caps - ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(secondChange)), + ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(caps.GetCapabilitiesHex(secondChange))), // inheritable ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(fmt.Sprintf("%016x", 0))), // permitted - ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(fullSet)), + ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(caps.GetCapabilitiesHex(fullSet))), )) testCmd := exec.CommandContext(ctx, testSetCaps)