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

[CWS] add discarders eBPF unit test #14471

Merged
merged 8 commits into from
Jan 13, 2023
Merged
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
1 change: 1 addition & 0 deletions .gitlab/source_test/ebpf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pkg/ebpf/bytecode/runtime/runtime-security.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

78 changes: 78 additions & 0 deletions pkg/security/ebpf/c/baloum.h
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions pkg/security/ebpf/c/dentry_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
18 changes: 15 additions & 3 deletions pkg/security/ebpf/c/discarders.h
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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 &params->timestamps[event_type-EVENT_FIRST_DISCARDER];
}
Expand Down Expand Up @@ -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;
Expand Down
194 changes: 194 additions & 0 deletions pkg/security/ebpf/c/discarders_test.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#ifndef _DISCARDERS_TEST_H
#define _DISCARDERS_TEST_H

#include "defs.h"
#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(&params);
}

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_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");

ret = _is_discarded_by_inode(EVENT_OPEN, mount_id, inode);
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 = 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");

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(EVENT_OPEN, mount_id, inode);
assert_not_zero(ret, "inode should be discarded");

ret = _is_discarded_by_inode(EVENT_CHMOD, mount_id, inode);
assert_not_zero(ret, "inode should be discarded");

return 0;
}

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");

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);

// 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, "able to discard the inode");

// shouldn't still be discarded
ret = _is_discarded_by_inode(EVENT_CHMOD, mount_id, inode);
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(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;
}

#endif
5 changes: 5 additions & 0 deletions pkg/security/ebpf/c/prebuilt/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -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";
6 changes: 6 additions & 0 deletions pkg/security/ebpf/c/tests.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef _TESTS_H
#define _TESTS_H

#include "discarders_test.h"

#endif
Loading