Skip to content
12 changes: 12 additions & 0 deletions fact-ebpf/src/bpf/events.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,15 @@ __always_inline static void submit_rmdir_event(struct submit_event_args_t* args)

__submit_event(args, path_hooks_support_bpf_d_path);
}

__always_inline static void submit_xattr_event(struct submit_event_args_t* args,
file_activity_type_t event_type,
const char* xattr_name) {
if (!reserve_event(args)) {
return;
}
args->event->type = event_type;
bpf_probe_read_str(args->event->xattr.name, XATTR_NAME_MAX_LEN, xattr_name);

__submit_event(args, false);
}
52 changes: 52 additions & 0 deletions fact-ebpf/src/bpf/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,58 @@ int BPF_PROG(trace_d_instantiate, struct dentry* dentry, struct inode* inode) {
return 0;
}

__always_inline static int handle_xattr(struct metrics_by_hook_t* hook_metrics,
struct dentry* dentry,
const char* xattr_name,
file_activity_type_t event_type) {
struct submit_event_args_t args = {.metrics = hook_metrics};

args.metrics->total++;

args.inode = inode_to_key(dentry->d_inode);
args.parent_inode = inode_to_key(BPF_CORE_READ(dentry, d_parent, d_inode));

args.monitored = inode_is_monitored(inode_get(&args.inode), inode_get(&args.parent_inode));

if (args.monitored == NOT_MONITORED) {
args.metrics->ignored++;
return 0;
}

// inode hooks don't provide a struct path, so filename is left empty.
// __submit_event requires a valid pointer for bpf_probe_read_str.
struct bound_path_t* bound_path = get_bound_path(BOUND_PATH_MAIN);
if (bound_path == NULL) {
args.metrics->error++;
return 0;
}
bound_path->path[0] = '\0';
args.filename = bound_path->path;
Comment on lines +410 to +418

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Would it be too complicated to change the behavior of __submit_event to check whether the path is valid?


submit_xattr_event(&args, event_type, xattr_name);
return 0;
}

SEC("lsm/inode_setxattr")
int BPF_PROG(trace_inode_setxattr, struct mnt_idmap* idmap, struct dentry* dentry,
const char* name, const void* value, size_t size, int flags) {
struct metrics_t* m = get_metrics();
if (m == NULL) {
return 0;
}
return handle_xattr(&m->inode_setxattr, dentry, name, FILE_ACTIVITY_SETXATTR);
}

