Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7b92eab
X-Smart-Branch-Parent: main
JoukoVirtanen Mar 23, 2026
0d9cf75
When a monitored file is created it is added to maps in kernel and us…
JoukoVirtanen Mar 15, 2026
b0113c2
Using inode_to_key directly instead of duplicating code. inode_to_key…
JoukoVirtanen Mar 16, 2026
2bf0f3d
Parent inode is added to events. That is used to get the correct path
JoukoVirtanen Mar 18, 2026
1b64a72
Using the inode directly to add to map instead of using the path to g…
JoukoVirtanen Mar 19, 2026
35462c4
Added host path to tests
JoukoVirtanen Mar 19, 2026
411c115
Only the open file event returns the parent inode, the rest return null
JoukoVirtanen Mar 19, 2026
3b09253
Removed blank space
JoukoVirtanen Mar 19, 2026
f37b40b
Fixed formatting errors
JoukoVirtanen Mar 19, 2026
283f50a
Refactored update_entry_with_inode and update_entry
JoukoVirtanen Mar 19, 2026
600d08c
Removed tests/test_inode_tracking.py
JoukoVirtanen Mar 19, 2026
1d1e383
Removed debugging and refactored handle_creation_event
JoukoVirtanen Mar 20, 2026
65f53a0
Not expecting host_path for ignored directories
JoukoVirtanen Mar 20, 2026
950c402
Fixed style. Removed unused ignored_dir
JoukoVirtanen Mar 20, 2026
a8471b0
Logic for checking if a file should be monitored moved to is_monitored
JoukoVirtanen Mar 23, 2026
5e145ca
Refactored so that NULL doesn't have to be passed to is_monitored man…
JoukoVirtanen Mar 23, 2026
0184180
Removed unneeded TODOs
JoukoVirtanen Mar 23, 2026
2b139ff
Fixed format after rebasing and updating edition to 2024
JoukoVirtanen Mar 23, 2026
93dd1d8
Collapsed if statements
JoukoVirtanen Mar 23, 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
23 changes: 15 additions & 8 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ __always_inline static void __submit_event(struct event_t* event,
file_activity_type_t event_type,
const char filename[PATH_MAX],
inode_key_t* inode,
inode_key_t* parent_inode,
bool use_bpf_d_path) {
event->type = event_type;
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);

struct helper_t* helper = get_helper();
Expand All @@ -46,31 +48,34 @@ __always_inline static void __submit_event(struct event_t* event,
__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* 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++;
return;
}

__submit_event(event, m, event_type, filename, inode, true);
__submit_event(event, m, event_type, filename, inode, parent_inode, 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* 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++;
return;
}

