Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fd0fd1c
X-Smart-Branch-Parent: main
JoukoVirtanen Apr 9, 2026
5b2167e
Added integration tests
JoukoVirtanen Mar 31, 2026
12f602e
Instrument inode tracking on directory being created
JoukoVirtanen Apr 1, 2026
8406a5a
Only tracking directories if the parent is monitored
JoukoVirtanen Apr 1, 2026
03bd26e
Removed some comments
JoukoVirtanen Apr 1, 2026
b1fc6f5
Combined two uses of BPF_CORE_READ
JoukoVirtanen Apr 2, 2026
dbea1a7
Added DIR_ACTIVITY_CREATION
JoukoVirtanen Apr 2, 2026
d1b4136
Added permalink to linux/stat.h
JoukoVirtanen Apr 2, 2026
a120084
Removing map entry in case of early return in lsm/d_instantiate
JoukoVirtanen Apr 2, 2026
fb7ce80
Using os.makedirs instead of os.mkdir three times
JoukoVirtanen Apr 2, 2026
60f96eb
Accumulate m.path_mkdir and m.d_instantiate
JoukoVirtanen Apr 3, 2026
651edd4
Checking pid_tgid earlier so if inode is null we still cleanup
JoukoVirtanen Apr 3, 2026
a57baf7
Not using BPF_F_NO_PREALLOC
JoukoVirtanen Apr 7, 2026
1fa3c77
Switched from BPF_MAP_TYPE_HASH to BPF_MAP_TYPE_LRU_HASH
JoukoVirtanen Apr 7, 2026
0d1929d
Apply suggestion from @Molter73
JoukoVirtanen Apr 7, 2026
e9634e5
Parameterized test
JoukoVirtanen Apr 7, 2026
11eba2e
Fixed verifier issue. Not sending directory creation events
JoukoVirtanen Apr 7, 2026
008b580
make format
JoukoVirtanen Apr 7, 2026
e86fa4e
Improved test Event constructor
JoukoVirtanen Apr 8, 2026
fd6bcbb
Remove is_dir_creation
JoukoVirtanen Apr 8, 2026
5f83efa
Removed unneeded S_ISDIR
JoukoVirtanen Apr 8, 2026
b97f03e
make format
JoukoVirtanen Apr 8, 2026
0874869
Update tests/test_path_mkdir.py
JoukoVirtanen Apr 9, 2026
ba14b12
Fixes after rebase
JoukoVirtanen Apr 9, 2026
e4cd882
Moved the check for null inode up
JoukoVirtanen Apr 9, 2026
34f8ff4
Not adding a new event type field to Event struct
JoukoVirtanen Apr 9, 2026
db4645b
make format
JoukoVirtanen Apr 9, 2026
c4ef0ac
Empty commit
JoukoVirtanen Apr 10, 2026
74e5543
cleanup: refactor submit event functions
Molter73 Apr 1, 2026
13e58f3
chore: simplify inode handling kernel side
Molter73 Apr 6, 2026
96c8f45
cleanup: move ringbuf event reservation to its own function
Molter73 Apr 10, 2026
d6fe74c
fix: prevent verifier error on bpf_d_path not being removed
Molter73 Apr 10, 2026
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
126 changes: 64 additions & 62 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,30 @@
#include <bpf/bpf_helpers.h>
// clang-format on

