#![cfg_attr(not(feature = "user"), no_std)]
pub const COMM_LEN: usize = 16;
pub const PATH_LEN: usize = 256;
pub const CREATOR_PATH_LEN: usize = 256;
pub const PATH_HASH_SCAN_LEN: usize = 64;
pub const MAX_FOLDER_HASHES: usize = 4;
pub const FNV_OFFSET: u64 = 0xcbf2_9ce4_8422_2325;
pub const FNV_PRIME: u64 = 0x100_0000_01b3;
pub fn fnv_hash(s: &str) -> u64 {
fnv_hash_bytes(s.as_bytes())
}
pub fn fnv_hash_bytes(bytes: &[u8]) -> u64 {
let mut h = FNV_OFFSET;
for &b in bytes {
h ^= b as u64;
h = h.wrapping_mul(FNV_PRIME);
}
h
}
pub mod dim {
pub const TARGET_FILENAME: u32 = 1 << 0;
pub const TARGET_FOLDER: u32 = 1 << 1;
pub const LANDING_FILENAME: u32 = 1 << 2;
pub const LANDING_FOLDER: u32 = 1 << 3;
pub const CREATOR_PROCESS: u32 = 1 << 4;
pub const CREATOR_COMM: u32 = 1 << 5;
pub const CREATOR_UID: u32 = 1 << 6;
pub const EXECUTION_UID: u32 = 1 << 7;
}
pub const MAX_RULES: usize = 32;
#[repr(C)]
#[derive(Copy, Clone)]
#[cfg_attr(feature = "user", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct AllowRule {
pub flags: u32,
pub creator_uid: u32,
pub execution_uid: u32,
pub _pad: u32,
pub creator_comm: [u8; COMM_LEN],
pub target_filename_hash: u64,
pub target_folder_hash: u64,
pub landing_filename_hash: u64,
pub landing_folder_hash: u64,
pub creator_process_hash: u64,
}
pub const XATTR_NAME: &str = "security.bpf.linprov.origin";
pub const EVENT_KIND_NETWORK_FILE_OPEN: u32 = 1;
pub const EVENT_KIND_EXECVE: u32 = 2;
pub const MODE_OBSERVE: u32 = 0;
pub const MODE_SOAK: u32 = 1; pub const MODE_ENFORCE: u32 = 2;
pub const ORIGIN_VERSION: u32 = 3;
#[repr(C)]
#[derive(Copy, Clone)]
#[cfg_attr(feature = "user", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct OriginRecord {
pub version: u32,
pub pid: u32,
pub ts_boot_ns: u64,
pub comm: [u8; COMM_LEN],
pub creator_uid: u32,
pub _pad: u32,
pub creator_path: [u8; CREATOR_PATH_LEN],
pub landing_filename: [u8; PATH_LEN],
}
#[repr(C)]
#[derive(Copy, Clone)]
#[cfg_attr(feature = "user", derive(bytemuck::Pod, bytemuck::Zeroable))]
pub struct Event {
pub kind: u32,
pub pid: u32,
pub tgid: u32,
pub status: i32,
pub comm: [u8; COMM_LEN],
pub origin: OriginRecord,
pub filename: [u8; PATH_LEN],
}
impl Event {
pub const SIZE: usize = core::mem::size_of::<Self>();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fnv_known_vectors() {
assert_eq!(fnv_hash(""), 0xcbf2_9ce4_8422_2325);
assert_eq!(fnv_hash("a"), 0xaf63_dc4c_8601_ec8c);
assert_eq!(fnv_hash("foobar"), 0x85944171_f73967e8);
}
#[test]
fn fnv_string_and_bytes_agree() {
let s = "/usr/bin/curl";
assert_eq!(fnv_hash(s), fnv_hash_bytes(s.as_bytes()));
}
#[test]
fn dim_flags_are_unique() {
let all = [
dim::TARGET_FILENAME,
dim::TARGET_FOLDER,
dim::LANDING_FILENAME,
dim::LANDING_FOLDER,
dim::CREATOR_PROCESS,
dim::CREATOR_COMM,
dim::CREATOR_UID,
dim::EXECUTION_UID,
];
let mut acc = 0u32;
for d in all {
assert_eq!(d.count_ones(), 1, "each dim is one bit");
assert_eq!(acc & d, 0, "dim {d:#b} overlaps with prior {acc:#b}");
acc |= d;
}
}
#[test]
fn origin_record_size_is_v3_expected() {
assert_eq!(core::mem::size_of::<OriginRecord>(), 552);
}
#[test]
fn allow_rule_size_has_no_padding() {
assert_eq!(core::mem::size_of::<AllowRule>(), 72);
}
#[test]
fn fnv_constants_match_reference() {
assert_eq!(FNV_OFFSET, 0xcbf2_9ce4_8422_2325);
assert_eq!(FNV_PRIME, 0x100_0000_01b3);
}
}