From 4d8ec5e74a301e38f56a3e642d8d879b8665203c Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Fri, 18 Nov 2022 19:10:25 +0100 Subject: [PATCH 1/8] [CWS] add discarder retention ut --- go.mod | 2 + go.sum | 2 + pkg/ebpf/bytecode/runtime/runtime-security.go | 2 +- pkg/security/ebpf/c/baloum.h | 78 +++++++++++++++++++ pkg/security/ebpf/c/dentry_resolver.h | 1 + pkg/security/ebpf/c/discarders_test.h | 71 +++++++++++++++++ pkg/security/ebpf/c/prebuilt/probe.c | 5 ++ pkg/security/ebpf/c/tests.h | 6 ++ pkg/security/ebpf/loader.go | 13 ++++ pkg/security/ebpf/tests/discarders_test.go | 63 +++++++++++++++ pkg/security/probe/probe.go | 12 +-- tasks/system_probe.py | 6 +- 12 files changed, 247 insertions(+), 14 deletions(-) create mode 100644 pkg/security/ebpf/c/baloum.h create mode 100644 pkg/security/ebpf/c/discarders_test.h create mode 100644 pkg/security/ebpf/c/tests.h create mode 100644 pkg/security/ebpf/tests/discarders_test.go diff --git a/go.mod b/go.mod index 61bedb314574d2..91d27874736d53 100644 --- a/go.mod +++ b/go.mod @@ -452,6 +452,8 @@ require ( mellium.im/sasl v0.3.1 // indirect ) +require github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4 + replace github.com/pahanini/go-grpc-bidirectional-streaming-example v0.0.0-20211027164128-cc6111af44be => github.com/DataDog/go-grpc-bidirectional-streaming-example v0.0.0-20221024060302-b9cf785c02fe // Fixing a CVE on a transitive dep of k8s/etcd, should be cleaned-up once k8s.io/apiserver dep is removed (but double-check with `go mod why` that no other dep pulls it) diff --git a/go.sum b/go.sum index 1e78a57659ba2c..9a5b1c32cc642a 100644 --- a/go.sum +++ b/go.sum @@ -1528,6 +1528,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= +github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4 h1:AUa6BxTG3rp0tQFCQ+cfeXJd9jwI2x62S9FaYsybXzU= +github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4/go.mod h1:1+GWOH32bsIEAHknYja6/H1efcDs+/Q2XrtYMM200Ho= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= diff --git a/pkg/ebpf/bytecode/runtime/runtime-security.go b/pkg/ebpf/bytecode/runtime/runtime-security.go index 1bdafbcecfafa3..b1e47ef992ec4c 100644 --- a/pkg/ebpf/bytecode/runtime/runtime-security.go +++ b/pkg/ebpf/bytecode/runtime/runtime-security.go @@ -4,4 +4,4 @@ package runtime -var RuntimeSecurity = newAsset("runtime-security.c", "5561235e358dae918931ad84862a6b6e92bed10c6d56a1b17137005d915e4dd2") +var RuntimeSecurity = newAsset("runtime-security.c", "371ee4726c29eb13a0eaddf72636bd72a28cebd88d537718f7ccd72d7e3d13b3") diff --git a/pkg/security/ebpf/c/baloum.h b/pkg/security/ebpf/c/baloum.h new file mode 100644 index 00000000000000..03cea27711f68b --- /dev/null +++ b/pkg/security/ebpf/c/baloum.h @@ -0,0 +1,78 @@ +#ifdef __BALOUM__ + +#ifndef _BALOUM_H__ +#define _BALOUM_H__ + +struct baloum_ctx +{ + __u64 arg0; + __u64 arg1; + __u64 arg2; + __u64 arg3; + __u64 arg4; +}; +static void *(*baloum_malloc)(__u32 size) = (void *)0xffff; +static int (*baloum_call)(struct baloum_ctx *ctx, const char *section) = (void *)0xfffe; +static int (*baloum_strcmp)(const char *s1, const char *s2) = (void *)0xfffd; +static int (*baloum_memcmp)(const void *b1, const void *b2, __u32 size) = (void *)0xfffc; +static int (*baloum_sleep)(__u64 ns) = (void *)0xfffb; + +#define assert_memcmp(b1, b2, s, msg) \ + if (baloum_memcmp(b1, b2, s) != 0) \ + { \ + bpf_printk("assert line %d : b1 != b2 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_strcmp(s1, s2, msg) \ + if (baloum_strcmp(s1, s2) != 0) \ + { \ + bpf_printk("assert line %d : s1 != s2 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_equals(v1, v2, msg) \ + if (v1 != v2) \ + { \ + bpf_printk("assert line %d : v1 != v2 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_zero(v1, msg) \ + if (v1 != 0) \ + { \ + bpf_printk("assert line %d : v1 == 0 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_not_zero(v1, msg) \ + if (v1 == 0) \ + { \ + bpf_printk("assert line %d : v1 != 0 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_not_equals(v1, v2, msg) \ + if (v1 == v2) \ + { \ + bpf_printk("assert line %d : v1 == v2 : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_not_null(v1, msg) \ + if (v1 == NULL) \ + { \ + bpf_printk("assert line %d : v1 == NULL : %s", __LINE__, msg); \ + return -1; \ + } + +#define assert_null(v1, msg) \ + if (v1 != NULL) \ + { \ + bpf_printk("assert line %d : v1 != NULL : %s", __LINE__, msg); \ + return -1; \ + } + +#endif + +#endif \ No newline at end of file diff --git a/pkg/security/ebpf/c/dentry_resolver.h b/pkg/security/ebpf/c/dentry_resolver.h index f58dec606cfbb6..4c8d41346ddd4d 100644 --- a/pkg/security/ebpf/c/dentry_resolver.h +++ b/pkg/security/ebpf/c/dentry_resolver.h @@ -125,6 +125,7 @@ int __attribute__((always_inline)) resolve_dentry_tail_call(void *ctx, struct de } *params = (struct is_discarded_by_inode_t){ .discarder_type = input->discarder_type, + // TODO(safchain) do we need the pid ????? .tgid = bpf_get_current_pid_tgid() >> 32, .now = bpf_ktime_get_ns(), .ad_state = input->ad_state, diff --git a/pkg/security/ebpf/c/discarders_test.h b/pkg/security/ebpf/c/discarders_test.h new file mode 100644 index 00000000000000..a8f5ffe76927d0 --- /dev/null +++ b/pkg/security/ebpf/c/discarders_test.h @@ -0,0 +1,71 @@ +#ifndef _DISCARDERS_TEST_H +#define _DISCARDERS_TEST_H + +#include "defs.h" +#include "discarders.h" +#include "baloum.h" + +SEC("test/discarders_retention") +int test_discarders_retention() +{ + u32 mount_id = 123; + u64 inode = 456; + + int ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + struct inode_discarder_t key = { + .path_key = { + .ino = inode, + .mount_id = mount_id, + } + }; + + struct inode_discarder_params_t *inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + assert_not_null(inode_params, "unable to find the inode discarder entry"); + + struct is_discarded_by_inode_t params = { + .discarder_type = EVENT_OPEN, + .discarder = { + .path_key.ino = inode, + .path_key.mount_id = mount_id, + } + }; + + ret = is_discarded_by_inode(¶ms); + assert_not_zero(ret, "inode should be discarded"); + + // expire the discarder + expire_inode_discarders(mount_id, inode); + + // the entry should still be there + inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + assert_not_null(inode_params, "unable to find the inode discarder entry"); + + // but should be discarded anymore + ret = is_discarded_by_inode(¶ms); + assert_zero(ret, "inode shouldn't be discarded"); + + // we shouldn't be able to add a new discarder for the same inode during the retention period + // TODO(safchain) should return an error value + ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + // shouldn't still be discarded + ret = is_discarded_by_inode(¶ms); + assert_zero(ret, "inode shouldn't be discarded"); + + // wait the retention period + baloum_sleep(get_discarder_retention() + 1); + + // the retention period is now over, we should be able to add a discarder + ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = is_discarded_by_inode(¶ms); + assert_not_zero(ret, "inode should be discarded"); + + return 0; +} + +#endif \ No newline at end of file diff --git a/pkg/security/ebpf/c/prebuilt/probe.c b/pkg/security/ebpf/c/prebuilt/probe.c index 8dafff3b5f3c44..b6087dd65f0d7a 100644 --- a/pkg/security/ebpf/c/prebuilt/probe.c +++ b/pkg/security/ebpf/c/prebuilt/probe.c @@ -105,6 +105,11 @@ void __attribute__((always_inline)) invalidate_inode(struct pt_regs *ctx, u32 mo } } +// unit tests +#ifdef __BALOUM__ +#include "tests.h" +#endif + __u32 _version SEC("version") = 0xFFFFFFFE; char LICENSE[] SEC("license") = "GPL"; diff --git a/pkg/security/ebpf/c/tests.h b/pkg/security/ebpf/c/tests.h new file mode 100644 index 00000000000000..e6af9b1fb83e2a --- /dev/null +++ b/pkg/security/ebpf/c/tests.h @@ -0,0 +1,6 @@ +#ifndef _TESTS_H +#define _TESTS_H + +#include "discarders_test.h" + +#endif \ No newline at end of file diff --git a/pkg/security/ebpf/loader.go b/pkg/security/ebpf/loader.go index 9cef286ce22680..8f4d94aff46581 100644 --- a/pkg/security/ebpf/loader.go +++ b/pkg/security/ebpf/loader.go @@ -9,10 +9,13 @@ package ebpf import ( + "strings" + "github.com/DataDog/datadog-agent/pkg/ebpf/bytecode" "github.com/DataDog/datadog-agent/pkg/security/config" "github.com/DataDog/datadog-agent/pkg/security/seclog" "github.com/DataDog/datadog-go/v5/statsd" + manager "github.com/DataDog/ebpf-manager" ) // ProbeLoader defines an eBPF ProbeLoader @@ -95,3 +98,13 @@ func (l *OffsetGuesserLoader) Close() error { func (l *OffsetGuesserLoader) Load() (bytecode.AssetReader, error) { return bytecode.GetReader(l.config.BPFDir, "runtime-security-offset-guesser.o") } + +// IsSyscallWrapperRequired checks whether the wrapper is required +func IsSyscallWrapperRequired() (bool, error) { + openSyscall, err := manager.GetSyscallFnName("open") + if err != nil { + return false, err + } + + return !strings.HasPrefix(openSyscall, "SyS_") && !strings.HasPrefix(openSyscall, "sys_"), nil +} diff --git a/pkg/security/ebpf/tests/discarders_test.go b/pkg/security/ebpf/tests/discarders_test.go new file mode 100644 index 00000000000000..53b39c3e733be2 --- /dev/null +++ b/pkg/security/ebpf/tests/discarders_test.go @@ -0,0 +1,63 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build linux && ebpf_bindata +// +build linux,ebpf_bindata + +package tests + +import ( + "testing" + + "github.com/DataDog/datadog-agent/pkg/security/config" + secebpf "github.com/DataDog/datadog-agent/pkg/security/ebpf" + "github.com/DataDog/datadog-go/v5/statsd" + "github.com/cilium/ebpf" + "github.com/safchain/baloum/pkg/baloum" + "go.uber.org/zap" +) + +func TestDiscarderRetention(t *testing.T) { + logger, _ := zap.NewDevelopment() + defer logger.Sync() + + suggar := logger.Sugar() + + useSyscallWrapper, err := secebpf.IsSyscallWrapperRequired() + if err != nil { + t.Fatal(err) + } + + loader := secebpf.NewProbeLoader(&config.Config{}, useSyscallWrapper, &statsd.NoOpClient{}) + reader, _, err := loader.Load() + if err != nil { + t.Fatal(err) + } + + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + suggar.Fatal(err) + } + + tgid := uint64(33) + + fncs := baloum.Fncs{ + GetCurrentPidTgid: func(vm *baloum.VM) (uint64, error) { + return tgid, nil + }, + TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) { + suggar.Debugf(format, args...) + }, + } + + vm := baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: suggar}) + + var ctx baloum.Context + + code, err := vm.RunProgram(ctx, "test/discarders_retention") + if err != nil || code != 0 { + suggar.Fatalf("unexpected error: %v, %d", err, code) + } +} diff --git a/pkg/security/probe/probe.go b/pkg/security/probe/probe.go index 22239b611ca79b..d31e4a4d3ae7b6 100644 --- a/pkg/security/probe/probe.go +++ b/pkg/security/probe/probe.go @@ -15,7 +15,6 @@ import ( "os" "path/filepath" "runtime" - "strings" "sync" "time" @@ -221,20 +220,11 @@ func (p *Probe) VerifyEnvironment() *multierror.Error { return err } -func isSyscallWrapperRequired() (bool, error) { - openSyscall, err := manager.GetSyscallFnName("open") - if err != nil { - return false, err - } - - return !strings.HasPrefix(openSyscall, "SyS_") && !strings.HasPrefix(openSyscall, "sys_"), nil -} - // Init initializes the probe func (p *Probe) Init() error { p.startTime = time.Now() - useSyscallWrapper, err := isSyscallWrapperRequired() + useSyscallWrapper, err := ebpf.IsSyscallWrapperRequired() if err != nil { return err } diff --git a/tasks/system_probe.py b/tasks/system_probe.py index d4f45217623539..1a7f0611f1358d 100644 --- a/tasks/system_probe.py +++ b/tasks/system_probe.py @@ -68,7 +68,7 @@ def ninja_define_windows_resources(ctx, nw, major_version): def ninja_define_ebpf_compiler(nw, strip_object_files=False, kernel_release=None): nw.variable("target", "-emit-llvm") - nw.variable("ebpfflags", get_ebpf_build_flags()) + nw.variable("ebpfflags", get_ebpf_build_flags(True)) nw.variable("kheaders", get_kernel_headers_flags(kernel_release)) nw.rule( @@ -893,7 +893,7 @@ def get_linux_header_dirs(kernel_release=None, minimal_kernel_release=None): return dirs -def get_ebpf_build_flags(): +def get_ebpf_build_flags(test=False): flags = [] flags.extend( [ @@ -904,6 +904,8 @@ def get_ebpf_build_flags(): '-DCOMPILE_PREBUILT', ] ) + if test: + flags.extend(['-D__BALOUM__']) flags.extend( [ '-Wno-unused-value', From 5abb60d8c9712a4452267da70be2e3d9cb016274 Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Wed, 23 Nov 2022 20:38:02 +0100 Subject: [PATCH 2/8] add another test --- pkg/ebpf/bytecode/runtime/runtime-security.go | 2 +- pkg/security/ebpf/c/discarders.h | 4 +- pkg/security/ebpf/c/discarders_test.h | 58 ++++++++++++++++++ pkg/security/ebpf/tests/discarders_test.go | 61 ++++++++++++++----- tasks/system_probe.py | 25 +++++--- 5 files changed, 122 insertions(+), 28 deletions(-) diff --git a/pkg/ebpf/bytecode/runtime/runtime-security.go b/pkg/ebpf/bytecode/runtime/runtime-security.go index b1e47ef992ec4c..f8265109a3786b 100644 --- a/pkg/ebpf/bytecode/runtime/runtime-security.go +++ b/pkg/ebpf/bytecode/runtime/runtime-security.go @@ -4,4 +4,4 @@ package runtime -var RuntimeSecurity = newAsset("runtime-security.c", "371ee4726c29eb13a0eaddf72636bd72a28cebd88d537718f7ccd72d7e3d13b3") +var RuntimeSecurity = newAsset("runtime-security.c", "5d0851c4f57bf54d9e4053d970d6f955f37bacb3ad3a50f12d389d507a9aac8a") diff --git a/pkg/security/ebpf/c/discarders.h b/pkg/security/ebpf/c/discarders.h index 0800cfe7e568ce..8ecb928fc0cfba 100644 --- a/pkg/security/ebpf/c/discarders.h +++ b/pkg/security/ebpf/c/discarders.h @@ -43,9 +43,9 @@ struct bpf_map_def SEC("maps/discarders_revision") discarders_revision = { }; u64 __attribute__((always_inline)) get_discarder_retention() { - u64 retention; + u64 retention = 0; LOAD_CONSTANT("discarder_retention", retention); - return retention; + return retention ? retention : SEC_TO_NS(5); } int __attribute__((always_inline)) monitor_discarder_added(u64 event_type) { diff --git a/pkg/security/ebpf/c/discarders_test.h b/pkg/security/ebpf/c/discarders_test.h index a8f5ffe76927d0..e5d11ee6b47b22 100644 --- a/pkg/security/ebpf/c/discarders_test.h +++ b/pkg/security/ebpf/c/discarders_test.h @@ -5,6 +5,64 @@ #include "discarders.h" #include "baloum.h" +SEC("test/discarders_event_mask") +int test_discarders_event_mask() +{ + u32 mount_id = 123; + u64 inode = 456; + + int ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + struct inode_discarder_t key = { + .path_key = { + .ino = inode, + .mount_id = mount_id, + } + }; + + struct inode_discarder_params_t *inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + assert_not_null(inode_params, "unable to find the inode discarder entry"); + + ret = mask_has_event(inode_params->params.event_mask, EVENT_OPEN); + assert_not_zero(ret, "event not found in mask"); + + struct is_discarded_by_inode_t params = { + .discarder_type = EVENT_OPEN, + .discarder = { + .path_key.ino = inode, + .path_key.mount_id = mount_id, + } + }; + + ret = is_discarded_by_inode(¶ms); + assert_not_zero(ret, "inode should be discarded"); + + // add another event type + ret = discard_inode(EVENT_CHMOD, mount_id, inode, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + // check that we have now both open and chmod event discarded + inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + assert_not_null(inode_params, "unable to find the inode discarder entry"); + + ret = mask_has_event(inode_params->params.event_mask, EVENT_OPEN); + assert_not_zero(ret, "event not found in mask"); + + ret = mask_has_event(inode_params->params.event_mask, EVENT_CHMOD); + assert_not_zero(ret, "event not found in mask"); + + ret = is_discarded_by_inode(¶ms); + assert_not_zero(ret, "inode should be discarded"); + + params.discarder_type = EVENT_CHMOD; + + ret = is_discarded_by_inode(¶ms); + assert_not_zero(ret, "inode should be discarded"); + + return 0; +} + SEC("test/discarders_retention") int test_discarders_retention() { diff --git a/pkg/security/ebpf/tests/discarders_test.go b/pkg/security/ebpf/tests/discarders_test.go index 53b39c3e733be2..ab34ec7ee3a958 100644 --- a/pkg/security/ebpf/tests/discarders_test.go +++ b/pkg/security/ebpf/tests/discarders_test.go @@ -16,15 +16,37 @@ import ( "github.com/DataDog/datadog-go/v5/statsd" "github.com/cilium/ebpf" "github.com/safchain/baloum/pkg/baloum" - "go.uber.org/zap" ) -func TestDiscarderRetention(t *testing.T) { - logger, _ := zap.NewDevelopment() - defer logger.Sync() +type testLogger struct { + t *testing.T +} + +func (l *testLogger) Info(params ...interface{}) { + l.t.Log(params...) +} + +func (l *testLogger) Infof(format string, params ...interface{}) { + l.t.Logf(format, params...) +} + +func (l *testLogger) Debug(params ...interface{}) { + l.t.Log(params...) +} + +func (l *testLogger) Debugf(format string, params ...interface{}) { + l.t.Logf(format, params...) +} + +func (l *testLogger) Error(params ...interface{}) { + l.t.Error(params...) +} - suggar := logger.Sugar() +func (l *testLogger) Errorf(format string, params ...interface{}) { + l.t.Errorf(format, params...) +} +func newVM(t *testing.T) *baloum.VM { useSyscallWrapper, err := secebpf.IsSyscallWrapperRequired() if err != nil { t.Fatal(err) @@ -38,26 +60,33 @@ func TestDiscarderRetention(t *testing.T) { spec, err := ebpf.LoadCollectionSpecFromReader(reader) if err != nil { - suggar.Fatal(err) + t.Fatal(err) } - tgid := uint64(33) - fncs := baloum.Fncs{ - GetCurrentPidTgid: func(vm *baloum.VM) (uint64, error) { - return tgid, nil - }, - TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) { - suggar.Debugf(format, args...) + TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) error { + t.Logf(format, args...) + return nil }, } - vm := baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: suggar}) + return baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: &testLogger{t: t}}) +} + +func TestDiscarderEventMask(t *testing.T) { + var ctx baloum.Context + + code, err := newVM(t).RunProgram(ctx, "test/discarders_event_mask") + if err != nil || code != 0 { + t.Errorf("unexpected error: %v, %d", err, code) + } +} +func TestDiscarderRetention(t *testing.T) { var ctx baloum.Context - code, err := vm.RunProgram(ctx, "test/discarders_retention") + code, err := newVM(t).RunProgram(ctx, "test/discarders_retention") if err != nil || code != 0 { - suggar.Fatalf("unexpected error: %v, %d", err, code) + t.Errorf("unexpected error: %v, %d", err, code) } } diff --git a/tasks/system_probe.py b/tasks/system_probe.py index 1a7f0611f1358d..3eeb91c63bd45c 100644 --- a/tasks/system_probe.py +++ b/tasks/system_probe.py @@ -66,9 +66,9 @@ def ninja_define_windows_resources(ctx, nw, major_version): ) -def ninja_define_ebpf_compiler(nw, strip_object_files=False, kernel_release=None): +def ninja_define_ebpf_compiler(nw, strip_object_files=False, kernel_release=None, with_unit_test=False): nw.variable("target", "-emit-llvm") - nw.variable("ebpfflags", get_ebpf_build_flags(True)) + nw.variable("ebpfflags", get_ebpf_build_flags(with_unit_test)) nw.variable("kheaders", get_kernel_headers_flags(kernel_release)) nw.rule( @@ -353,6 +353,7 @@ def ninja_generate( debug=False, strip_object_files=False, kernel_release=None, + with_unit_test=False, ): build_dir = os.path.join("pkg", "ebpf", "bytecode", "build") co_re_build_dir = os.path.join(build_dir, "co-re") @@ -371,7 +372,7 @@ def ninja_generate( nw.build(inputs=[in_path], outputs=[rcout], rule="windmc", variables={"rcdir": in_dir}) nw.build(inputs=[rcout], outputs=["cmd/system-probe/rsrc.syso"], rule="windres") else: - ninja_define_ebpf_compiler(nw, strip_object_files, kernel_release) + ninja_define_ebpf_compiler(nw, strip_object_files, kernel_release, with_unit_test) ninja_define_co_re_compiler(nw) ninja_network_ebpf_programs(nw, build_dir, co_re_build_dir) ninja_security_ebpf_programs(nw, build_dir, debug, kernel_release) @@ -397,6 +398,7 @@ def build( debug=False, strip_object_files=False, strip_binary=False, + with_unit_test=False ): """ Build the system-probe @@ -409,6 +411,7 @@ def build( kernel_release=kernel_release, debug=debug, strip_object_files=strip_object_files, + with_unit_test=with_unit_test, ) build_sysprobe_binary( @@ -893,7 +896,7 @@ def get_linux_header_dirs(kernel_release=None, minimal_kernel_release=None): return dirs -def get_ebpf_build_flags(test=False): +def get_ebpf_build_flags(unit_test=False): flags = [] flags.extend( [ @@ -904,7 +907,7 @@ def get_ebpf_build_flags(test=False): '-DCOMPILE_PREBUILT', ] ) - if test: + if unit_test: flags.extend(['-D__BALOUM__']) flags.extend( [ @@ -984,10 +987,11 @@ def run_ninja( kernel_release=None, debug=False, strip_object_files=False, + with_unit_test=False, ): check_for_ninja(ctx) nf_path = os.path.join(ctx.cwd, 'system-probe.ninja') - ninja_generate(ctx, nf_path, windows, major_version, arch, debug, strip_object_files, kernel_release) + ninja_generate(ctx, nf_path, windows, major_version, arch, debug, strip_object_files, kernel_release, with_unit_test) explain_opt = "-d explain" if explain else "" if task: ctx.run(f"ninja {explain_opt} -f {nf_path} -t {task}") @@ -1044,6 +1048,7 @@ def build_object_files( kernel_release=None, debug=False, strip_object_files=False, + with_unit_test=False, ): build_dir = os.path.join("pkg", "ebpf", "bytecode", "build") @@ -1071,6 +1076,7 @@ def build_object_files( kernel_release=kernel_release, debug=debug, strip_object_files=strip_object_files, + with_unit_test=with_unit_test, ) if not windows: @@ -1082,7 +1088,7 @@ def build_object_files( def build_cws_object_files( - ctx, major_version='7', arch=CURRENT_ARCH, kernel_release=None, debug=False, strip_object_files=False + ctx, major_version='7', arch=CURRENT_ARCH, kernel_release=None, debug=False, strip_object_files=False, with_unit_test=False ): run_ninja( ctx, @@ -1092,12 +1098,13 @@ def build_cws_object_files( debug=debug, strip_object_files=strip_object_files, kernel_release=kernel_release, + unit_test=with_unit_test, ) @task -def object_files(ctx, kernel_release=None): - build_object_files(ctx, kernel_release=kernel_release) +def object_files(ctx, kernel_release=None, with_unit_test=False): + build_object_files(ctx, kernel_release=kernel_release, with_unit_test=with_unit_test) def clean_object_files( From c4f2f5ae48222f2517fd8396c73b8e9a467ab0e6 Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Wed, 23 Nov 2022 20:52:31 +0100 Subject: [PATCH 3/8] add a unit test task --- tasks/security_agent.py | 17 +++++++++++++++++ tasks/system_probe.py | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/tasks/security_agent.py b/tasks/security_agent.py index d290c8abd766b5..1112d6f6eaf214 100644 --- a/tasks/security_agent.py +++ b/tasks/security_agent.py @@ -707,3 +707,20 @@ def kitchen_prepare(ctx): ctx.run(f"mkdir -p {ebpf_runtime_dir}") ctx.run(f"cp {bytecode_build_dir}/runtime-security* {ebpf_bytecode_dir}") ctx.run(f"cp {bytecode_build_dir}/runtime/runtime-security* {ebpf_runtime_dir}") + + +@task +def run_ebpf_unit_tests(ctx, verbose=False): + build_cws_object_files( + ctx, + major_version='7', + arch=CURRENT_ARCH, + kernel_release=None, + with_unit_test=True, + ) + + flags = '-tags ebpf_bindata' + if verbose: + flags += " -test.v" + + ctx.run(f"go test {flags} ./pkg/security/ebpf/tests/...") diff --git a/tasks/system_probe.py b/tasks/system_probe.py index 3eeb91c63bd45c..6101943f7cd2f5 100644 --- a/tasks/system_probe.py +++ b/tasks/system_probe.py @@ -1098,7 +1098,7 @@ def build_cws_object_files( debug=debug, strip_object_files=strip_object_files, kernel_release=kernel_release, - unit_test=with_unit_test, + with_unit_test=with_unit_test, ) From e8b4e02ab565b7feca05b7a88d62573c04acb56e Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Thu, 24 Nov 2022 10:30:04 +0100 Subject: [PATCH 4/8] add trace param --- pkg/security/ebpf/tests/discarders_test.go | 61 --------------- pkg/security/ebpf/tests/helpers_test.go | 89 ++++++++++++++++++++++ tasks/security_agent.py | 8 +- 3 files changed, 95 insertions(+), 63 deletions(-) create mode 100644 pkg/security/ebpf/tests/helpers_test.go diff --git a/pkg/security/ebpf/tests/discarders_test.go b/pkg/security/ebpf/tests/discarders_test.go index ab34ec7ee3a958..a0e97fbcf48a22 100644 --- a/pkg/security/ebpf/tests/discarders_test.go +++ b/pkg/security/ebpf/tests/discarders_test.go @@ -11,71 +11,11 @@ package tests import ( "testing" - "github.com/DataDog/datadog-agent/pkg/security/config" - secebpf "github.com/DataDog/datadog-agent/pkg/security/ebpf" - "github.com/DataDog/datadog-go/v5/statsd" - "github.com/cilium/ebpf" "github.com/safchain/baloum/pkg/baloum" ) -type testLogger struct { - t *testing.T -} - -func (l *testLogger) Info(params ...interface{}) { - l.t.Log(params...) -} - -func (l *testLogger) Infof(format string, params ...interface{}) { - l.t.Logf(format, params...) -} - -func (l *testLogger) Debug(params ...interface{}) { - l.t.Log(params...) -} - -func (l *testLogger) Debugf(format string, params ...interface{}) { - l.t.Logf(format, params...) -} - -func (l *testLogger) Error(params ...interface{}) { - l.t.Error(params...) -} - -func (l *testLogger) Errorf(format string, params ...interface{}) { - l.t.Errorf(format, params...) -} - -func newVM(t *testing.T) *baloum.VM { - useSyscallWrapper, err := secebpf.IsSyscallWrapperRequired() - if err != nil { - t.Fatal(err) - } - - loader := secebpf.NewProbeLoader(&config.Config{}, useSyscallWrapper, &statsd.NoOpClient{}) - reader, _, err := loader.Load() - if err != nil { - t.Fatal(err) - } - - spec, err := ebpf.LoadCollectionSpecFromReader(reader) - if err != nil { - t.Fatal(err) - } - - fncs := baloum.Fncs{ - TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) error { - t.Logf(format, args...) - return nil - }, - } - - return baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: &testLogger{t: t}}) -} - func TestDiscarderEventMask(t *testing.T) { var ctx baloum.Context - code, err := newVM(t).RunProgram(ctx, "test/discarders_event_mask") if err != nil || code != 0 { t.Errorf("unexpected error: %v, %d", err, code) @@ -84,7 +24,6 @@ func TestDiscarderEventMask(t *testing.T) { func TestDiscarderRetention(t *testing.T) { var ctx baloum.Context - code, err := newVM(t).RunProgram(ctx, "test/discarders_retention") if err != nil || code != 0 { t.Errorf("unexpected error: %v, %d", err, code) diff --git a/pkg/security/ebpf/tests/helpers_test.go b/pkg/security/ebpf/tests/helpers_test.go new file mode 100644 index 00000000000000..85463abb66088f --- /dev/null +++ b/pkg/security/ebpf/tests/helpers_test.go @@ -0,0 +1,89 @@ +// Unless explicitly stated otherwise all files in this repository are licensed +// under the Apache License Version 2.0. +// This product includes software developed at Datadog (https://www.datadoghq.com/). +// Copyright 2016-present Datadog, Inc. + +//go:build linux && ebpf_bindata +// +build linux,ebpf_bindata + +package tests + +import ( + "flag" + "os" + "testing" + + "github.com/DataDog/datadog-agent/pkg/security/config" + secebpf "github.com/DataDog/datadog-agent/pkg/security/ebpf" + "github.com/DataDog/datadog-go/v5/statsd" + "github.com/cilium/ebpf" + "github.com/safchain/baloum/pkg/baloum" +) + +type testLogger struct { + t *testing.T + trace bool +} + +func (l *testLogger) Info(params ...interface{}) { + l.t.Log(params...) +} + +func (l *testLogger) Infof(format string, params ...interface{}) { + l.t.Logf(format, params...) +} + +func (l *testLogger) Debug(params ...interface{}) { + if l.trace { + l.t.Log(params...) + } +} + +func (l *testLogger) Debugf(format string, params ...interface{}) { + if l.trace { + l.t.Logf(format, params...) + } +} + +func (l *testLogger) Error(params ...interface{}) { + l.t.Error(params...) +} + +func (l *testLogger) Errorf(format string, params ...interface{}) { + l.t.Errorf(format, params...) +} + +var trace bool + +func newVM(t *testing.T) *baloum.VM { + useSyscallWrapper, err := secebpf.IsSyscallWrapperRequired() + if err != nil { + t.Fatal(err) + } + + loader := secebpf.NewProbeLoader(&config.Config{}, useSyscallWrapper, &statsd.NoOpClient{}) + reader, _, err := loader.Load() + if err != nil { + t.Fatal(err) + } + + spec, err := ebpf.LoadCollectionSpecFromReader(reader) + if err != nil { + t.Fatal(err) + } + + fncs := baloum.Fncs{ + TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) error { + t.Logf(format, args...) + return nil + }, + } + + return baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: &testLogger{t: t, trace: trace}}) +} + +func TestMain(m *testing.M) { + flag.BoolVar(&trace, "trace", false, "enable eBPF VM instruction tracing") + flag.Parse() + os.Exit(m.Run()) +} diff --git a/tasks/security_agent.py b/tasks/security_agent.py index 1112d6f6eaf214..ca93aeb999c403 100644 --- a/tasks/security_agent.py +++ b/tasks/security_agent.py @@ -710,7 +710,7 @@ def kitchen_prepare(ctx): @task -def run_ebpf_unit_tests(ctx, verbose=False): +def run_ebpf_unit_tests(ctx, verbose=False, trace=False): build_cws_object_files( ctx, major_version='7', @@ -723,4 +723,8 @@ def run_ebpf_unit_tests(ctx, verbose=False): if verbose: flags += " -test.v" - ctx.run(f"go test {flags} ./pkg/security/ebpf/tests/...") + args = '-args' + if trace: + args += " -trace" + + ctx.run(f"go test {flags} ./pkg/security/ebpf/tests/... {args}") From dc421bf04a6bab37a2d63f523215f38a8522df5f Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Thu, 24 Nov 2022 10:32:11 +0100 Subject: [PATCH 5/8] make eBPF test part of the CI --- .gitlab/source_test/ebpf.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab/source_test/ebpf.yml b/.gitlab/source_test/ebpf.yml index 6c42e4bd0c5066..9c8c8a28a6648b 100644 --- a/.gitlab/source_test/ebpf.yml +++ b/.gitlab/source_test/ebpf.yml @@ -49,6 +49,7 @@ - inv -e system-probe.object-files - invoke -e golangci-lint --build system-probe ./pkg - !reference [.build_sysprobe_artifacts] + - invoke -e security-agent.run-ebpf-unit-tests --verbose - invoke -e security-agent.kitchen-prepare - cp /tmp/clang-bpf $DD_AGENT_TESTING_DIR/site-cookbooks/dd-system-probe-check/files/clang-bpf - cp /tmp/llc-bpf $DD_AGENT_TESTING_DIR/site-cookbooks/dd-system-probe-check/files/llc-bpf From 4b17421e296d68a5921931babf7f5a8b5e381794 Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Fri, 25 Nov 2022 19:00:17 +0100 Subject: [PATCH 6/8] fake time to speed up tests --- pkg/security/ebpf/tests/helpers_test.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/security/ebpf/tests/helpers_test.go b/pkg/security/ebpf/tests/helpers_test.go index 85463abb66088f..a0192ba542b093 100644 --- a/pkg/security/ebpf/tests/helpers_test.go +++ b/pkg/security/ebpf/tests/helpers_test.go @@ -12,6 +12,7 @@ import ( "flag" "os" "testing" + "time" "github.com/DataDog/datadog-agent/pkg/security/config" secebpf "github.com/DataDog/datadog-agent/pkg/security/ebpf" @@ -72,11 +73,21 @@ func newVM(t *testing.T) *baloum.VM { t.Fatal(err) } + var now time.Time + fncs := baloum.Fncs{ TracePrintk: func(vm *baloum.VM, format string, args ...interface{}) error { t.Logf(format, args...) return nil }, + // fake the time duration to speed up the tests + KtimeGetNS: func(vm *baloum.VM) (uint64, error) { + return uint64(now.UnixNano()), nil + }, + Sleep: func(vm *baloum.VM, duration time.Duration) error { + now = now.Add(duration) + return nil + }, } return baloum.NewVM(spec, baloum.Opts{Fncs: fncs, Logger: &testLogger{t: t, trace: trace}}) From 65375c419a22299faeb0d952244776c65e46a635 Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Tue, 6 Dec 2022 16:56:49 +0100 Subject: [PATCH 7/8] bump baloum version --- pkg/security/ebpf/tests/discarders_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/security/ebpf/tests/discarders_test.go b/pkg/security/ebpf/tests/discarders_test.go index a0e97fbcf48a22..3450c5a7c1dec1 100644 --- a/pkg/security/ebpf/tests/discarders_test.go +++ b/pkg/security/ebpf/tests/discarders_test.go @@ -15,16 +15,16 @@ import ( ) func TestDiscarderEventMask(t *testing.T) { - var ctx baloum.Context - code, err := newVM(t).RunProgram(ctx, "test/discarders_event_mask") + var ctx baloum.StdContext + code, err := newVM(t).RunProgram(&ctx, "test/discarders_event_mask") if err != nil || code != 0 { t.Errorf("unexpected error: %v, %d", err, code) } } func TestDiscarderRetention(t *testing.T) { - var ctx baloum.Context - code, err := newVM(t).RunProgram(ctx, "test/discarders_retention") + var ctx baloum.StdContext + code, err := newVM(t).RunProgram(&ctx, "test/discarders_retention") if err != nil || code != 0 { t.Errorf("unexpected error: %v, %d", err, code) } From 3d4ad5878ce8e6b7dd2ad996e1c98e6683203228 Mon Sep 17 00:00:00 2001 From: Sylvain Afchain Date: Thu, 29 Dec 2022 13:18:43 +0100 Subject: [PATCH 8/8] add more tests --- go.mod | 3 +- go.sum | 4 +- pkg/ebpf/bytecode/runtime/runtime-security.go | 2 +- pkg/security/ebpf/c/discarders.h | 14 +- pkg/security/ebpf/c/discarders_test.h | 165 ++++++++++++------ pkg/security/ebpf/tests/discarders_test.go | 16 ++ tasks/system_probe.py | 14 +- 7 files changed, 159 insertions(+), 59 deletions(-) diff --git a/go.mod b/go.mod index 91d27874736d53..b44cfc755f2ee5 100644 --- a/go.mod +++ b/go.mod @@ -430,6 +430,7 @@ require ( require ( github.com/DataDog/go-libddwaf v0.0.0-20221118110754-0372d7c76b8a github.com/go-redis/redis/v9 v9.0.0-rc.2 + github.com/safchain/baloum v0.0.0-20221229104256-b1fc8f70a86b github.com/streadway/amqp v1.0.0 github.com/uptrace/bun v1.1.9 github.com/uptrace/bun/dialect/pgdialect v1.1.9 @@ -452,8 +453,6 @@ require ( mellium.im/sasl v0.3.1 // indirect ) -require github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4 - replace github.com/pahanini/go-grpc-bidirectional-streaming-example v0.0.0-20211027164128-cc6111af44be => github.com/DataDog/go-grpc-bidirectional-streaming-example v0.0.0-20221024060302-b9cf785c02fe // Fixing a CVE on a transitive dep of k8s/etcd, should be cleaned-up once k8s.io/apiserver dep is removed (but double-check with `go mod why` that no other dep pulls it) diff --git a/go.sum b/go.sum index 9a5b1c32cc642a..e61bd8004d87bd 100644 --- a/go.sum +++ b/go.sum @@ -1528,8 +1528,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4 h1:AUa6BxTG3rp0tQFCQ+cfeXJd9jwI2x62S9FaYsybXzU= -github.com/safchain/baloum v0.0.0-20221206135539-a3b4d52f28b4/go.mod h1:1+GWOH32bsIEAHknYja6/H1efcDs+/Q2XrtYMM200Ho= +github.com/safchain/baloum v0.0.0-20221229104256-b1fc8f70a86b h1:cTiH46CYvPhgOlE0t82N+rgQw44b7vB39ay+P+wiVz8= +github.com/safchain/baloum v0.0.0-20221229104256-b1fc8f70a86b/go.mod h1:1+GWOH32bsIEAHknYja6/H1efcDs+/Q2XrtYMM200Ho= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= diff --git a/pkg/ebpf/bytecode/runtime/runtime-security.go b/pkg/ebpf/bytecode/runtime/runtime-security.go index f8265109a3786b..96d1c5c0dee5e3 100644 --- a/pkg/ebpf/bytecode/runtime/runtime-security.go +++ b/pkg/ebpf/bytecode/runtime/runtime-security.go @@ -4,4 +4,4 @@ package runtime -var RuntimeSecurity = newAsset("runtime-security.c", "5d0851c4f57bf54d9e4053d970d6f955f37bacb3ad3a50f12d389d507a9aac8a") +var RuntimeSecurity = newAsset("runtime-security.c", "284489038bc6dfaa1850e06eb8e909c91bcd6a6a1eaf29233b344f8413361b8e") diff --git a/pkg/security/ebpf/c/discarders.h b/pkg/security/ebpf/c/discarders.h index 8ecb928fc0cfba..3f043a23e4a18c 100644 --- a/pkg/security/ebpf/c/discarders.h +++ b/pkg/security/ebpf/c/discarders.h @@ -162,7 +162,7 @@ u64* __attribute__((always_inline)) get_discarder_timestamp(struct discarder_par // This function is doing the same thing as the one before, but can only work if `params` is a pointer to a map value // and not a pointer to the stack since kernels < 4.15 does not allow this. On the other hand it is faster and needs less // instructions. -u64* __attribute__((always_inline)) get_discarder_timestamp_from_map(struct discarder_params_t *params, u64 event_type) { +u64 * __attribute__((always_inline)) get_discarder_timestamp_from_map(struct discarder_params_t *params, u64 event_type) { if (EVENT_FIRST_DISCARDER <= event_type && event_type < EVENT_LAST_DISCARDER) { return ¶ms->timestamps[event_type-EVENT_FIRST_DISCARDER]; } @@ -213,6 +213,18 @@ struct bpf_map_def SEC("maps/inode_discarders") inode_discarders = { int __attribute__((always_inline)) expire_inode_discarders(u32 mount_id, u64 inode); +struct inode_discarder_params_t * __attribute__((always_inline)) get_inode_discarder_params(u32 mount_id, u64 inode, u32 is_leaf) { + struct inode_discarder_t key = { + .path_key = { + .ino = inode, + .mount_id = mount_id, + }, + .is_leaf = is_leaf, + }; + + return bpf_map_lookup_elem(&inode_discarders, &key); +} + int __attribute__((always_inline)) discard_inode(u64 event_type, u32 mount_id, u64 inode, u64 timeout, u32 is_leaf) { if (!mount_id || !inode) { return 0; diff --git a/pkg/security/ebpf/c/discarders_test.h b/pkg/security/ebpf/c/discarders_test.h index e5d11ee6b47b22..0afd31605f5b3f 100644 --- a/pkg/security/ebpf/c/discarders_test.h +++ b/pkg/security/ebpf/c/discarders_test.h @@ -5,6 +5,18 @@ #include "discarders.h" #include "baloum.h" +int __attribute__((always_inline)) _is_discarded_by_inode(u64 event_type, u32 mount_id, u64 inode) { + struct is_discarded_by_inode_t params = { + .discarder_type = event_type, + .discarder = { + .path_key.ino = inode, + .path_key.mount_id = mount_id, + } + }; + + return is_discarded_by_inode(¶ms); +} + SEC("test/discarders_event_mask") int test_discarders_event_mask() { @@ -14,28 +26,13 @@ int test_discarders_event_mask() int ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); assert_zero(ret, "failed to discard the inode"); - struct inode_discarder_t key = { - .path_key = { - .ino = inode, - .mount_id = mount_id, - } - }; - - struct inode_discarder_params_t *inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + struct inode_discarder_params_t *inode_params = get_inode_discarder_params(mount_id, inode, 0); assert_not_null(inode_params, "unable to find the inode discarder entry"); ret = mask_has_event(inode_params->params.event_mask, EVENT_OPEN); assert_not_zero(ret, "event not found in mask"); - struct is_discarded_by_inode_t params = { - .discarder_type = EVENT_OPEN, - .discarder = { - .path_key.ino = inode, - .path_key.mount_id = mount_id, - } - }; - - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode); assert_not_zero(ret, "inode should be discarded"); // add another event type @@ -43,7 +40,7 @@ int test_discarders_event_mask() assert_zero(ret, "failed to discard the inode"); // check that we have now both open and chmod event discarded - inode_params = bpf_map_lookup_elem(&inode_discarders, &key); + inode_params = get_inode_discarder_params(mount_id, inode, 0); assert_not_null(inode_params, "unable to find the inode discarder entry"); ret = mask_has_event(inode_params->params.event_mask, EVENT_OPEN); @@ -52,12 +49,10 @@ int test_discarders_event_mask() ret = mask_has_event(inode_params->params.event_mask, EVENT_CHMOD); assert_not_zero(ret, "event not found in mask"); - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode); assert_not_zero(ret, "inode should be discarded"); - params.discarder_type = EVENT_CHMOD; - - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_CHMOD, mount_id, inode); assert_not_zero(ret, "inode should be discarded"); return 0; @@ -72,45 +67,23 @@ int test_discarders_retention() int ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); assert_zero(ret, "failed to discard the inode"); - struct inode_discarder_t key = { - .path_key = { - .ino = inode, - .mount_id = mount_id, - } - }; - - struct inode_discarder_params_t *inode_params = bpf_map_lookup_elem(&inode_discarders, &key); - assert_not_null(inode_params, "unable to find the inode discarder entry"); - - struct is_discarded_by_inode_t params = { - .discarder_type = EVENT_OPEN, - .discarder = { - .path_key.ino = inode, - .path_key.mount_id = mount_id, - } - }; - - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode); assert_not_zero(ret, "inode should be discarded"); // expire the discarder expire_inode_discarders(mount_id, inode); - // the entry should still be there - inode_params = bpf_map_lookup_elem(&inode_discarders, &key); - assert_not_null(inode_params, "unable to find the inode discarder entry"); - - // but should be discarded anymore - ret = is_discarded_by_inode(¶ms); + // shouldn't be discarded anymore + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode); assert_zero(ret, "inode shouldn't be discarded"); // we shouldn't be able to add a new discarder for the same inode during the retention period // TODO(safchain) should return an error value ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); - assert_zero(ret, "failed to discard the inode"); + assert_zero(ret, "able to discard the inode"); // shouldn't still be discarded - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_CHMOD, mount_id, inode); assert_zero(ret, "inode shouldn't be discarded"); // wait the retention period @@ -120,7 +93,99 @@ int test_discarders_retention() ret = discard_inode(EVENT_OPEN, mount_id, inode, 0, 0); assert_zero(ret, "failed to discard the inode"); - ret = is_discarded_by_inode(¶ms); + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode); + assert_not_zero(ret, "inode should be discarded"); + + return 0; +} + +SEC("test/discarders_revision") +int test_discarders_revision() +{ + u32 mount_id1 = 123; + u64 inode1 = 456; + + u32 mount_id2 = 456; + u64 inode2 = 789; + + int ret = discard_inode(EVENT_OPEN, mount_id1, inode1, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_not_zero(ret, "inode should be discarded"); + + ret = discard_inode(EVENT_OPEN, mount_id2, inode2, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id2, inode2); + assert_not_zero(ret, "inode should be discarded"); + + // expire the discarders + bump_discarders_revision(); + + // now all the discarders whatever their mount id should be discarded + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_zero(ret, "inode shouldn't be discarded"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id2, inode2); + assert_zero(ret, "inode shouldn't be discarded"); + + // check that we added a retention period + ret = discard_inode(EVENT_OPEN, mount_id1, inode1, 0, 0); + assert_zero(ret, "able to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_zero(ret, "inode shouldn't be discarded"); + + // wait the retention period + baloum_sleep(get_discarder_retention() + 1); + + ret = discard_inode(EVENT_OPEN, mount_id1, inode1, 0, 0); + assert_zero(ret, "able to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_not_zero(ret, "inode should be discarded"); + + return 0; +} + +SEC("test/discarders_mount_revision") +int test_discarders_mount_revision() +{ + u32 mount_id1 = 123; + u64 inode1 = 456; + + u32 mount_id2 = 456; + u64 inode2 = 789; + + int ret = discard_inode(EVENT_OPEN, mount_id1, inode1, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_not_zero(ret, "inode should be discarded"); + + ret = discard_inode(EVENT_OPEN, mount_id2, inode2, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id2, inode2); + assert_not_zero(ret, "inode should be discarded"); + + // bump the revision + bump_mount_discarder_revision(mount_id1); + + // now the inode1 shouldn't be discarded anymore + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); + assert_zero(ret, "inode shouldn't be discarded"); + + // while node2 should still be + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id2, inode2); + assert_not_zero(ret, "inode should be discarded"); + + // we are allowed to re-add inode1 right away + ret = discard_inode(EVENT_OPEN, mount_id1, inode1, 0, 0); + assert_zero(ret, "failed to discard the inode"); + + ret = _is_discarded_by_inode(EVENT_OPEN, mount_id1, inode1); assert_not_zero(ret, "inode should be discarded"); return 0; diff --git a/pkg/security/ebpf/tests/discarders_test.go b/pkg/security/ebpf/tests/discarders_test.go index 3450c5a7c1dec1..06d0b9d400f2da 100644 --- a/pkg/security/ebpf/tests/discarders_test.go +++ b/pkg/security/ebpf/tests/discarders_test.go @@ -29,3 +29,19 @@ func TestDiscarderRetention(t *testing.T) { t.Errorf("unexpected error: %v, %d", err, code) } } + +func TestDiscarderRevision(t *testing.T) { + var ctx baloum.StdContext + code, err := newVM(t).RunProgram(&ctx, "test/discarders_revision") + if err != nil || code != 0 { + t.Errorf("unexpected error: %v, %d", err, code) + } +} + +func TestDiscarderMountRevision(t *testing.T) { + var ctx baloum.StdContext + code, err := newVM(t).RunProgram(&ctx, "test/discarders_mount_revision") + if err != nil || code != 0 { + t.Errorf("unexpected error: %v, %d", err, code) + } +} diff --git a/tasks/system_probe.py b/tasks/system_probe.py index 6101943f7cd2f5..f5d7a298cc02d7 100644 --- a/tasks/system_probe.py +++ b/tasks/system_probe.py @@ -398,7 +398,7 @@ def build( debug=False, strip_object_files=False, strip_binary=False, - with_unit_test=False + with_unit_test=False, ): """ Build the system-probe @@ -991,7 +991,9 @@ def run_ninja( ): check_for_ninja(ctx) nf_path = os.path.join(ctx.cwd, 'system-probe.ninja') - ninja_generate(ctx, nf_path, windows, major_version, arch, debug, strip_object_files, kernel_release, with_unit_test) + ninja_generate( + ctx, nf_path, windows, major_version, arch, debug, strip_object_files, kernel_release, with_unit_test + ) explain_opt = "-d explain" if explain else "" if task: ctx.run(f"ninja {explain_opt} -f {nf_path} -t {task}") @@ -1088,7 +1090,13 @@ def build_object_files( def build_cws_object_files( - ctx, major_version='7', arch=CURRENT_ARCH, kernel_release=None, debug=False, strip_object_files=False, with_unit_test=False + ctx, + major_version='7', + arch=CURRENT_ARCH, + kernel_release=None, + debug=False, + strip_object_files=False, + with_unit_test=False, ): run_ninja( ctx,