diff --git a/crates/cardwire-ebpf-userspace/src/lib.rs b/crates/cardwire-ebpf-userspace/src/lib.rs index 8e7ffe01..07d603d9 100644 --- a/crates/cardwire-ebpf-userspace/src/lib.rs +++ b/crates/cardwire-ebpf-userspace/src/lib.rs @@ -1,12 +1,14 @@ //! main lib code of cardwire-ebpf mod errors; +use std::{fs, path::Path}; + pub use crate::errors::{CardwireEbpfError, CardwireEbpfResult}; use aya::{ Btf, Ebpf, maps::{Array, HashMap, MapError, RingBuf}, programs::{Lsm, TracePoint} }; use aya_log::EbpfLogger; -use log::{Log, error, info}; +use log::{Log, error, info, warn}; use tokio::io::{Interest, unix::AsyncFd}; pub enum EbpfSettings { @@ -32,8 +34,8 @@ impl EbpfBlocker { let btf = Btf::from_sys_fs().map_err(CardwireEbpfError::aya)?; - let load_list: [&str; 3] = ["file_open", "inode_permission", "inode_getattr"]; - for entity in load_list { + let lsm_load_list: [&str; 3] = ["file_open", "inode_permission", "inode_getattr"]; + for entity in lsm_load_list { let program: &mut Lsm = ebpf .program_mut(entity) .ok_or_else(|| CardwireEbpfError::missing_lsm(entity))? @@ -62,35 +64,73 @@ impl EbpfBlocker { close_program .attach("sched", "sched_process_exit") .map_err(CardwireEbpfError::aya)?; - // to hide files - let cardwire_sys_enter_getdents64: &mut TracePoint = ebpf - .program_mut("tracepoint_enter_getdents64") - .ok_or_else(|| CardwireEbpfError::missing_lsm("tracepoint_enter_getdents64"))? - .try_into() - .map_err(CardwireEbpfError::aya)?; - cardwire_sys_enter_getdents64 - .load() - .map_err(CardwireEbpfError::aya)?; + /* + This part can get rejected by the kernel if the lockdown is enabled, we warn but we do not exit carwired, it will just run in a weakened state + sys_exit_getdents64 re-write userspace memory to hide an entry (file/folder), it can be rejected + Only load sys_enter_getdents64 (syscall that will populate the CW_DIRENT MAP) if sys_exit_getdents64 doesnt fail, else the map will overflow + */ + + let mut did_sys_exit_getdents64_success = false; - cardwire_sys_enter_getdents64 - .attach("syscalls", "sys_enter_getdents64") - .map_err(CardwireEbpfError::aya)?; - // to hide files let cardwire_sys_exit_getdents64: &mut TracePoint = ebpf .program_mut("tracepoint_exit_getdents64") .ok_or_else(|| CardwireEbpfError::missing_lsm("tracepoint_exit_getdents64"))? .try_into() .map_err(CardwireEbpfError::aya)?; - cardwire_sys_exit_getdents64 + // Try to load the program into the kernel, if success attach it, else just warn the user + match cardwire_sys_exit_getdents64 .load() - .map_err(CardwireEbpfError::aya)?; + .map_err(CardwireEbpfError::aya) + { + Ok(_) => { + did_sys_exit_getdents64_success = true; + cardwire_sys_exit_getdents64 + .attach("syscalls", "sys_exit_getdents64") + .map_err(CardwireEbpfError::aya)?; + } + Err(err) => { + // If we cannot load the program, it usually mean the kernel lockdown is enabled + let lockdown = is_lockdown_enabled(); + warn!( + "Failed to load sys_exit_getdents64. Lockdown status: {}", + lockdown + ); + warn!("{}", err); + warn!("falling back to a weakened cardwired..."); + } + }; + + // Now we try to load sys_enter_getdents64 - cardwire_sys_exit_getdents64 - .attach("syscalls", "sys_exit_getdents64") + let cardwire_sys_enter_getdents64: &mut TracePoint = ebpf + .program_mut("tracepoint_enter_getdents64") + .ok_or_else(|| CardwireEbpfError::missing_lsm("tracepoint_enter_getdents64"))? + .try_into() .map_err(CardwireEbpfError::aya)?; + if did_sys_exit_getdents64_success { + match cardwire_sys_enter_getdents64 + .load() + .map_err(CardwireEbpfError::aya) + { + Ok(_) => { + cardwire_sys_enter_getdents64 + .attach("syscalls", "sys_enter_getdents64") + .map_err(CardwireEbpfError::aya)?; + } + Err(err) => { + let lockdown = is_lockdown_enabled(); + warn!( + "Failed to load sys_enter_getdents64. Lockdown status: {}", + lockdown + ); + warn!("{}", err); + warn!("falling back to a weakened cardwired..."); + } + }; + } Ok(Self { ebpf }) } @@ -370,6 +410,17 @@ impl EbpfBlocker { } } +fn is_lockdown_enabled() -> bool { + let path = Path::new("/sys/kernel/security/lockdown"); + if let Ok(entry) = fs::read_to_string(path) + && (entry.contains("[integrity]") || entry.contains("[confidentiality]")) + { + return true; + } + + false +} + #[cfg(test)] mod tests { use super::*;