Skip to content
Merged
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
4 changes: 3 additions & 1 deletion bombini-common/src/event/file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ pub struct FileMsg {
pub name: [u8; MAX_FILENAME_SIZE],
/// flags passed to open()
/// or mount flags from sb_mount()
/// mmap flags
/// mmap flags, or ioctl cmd
pub flags: u32,
/// mmap protection falgs
pub prot: u32,
Expand All @@ -41,3 +41,5 @@ pub const HOOK_PATH_CHOWN: u8 = 4;
pub const HOOK_SB_MOUNT: u8 = 5;

pub const HOOK_MMAP_FILE: u8 = 6;

pub const HOOK_FILE_IOCTL: u8 = 7;
65 changes: 63 additions & 2 deletions bombini-detectors-ebpf/src/bin/filemon/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ use bombini_common::config::filemon::Config;

use bombini_common::constants::{MAX_FILENAME_SIZE, MAX_FILE_PATH, MAX_FILE_PREFIX};
use bombini_common::event::file::{
HOOK_FILE_OPEN, HOOK_MMAP_FILE, HOOK_PATH_CHMOD, HOOK_PATH_CHOWN, HOOK_PATH_TRUNCATE,
HOOK_PATH_UNLINK, HOOK_SB_MOUNT,
HOOK_FILE_IOCTL, HOOK_FILE_OPEN, HOOK_MMAP_FILE, HOOK_PATH_CHMOD, HOOK_PATH_CHOWN,
HOOK_PATH_TRUNCATE, HOOK_PATH_UNLINK, HOOK_SB_MOUNT,
};
use bombini_common::event::process::ProcInfo;
use bombini_common::event::{Event, MSG_FILE};
Expand Down Expand Up @@ -501,6 +501,67 @@ fn try_mmap_file(ctx: LsmContext, event: &mut Event) -> Result<i32, i32> {
Ok(0)
}

#[lsm(hook = "file_ioctl")]
pub fn file_ioctl_capture(ctx: LsmContext) -> i32 {
event_capture!(ctx, MSG_FILE, false, try_file_ioctl)
}

fn try_file_ioctl(ctx: LsmContext, event: &mut Event) -> Result<i32, i32> {
let Some(config_ptr) = FILEMON_CONFIG.get_ptr(0) else {
return Err(0);
};
let config = unsafe { config_ptr.as_ref() };
let Some(config) = config else {
return Err(0);
};
let Event::File(event) = event else {
return Err(0);
};
let pid = (bpf_get_current_pid_tgid() >> 32) as u32;
let proc = unsafe { PROCMON_PROC_MAP.get(&pid) };
let Some(proc) = proc else {
return Err(0);
};

// Filter event by process
let allow = if !config.filter_mask.is_empty() {
let process_filter: ProcessFilter = ProcessFilter::new(
&FILEMON_FILTER_UID_MAP,
&FILEMON_FILTER_EUID_MAP,
&FILEMON_FILTER_AUID_MAP,
&FILEMON_FILTER_BINNAME_MAP,
&FILEMON_FILTER_BINPATH_MAP,
&FILEMON_FILTER_BINPREFIX_MAP,
);
if config.deny_list {
!process_filter.filter(config.filter_mask, proc)
} else {
process_filter.filter(config.filter_mask, proc)
}
} else {
true
};

// Skip argument parsing if event is not exported
if !allow {
return Err(0);
}

event.hook = HOOK_FILE_IOCTL;
unsafe {
let fp: *const file = ctx.arg(0);
event.i_mode = (*(*fp).f_inode).i_mode;
event.flags = ctx.arg(1);
let _ = bpf_d_path(
&(*fp).f_path as *const _ as *mut aya_ebpf::bindings::path,
event.path.as_mut_ptr() as *mut _,
event.path.len() as u32,
);
}
util::copy_proc(proc, &mut event.process);
Ok(0)
}

#[panic_handler]
fn panic(_info: &core::panic::PanicInfo) -> ! {
unsafe { core::hint::unreachable_unchecked() }
Expand Down
11 changes: 11 additions & 0 deletions bombini/src/detector/filemon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,17 @@ impl Detector for FileMon {
mmap_file.attach()?;
}
}
if let Some(file_ioctl_cfg) = self.config.file_ioctl {
if !file_ioctl_cfg.disable {
let file_ioctl: &mut Lsm = self
.ebpf
.program_mut("file_ioctl_capture")
.unwrap()
.try_into()?;
file_ioctl.load("file_ioctl", &btf)?;
file_ioctl.attach()?;
}
}
Ok(())
}
}
Expand Down
5 changes: 4 additions & 1 deletion bombini/src/proto/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ pub struct FileMonConfig {
/// security_mmap_file config.
#[prost(message, optional, tag = "7")]
pub mmap_file: ::core::option::Option<FileHookConfig>,
/// Filter File events by Process information.
/// security_file_ioctl config.
#[prost(message, optional, tag = "8")]
pub file_ioctl: ::core::option::Option<FileHookConfig>,
/// Filter File events by Process information.
#[prost(message, optional, tag = "9")]
pub process_filter: ::core::option::Option<ProcessFilter>,
}
/// FileMon hook configuration
Expand Down
27 changes: 25 additions & 2 deletions bombini/src/transmuter/file.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Transmutes FileEvent to serialized format

