pub fn classify_bpf_program(prog_type: &str, name: &str) -> bool {
match prog_type {
"tracing" | "raw_tracepoint" => name.is_empty(),
"kprobe" | "raw_tracepoint_writable" | "lsm" => true,
_ => false,
}
}
const CAP_NET_RAW: u64 = 1 << 13;
const CAP_SYS_MODULE: u64 = 1 << 16;
const CAP_SYS_PTRACE: u64 = 1 << 19;
const CAP_SYS_ADMIN: u64 = 1 << 21;
const SUSPICIOUS_CAPS: &[(u64, &str)] = &[
(CAP_SYS_ADMIN, "CAP_SYS_ADMIN"),
(CAP_SYS_PTRACE, "CAP_SYS_PTRACE"),
(CAP_SYS_MODULE, "CAP_SYS_MODULE"),
(CAP_NET_RAW, "CAP_NET_RAW"),
];
pub fn classify_capabilities(effective: u64, uid: u32) -> (bool, Vec<String>) {
if uid == 0 {
return (false, Vec::new());
}
let mut suspicious_names = Vec::new();
for &(cap_bit, cap_label) in SUSPICIOUS_CAPS {
if effective & cap_bit != 0 {
suspicious_names.push(cap_label.to_string());
}
}
let is_suspicious = !suspicious_names.is_empty();
(is_suspicious, suspicious_names)
}
pub fn classify_cgroup(path: &str) -> (bool, String) {
const RUNTIME_PREFIXES: &[&str] = &["/docker/", "/lxc/", "/kubepods/", "/containerd/"];
for prefix in RUNTIME_PREFIXES {
if let Some(idx) = path.find(prefix) {
let after_prefix = &path[idx + prefix.len()..];
let id = after_prefix.split('/').next().unwrap_or("").to_string();
return (true, id);
}
}
(false, String::new())
}
pub fn classify_afinfo_hook(hook_addr: u64, kernel_start: u64, kernel_end: u64) -> bool {
if hook_addr == 0 {
return false;
}
!(kernel_start <= hook_addr && hook_addr <= kernel_end)
}
fn is_likely_kernel_thread_heuristic(pid: u32) -> bool {
pid <= 2
}
pub fn classify_shared_creds(pid: u32, shared_with: &[u32], uid: u32) -> bool {
if shared_with.contains(&1) && pid != 1 {
if uid == 0 && is_likely_kernel_thread_heuristic(pid) {
return false;
}
return true;
}
if uid == 0 && is_likely_kernel_thread_heuristic(pid) {
return false;
}
!shared_with.is_empty()
}
pub fn classify_idt_entry(handler_addr: u64, kernel_start: u64, kernel_end: u64) -> bool {
if handler_addr == 0 {
return false;
}
!(kernel_start <= handler_addr && handler_addr <= kernel_end)
}
const KERNEL_THREAD_COMMS: &[&str] = &["kthread", "kworker", "migration", "ksoftirqd", "rcu_"];
pub fn classify_container_escape(comm: &str, indicator: &str) -> bool {
let is_kernel = KERNEL_THREAD_COMMS
.iter()
.any(|prefix| comm.starts_with(prefix));
if is_kernel {
return false;
}
matches!(indicator, "namespace_mismatch" | "host_mount_access")
}
const KNOWN_BENIGN_COMMS: &[&str] = &[
"apt",
"apt-get",
"apt-check",
"aptd",
"dpkg",
"dpkg-deb",
"yum",
"dnf",
"rpm",
"rpmdb",
"packagekitd",
"unattended-upgr",
];
pub fn classify_deleted_exe(exe_path: &str, comm: &str) -> bool {
if !exe_path.contains("(deleted)") {
return false;
}
if exe_path.is_empty() {
return false;
}
if comm.is_empty() {
return false;
}
let comm_lower = comm.to_lowercase();
for &benign in KNOWN_BENIGN_COMMS {
if comm_lower == benign {
return false;
}
}
true
}
const SUSPICIOUS_EXTENSIONS: &[&str] = &[".so", ".py", ".sh", ".elf", ".bin"];
pub fn classify_hidden_dentry(nlink: u32, filename: &str) -> bool {
if filename.is_empty() {
return false;
}
let name_lower = filename.to_lowercase();
if nlink > 0 {
return SUSPICIOUS_EXTENSIONS
.iter()
.any(|ext| name_lower.ends_with(ext));
}
true
}
const SUSPICIOUS_MAP_NAMES: &[&str] = &[
"rootkit",
"hide_",
"hook",
"intercept",
"stealth",
"secret",
"covert",
"keylog",
"exfil",
];
pub fn classify_ebpf_map(map_type: u32, name: &str, _value_size: u32) -> bool {
let name_lower = name.to_lowercase();
let suspicious_name = SUSPICIOUS_MAP_NAMES.iter().any(|p| name_lower.contains(p));
let high_risk_type = matches!(map_type, 3 | 26);
suspicious_name || high_risk_type
}
pub fn classify_ftrace_hook(func: u64, stext: u64, etext: u64) -> bool {
func < stext || func >= etext
}
pub fn classify_futex(key_address: u64, owner_pid: u32, waiter_count: u32) -> bool {
waiter_count > 1000 || (key_address > 0x7FFF_FFFF_FFFF && owner_pid > 0)
}
const IORING_OP_SENDMSG: u8 = 9;
const IORING_OP_RECVMSG: u8 = 10;
const IORING_OP_CONNECT: u8 = 16;
const SENSITIVE_OPCODES: &[u8] = &[IORING_OP_SENDMSG, IORING_OP_RECVMSG, IORING_OP_CONNECT];
pub fn classify_io_uring(opcodes: &[u8], seccomp_mode: u32) -> bool {
if seccomp_mode == 0 {
return false;
}
opcodes.iter().any(|op| SENSITIVE_OPCODES.contains(op))
}
pub fn classify_iomem(name: &str, start: u64, end: u64) -> bool {
let size = end.saturating_sub(start);
if name.is_empty() && size > 1024 * 1024 {
return true;
}
if name.chars().any(|c| c.is_control() || !c.is_ascii()) {
return true;
}
#[allow(clippy::items_after_statements)]
const KERNEL_TEXT_START: u64 = 0xffff_ffff_8100_0000;
#[allow(clippy::items_after_statements)]
const KERNEL_TEXT_END: u64 = 0xffff_ffff_8200_0000;
if start < KERNEL_TEXT_END && end > KERNEL_TEXT_START && name != "Kernel code" {
return true;
}
false
}
pub fn classify_kernel_timer(function: u64, kernel_start: u64, kernel_end: u64) -> bool {
if function == 0 {
return false;
}
!(function >= kernel_start && function <= kernel_end)
}
pub fn classify_notifier(notifier_call: u64, stext: u64, etext: u64) -> bool {
notifier_call < stext || notifier_call >= etext
}
const SUSPICIOUS_KMSG_PATTERNS: &[&str] = &[
"rootkit",
"hide",
"call trace",
"kernel bug",
"general protection",
];
pub fn classify_kmsg(text: &str) -> bool {
let lower = text.to_lowercase();
SUSPICIOUS_KMSG_PATTERNS.iter().any(|p| lower.contains(p))
}
const KERNEL_SPACE_MIN: u64 = 0xFFFF_0000_0000_0000;
fn looks_like_hex_name(name: &str) -> bool {
let mut run = 0u32;
for ch in name.chars() {
if ch.is_ascii_hexdigit() {
run += 1;
if run >= 8 {
return true;
}
} else {
run = 0;
}
}
false
}
pub fn classify_kthread(name: &str, start_fn_addr: u64) -> (bool, Option<String>) {
if name.is_empty() {
return (true, Some("unnamed kernel thread".into()));
}
if KERNEL_THREAD_COMMS.iter().any(|p| name.starts_with(p)) {
return (false, None);
}
if start_fn_addr != 0 && start_fn_addr < KERNEL_SPACE_MIN {
return (
true,
Some(format!(
"thread function at userspace address {start_fn_addr:#x}"
)),
);
}
if looks_like_hex_name(name) {
return (
true,
Some(format!("name '{name}' contains suspicious hex pattern")),
);
}
(false, None)
}
fn parse_ld_preload(value: &str) -> Vec<String> {
value
.split(|c: char| c == ':' || c.is_ascii_whitespace())
.filter(|s| !s.is_empty())
.map(String::from)
.collect()
}
fn is_suspicious_ld_path(path: &str, safe_prefixes: &[&str]) -> bool {
if path.starts_with("/tmp/") || path == "/tmp" {
return true;
}
if path.starts_with("/dev/shm/") || path == "/dev/shm" {
return true;
}
if path
.split('/')
.any(|component| !component.is_empty() && component.starts_with('.'))
{
return true;
}
if !safe_prefixes.iter().any(|prefix| path.starts_with(prefix)) {
return true;
}
false
}
pub fn classify_ld_preload(value: &str) -> bool {
const SAFE_PREFIXES: &[&str] = &[
"/usr/lib/",
"/usr/lib64/",
"/usr/lib32/",
"/usr/local/lib/",
"/usr/local/lib64/",
"/lib/",
"/lib64/",
"/lib32/",
];
let libraries = parse_ld_preload(value);
libraries
.iter()
.any(|lib| is_suspicious_ld_path(lib, SAFE_PREFIXES))
}
pub fn classify_library(lib_path: &str) -> bool {
let path = lib_path.trim();
if path.ends_with("(deleted)") {
return true;
}
let clean = path.strip_suffix(" (deleted)").unwrap_or(path);
if clean.starts_with("/tmp/")
|| clean == "/tmp"
|| clean.starts_with("/dev/shm/")
|| clean == "/dev/shm"
|| clean.starts_with("/var/tmp/")
|| clean == "/var/tmp"
{
return true;
}
if let Some(basename) = clean.rsplit('/').next() {
if basename.starts_with('.') && !basename.is_empty() {
return true;
}
}
if !std::path::Path::new(clean)
.extension()
.is_some_and(|e| e.eq_ignore_ascii_case("so"))
&& !clean.contains(".so.")
{
return true;
}
false
}
const BENIGN_MEMFD_PREFIXES: &[&str] = &[
"shm",
"pulseaudio",
"wayland",
"dbus",
"chrome",
"firefox",
"v8",
];
const SUSPICIOUS_MEMFD_NAMES: &[&str] =
&["payload", "shellcode", "stage", "loader", "inject", "hack"];
pub fn classify_memfd(name: &str, is_executable: bool) -> bool {
if is_executable {
return true;
}
let name_lower = name.to_lowercase();
for prefix in BENIGN_MEMFD_PREFIXES {
if name_lower.starts_with(prefix) {
return false;
}
}
if name.is_empty() {
return true;
}
for s in SUSPICIOUS_MEMFD_NAMES {
if name_lower.contains(s) {
return true;
}
}
false
}
pub fn classify_module_visibility(
in_module_list: bool,
in_kobj_list: bool,
in_memory_map: bool,
) -> bool {
let present_count = [in_module_list, in_kobj_list, in_memory_map]
.iter()
.filter(|&&v| v)
.count();
present_count > 0 && present_count < 3
}
pub fn classify_mount(fs_type: &str, dev_name: &str, mnt_root: &str) -> bool {
let _ = dev_name;
match fs_type {
"tmpfs" | "ramfs" => {
!matches!(
mnt_root,
"/tmp" | "/run" | "/dev/shm" | "/run/lock" | "/run/user" | "/"
) && !mnt_root.starts_with("/run/")
&& !mnt_root.starts_with("/tmp/")
&& !mnt_root.starts_with("/dev/")
}
"overlay" | "overlayfs" => {
!mnt_root.starts_with("/var/lib/docker") && !mnt_root.starts_with("/var/lib/containerd")
}
_ => false,
}
}
const SUSPICIOUS_OOM_NAMES: &[&str] = &[
"auditd",
"sshd",
"systemd",
"journald",
"rsyslogd",
"containerd",
"dockerd",
];
pub fn classify_oom_victim(comm: &str, pid: u32) -> bool {
let lower = comm.to_ascii_lowercase();
SUSPICIOUS_OOM_NAMES.iter().any(|n| lower.contains(n)) || pid < 100
}
const SYSTEM_LIB_PREFIXES: &[&str] =
&["/lib", "/usr/lib", "/usr/lib64", "/lib64", "/usr/local/lib"];
pub fn classify_pam_hook(path: &str) -> bool {
if path.is_empty() {
return false;
}
let lower = path.to_lowercase();
if !lower.contains("pam") {
return false;
}
!SYSTEM_LIB_PREFIXES
.iter()
.any(|prefix| path.starts_with(prefix))
}
pub fn classify_perf_event(event_type: u32, config: u64) -> bool {
match event_type {
3 => (config & 0xFF) <= 2, 4 => true, _ => false,
}
}
const PF_KTHREAD: u64 = 0x0020_0000;
const VSIZE_ABUSE_THRESHOLD: u64 = 100 * 1024 * 1024 * 1024;
pub fn classify_psaux(state: u64, uid: u32, flags: u64, vsize: u64) -> bool {
if state == 16 && uid == 0 {
return true;
}
if (flags & PF_KTHREAD) != 0 && uid != 0 {
return true;
}
if vsize > VSIZE_ABUSE_THRESHOLD {
return true;
}
false
}
const KNOWN_DEBUGGERS: &[&str] = &["gdb", "lldb", "strace", "ltrace", "valgrind", "perf"];
const HIGH_VALUE_TARGETS: &[&str] = &["sshd", "login", "passwd", "sudo", "su", "gpg-agent"];
pub fn classify_ptrace(tracer_name: &str, tracee_name: &str) -> bool {
if tracer_name.is_empty() {
return true;
}
if KNOWN_DEBUGGERS.contains(&tracer_name) {
return false;
}
if HIGH_VALUE_TARGETS.contains(&tracee_name) {
return true;
}
if tracer_name == tracee_name {
return true;
}
false
}
const BENIGN_AF_PACKET: &[&str] = &[
"tcpdump",
"wireshark",
"dumpcap",
"dhclient",
"dhcpcd",
"arping",
"ping",
"ping6",
];
const BENIGN_SOCK_RAW: &[&str] = &["ping", "ping6", "traceroute", "traceroute6", "arping"];
pub fn classify_raw_socket(comm: &str, socket_type: &str, is_promiscuous: bool) -> bool {
if is_promiscuous {
return true;
}
let comm_lower = comm.to_lowercase();
if socket_type == "AF_PACKET" {
return !BENIGN_AF_PACKET.iter().any(|&b| comm_lower == b);
}
if socket_type == "SOCK_RAW" {
return !BENIGN_SOCK_RAW.iter().any(|&b| comm_lower == b);
}
false
}
pub fn classify_signal_handler(signal: u32, handler: u64) -> bool {
match signal {
15 | 1 => handler == 1,
11 => handler != 0 && handler != 1,
9 => handler != 0,
_ => false,
}
}
const SUSPICIOUS_EXEC_PATTERNS: &[&str] = &[
"/tmp/",
"/dev/shm/",
"/var/tmp/",
"curl",
"wget",
"bash -c",
"sh -c",
"python",
"perl",
"ruby",
"nc ",
"ncat",
"base64",
];
const SAFE_EXEC_PREFIXES: &[&str] = &["/usr/", "/bin/", "/sbin/", "/lib/"];
const KNOWN_SAFE_UNITS: &[&str] = &["systemd-", "NetworkManager", "dbus", "cron", "ssh"];
const UNIT_EXTENSIONS: &[&str] = &[".service", ".timer", ".socket", ".path", ".mount"];
pub fn classify_systemd_unit(unit_name: &str, exec_start: &str) -> bool {
if KNOWN_SAFE_UNITS
.iter()
.any(|prefix| unit_name.starts_with(prefix))
{
return false;
}
if SAFE_EXEC_PREFIXES
.iter()
.any(|prefix| exec_start.starts_with(prefix))
{
return false;
}
if SUSPICIOUS_EXEC_PATTERNS
.iter()
.any(|pat| exec_start.contains(pat))
{
return true;
}
let stem = UNIT_EXTENSIONS
.iter()
.find_map(|ext| unit_name.strip_suffix(ext))
.unwrap_or(unit_name);
if stem.len() >= 8
&& stem
.chars()
.all(|c| c.is_ascii_hexdigit() && !c.is_ascii_uppercase())
{
return true;
}
false
}
pub fn classify_tmpfs_file(filename: &str, mode: u32) -> bool {
let is_regular_file = (mode & 0o170_000) == 0o100_000;
let is_exec = is_regular_file && (mode & 0o111) != 0;
let is_hidden = filename.starts_with('.') && filename.len() > 1;
is_exec || is_hidden
}
pub fn classify_unix_socket(path: &str, owner_pid: u32) -> bool {
let is_abstract = path.is_empty() || path.starts_with('@');
if is_abstract && owner_pid >= 1000 {
return true;
}
if path.starts_with("/tmp") || path.starts_with("/dev/shm") {
return true;
}
false
}
const SUSPICIOUS_DAEMON_NAMES: &[&str] = &[
"sshd",
"httpd",
"nginx",
"apache",
"mysqld",
"postgres",
"redis",
"memcached",
"mongod",
"named",
"bind",
"cupsd",
"cron",
"atd",
];
pub fn classify_zombie_orphan(is_zombie: bool, is_orphan: bool, ppid: u32, comm: &str) -> bool {
if is_zombie && ppid == 1 {
return true;
}
if is_orphan {
let lower = comm.to_lowercase();
if SUSPICIOUS_DAEMON_NAMES
.iter()
.any(|&name| lower.contains(name))
{
return true;
}
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn heuristics_bpf_kprobe_is_suspicious() {
assert!(classify_bpf_program("kprobe", "my_hook"));
}
#[test]
fn heuristics_bpf_lsm_is_suspicious() {
assert!(classify_bpf_program("lsm", ""));
}
#[test]
fn heuristics_bpf_xdp_benign() {
assert!(!classify_bpf_program("xdp", "firewall"));
}
#[test]
fn heuristics_bpf_unnamed_tracing_suspicious() {
assert!(classify_bpf_program("tracing", ""));
}
#[test]
fn heuristics_bpf_named_tracing_benign() {
assert!(!classify_bpf_program("tracing", "named_prog"));
}
#[test]
fn heuristics_capabilities_root_never_suspicious() {
let (susp, names) = classify_capabilities(u64::MAX, 0);
assert!(!susp);
assert!(names.is_empty());
}
#[test]
fn heuristics_capabilities_non_root_sys_admin_suspicious() {
let cap_sys_admin: u64 = 1 << 21;
let (susp, names) = classify_capabilities(cap_sys_admin, 1000);
assert!(susp);
assert!(!names.is_empty());
}
#[test]
fn heuristics_capabilities_non_root_no_caps_benign() {
let (susp, names) = classify_capabilities(0, 1000);
assert!(!susp);
assert!(names.is_empty());
}
#[test]
fn heuristics_cgroup_docker_detected() {
let (in_container, id) = classify_cgroup("/docker/abc123def456");
assert!(in_container);
assert_eq!(id, "abc123def456");
}
#[test]
fn heuristics_cgroup_bare_root_not_container() {
let (in_container, id) = classify_cgroup("/");
assert!(!in_container);
assert!(id.is_empty());
}
#[test]
fn heuristics_afinfo_null_not_hooked() {
assert!(!classify_afinfo_hook(0, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_afinfo_in_range_benign() {
assert!(!classify_afinfo_hook(0xffff1000, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_afinfo_outside_range_suspicious() {
assert!(classify_afinfo_hook(
0x0000_dead_beef,
0xffff0000,
0xffff8000
));
}
#[test]
fn heuristics_shared_creds_userspace_shares_with_init_suspicious() {
assert!(classify_shared_creds(500, &[1], 1000));
}
#[test]
fn heuristics_shared_creds_empty_list_benign() {
assert!(!classify_shared_creds(500, &[], 1000));
}
#[test]
fn heuristics_shared_creds_kernel_thread_shares_with_init_benign() {
assert!(!classify_shared_creds(2, &[1], 0));
}
#[test]
fn heuristics_idt_null_not_hooked() {
assert!(!classify_idt_entry(0, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_idt_in_kernel_range_benign() {
assert!(!classify_idt_entry(0xffff2000, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_idt_outside_range_suspicious() {
assert!(classify_idt_entry(0x1234, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_container_escape_namespace_mismatch_suspicious() {
assert!(classify_container_escape("bash", "namespace_mismatch"));
}
#[test]
fn heuristics_container_escape_kernel_thread_benign() {
assert!(!classify_container_escape(
"kworker/0:0",
"namespace_mismatch"
));
}
#[test]
fn heuristics_container_escape_unknown_indicator_benign() {
assert!(!classify_container_escape("bash", "some_other_thing"));
}
#[test]
fn heuristics_deleted_exe_not_deleted_benign() {
assert!(!classify_deleted_exe("/usr/bin/bash", "bash"));
}
#[test]
fn heuristics_deleted_exe_suspicious() {
assert!(classify_deleted_exe("/tmp/evil (deleted)", "evil"));
}
#[test]
fn heuristics_deleted_exe_empty_comm_benign() {
assert!(!classify_deleted_exe("/tmp/x (deleted)", ""));
}
#[test]
fn heuristics_hidden_dentry_nlink_zero_suspicious() {
assert!(classify_hidden_dentry(0, "normal.txt"));
}
#[test]
fn heuristics_hidden_dentry_empty_filename_benign() {
assert!(!classify_hidden_dentry(0, ""));
}
#[test]
fn heuristics_hidden_dentry_linked_no_suspicious_ext_benign() {
assert!(!classify_hidden_dentry(1, "readme.txt"));
}
#[test]
fn heuristics_ebpf_map_ringbuf_suspicious() {
assert!(classify_ebpf_map(26, "benign_name", 8));
}
#[test]
fn heuristics_ebpf_map_perf_event_array_suspicious() {
assert!(classify_ebpf_map(3, "benign_name", 8));
}
#[test]
fn heuristics_ebpf_map_hash_benign_name_benign() {
assert!(!classify_ebpf_map(1, "counters", 8));
}
#[test]
fn heuristics_ftrace_in_text_benign() {
assert!(!classify_ftrace_hook(0x1000, 0x1000, 0x2000));
}
#[test]
fn heuristics_ftrace_outside_text_suspicious() {
assert!(classify_ftrace_hook(0x500, 0x1000, 0x2000));
}
#[test]
fn heuristics_futex_high_waiter_count_suspicious() {
assert!(classify_futex(0x1000, 0, 1001));
}
#[test]
fn heuristics_futex_normal_benign() {
assert!(!classify_futex(0x1000, 0, 5));
}
#[test]
fn heuristics_futex_kernel_key_userspace_owner_suspicious() {
assert!(classify_futex(0xffff_0000_0000, 1234, 0));
}
#[test]
fn heuristics_io_uring_no_seccomp_benign() {
assert!(!classify_io_uring(&[1, 2, 3], 0));
}
#[test]
fn heuristics_io_uring_no_opcodes_benign() {
assert!(!classify_io_uring(&[], 1));
}
#[test]
fn heuristics_iomem_kernel_code_name_benign() {
assert!(!classify_iomem(
"Kernel code",
0xffff_ffff_8100_0000,
0xffff_ffff_8180_0000
));
}
#[test]
fn heuristics_iomem_empty_name_small_region_benign() {
assert!(!classify_iomem("", 0, 1024));
}
#[test]
fn heuristics_iomem_empty_name_large_region_suspicious() {
assert!(classify_iomem("", 0, 2 * 1024 * 1024));
}
#[test]
fn heuristics_kernel_timer_null_benign() {
assert!(!classify_kernel_timer(0, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_kernel_timer_in_range_benign() {
assert!(!classify_kernel_timer(0xffff1000, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_kernel_timer_outside_range_suspicious() {
assert!(classify_kernel_timer(0x1234, 0xffff0000, 0xffff8000));
}
#[test]
fn heuristics_notifier_in_text_benign() {
assert!(!classify_notifier(0x1000, 0x1000, 0x2000));
}
#[test]
fn heuristics_notifier_below_stext_suspicious() {
assert!(classify_notifier(0x500, 0x1000, 0x2000));
}
#[test]
fn heuristics_kmsg_normal_message_benign() {
assert!(!classify_kmsg("USB device connected"));
}
#[test]
fn heuristics_kthread_empty_name_suspicious() {
let (susp, reason) = classify_kthread("", 0xffff_8000_0000);
assert!(susp);
assert!(reason.is_some());
}
#[test]
fn heuristics_kthread_named_kernel_fn_benign() {
let (susp, _) = classify_kthread("kworker/0:0", 0xffff_8000_1234);
assert!(!susp);
}
#[test]
fn heuristics_ld_preload_tmp_path_suspicious() {
assert!(classify_ld_preload("/tmp/evil.so"));
}
#[test]
fn heuristics_ld_preload_system_lib_benign() {
assert!(!classify_ld_preload("/usr/lib/libfoo.so"));
}
#[test]
fn heuristics_library_deleted_suspicious() {
assert!(classify_library("/usr/lib/libfoo.so (deleted)"));
}
#[test]
fn heuristics_library_normal_benign() {
assert!(!classify_library("/usr/lib/libc.so.6"));
}
#[test]
fn heuristics_library_tmp_suspicious() {
assert!(classify_library("/tmp/inject.so"));
}
#[test]
fn heuristics_memfd_executable_suspicious() {
assert!(classify_memfd("legit_name", true));
}
#[test]
fn heuristics_memfd_empty_name_suspicious() {
assert!(classify_memfd("", false));
}
#[test]
fn heuristics_module_visibility_all_present_benign() {
assert!(!classify_module_visibility(true, true, true));
}
#[test]
fn heuristics_module_visibility_partial_hidden() {
assert!(classify_module_visibility(true, false, true));
}
#[test]
fn heuristics_module_visibility_all_absent_benign() {
assert!(!classify_module_visibility(false, false, false));
}
#[test]
fn heuristics_mount_known_tmpfs_root_benign() {
assert!(!classify_mount("tmpfs", "tmpfs", "/tmp"));
}
#[test]
fn heuristics_mount_unknown_tmpfs_suspicious() {
assert!(classify_mount("tmpfs", "tmpfs", "/secret_staging"));
}
#[test]
fn heuristics_mount_ext4_benign() {
assert!(!classify_mount("ext4", "/dev/sda1", "/"));
}
#[test]
fn heuristics_oom_victim_low_pid_suspicious() {
assert!(classify_oom_victim("bash", 5));
}
#[test]
fn heuristics_oom_victim_normal_benign() {
assert!(!classify_oom_victim("chrome", 5000));
}
#[test]
fn heuristics_pam_hook_empty_benign() {
assert!(!classify_pam_hook(""));
}
#[test]
fn heuristics_pam_hook_system_lib_benign() {
assert!(!classify_pam_hook("/lib/x86_64-linux-gnu/libpam.so.0"));
}
#[test]
fn heuristics_pam_hook_tmp_suspicious() {
assert!(classify_pam_hook("/tmp/fakepam.so"));
}
#[test]
fn heuristics_perf_event_raw_pmu_suspicious() {
assert!(classify_perf_event(4, 0));
}
#[test]
fn heuristics_perf_event_software_benign() {
assert!(!classify_perf_event(1, 0));
}
#[test]
fn heuristics_psaux_zombie_root_suspicious() {
assert!(classify_psaux(16, 0, 0, 0));
}
#[test]
fn heuristics_psaux_normal_process_benign() {
assert!(!classify_psaux(1, 1000, 0, 4096));
}
#[test]
fn heuristics_ptrace_empty_tracer_suspicious() {
assert!(classify_ptrace("", "bash"));
}
#[test]
fn heuristics_ptrace_gdb_tracing_bash_benign() {
assert!(!classify_ptrace("gdb", "bash"));
}
#[test]
fn heuristics_raw_socket_promiscuous_suspicious() {
assert!(classify_raw_socket("tcpdump", "AF_PACKET", true));
}
#[test]
fn heuristics_raw_socket_tcpdump_not_promiscuous_benign() {
assert!(!classify_raw_socket("tcpdump", "AF_PACKET", false));
}
#[test]
fn heuristics_signal_handler_sigterm_ignored_suspicious() {
assert!(classify_signal_handler(15, 1));
}
#[test]
fn heuristics_signal_handler_sigterm_default_benign() {
assert!(!classify_signal_handler(15, 0));
}
#[test]
fn heuristics_signal_handler_sigkill_nonzero_suspicious() {
assert!(classify_signal_handler(9, 0x1234));
}
#[test]
fn heuristics_systemd_unit_suspicious_exec_start() {
assert!(classify_systemd_unit("evil.service", "/tmp/backdoor.sh"));
}
#[test]
fn heuristics_tmpfs_file_executable_regular_suspicious() {
assert!(classify_tmpfs_file("payload", 0o100_755));
}
#[test]
fn heuristics_tmpfs_file_hidden_suspicious() {
assert!(classify_tmpfs_file(".hidden", 0o100_644));
}
#[test]
fn heuristics_tmpfs_file_normal_benign() {
assert!(!classify_tmpfs_file("readme.txt", 0o100_644));
}
#[test]
fn heuristics_unix_socket_abstract_high_uid_suspicious() {
assert!(classify_unix_socket("", 1234));
}
#[test]
fn heuristics_unix_socket_system_path_benign() {
assert!(!classify_unix_socket("/var/run/docker.sock", 500));
}
#[test]
fn heuristics_unix_socket_tmp_suspicious() {
assert!(classify_unix_socket("/tmp/evil.sock", 500));
}
#[test]
fn heuristics_zombie_orphan_reparented_to_init_suspicious() {
assert!(classify_zombie_orphan(true, false, 1, "bash"));
}
#[test]
fn heuristics_zombie_orphan_normal_benign() {
assert!(!classify_zombie_orphan(false, false, 1234, "chrome"));
}
}