Skip to content
Open
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
49 changes: 38 additions & 11 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,12 @@
#include <bpf/bpf_helpers.h>
// clang-format on

__always_inline static void submit_event(struct metrics_by_hook_t* m,
file_activity_type_t event_type,
const char filename[PATH_MAX],
inode_key_t* inode,
bool use_bpf_d_path) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
return;
}

__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,
bool use_bpf_d_path) {
event->type = event_type;
event->timestamp = bpf_ktime_get_boot_ns();
inode_copy_or_reset(&event->inode, inode);
Expand All @@ -46,3 +41,35 @@ __always_inline static void submit_event(struct metrics_by_hook_t* m,
m->error++;
bpf_ringbuf_discard(event, 0);
}

__always_inline static void submit_event(struct metrics_by_hook_t* m,
file_activity_type_t event_type,
const char filename[PATH_MAX],
inode_key_t* inode,
bool use_bpf_d_path) {
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, use_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,
umode_t mode,
umode_t old_mode,
bool use_bpf_d_path) {
struct event_t* event = bpf_ringbuf_reserve(&rb, sizeof(struct event_t), 0);
if (event == NULL) {
m->ringbuffer_full++;
return;
}

event->chmod.new = mode;
event->chmod.old = old_mode;

__submit_event(event, m, FILE_ACTIVITY_CHMOD, filename, inode, use_bpf_d_path);
}
52 changes: 50 additions & 2 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
m->path_unlink.total++;

struct bound_path_t* path = NULL;
if (path_unlink_supports_bpf_d_path) {
if (path_hooks_support_bpf_d_path) {
path = path_read(dir);
} else {
path = path_read_no_d_path(dir);
Expand Down Expand Up @@ -118,10 +118,58 @@ int BPF_PROG(trace_path_unlink, struct path* dir, struct dentry* dentry) {
FILE_ACTIVITY_UNLINK,
path->path,
&inode_key,
path_unlink_supports_bpf_d_path);
path_hooks_support_bpf_d_path);
return 0;

error:
m->path_unlink.error++;
return 0;
}

SEC("lsm/path_chmod")
int BPF_PROG(trace_path_chmod, struct path* path, umode_t mode) {
struct metrics_t* m = get_metrics();
if (m == NULL) {
return 0;
}

m->path_chmod.total++;

struct bound_path_t* bound_path = NULL;
if (path_hooks_support_bpf_d_path) {
bound_path = path_read(path);
} else {
bound_path = path_read_no_d_path(path);
}

if (bound_path == NULL) {
bpf_printk("Failed to read path");
m->path_chmod.error++;
return 0;
}

inode_key_t inode_key = inode_to_key(path->dentry->d_inode);
const inode_value_t* inode = inode_get(&inode_key);

switch (inode_is_monitored(inode)) {
case NOT_MONITORED:
if (!is_monitored(bound_path)) {
m->path_chmod.ignored++;
return 0;
}
break;

case MONITORED:
break;
}

umode_t old_mode = BPF_CORE_READ(path, dentry, d_inode, i_mode);
submit_mode_event(&m->path_chmod,
bound_path->path,
&inode_key,
mode,
old_mode,
path_hooks_support_bpf_d_path);

return 0;
}
2 changes: 1 addition & 1 deletion fact-ebpf/src/bpf/maps.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,6 @@ __always_inline static struct metrics_t* get_metrics() {
}

uint64_t host_mount_ns;
volatile const bool path_unlink_supports_bpf_d_path;
volatile const bool path_hooks_support_bpf_d_path;

// clang-format on
8 changes: 8 additions & 0 deletions fact-ebpf/src/bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ typedef enum file_activity_type_t {
FILE_ACTIVITY_OPEN = 0,
FILE_ACTIVITY_CREATION,
FILE_ACTIVITY_UNLINK,
FILE_ACTIVITY_CHMOD,
} file_activity_type_t;

struct event_t {
Expand All @@ -55,6 +56,12 @@ struct event_t {
char filename[PATH_MAX];
inode_key_t inode;
file_activity_type_t type;
union {
struct {
short unsigned int new;
short unsigned int old;
} chmod;
};
};

/**
Expand Down Expand Up @@ -83,4 +90,5 @@ struct metrics_by_hook_t {
struct metrics_t {
struct metrics_by_hook_t file_open;
struct metrics_by_hook_t path_unlink;
struct metrics_by_hook_t path_chmod;
};
1 change: 1 addition & 0 deletions fact-ebpf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ impl metrics_t {
let mut m = metrics_t { ..*self };
m.file_open = m.file_open.accumulate(&other.file_open);
m.path_unlink = m.path_unlink.accumulate(&other.path_unlink);
m.path_chmod = m.path_chmod.accumulate(&other.path_chmod);
m
}
}
Expand Down
8 changes: 4 additions & 4 deletions fact/src/bpf/checks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use aya::{programs::Lsm, Btf};
use log::debug;

pub(super) struct Checks {
pub(super) path_unlink_supports_bpf_d_path: bool,
pub(super) path_hooks_support_bpf_d_path: bool,
}

impl Checks {
Expand All @@ -16,11 +16,11 @@ impl Checks {
.program_mut("check_path_unlink_supports_bpf_d_path")
.context("Failed to find 'check_path_unlink_supports_bpf_d_path' program")?;
let prog: &mut Lsm = prog.try_into()?;
let path_unlink_supports_bpf_d_path = prog.load("path_unlink", btf).is_ok();
debug!("path_unlink_supports_bpf_d_path: {path_unlink_supports_bpf_d_path}");
let path_hooks_support_bpf_d_path = prog.load("path_unlink", btf).is_ok();
debug!("path_unlink_supports_bpf_d_path: {path_hooks_support_bpf_d_path}");

Ok(Checks {
path_unlink_supports_bpf_d_path,
path_hooks_support_bpf_d_path,
})
}
}
72 changes: 47 additions & 25 deletions fact/src/bpf/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use std::{io, path::PathBuf};
use anyhow::{bail, Context};
use aya::{
maps::{Array, HashMap, LpmTrie, MapData, PerCpuArray, RingBuf},
programs::Lsm,
programs::Program,
Btf, Ebpf,
};
use checks::Checks;
use libc::c_char;
use log::{debug, error, info};
use log::{error, info};
use tokio::{
io::unix::AsyncFd,
sync::{mpsc, watch},
Expand Down Expand Up @@ -48,8 +48,8 @@ impl Bpf {
let obj = aya::EbpfLoader::new()
.set_global("host_mount_ns", &host_info::get_host_mount_ns(), true)
.set_global(
"path_unlink_supports_bpf_d_path",
&(checks.path_unlink_supports_bpf_d_path as u8),
"path_hooks_support_bpf_d_path",
&(checks.path_hooks_support_bpf_d_path as u8),
true,
)
.set_max_entries(RINGBUFFER_NAME, ringbuf_size * 1024)
Expand Down Expand Up @@ -143,24 +143,28 @@ impl Bpf {
Ok(())
}

fn load_lsm_prog(&mut self, name: &str, hook: &str, btf: &Btf) -> anyhow::Result<()> {
let Some(prog) = self.obj.program_mut(name) else {
bail!("{name} program not found");
};
let prog: &mut Lsm = prog.try_into()?;
prog.load(hook, btf)?;
Ok(())
}

fn load_progs(&mut self, btf: &Btf) -> anyhow::Result<()> {
self.load_lsm_prog("trace_file_open", "file_open", btf)?;
self.load_lsm_prog("trace_path_unlink", "path_unlink", btf)
for (name, prog) in self.obj.programs_mut() {
// The format used for our hook names is `trace_<hook>`, so
// we can just strip trace_ to get the hook name we need for
// loading.
let Some(hook) = name.strip_prefix("trace_") else {
bail!("Invalid hook name: {name}");
};
match prog {
Program::Lsm(prog) => prog.load(hook, btf)?,
u => unimplemented!("{u:?}"),
}
}
Ok(())
}

fn attach_progs(&mut self) -> anyhow::Result<()> {
for (_, prog) in self.obj.programs_mut() {
let prog: &mut Lsm = prog.try_into()?;
prog.attach()?;
match prog {
Program::Lsm(prog) => prog.attach()?,
u => unimplemented!("{u:?}"),
};
}
Ok(())
}
Expand Down Expand Up @@ -192,7 +196,6 @@ impl Bpf {
Ok(event) => event,
Err(e) => {
error!("Failed to parse event: '{e}'");
debug!("Event: {event:?}");
event_counter.dropped();
continue;
}
Expand Down Expand Up @@ -225,15 +228,14 @@ impl Bpf {

#[cfg(all(test, feature = "bpf-test"))]
mod bpf_tests {
use std::{env, path::PathBuf, time::Duration};
use std::{env, os::unix::fs::PermissionsExt, path::PathBuf, time::Duration};

use fact_ebpf::file_activity_type_t;
use tempfile::NamedTempFile;
use tokio::{sync::watch, time::timeout};

use crate::{
config::{reloader::Reloader, FactConfig},
event::process::Process,
event::{process::Process, EventTestData},
host_info,
metrics::exporter::Exporter,
};
Expand Down Expand Up @@ -270,20 +272,39 @@ mod bpf_tests {
let file = NamedTempFile::new_in(monitored_path).expect("Failed to create temporary file");
println!("Created {file:?}");

// Trigger permission changes
let mut perms = file
.path()
.metadata()
.expect("Failed to read file permissions")
.permissions();
let old_perm = perms.mode() as u16;
let new_perm: u16 = 0o666;
perms.set_mode(new_perm as u32);
std::fs::set_permissions(file.path(), perms).expect("Failed to set file permissions");

let current = Process::current();
let file_path = file.path().to_path_buf();

let expected_events = [
Event::new(
file_activity_type_t::FILE_ACTIVITY_CREATION,
EventTestData::Creation,
host_info::get_hostname(),
file_path.clone(),
PathBuf::new(), // host path is resolved by HostScanner
current.clone(),
)
.unwrap(),
Event::new(
EventTestData::Chmod(new_perm, old_perm),
host_info::get_hostname(),
file_path.clone(),
PathBuf::new(), // host path is resolved by HostScanner
current.clone(),
)
.unwrap(),
Event::new(
file_activity_type_t::FILE_ACTIVITY_UNLINK,
EventTestData::Unlink,
host_info::get_hostname(),
file_path,
PathBuf::new(), // host path is resolved by HostScanner
Expand All @@ -295,12 +316,13 @@ mod bpf_tests {
// Close the file, removing it
file.close().expect("Failed to close temp file");

println!("Expected: {expected_events:?}");
let wait = timeout(Duration::from_secs(1), async move {
for expected in expected_events {
println!("expected: {expected:#?}");
while let Some(event) = rx.recv().await {
println!("{event:?}");
println!("{event:#?}");
if event == expected {
println!("Found!");
break;
}
}
Expand Down
Loading
Loading