From 3edf813286663eb306c4d32d97abba1e5cdf9dc9 Mon Sep 17 00:00:00 2001 From: Mauro Ezequiel Moltrasio Date: Thu, 18 Dec 2025 18:24:52 +0100 Subject: [PATCH] feat(ebpf): use bpf_loop for local d_path implementation Part of ROX-32059. The local implementation of d_path now uses bpf_loop, which enables it to iterate further than the previously implemented loop that did at most 16 levels of path. --- fact-ebpf/src/bpf/d_path.h | 132 ++++++++++++++++++++++--------------- fact/src/bpf/mod.rs | 81 ++++++++++++----------- 2 files changed, 122 insertions(+), 91 deletions(-) diff --git a/fact-ebpf/src/bpf/d_path.h b/fact-ebpf/src/bpf/d_path.h index 506b1c06..f74f37f5 100644 --- a/fact-ebpf/src/bpf/d_path.h +++ b/fact-ebpf/src/bpf/d_path.h @@ -1,13 +1,74 @@ #pragma once // clang-format off -#include "maps.h" #include "vmlinux.h" +#include "maps.h" + #include #include // clang-format on +struct d_path_ctx { + struct helper_t* helper; + struct path root; + struct mount* mnt; + struct dentry* dentry; + int offset; + int buflen; + bool success; +}; + +static long __d_path_inner(uint32_t index, void* _ctx) { + struct d_path_ctx* ctx = (struct d_path_ctx*)_ctx; + struct dentry* dentry = ctx->dentry; + struct dentry* parent = BPF_CORE_READ(dentry, d_parent); + struct mount* mnt = ctx->mnt; + struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root); + + if (dentry == mnt_root) { + struct mount* m = BPF_CORE_READ(mnt, mnt_parent); + if (m != mnt) { + ctx->dentry = BPF_CORE_READ(mnt, mnt_mountpoint); + ctx->mnt = m; + return 0; + } + ctx->success = true; + return 1; + } + + if (dentry == parent) { + return 1; + } + + struct qstr d_name; + BPF_CORE_READ_INTO(&d_name, dentry, d_name); + int len = d_name.len & (PATH_MAX - 1); + if (len <= 0 || len >= ctx->buflen) { + return 1; + } + + int offset = ctx->offset - len; + if (offset <= 0) { + return 1; + } + offset &= PATH_MAX - 1; + + if (bpf_probe_read_kernel(&ctx->helper->buf[offset], len, d_name.name) != 0) { + return 1; + } + + offset--; + if (offset <= 0) { + return 1; + } + ctx->helper->buf[offset] = '/'; + + ctx->offset = offset; + ctx->dentry = parent; + return 0; +} + /** * Reimplementation of the kernel d_path function. * @@ -19,66 +80,31 @@ __always_inline static long __d_path(const struct path* path, char* buf, int buf return -1; } - struct helper_t* helper = get_helper(); - if (helper == NULL) { + int offset = (buflen - 1) & (PATH_MAX - 1); + struct d_path_ctx ctx = { + .buflen = buflen, + .helper = get_helper(), + .offset = offset, + }; + + if (ctx.helper == NULL) { return -1; } struct task_struct* task = (struct task_struct*)bpf_get_current_task(); - int offset = (buflen - 1) & (PATH_MAX - 1); - helper->buf[offset] = '\0'; // Ensure null termination - - struct path root; - BPF_CORE_READ_INTO(&root, task, fs, root); - struct mount* mnt = container_of(path->mnt, struct mount, mnt); - struct dentry* dentry; - BPF_CORE_READ_INTO(&dentry, path, dentry); - - for (int i = 0; i < 16 && (dentry != root.dentry || &mnt->mnt != root.mnt); i++) { - struct dentry* parent = BPF_CORE_READ(dentry, d_parent); - struct dentry* mnt_root = BPF_CORE_READ(mnt, mnt.mnt_root); - - if (dentry == mnt_root) { - struct mount* m = BPF_CORE_READ(mnt, mnt_parent); - if (m != mnt) { - dentry = BPF_CORE_READ(mnt, mnt_mountpoint); - mnt = m; - continue; - } - break; - } - - if (dentry == parent) { - return -1; - } + ctx.helper->buf[offset] = '\0'; // Ensure null termination - struct qstr d_name; - BPF_CORE_READ_INTO(&d_name, dentry, d_name); - int len = d_name.len; - if (len <= 0 || len >= buflen) { - return -1; - } + BPF_CORE_READ_INTO(&ctx.root, task, fs, root); + ctx.mnt = container_of(path->mnt, struct mount, mnt); + BPF_CORE_READ_INTO(&ctx.dentry, path, dentry); - offset -= len; - if (offset <= 0) { - return -1; - } - - if (bpf_probe_read_kernel(&helper->buf[offset], len, d_name.name) != 0) { - return -1; - } - - offset--; - if (offset <= 0) { - return -1; - } - helper->buf[offset] = '/'; - - dentry = parent; + long res = bpf_loop(PATH_MAX, __d_path_inner, &ctx, 0); + if (res <= 0 || !ctx.success) { + return -1; } - bpf_probe_read_str(buf, buflen, &helper->buf[offset]); - return buflen - offset; + bpf_probe_read_str(buf, buflen, &ctx.helper->buf[ctx.offset & (PATH_MAX - 1)]); + return buflen - ctx.offset; } __always_inline static long d_path(struct path* path, char* buf, int buflen, bool use_bpf_helper) { diff --git a/fact/src/bpf/mod.rs b/fact/src/bpf/mod.rs index 2a6b85e2..d8d6babf 100644 --- a/fact/src/bpf/mod.rs +++ b/fact/src/bpf/mod.rs @@ -227,7 +227,6 @@ impl Bpf { mod bpf_tests { use std::{env, path::PathBuf, time::Duration}; - use anyhow::Context; use fact_ebpf::file_activity_type_t; use tempfile::NamedTempFile; use tokio::{sync::watch, time::timeout}; @@ -241,16 +240,8 @@ mod bpf_tests { use super::*; - fn get_executor() -> anyhow::Result { - let executor = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .context("Failed building tokio runtime")?; - Ok(executor) - } - - #[test] - fn test_basic() { + #[tokio::test] + async fn test_basic() { if let Ok(value) = std::env::var("FACT_LOGLEVEL") { let value = value.to_lowercase(); if value == "debug" || value == "trace" { @@ -258,55 +249,69 @@ mod bpf_tests { } } - let executor = get_executor().unwrap(); let monitored_path = env!("CARGO_MANIFEST_DIR"); let monitored_path = PathBuf::from(monitored_path); let paths = vec![monitored_path.clone()]; let mut config = FactConfig::default(); config.set_paths(paths); let reloader = Reloader::from(config); - executor.block_on(async { - let (tx, mut rx) = mpsc::channel(100); - let mut bpf = Bpf::new(reloader.paths(), reloader.config().ringbuf_size(), tx) - .expect("Failed to load BPF code"); - let (run_tx, run_rx) = watch::channel(true); - // Create a metrics exporter, but don't start it - let exporter = Exporter::new(bpf.take_metrics().unwrap()); + let (tx, mut rx) = mpsc::channel(100); + let mut bpf = Bpf::new(reloader.paths(), reloader.config().ringbuf_size(), tx) + .expect("Failed to load BPF code"); + let (run_tx, run_rx) = watch::channel(true); + // Create a metrics exporter, but don't start it + let exporter = Exporter::new(bpf.take_metrics().unwrap()); + + let handle = bpf.start(run_rx, exporter.metrics.bpf_worker.clone()); - let handle = bpf.start(run_rx, exporter.metrics.bpf_worker.clone()); + tokio::time::sleep(Duration::from_millis(500)).await; - tokio::time::sleep(Duration::from_millis(500)).await; + // Create a file + let file = NamedTempFile::new_in(monitored_path).expect("Failed to create temporary file"); + println!("Created {file:?}"); - // Create a file - let file = - NamedTempFile::new_in(monitored_path).expect("Failed to create temporary file"); - println!("Created {file:?}"); + let current = Process::current(); + let file_path = file.path().to_path_buf(); - let expected = Event::new( + let expected_events = [ + Event::new( file_activity_type_t::FILE_ACTIVITY_CREATION, host_info::get_hostname(), - file.path().to_path_buf(), + file_path.clone(), + PathBuf::new(), // host path is resolved by HostScanner + current.clone(), + ) + .unwrap(), + Event::new( + file_activity_type_t::FILE_ACTIVITY_UNLINK, + host_info::get_hostname(), + file_path, PathBuf::new(), // host path is resolved by HostScanner - Process::current(), + current, ) - .unwrap(); + .unwrap(), + ]; - println!("Expected: {expected:?}"); - let wait = timeout(Duration::from_secs(1), async move { + // 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 { while let Some(event) = rx.recv().await { println!("{event:?}"); if event == expected { break; } } - }); - - tokio::select! { - res = wait => res.unwrap(), - res = handle => res.unwrap().unwrap(), } - - run_tx.send(false).unwrap(); }); + + tokio::select! { + res = wait => res.unwrap(), + res = handle => res.unwrap().unwrap(), + } + + run_tx.send(false).unwrap(); } }