SEC("lsm/inode_removexattr")
int BPF_PROG(trace_inode_removexattr, struct mnt_idmap* idmap, struct dentry* dentry,
const char* name) {
struct metrics_t* m = get_metrics();
if (m == NULL) {
return 0;
}
return handle_xattr(&m->inode_removexattr, dentry, name, FILE_ACTIVITY_REMOVEXATTR);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

SEC("lsm/path_rmdir")
int BPF_PROG(trace_path_rmdir, struct path* dir, struct dentry* dentry) {
struct metrics_t* m = get_metrics();
Expand Down
10 changes: 10 additions & 0 deletions fact-ebpf/src/bpf/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@

#define LINEAGE_MAX 2

// Matches Linux kernel XATTR_NAME_MAX (255) + null terminator

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you get a github or elixir.bootlin to where this definition is?

#define XATTR_NAME_MAX_LEN 256

#define LPM_SIZE_MAX 256

typedef struct lineage_t {
Expand Down Expand Up @@ -64,6 +67,8 @@ typedef enum file_activity_type_t {
FILE_ACTIVITY_RENAME,
DIR_ACTIVITY_CREATION,
DIR_ACTIVITY_UNLINK,
FILE_ACTIVITY_SETXATTR,
FILE_ACTIVITY_REMOVEXATTR,
} file_activity_type_t;

struct event_t {
Expand All @@ -90,6 +95,9 @@ struct event_t {
inode_key_t inode;
monitored_t monitored;
} rename;
struct {
char name[XATTR_NAME_MAX_LEN];
} xattr;
};
};

Expand Down Expand Up @@ -132,4 +140,6 @@ struct metrics_t {
struct metrics_by_hook_t path_mkdir;
struct metrics_by_hook_t d_instantiate;
struct metrics_by_hook_t path_rmdir;
struct metrics_by_hook_t inode_setxattr;
struct metrics_by_hook_t inode_removexattr;
};
2 changes: 2 additions & 0 deletions fact-ebpf/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,8 @@ impl metrics_t {
self.path_mkdir = self.path_mkdir.accumulate(&other.path_mkdir);
self.path_rmdir = self.path_rmdir.accumulate(&other.path_rmdir);
self.d_instantiate = self.d_instantiate.accumulate(&other.d_instantiate);
self.inode_setxattr = self.inode_setxattr.accumulate(&other.inode_setxattr);
self.inode_removexattr = self.inode_removexattr.accumulate(&other.inode_removexattr);
self
}
}
Expand Down
55 changes: 54 additions & 1 deletion fact/src/event/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ use std::{
use globset::GlobSet;
use serde::Serialize;

use fact_ebpf::{PATH_MAX, event_t, file_activity_type_t, inode_key_t, monitored_t};
use fact_ebpf::{
PATH_MAX, XATTR_NAME_MAX_LEN, event_t, file_activity_type_t, inode_key_t, monitored_t,
};

use crate::host_info;
use process::Process;
Expand Down Expand Up @@ -131,6 +133,10 @@ impl Event {
matches!(self.file, FileData::Creation(_) | FileData::MkDir(_))
}

pub fn is_xattr(&self) -> bool {
matches!(self.file, FileData::SetXattr(_) | FileData::RemoveXattr(_))
}

pub fn is_mkdir(&self) -> bool {
matches!(self.file, FileData::MkDir(_))
}
Expand Down Expand Up @@ -162,6 +168,8 @@ impl Event {
FileData::Chmod(data) => &data.inner.inode,
FileData::Chown(data) => &data.inner.inode,
FileData::Rename(data) => &data.new.inode,
FileData::SetXattr(data) => &data.inner.inode,
FileData::RemoveXattr(data) => &data.inner.inode,
}
}

Expand All @@ -176,6 +184,8 @@ impl Event {
FileData::Chmod(data) => &data.inner.parent_inode,
FileData::Chown(data) => &data.inner.parent_inode,
FileData::Rename(data) => &data.new.parent_inode,
FileData::SetXattr(data) => &data.inner.parent_inode,
FileData::RemoveXattr(data) => &data.inner.parent_inode,
}
}

Expand All @@ -199,6 +209,8 @@ impl Event {
FileData::Chmod(data) => &data.inner.filename,
FileData::Chown(data) => &data.inner.filename,
FileData::Rename(data) => &data.new.filename,
FileData::SetXattr(data) => &data.inner.filename,
FileData::RemoveXattr(data) => &data.inner.filename,
}
}

Expand All @@ -219,6 +231,8 @@ impl Event {
FileData::Chmod(data) => &data.inner.host_file,
FileData::Chown(data) => &data.inner.host_file,
FileData::Rename(data) => &data.new.host_file,
FileData::SetXattr(data) => &data.inner.host_file,
FileData::RemoveXattr(data) => &data.inner.host_file,
}
}

Expand All @@ -243,6 +257,8 @@ impl Event {
FileData::Chmod(data) => data.inner.host_file = host_path,
FileData::Chown(data) => data.inner.host_file = host_path,
FileData::Rename(data) => data.new.host_file = host_path,
FileData::SetXattr(data) => data.inner.host_file = host_path,
FileData::RemoveXattr(data) => data.inner.host_file = host_path,
}
}

Expand All @@ -264,6 +280,8 @@ impl Event {
FileData::Chmod(data) => data.inner.monitored,
FileData::Chown(data) => data.inner.monitored,
FileData::Rename(data) => data.new.monitored,
FileData::SetXattr(data) => data.inner.monitored,
FileData::RemoveXattr(data) => data.inner.monitored,
}
}

Expand Down Expand Up @@ -356,6 +374,8 @@ pub enum FileData {
Chmod(ChmodFileData),
Chown(ChownFileData),
Rename(RenameFileData),
SetXattr(XattrFileData),
RemoveXattr(XattrFileData),
}

impl FileData {
Expand Down Expand Up @@ -407,6 +427,18 @@ impl FileData {
};
FileData::Rename(data)
}
file_activity_type_t::FILE_ACTIVITY_SETXATTR => {
let xattr_name = slice_to_string(
&unsafe { extra_data.xattr }.name[..XATTR_NAME_MAX_LEN as usize],
)?;
FileData::SetXattr(XattrFileData { inner, xattr_name })
}
file_activity_type_t::FILE_ACTIVITY_REMOVEXATTR => {
let xattr_name = slice_to_string(
&unsafe { extra_data.xattr }.name[..XATTR_NAME_MAX_LEN as usize],
)?;
FileData::RemoveXattr(XattrFileData { inner, xattr_name })
}
invalid => unreachable!("Invalid event type: {invalid:?}"),
};

