Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

tetragon: capset selector fix #2454

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion bpf/include/vmlinux.h
Original file line number Diff line number Diff line change
Expand Up @@ -4263,7 +4263,9 @@ struct kernel_cap_struct {
__u32 cap[2];
};

typedef struct kernel_cap_struct kernel_cap_t;
typedef struct kernel_cap_struct kernel_cap_t___old;

typedef struct { u64 val; } kernel_cap_t;

struct group_info;

Expand Down
18 changes: 17 additions & 1 deletion bpf/process/types/basic.h
Original file line number Diff line number Diff line change
Expand Up @@ -1583,6 +1583,20 @@ filter_32ty(struct selector_arg_filter *filter, char *args)
return 0;
}

static inline __attribute__((always_inline)) long
filter_kernel_cap(struct selector_arg_filter *filter, char *args)
{
__u64 val = 0;

if (bpf_core_field_exists(((kernel_cap_t *)0)->val)) {
kernel_cap_t *n = (kernel_cap_t *)args;
probe_read(&val, sizeof(__u64), _(&n->val));
return filter_64ty(filter, (char *)&val, false);
}

return 0;
}

static inline __attribute__((always_inline)) size_t type_to_min_size(int type,
int argm)
{
Expand Down Expand Up @@ -1828,11 +1842,13 @@ selector_arg_offset(__u8 *f, struct msg_generic_kprobe *e, __u32 selidx,
set32bit = e->sel.is32BitSyscall;
case s64_ty:
case u64_ty:
pass &= filter_64ty(filter, args, set32bit);
break;
case kernel_cap_ty:
case cap_inh_ty:
case cap_prm_ty:
case cap_eff_ty:
pass &= filter_64ty(filter, args, set32bit);
pass &= filter_kernel_cap(filter, args);
break;
case size_type:
case int_type:
Expand Down
4 changes: 2 additions & 2 deletions contrib/tester-progs/capabilities-tester.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ void clear_my_cap(cap_value_t c, cap_flag_t ty)
cap_t cap;
cap_value_t cap_list[CAP_LAST_CAP+1];

cap = cap_get_proc();
if (cap == NULL)
cap = cap_get_proc();
if (cap == NULL)
errExit("cap_get_proc");

cap_list[0] = c;
Expand Down
12 changes: 12 additions & 0 deletions pkg/selectors/kernel.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,12 @@ func writeMatchValuesInMap(k *KernelSelectorState, values []string, ty uint32, o
return fmt.Errorf("MatchArgs value %s invalid: %w", v, err)
}
binary.LittleEndian.PutUint64(val[:], uint64(i))
case gt.GenericKernelCap, gt.GenericCapInheritable, gt.GenericCapPermitted, gt.GenericCapEffective:
i, err := strconv.ParseUint(v, 0, 64)
if err != nil {
return fmt.Errorf("MatchArgs value %s invalid: %w", v, err)
}
WriteSelectorUint64(&k.data, uint64(i))
default:
return fmt.Errorf("Unknown type: %d", ty)
}
Expand Down Expand Up @@ -643,6 +649,12 @@ func writeMatchValues(k *KernelSelectorState, values []string, ty, op uint32) er
return fmt.Errorf("MatchArgs type sock, skb and net_device do not support operator %s", selectorOpStringTable[op])
case gt.GenericCharIovec:
return fmt.Errorf("MatchArgs values %s unsupported", v)
case gt.GenericKernelCap, gt.GenericCapInheritable, gt.GenericCapPermitted, gt.GenericCapEffective:
i, err := strconv.ParseUint(v, 0, 64)
if err != nil {
return fmt.Errorf("MatchArgs value %s invalid: %w", v, err)
}
WriteSelectorUint64(&k.data, uint64(i))
}
}
return nil
Expand Down
75 changes: 43 additions & 32 deletions pkg/sensors/tracing/kprobe_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6553,6 +6553,29 @@ func TestProcessSetCap(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), tus.Conf().CmdWaitTime)
defer cancel()

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, currentInheritable := caps.GetPIDCaps(filepath.Join(option.Config.ProcFS, fmt.Sprint(os.Getpid()), "status"))
matchedInheritable := fullSet & currentInheritable

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 full capabilities set active
if caps.AreSubset(fullSet, currentPermitted) == false ||
caps.AreSubset(fullSet, currentEffective) == false {
// full capabilities set is not set in current permitted
t.Skipf("Skipping test since current Permitted or Effective capabilities are not a full capabilities set %s - %s",
caps.GetCapabilitiesHex(currentPermitted), caps.GetCapabilitiesHex(currentEffective))
}