__submit_event(event, m, FILE_ACTIVITY_UNLINK, filename, inode, path_hooks_support_bpf_d_path);
__submit_event(event, m, FILE_ACTIVITY_UNLINK, filename, inode, parent_inode, 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,
umode_t mode,
umode_t old_mode) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
Expand All @@ -82,12 +87,13 @@ __always_inline static void submit_mode_event(struct metrics_by_hook_t* m,
event->chmod.new = mode;
event->chmod.old = old_mode;

__submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, path_hooks_support_bpf_d_path);
__submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, parent_inode, 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,
unsigned long long uid,
unsigned long long gid,
unsigned long long old_uid,
Expand All @@ -103,14 +109,15 @@ __always_inline static void submit_ownership_event(struct metrics_by_hook_t* m,
event->chown.old.uid = old_uid;
event->chown.old.gid = old_gid;

__submit_event(event, m, FILE_ACTIVITY_CHOWN, filename, inode, path_hooks_support_bpf_d_path);
__submit_event(event, m, FILE_ACTIVITY_CHOWN, filename, inode, parent_inode, 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],
const char old_filename[PATH_MAX],
inode_key_t* new_inode,
inode_key_t* old_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++;
Expand All @@ -120,5 +127,5 @@ __always_inline static void submit_rename_event(struct metrics_by_hook_t* m,
bpf_probe_read_str(event->rename.old_filename, PATH_MAX, old_filename);
inode_copy_or_reset(&event->rename.old_inode, old_inode);

__submit_event(event, m, FILE_ACTIVITY_RENAME, new_filename, new_inode, path_hooks_support_bpf_d_path);
__submit_event(event, m, FILE_ACTIVITY_RENAME, new_filename, new_inode, new_parent_inode, path_hooks_support_bpf_d_path);
}
32 changes: 21 additions & 11 deletions fact-ebpf/src/bpf/file.h
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we unify the logic for checking if an inode is monitored in inode_is_monitored? Just have that method take both the inode and parent_inode, then return one of MONITORED, NOT_MONITORED or PARENT_MONITRED values? I think that might be a bit cleaner and we don't need a dedicated _with_parent function.

Original file line number Diff line number Diff line change
Expand Up @@ -27,18 +27,28 @@ __always_inline static bool path_is_monitored(struct bound_path_t* path) {
return res;
}

__always_inline static bool is_monitored(inode_key_t inode, struct bound_path_t* path, inode_key_t** submit) {
__always_inline static inode_monitored_t is_monitored_with_parent(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);

switch (inode_is_monitored(inode_value)) {
case NOT_MONITORED:
*submit = NULL;
if (path_is_monitored(path)) {
return true;
}
return false;
case MONITORED:
break;
if (inode_is_monitored(inode_value) == MONITORED) {
return MONITORED;
}
return true;

if (parent != NULL) {
const inode_value_t* volatile parent_value = inode_get(parent);
if (inode_is_monitored(parent_value) == MONITORED) {
return PARENT_MONITORED;
}
}

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

return NOT_MONITORED;
}

__always_inline static inode_monitored_t is_monitored(inode_key_t inode, struct bound_path_t* path, inode_key_t** submit) {
return is_monitored_with_parent(inode, path, NULL, submit);
}
19 changes: 14 additions & 5 deletions fact-ebpf/src/bpf/inode.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,21 +33,21 @@ __always_inline static inode_key_t inode_to_key(struct inode* inode) {
return key;
}

unsigned long magic = inode->i_sb->s_magic;
unsigned long magic = BPF_CORE_READ(inode, i_sb, s_magic);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

inode_to_key needs to be able to use untrusted pointers, so that it can get the key for a parent inode. To get it to be able to use untrusted pointers, BPF_CORE_READ is used.

switch (magic) {
case BTRFS_SUPER_MAGIC:
if (bpf_core_type_exists(struct btrfs_inode)) {
struct btrfs_inode* btrfs_inode = container_of(inode, struct btrfs_inode, vfs_inode);
key.inode = inode->i_ino;
key.inode = BPF_CORE_READ(inode, i_ino);
key.dev = BPF_CORE_READ(btrfs_inode, root, anon_dev);
break;
}
// If the btrfs_inode does not exist, most likely it is not
// supported on the system. Fallback to the generic implementation
// just in case.
default:
key.inode = inode->i_ino;
key.dev = inode->i_sb->s_dev;
key.inode = BPF_CORE_READ(inode, i_ino);
key.dev = BPF_CORE_READ(inode, i_sb, s_dev);
break;
}

Expand All @@ -58,13 +58,21 @@ __always_inline static inode_key_t inode_to_key(struct inode* inode) {
return key;
}

__always_inline static inode_value_t* inode_get(struct inode_key_t* inode) {
__always_inline static inode_value_t* inode_get(const struct inode_key_t* inode) {
if (inode == NULL) {
return NULL;
}
return bpf_map_lookup_elem(&inode_map, inode);
}

__always_inline static long inode_add(struct inode_key_t* inode) {
if (inode == NULL) {
return -1;
}
inode_value_t value = 0;
return bpf_map_update_elem(&inode_map, inode, &value, BPF_ANY);
}

__always_inline static long inode_remove(struct inode_key_t* inode) {
if (inode == NULL) {
return 0;
Expand All @@ -75,6 +83,7 @@ __always_inline static long inode_remove(struct inode_key_t* inode) {
typedef enum inode_monitored_t {
NOT_MONITORED = 0,
MONITORED,
PARENT_MONITORED,
} inode_monitored_t;

/**
Expand Down
34 changes: 24 additions & 10 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,21 @@ int BPF_PROG(trace_file_open, struct file* file) {
inode_key_t inode_key = inode_to_key(file->f_inode);
inode_key_t* inode_to_submit = &inode_key;

if (!is_monitored(inode_key, path, &inode_to_submit)) {
struct dentry* parent_dentry = BPF_CORE_READ(file, f_path.dentry, d_parent);
struct inode* parent_inode_ptr = parent_dentry ? BPF_CORE_READ(parent_dentry, d_inode) : NULL;
inode_key_t parent_key = inode_to_key(parent_inode_ptr);

inode_monitored_t status = is_monitored_with_parent(inode_key, path, &parent_key, &inode_to_submit);

if (status == PARENT_MONITORED && event_type == FILE_ACTIVITY_CREATION) {
inode_add(&inode_key);
}

if (status == NOT_MONITORED) {
goto ignored;
}

submit_open_event(&m->file_open, event_type, path->path, inode_to_submit);
submit_open_event(&m->file_open, event_type, path->path, inode_to_submit, &parent_key);

return 0;

Expand Down Expand Up @@ -79,14 +89,15 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
inode_key_t inode_key = inode_to_key(dentry->d_inode);
inode_key_t* inode_to_submit = &inode_key;

if (!is_monitored(inode_key, path, &inode_to_submit)) {
if (is_monitored(inode_key, path, &inode_to_submit) == NOT_MONITORED) {
m->path_unlink.ignored++;
return 0;
}

submit_unlink_event(&m->path_unlink,
path->path,
inode_to_submit);
inode_to_submit,
NULL);
return 0;
}

Expand All @@ -109,7 +120,7 @@ int BPF_PROG(trace_path_chmod, struct path* path, umode_t mode) {
inode_key_t inode_key = inode_to_key(path->dentry->d_inode);
inode_key_t* inode_to_submit = &inode_key;

if (!is_monitored(inode_key, bound_path, &inode_to_submit)) {
if (is_monitored(inode_key, bound_path, &inode_to_submit) == NOT_MONITORED) {
m->path_chmod.ignored++;
return 0;
}
Expand All @@ -118,6 +129,7 @@ int BPF_PROG(trace_path_chmod, struct path* path, umode_t mode) {
submit_mode_event(&m->path_chmod,
bound_path->path,
inode_to_submit,
NULL,
mode,
old_mode);

Expand Down Expand Up @@ -146,7 +158,7 @@ int BPF_PROG(trace_path_chown, struct path* path, unsigned long long uid, unsign
inode_key_t inode_key = inode_to_key(path->dentry->d_inode);
inode_key_t* inode_to_submit = &inode_key;

if (!is_monitored(inode_key, bound_path, &inode_to_submit)) {
if (is_monitored(inode_key, bound_path, &inode_to_submit) == NOT_MONITORED) {
m->path_chown.ignored++;
return 0;
}
Expand All @@ -158,6 +170,7 @@ int BPF_PROG(trace_path_chown, struct path* path, unsigned long long uid, unsign
submit_ownership_event(&m->path_chown,
bound_path->path,
inode_to_submit,
NULL,
uid,
gid,
old_uid,
Expand Down Expand Up @@ -195,10 +208,10 @@ int BPF_PROG(trace_path_rename, struct path* old_dir,
inode_key_t* old_inode_submit = &old_inode;
inode_key_t* new_inode_submit = &new_inode;

bool old_monitored = is_monitored(old_inode, old_path, &old_inode_submit);
bool new_monitored = is_monitored(new_inode, new_path, &new_inode_submit);
inode_monitored_t old_monitored = is_monitored(old_inode, old_path, &old_inode_submit);
inode_monitored_t new_monitored = is_monitored(new_inode, new_path, &new_inode_submit);

if (!old_monitored && !new_monitored) {
if (old_monitored == NOT_MONITORED && new_monitored == NOT_MONITORED) {
m->path_rename.ignored++;
return 0;
}
Expand All @@ -207,7 +220,8 @@ int BPF_PROG(trace_path_rename, struct path* old_dir,
new_path->path,
old_path->path,
old_inode_submit,
new_inode_submit);
new_inode_submit,
NULL);
return 0;

error:
Expand Down
1 change: 1 addition & 0 deletions fact-ebpf/src/bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ struct event_t {
process_t process;
char filename[PATH_MAX];
inode_key_t inode;
inode_key_t parent_inode;
file_activity_type_t type;
union {
struct {
Expand Down
33 changes: 29 additions & 4 deletions fact/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ impl Event {
filename,
host_file,
inode: Default::default(),
parent_inode: Default::default(),
};
let file = match data {
EventTestData::Creation => FileData::Creation(inner),
Expand Down Expand Up @@ -125,6 +126,10 @@ impl Event {
})
}

pub fn is_creation(&self) -> bool {
matches!(self.file, FileData::Creation(_))
}

/// Unwrap the inner FileData and return the inode that triggered
/// the event.
///
Expand All @@ -141,6 +146,18 @@ impl Event {
}
}

/// Get the parent inode for the file in this event.
pub fn get_parent_inode(&self) -> &inode_key_t {
match &self.file {
FileData::Open(data) => &data.parent_inode,
FileData::Creation(data) => &data.parent_inode,
FileData::Unlink(data) => &data.parent_inode,
FileData::Chmod(data) => &data.inner.parent_inode,
FileData::Chown(data) => &data.inner.parent_inode,
FileData::Rename(data) => &data.new.parent_inode,
}
}

/// Same as `get_inode` but returning the 'old' inode for operations
/// like rename. For operations that involve a single inode, `None`
/// will be returned.
Expand All @@ -151,7 +168,7 @@ impl Event {
}
}

fn get_filename(&self) -> &PathBuf {
pub fn get_filename(&self) -> &PathBuf {
match &self.file {
FileData::Open(data) => &data.filename,
FileData::Creation(data) => &data.filename,
Expand Down Expand Up @@ -233,6 +250,7 @@ impl TryFrom<&event_t> for Event {
value.type_,
value.filename,
value.inode,
value.parent_inode,
value.__bindgen_anon_1,
)?;

Expand Down Expand Up @@ -282,9 +300,10 @@ impl FileData {
event_type: file_activity_type_t,
filename: [c_char; PATH_MAX as usize],
inode: inode_key_t,
parent_inode: inode_key_t,
extra_data: fact_ebpf::event_t__bindgen_ty_1,
) -> anyhow::Result<Self> {
let inner = BaseFileData::new(filename, inode)?;
let inner = BaseFileData::new(filename, inode, parent_inode)?;
let file = match event_type {
file_activity_type_t::FILE_ACTIVITY_OPEN => FileData::Open(inner),
file_activity_type_t::FILE_ACTIVITY_CREATION => FileData::Creation(inner),
Expand Down Expand Up @@ -312,7 +331,7 @@ impl FileData {
let old_inode = unsafe { extra_data.rename.old_inode };
let data = RenameFileData {
new: inner,
old: BaseFileData::new(old_filename, old_inode)?,
old: BaseFileData::new(old_filename, old_inode, Default::default())?,
};
FileData::Rename(data)
}
Expand Down Expand Up @@ -376,14 +395,20 @@ pub struct BaseFileData {
pub filename: PathBuf,
host_file: PathBuf,
inode: inode_key_t,
parent_inode: inode_key_t,
}

impl BaseFileData {
pub fn new(filename: [c_char; PATH_MAX as usize], inode: inode_key_t) -> anyhow::Result<Self> {
pub fn new(
filename: [c_char; PATH_MAX as usize],
inode: inode_key_t,
parent_inode: inode_key_t,
) -> anyhow::Result<Self> {
Ok(BaseFileData {
filename: sanitize_d_path(&filename),
host_file: PathBuf::new(), // this field is set by HostScanner
inode,
parent_inode,
})
}
}
Expand Down
Loading
Loading