__always_inline static void __submit_event(struct event_t* event,
struct metrics_by_hook_t* m,
file_activity_type_t event_type,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode,
struct submit_event_args_t {
struct event_t* event;
struct metrics_by_hook_t* metrics;
const char* filename;
inode_key_t inode;
inode_key_t parent_inode;
};

__always_inline static bool reserve_event(struct submit_event_args_t* args) {
args->event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (args->event == NULL) {
args->metrics->ringbuffer_full++;
return false;
}
return true;
}

__always_inline static void __submit_event(struct submit_event_args_t* args,
bool use_bpf_d_path) {
event->type = event_type;
struct event_t* event = args->event;
event->timestamp = bpf_ktime_get_boot_ns();
inode_copy_or_reset(&event->inode, inode);
inode_copy_or_reset(&event->parent_inode, parent_inode);
bpf_probe_read_str(event->filename, PATH_MAX, filename);
inode_copy(&event->inode, &args->inode);
inode_copy(&event->parent_inode, &args->parent_inode);
bpf_probe_read_str(event->filename, PATH_MAX, args->filename);

struct helper_t* helper = get_helper();
if (helper == NULL) {
Expand All @@ -36,96 +48,86 @@ __always_inline static void __submit_event(struct event_t* event,
goto error;
}

m->added++;
args->metrics->added++;
bpf_ringbuf_submit(event, 0);
return;

error:
m->error++;
args->metrics->error++;
bpf_ringbuf_discard(event, 0);
}

__always_inline static void submit_open_event(struct metrics_by_hook_t* m,
file_activity_type_t event_type,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
__always_inline static void submit_open_event(struct submit_event_args_t* args,
file_activity_type_t event_type) {
if (!reserve_event(args)) {
return;
}
args->event->type = event_type;

__submit_event(event, m, event_type, filename, inode, parent_inode, true);
__submit_event(args, true);
}

__always_inline static void submit_unlink_event(struct metrics_by_hook_t* m,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
__always_inline static void submit_unlink_event(struct submit_event_args_t* args) {
if (!reserve_event(args)) {
return;
}
args->event->type = FILE_ACTIVITY_UNLINK;

__submit_event(event, m, FILE_ACTIVITY_UNLINK, filename, inode, parent_inode, path_hooks_support_bpf_d_path);
__submit_event(args, path_hooks_support_bpf_d_path);
}

__always_inline static void submit_mode_event(struct metrics_by_hook_t* m,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode,
__always_inline static void submit_mode_event(struct submit_event_args_t* args,
umode_t mode,
umode_t old_mode) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
if (!reserve_event(args)) {
return;
}

event->chmod.new = mode;
event->chmod.old = old_mode;
args->event->type = FILE_ACTIVITY_CHMOD;
args->event->chmod.new = mode;
args->event->chmod.old = old_mode;

__submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, parent_inode, path_hooks_support_bpf_d_path);
__submit_event(args, path_hooks_support_bpf_d_path);
}

__always_inline static void submit_ownership_event(struct metrics_by_hook_t* m,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode,
__always_inline static void submit_ownership_event(struct submit_event_args_t* args,
unsigned long long uid,
unsigned long long gid,
unsigned long long old_uid,
unsigned long long old_gid) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
if (!reserve_event(args)) {
return;
}

event->chown.new.uid = uid;
event->chown.new.gid = gid;
event->chown.old.uid = old_uid;
event->chown.old.gid = old_gid;
args->event->type = FILE_ACTIVITY_CHOWN;
args->event->chown.new.uid = uid;
args->event->chown.new.gid = gid;
args->event->chown.old.uid = old_uid;
args->event->chown.old.gid = old_gid;

__submit_event(event, m, FILE_ACTIVITY_CHOWN, filename, inode, parent_inode, path_hooks_support_bpf_d_path);
__submit_event(args, path_hooks_support_bpf_d_path);
}

__always_inline static void submit_rename_event(struct metrics_by_hook_t* m,
const char new_filename[PATH_MAX],
__always_inline static void submit_rename_event(struct submit_event_args_t* args,
const char old_filename[PATH_MAX],
inode_key_t* new_inode,
inode_key_t* old_inode,
inode_key_t* new_parent_inode) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
inode_key_t* old_inode) {
if (!reserve_event(args)) {
return;
}

bpf_probe_read_str(event->rename.old_filename, PATH_MAX, old_filename);
inode_copy_or_reset(&event->rename.old_inode, old_inode);
args->event->type = FILE_ACTIVITY_RENAME;
bpf_probe_read_str(args->event->rename.old_filename, PATH_MAX, old_filename);
inode_copy(&args->event->rename.old_inode, old_inode);

__submit_event(args, path_hooks_support_bpf_d_path);
}

__always_inline static void submit_mkdir_event(struct submit_event_args_t* args) {
if (!reserve_event(args)) {
return;
}
args->event->type = DIR_ACTIVITY_CREATION;

__submit_event(event, m, FILE_ACTIVITY_RENAME, new_filename, new_inode, new_parent_inode, path_hooks_support_bpf_d_path);
// d_instantiate doesn't support bpf_d_path, so we use false and rely on the stashed path from path_mkdir
__submit_event(args, false);
}
22 changes: 19 additions & 3 deletions fact-ebpf/src/bpf/file.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,35 @@ __always_inline static bool path_is_monitored(struct bound_path_t* path) {
return res;
}

__always_inline static inode_monitored_t is_monitored(inode_key_t inode, struct bound_path_t* path, const inode_key_t* parent, inode_key_t** submit) {
const inode_value_t* volatile inode_value = inode_get(&inode);
__always_inline static inode_monitored_t is_monitored(inode_key_t* inode, struct bound_path_t* path, const inode_key_t* parent) {
const inode_value_t* volatile inode_value = inode_get(inode);
const inode_value_t* volatile parent_value = inode_get(parent);

inode_monitored_t status = inode_is_monitored(inode_value, parent_value);
if (status != NOT_MONITORED) {
return status;
}

*submit = NULL;
inode_reset(inode);
if (path_is_monitored(path)) {
return MONITORED;
}

return NOT_MONITORED;
}

// Check if a new directory should be tracked based on its parent and path.
// This is used during mkdir operations where the child inode doesn't exist yet.
__always_inline static inode_monitored_t should_track_mkdir(inode_key_t parent_inode, struct bound_path_t* child_path) {
const inode_value_t* volatile parent_value = inode_get(&parent_inode);

if (parent_value != NULL) {
return PARENT_MONITORED;
}

if (path_is_monitored(child_path)) {
return MONITORED;
}

return NOT_MONITORED;
}
16 changes: 8 additions & 8 deletions fact-ebpf/src/bpf/inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@ __always_inline static long inode_remove(struct inode_key_t* inode) {
return bpf_map_delete_elem(&inode_map, inode);
}

__always_inline static void inode_reset(struct inode_key_t* inode) {
inode->inode = 0;
inode->dev = 0;
}

typedef enum inode_monitored_t {
NOT_MONITORED = 0,
MONITORED,
Expand All @@ -99,16 +104,11 @@ __always_inline static inode_monitored_t inode_is_monitored(const inode_value_t*
return NOT_MONITORED;
}

__always_inline static void inode_copy_or_reset(inode_key_t* dst, const inode_key_t* src) {
__always_inline static void inode_copy(inode_key_t* dst, const inode_key_t* src) {
if (dst == NULL) {
return;
}

if (src != NULL) {
dst->inode = src->inode;
dst->dev = src->dev;
} else {
dst->inode = 0;
dst->dev = 0;
}
dst->inode = src->inode;
dst->dev = src->dev;
}
Loading
Loading