Expand All @@ -433,6 +465,12 @@ impl From<FileData> for fact_api::file_activity::File {
FileData::RmDir(_) => {
unreachable!("RmDir event reached protobuf conversion");
}
FileData::SetXattr(_) => {
unreachable!("SetXattr event reached protobuf conversion");
}
FileData::RemoveXattr(_) => {
unreachable!("RemoveXattr event reached protobuf conversion");
}
FileData::Unlink(event) => {
let activity = Some(fact_api::FileActivityBase::from(event));
let f_act = fact_api::FileUnlink { activity };
Expand Down Expand Up @@ -465,6 +503,8 @@ impl PartialEq for FileData {
(FileData::Unlink(this), FileData::Unlink(other)) => this == other,
(FileData::Chmod(this), FileData::Chmod(other)) => this == other,
(FileData::Rename(this), FileData::Rename(other)) => this == other,
(FileData::SetXattr(this), FileData::SetXattr(other)) => this == other,
(FileData::RemoveXattr(this), FileData::RemoveXattr(other)) => this == other,
_ => false,
}
}
Expand Down Expand Up @@ -595,6 +635,19 @@ impl PartialEq for RenameFileData {
}
}

#[derive(Debug, Clone, Serialize)]
pub struct XattrFileData {
inner: BaseFileData,
xattr_name: String,
}

#[cfg(test)]
impl PartialEq for XattrFileData {
fn eq(&self, other: &Self) -> bool {
self.xattr_name == other.xattr_name && self.inner == other.inner
}
}

#[cfg(test)]
mod test_utils {
use std::os::raw::c_char;
Expand Down
4 changes: 2 additions & 2 deletions fact/src/host_scanner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,8 +452,8 @@ You can increase this limit with:
self.handle_unlink_event(&event);
}

// Skip directory creation and deletion events - we track them internally but don't send to sensor
if event.is_mkdir() || event.is_rmdir() {
// Skip events that are tracked internally but not yet sent to sensor
if event.is_mkdir() || event.is_rmdir() || event.is_xattr() {
continue;
}

Expand Down
18 changes: 18 additions & 0 deletions fact/src/metrics/kernel_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ pub struct KernelMetrics {
path_mkdir: EventCounter,
path_rmdir: EventCounter,
d_instantiate: EventCounter,
inode_setxattr: EventCounter,
inode_removexattr: EventCounter,
map: PerCpuArray<MapData, metrics_t>,
}

Expand Down Expand Up @@ -61,6 +63,16 @@ impl KernelMetrics {
"Events processed by the d_instantiate LSM hook",
&[], // Labels are not needed since `collect` will add them all
);
let inode_setxattr = EventCounter::new(
"kernel_inode_setxattr_events",
"Events processed by the inode_setxattr LSM hook",
&[], // Labels are not needed since `collect` will add them all
);
let inode_removexattr = EventCounter::new(
"kernel_inode_removexattr_events",
"Events processed by the inode_removexattr LSM hook",
&[], // Labels are not needed since `collect` will add them all
);

file_open.register(reg);
path_unlink.register(reg);
Expand All @@ -70,6 +82,8 @@ impl KernelMetrics {
path_mkdir.register(reg);
path_rmdir.register(reg);
d_instantiate.register(reg);
inode_setxattr.register(reg);
inode_removexattr.register(reg);

KernelMetrics {
file_open,
Expand All @@ -80,6 +94,8 @@ impl KernelMetrics {
path_mkdir,
path_rmdir,
d_instantiate,
inode_setxattr,
inode_removexattr,
map: kernel_metrics,
}
}
Expand Down Expand Up @@ -132,6 +148,8 @@ impl KernelMetrics {
KernelMetrics::refresh_labels(&self.path_mkdir, &metrics.path_mkdir);
KernelMetrics::refresh_labels(&self.path_rmdir, &metrics.path_rmdir);
KernelMetrics::refresh_labels(&self.d_instantiate, &metrics.d_instantiate);
KernelMetrics::refresh_labels(&self.inode_setxattr, &metrics.inode_setxattr);
KernelMetrics::refresh_labels(&self.inode_removexattr, &metrics.inode_removexattr);

Ok(())
}
Expand Down
Loading
Loading