use bombini_common::event::file::{
FileMsg, HOOK_FILE_OPEN, HOOK_MMAP_FILE, HOOK_PATH_CHMOD, HOOK_PATH_CHOWN, HOOK_PATH_TRUNCATE,
HOOK_PATH_UNLINK, HOOK_SB_MOUNT,
FileMsg, HOOK_FILE_IOCTL, HOOK_FILE_OPEN, HOOK_MMAP_FILE, HOOK_PATH_CHMOD, HOOK_PATH_CHOWN,
HOOK_PATH_TRUNCATE, HOOK_PATH_UNLINK, HOOK_SB_MOUNT,
};

use bitflags::bitflags;
Expand Down Expand Up @@ -240,6 +240,16 @@ pub struct MmapInfo {
flags: SharingType,
}

#[derive(Clone, Debug, Serialize)]
pub struct IoctlInfo {
/// full path
path: String,
/// i_mode
i_mode: Imode,
/// cmd
cmd: u32,
}

#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type")]
#[repr(u8)]
Expand All @@ -252,6 +262,7 @@ pub enum LsmFileHook {
PathChown(ChownInfo),
SbMount(MountInfo),
MmapFile(MmapInfo),
FileIoctl(IoctlInfo),
}

impl FileEvent {
Expand Down Expand Up @@ -339,6 +350,18 @@ impl FileEvent {
timestamp: transmute_ktime(ktime),
}
}
HOOK_FILE_IOCTL => {
let info = IoctlInfo {
path: str_from_bytes(&event.path),
i_mode: event.i_mode.into(),
cmd: event.flags,
};
Self {
process: Process::new(event.process),
hook: LsmFileHook::FileIoctl(info),
timestamp: transmute_ktime(ktime),
}
}
_ => {
panic!("unsupported LSM BPF File hook");
}
Expand Down
71 changes: 71 additions & 0 deletions bombini/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,77 @@ fn test_filemon_chmod_file() {
let _ = fs::remove_dir_all(bombini_temp_dir);
}