strEffective := caps.GetCapabilitiesHex(firstChange)
strInheritable := caps.GetCapabilitiesHex(matchedInheritable)
strFullSet := caps.GetCapabilitiesHex(fullSet)

tracingPolicy := `
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
Expand All @@ -6579,36 +6602,23 @@ spec:
selectors:
- matchArgs:
- index: 2
operator: "NotEqual"
# Ensure we match the mask fullSet&0xffffffff00000000 | uint64(0xffdffffe) -> removes CAP_SYS_ADMIN and CAP_CHOWN
operator: "Mask"
values:
- "0"
- matchArgs:
- ` + fmt.Sprintf("0x%s", caps.GetCapabilitiesHex(secondChange)) + `
- index: 3
# Ensure we match the cleared inheritable set
operator: "Equal"
values:
- ` + fmt.Sprintf("0x%s", strInheritable) + `
- index: 4
operator: "NotEqual"
# Ensure we match the full capability set
operator: "Equal"
values:
- "0"
- ` + fmt.Sprintf("0x%s", strFullSet) + `
`

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 full capabilities set active
if caps.AreSubset(fullSet, currentPermitted) == false ||
caps.AreSubset(fullSet, currentEffective) == false {
// full capabilities set is not set in current permitted
t.Skipf("Skipping test since current Permitted or Effective capabilities are not a full capabilities set %s - %s",
caps.GetCapabilitiesHex(currentPermitted), caps.GetCapabilitiesHex(currentEffective))
}

lastCap, _ := caps.GetCapability(caps.GetLastCap())
t.Logf("Test %s running with last capability:%d %s", t.Name(), caps.GetLastCap(), lastCap)
t.Logf("Test %s running with cap_permitted:%s - cap_effective:%s",
Expand All @@ -6624,33 +6634,34 @@ spec:
testSetCaps := testutils.RepoRootPath("contrib/tester-progs/change-capabilities")

t.Logf("Test %s Matching cap_permitted:%s - cap_inheritable:%s - cap_effective:%s",
t.Name(), caps.GetCapabilitiesHex(fullSet), fmt.Sprintf("%016x", 0), caps.GetCapabilitiesHex(firstChange))
t.Name(), strFullSet, strInheritable, strEffective)
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(caps.GetCapabilitiesHex(firstChange))),
ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(strEffective)),
// inheritable
ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(fmt.Sprintf("%016x", 0))),
ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(strInheritable)),
// permitted
ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(caps.GetCapabilitiesHex(fullSet))),
ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(strFullSet)),
))

strEffective = caps.GetCapabilitiesHex(secondChange)
t.Logf("Test %s Matching cap_permitted:%s - cap_inheritable:%s - cap_effective:%s",
t.Name(), caps.GetCapabilitiesHex(fullSet), fmt.Sprintf("%016x", 0), caps.GetCapabilitiesHex(secondChange))
t.Name(), strFullSet, caps.GetCapabilitiesHex(matchedInheritable), strEffective)
kpCheckers2 := 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(caps.GetCapabilitiesHex(secondChange))),
ec.NewKprobeArgumentChecker().WithCapEffectiveArg(sm.Full(strEffective)),
// inheritable
ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(fmt.Sprintf("%016x", 0))),
ec.NewKprobeArgumentChecker().WithCapInheritableArg(sm.Full(strInheritable)),
// permitted
ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(caps.GetCapabilitiesHex(fullSet))),
ec.NewKprobeArgumentChecker().WithCapPermittedArg(sm.Full(strFullSet)),
))

testCmd := exec.CommandContext(ctx, testSetCaps)
Expand Down
Loading