#[test]
fn test_filemon_ioctl_file() {
let (temp_dir, mut config, bpf_objs) = init_test_env();
let bombini_temp_dir = temp_dir.path();
let mut tmp_config = bombini_temp_dir.join("config/config.yaml");
let _ = fs::create_dir(bombini_temp_dir.join("config"));
let _ = fs::copy(&config, &tmp_config);
tmp_config.pop();
config.pop();
let _ = fs::copy(config.join("procmon.yaml"), tmp_config.join("procmon.yaml"));
let config_contents = r#"
file_ioctl:
disable: false
"#;
let filemon_config = tmp_config.join("filemon.yaml");
let _ = fs::write(&filemon_config, config_contents);
let _ = fs::copy(config.join("procmon.yaml"), tmp_config.join("procmon.yaml"));
let bombini_log =
File::create(bombini_temp_dir.join("bombini.log")).expect("can't create log file");
let event_log = temp_dir.path().join("events.log");

let bombini = Command::new(EXE_BOMBINI)
.args([
"--config-dir",
tmp_config.to_str().unwrap(),
"--bpf-objs",
bpf_objs.to_str().unwrap(),
"--event-log",
event_log.to_str().unwrap(),
"--detector",
"procmon",
"--detector",
"filemon",
])
.env("RUST_LOG", "debug")
.stderr(bombini_log.try_clone().unwrap())
.spawn();

if bombini.is_err() {
panic!("{:?}", bombini.err().unwrap());
}
let mut bombini = bombini.expect("failed to start bombini");
// Wait for detectors being loaded
thread::sleep(Duration::from_millis(2000));

let fdisk_status = Command::new("fdisk")
.args(["-l"])
.stdout(Stdio::null())
.stderr(Stdio::null())
.stdin(Stdio::null())
.status()
.expect("can't start fdisk");

assert!(fdisk_status.success());

// Wait Events being processed
thread::sleep(Duration::from_millis(500));

let _ = signal::kill(Pid::from_raw(bombini.id() as i32), Signal::SIGINT);

let _ = bombini.wait().unwrap();

// TODO: more precise check
let events = fs::read_to_string(&event_log).expect("can't read events");
ma::assert_ge!(events.matches("\"type\":\"FileEvent\"").count(), 1);
ma::assert_ge!(events.matches("\"type\":\"FileIoctl\"").count(), 1);
ma::assert_ge!(events.matches("\"path\":\"/dev/").count(), 1);

let _ = fs::remove_dir_all(bombini_temp_dir);
}

#[test]
fn test_filemon_chown_file() {
let (temp_dir, mut config, bpf_objs) = init_test_env();
Expand Down
2 changes: 2 additions & 0 deletions config/filemon.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ sb_mount:
disable: false
mmap_file:
disable: true
file_ioctl:
disable: true
40 changes: 37 additions & 3 deletions docs/detectors/filemon.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,18 @@ Detector for file operations. Each event has process information. Supported LSM
* `path_chmod` provides info about changing file permissions.
* `path_chown` provides info about changing file owner.
* `sb_mount` provides info about mounted devices.
* `file_ioctl` provides info about ioctl commands.

### Required Linux Kernel Version

* `file_open`: 5.15 or greater
* `mmap_file`: 5.15 or greater
* `path_truncate`: 6.5 or greater
* `sb_mount`: 5.15 or greater
* `file_ioctl`: 5.15 or greater
* `path_truncate`: 6.5 or greater
* `path_unlink`: 6.5 or greater
* `path_chmod`: 6.5 or greater
* `path_chown`: 6.5 or greater
* `sb_mount`: 6.5 or greater

### Config

Expand All @@ -31,6 +33,7 @@ Config represents a dictionary with supported LSM BPF file hooks:
* path_chmod
* path_chown
* sb_mount
* file_ioctl

For each file hook the following options are supported:

Expand Down Expand Up @@ -279,4 +282,35 @@ Event for `security_mmap_file`:
},
"timestamp": "2025-07-16T18:09:50.559Z"
}
```
```

Event for `security_file_ioctl`:

```
{
"type": "FileEvent",
"process": {
"pid": 42233,
"tid": 42233,
"ppid": 42231,
"uid": 1000,
"euid": 1000,
"auid": 1000,
"cap_inheritable": 0,
"cap_permitted": 0,
"cap_effective": 0,
"secureexec": "",
"filename": "sed",
"binary_path": "/usr/bin/sed",
"args": "--follow-symlinks s/// /dev/null",
"cgroup_name": "vte-spawn-db86626c-4758-4859-b61c-854f5c17628f.scope"
},
"hook": {
"type": "FileIoctl",
"path": "/dev/null",
"i_mode": "crw-rw-rw-",
"cmd": 21505
},
"timestamp": "2025-07-19T16:27:59.768Z"
}
```
4 changes: 3 additions & 1 deletion proto/config.proto
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,10 @@ message FileMonConfig {
FileHookConfig sb_mount = 6;
// security_mmap_file config.
FileHookConfig mmap_file = 7;
// security_file_ioctl config.
FileHookConfig file_ioctl = 8;
// Filter File events by Process information.
ProcessFilter process_filter = 8;
ProcessFilter process_filter = 9;
}

// FileMon hook configuration
Expand Down