Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/nornir

crates/mimir-ingest/src/boundary/tests.rs

//! The boundary's tables, map and BTF reader.

use std::collections::BTreeSet;

use mimir_schema::{EdgeKind, kind as nk, moniker};

use super::*;

fn gnu() -> Target {
    Target::parse("x86_64-unknown-linux-gnu").unwrap()
}

fn musl() -> Target {
    Target::parse("x86_64-unknown-linux-musl").unwrap()
}

#[test]
fn every_x86_64_syscall_has_exactly_one_class_list() {
    let t = table("x86_64").unwrap();
    assert!(t.len() > 380, "{}", t.len());
    for s in t {
        let lists: Vec<Class> = class::LISTS.iter().filter(|(_, l)| l.contains(&s.name)).map(|(c, _)| *c).collect();
        assert_eq!(lists.len(), 1, "{} is in {lists:?}", s.name);
    }
    // the lists name only real calls
    for (c, l) in class::LISTS {
        for n in *l {
            assert!(syscall("x86_64", n).is_some(), "{c}: {n} is no x86_64 syscall");
        }
    }
    assert_eq!(syscall("x86_64", "read").map(|s| (s.nr, s.entry)), Some((0, "sys_read")));
    assert_eq!(syscall("x86_64", "openat").map(|s| s.nr), Some(257));
    assert_eq!(syscall("x86_64", "connect").map(|s| s.nr), Some(42));
    assert_eq!(syscall("x86_64", "stat").map(|s| s.entry), Some("sys_newstat"));
    assert_eq!(class_of("connect"), Class::Network);
    assert_eq!(class_of("openat"), Class::File);
    assert_eq!(class_of("clone3"), Class::Process);
    assert_eq!(class_of("mmap"), Class::Memory);
    assert_eq!(class_of("clock_nanosleep"), Class::Time);
    assert_eq!(class_of(INDIRECT), Class::Indirect);
}

#[test]
fn the_libc_map_answers_by_libc_and_says_what_is_uncertain() {
    let is = |n: &str| syscall("x86_64", n).is_some();
    let calls = |s: &str, l: Libc| match libc_map::lookup(s, l, &is) {
        Some(Mapped::Calls(c)) => c,
        other => panic!("{s}: {other:?}"),
    };
    // glibc opens with openat, musl with open
    assert_eq!(calls("open64", Libc::Glibc), vec![("openat", true)]);
    assert_eq!(calls("open", Libc::Musl), vec![("open", true)]);
    // a thin wrapper is its own call, in both
    assert_eq!(calls("connect", Libc::Glibc), vec![("connect", true)]);
    assert_eq!(calls("read", Libc::Musl), vec![("read", true)]);
    assert_eq!(calls("lseek64", Libc::Glibc), vec![("lseek", true)]);
    // version-dependent / vDSO / contention: uncertain
    assert!(calls("fstat64", Libc::Glibc).iter().all(|(_, sure)| !sure));
    assert_eq!(calls("clock_gettime", Libc::Glibc), vec![("clock_gettime", false)]);
    assert!(calls("pthread_mutex_lock", Libc::Glibc).iter().all(|(c, sure)| *c == "futex" && !sure));
    // libc's syslog() is not the syslog system call
    assert!(calls("syslog", Libc::Glibc).iter().any(|(c, _)| *c == "socket"));
    assert_eq!(calls("syscall", Libc::Glibc), vec![(INDIRECT, false)]);
    assert_eq!(libc_map::lookup("memcpy", Libc::Glibc, &is), Some(Mapped::Pure));
    assert_eq!(libc_map::lookup("no_such_symbol", Libc::Glibc, &is), None);
    assert_eq!(Libc::of_target("x86_64-unknown-linux-musl"), Some(Libc::Musl));
    assert_eq!(Libc::of_target("x86_64-apple-darwin"), None);
}

fn ffi(id: &str, link: &str) -> OwnerFn {
    OwnerFn { id: id.into(), kind: nk::FFI.into(), name: link.into(), link_name: Some(link.into()) }
}

fn rust_fn(id: &str, name: &str) -> OwnerFn {
    OwnerFn { id: id.into(), kind: nk::FN.into(), name: name.into(), link_name: None }
}

#[test]
fn a_dependency_owner_maps_its_ffi_raw_doors_and_named_raw_callers() {
    let fns = vec![
        ffi("libc open64", "open64"),
        ffi("libc connect", "connect"),
        ffi("libc syscall", "syscall"),
        ffi("libc strlen", "strlen"),
        ffi("libc weird", "__weird_thing"),
        rust_fn("std statx", "statx"),
        rust_fn("std read", "read"),
    ];
    let raw: BTreeSet<String> = ["std statx".to_string()].into();
    let mut st = MapStats::default();
    let edges = map_edges("libc@0.2.177:lib", &fns, &raw, &gnu(), &mut st);
    let got: BTreeSet<(String, String, bool)> = edges.iter().map(|e| (e.from.clone(), e.to.clone(), e.partial == Some(true))).collect();
    let sc = |n: &str| moniker::syscall("x86_64", n);
    assert!(got.contains(&("libc open64".into(), sc("openat"), false)), "{got:?}");
    assert!(got.contains(&("libc connect".into(), sc("connect"), false)));
    assert!(got.contains(&("libc syscall".into(), sc(INDIRECT), true)));
    // std's `syscall!` fn: named statx, hands libc::syscall a number → statx, uncertain
    assert!(got.contains(&("std statx".into(), sc("statx"), true)));
    // a fn merely NAMED like a call, that calls no raw door, is not mapped
    assert!(!got.iter().any(|(f, _, _)| f == "std read"));
    assert!(edges.iter().all(|e| e.kind == EdgeKind::Syscalls));
    assert_eq!(st.ffi_pure, 1);
    assert_eq!(st.unmapped.get("__weird_thing"), Some(&1));
    // musl: open64 → open
    let mut st = MapStats::default();
    let m = map_edges("libc@0.2.177:lib", &fns[..1], &BTreeSet::new(), &musl(), &mut st);
    assert_eq!(m[0].to, sc("open"));
}

#[test]
fn raw_doors_are_recognised_by_their_ids() {
    assert!(is_raw_door_target("rust-analyzer cargo libc 0.2.177 unix/linux_like/linux/syscall()."));
    assert!(is_raw_door_target("rust-analyzer cargo libc https://github.com/rust-lang/rust/library/libc new/syscall()."));
    assert!(!is_raw_door_target("rust-analyzer cargo libc 0.2.177 unix/linux_like/linux/syscalls()."));
    assert!(is_raw_door_target("rust-analyzer cargo rustix 1.0.7 backend/arch/x86_64/syscall3_readonly()."));
    assert!(!is_raw_door_target("rust-analyzer cargo mycrate 1.0.0 syscall()."));
}

#[test]
fn the_sample_kernel_reads_into_types_functions_and_entry_links() {
    let bytes = btf::sample_kernel();
    let kr = kernel_rows(&bytes, "6.99.0-test", "@kernel@6.99.0-test+abc:btf").unwrap();
    assert_eq!(kr.arch, Some("x86_64"));
    let id = |ns: &str, n: &str| moniker::kernel_item("6.99.0-test", ns, n, None);
    let nodes: BTreeSet<&str> = kr.rows.nodes.iter().map(|n| n.id.as_str()).collect();
    for want in [id("struct", "task_struct"), id("struct", "pt_regs"), id("typedef", "pid_t"), id("func", "__x64_sys_read"), id("enum", "pid_type"), id("struct", "mm_struct")] {
        assert!(nodes.contains(want.as_str()), "{want} missing: {nodes:?}");
    }
    let has = |f: &str, t: &str, k: EdgeKind| kr.rows.edges.iter().any(|e| e.from == f && e.to == t && e.kind == k);
    // task_struct uses pid_t, list_head, and (through the anonymous union and a pointer) mm_struct
    assert!(has(&id("struct", "task_struct"), &id("typedef", "pid_t"), EdgeKind::UsesType));
    assert!(has(&id("struct", "task_struct"), &id("struct", "mm_struct"), EdgeKind::UsesType));
    assert!(has(&id("struct", "task_struct"), &id("struct", "list_head"), EdgeKind::UsesType));
    // the entry stub's prototype: const struct pt_regs *
    assert!(has(&id("func", "__x64_sys_read"), &id("struct", "pt_regs"), EdgeKind::UsesType));
    let sig = kr.rows.nodes.iter().find(|n| n.id == id("func", "__x64_sys_read")).and_then(|n| n.ty.clone()).unwrap();
    assert_eq!(sig, "long __x64_sys_read(const struct pt_regs * regs)");
    // syscalls → their entry: __x64_sys_* or, for a SYSCALL_DEFINE0 call, __do_sys_*
    assert!(has(&moniker::syscall("x86_64", "read"), &id("func", "__x64_sys_read"), EdgeKind::Calls));
    assert!(has(&moniker::syscall("x86_64", "connect"), &id("func", "__x64_sys_connect"), EdgeKind::Calls));
    assert!(has(&moniker::syscall("x86_64", "getpid"), &id("func", "__do_sys_getpid"), EdgeKind::Calls));
    assert_eq!(kr.entries_linked, 3);
    assert!(has(&moniker::syscall_crate(), &moniker::kernel_crate("6.99.0-test"), EdgeKind::DependsOn));
}

/// The box's own kernel (or `$MIMIR_TEST_BTF`): a real BTF file.
#[test]
fn a_real_kernel_btf_has_task_struct_and_the_read_entry() {
    let path = std::env::var_os("MIMIR_TEST_BTF").map(std::path::PathBuf::from).unwrap_or_else(|| BOX_BTF.into());
    if !path.is_file() && std::env::var_os("MIMIR_TEST_NO_BTF").is_some() {
        eprintln!("no BTF at {} and MIMIR_TEST_NO_BTF set: skipped", path.display());
        return;
    }
    // a container mounts its own /sys over a bind of /sys/kernel/btf: the
    // host's file is not there — mount it elsewhere and name it
    assert!(
        path.is_file(),
        "no BTF file at {} (in a container its own /sys hides a bind of /sys/kernel/btf: mount the host's vmlinux elsewhere and set MIMIR_TEST_BTF, or MIMIR_TEST_NO_BTF=1 to skip)",
        path.display()
    );
    let t = std::time::Instant::now();
    let kr = read_kernel_file(&path, "box").unwrap_or_else(|e| panic!("{}: {e:#}", path.display()));
    let ms = t.elapsed().as_millis();
    let ts = moniker::kernel_item("box", "struct", "task_struct", None);
    let node = kr.rows.nodes.iter().find(|n| n.id == ts).expect("task_struct");
    assert_eq!(node.kind, nk::STRUCT);
    assert!(node.ty.as_deref().unwrap_or("").contains("bytes"), "{:?}", node.ty);
    let read = moniker::kernel_item("box", "func", "__x64_sys_read", None);
    assert!(kr.rows.nodes.iter().any(|n| n.id == read && n.kind == nk::FN), "__x64_sys_read missing");
    assert!(kr.rows.edges.iter().any(|e| e.from == moniker::syscall("x86_64", "read") && e.to == read && e.kind == EdgeKind::Calls));
    assert!(kr.rows.edges.iter().any(|e| e.from == ts && e.kind == EdgeKind::UsesType), "task_struct uses no type");
    eprintln!(
        "real BTF {}: {} types → {} nodes, {} edges, {} syscalls linked ({} without an entry in BTF) in {ms} ms",
        path.display(),
        kr.types,
        kr.rows.nodes.len(),
        kr.rows.edges.len(),
        kr.entries_linked,
        kr.entries_missing.len()
    );
}

#[test]
fn table_rows_name_every_call_with_its_class() {
    let t = table_rows("x86_64").unwrap();
    let read = t.nodes.iter().find(|n| n.id == moniker::syscall("x86_64", "read")).unwrap();
    assert_eq!((read.kind.as_str(), read.ty.as_deref()), (nk::SYSCALL, Some("file")));
    assert_eq!(read.doc.as_deref(), Some("x86_64 #0 → sys_read"));
    assert!(t.nodes.iter().any(|n| n.id == moniker::syscall("x86_64", INDIRECT) && n.ty.as_deref() == Some("indirect")));
    assert_eq!(t.owner, "@syscall@x86_64.r1:table");
    assert_eq!(map_owner("libc@0.2.177:lib", &gnu()), "@syscall@libc-0.2.177-lib.gnu.r1:map");
}

fn arm_gnu() -> Target {
    Target::parse("aarch64-unknown-linux-gnu").unwrap()
}

fn arm_musl() -> Target {
    Target::parse("aarch64-unknown-linux-musl").unwrap()
}

/// The generic table arm64 builds: its own numbers, no legacy calls, one
/// class each, the same names as x86_64 where the call exists on both.
#[test]
fn the_aarch64_table_is_the_generic_one_with_its_own_numbers_and_classes() {
    let t = table("aarch64").expect("an aarch64 table");
    assert!(t.len() > 300, "{}", t.len());
    for s in t {
        let lists = class::LISTS.iter().filter(|(_, l)| l.contains(&s.name)).count();
        assert_eq!(lists, 1, "{} is in {lists} class lists", s.name);
    }
    let nr = |n: &str| syscall("aarch64", n).map(|s| s.nr);
    assert_eq!(nr("io_setup"), Some(0));
    assert_eq!(nr("openat"), Some(56));
    assert_eq!(nr("read"), Some(63));
    assert_eq!(nr("newfstatat"), Some(79));
    assert_eq!(nr("clone"), Some(220));
    assert_eq!(nr("mmap"), Some(222));
    assert_eq!(nr("clone3"), Some(435));
    assert_eq!(nr("memfd_secret"), Some(447), "the memfd_secret ABI arm64 builds");
    assert_eq!(nr("renameat"), Some(38), "the renameat ABI arm64 builds");
    assert_eq!(nr("listns"), Some(470), "past the tbl: the UAPI header");
    assert_eq!(syscall("aarch64", "fstat").map(|s| s.entry), Some("sys_newfstat"));
    for legacy in ["open", "stat", "fork", "pipe", "poll", "arch_prctl", "syscalls"] {
        assert!(nr(legacy).is_none(), "{legacy} is no aarch64 call");
    }
    assert_eq!(entry_prefix("aarch64"), Some("__arm64_"));
    let row = table_rows("aarch64").unwrap();
    assert_eq!(row.owner, "@syscall@aarch64.r1:table");
    let read = row.nodes.iter().find(|n| n.id == moniker::syscall("aarch64", "read")).unwrap();
    assert_eq!(read.doc.as_deref(), Some("aarch64 #63 → sys_read"));
    assert!(row.nodes.iter().any(|n| n.id == moniker::syscall("aarch64", INDIRECT)));
    assert_eq!((arm_gnu().arch, arm_gnu().libc), ("aarch64", Libc::Glibc));
    assert_eq!(Target::parse("riscv64gc-unknown-linux-gnu"), None);
}

/// A legacy call the libc map names (written for x86_64) is its stand-in
/// on aarch64, never dropped: the possible set stays a true maximum.
#[test]
fn an_aarch64_map_turns_every_legacy_call_into_its_generic_stand_in() {
    assert_eq!(calls_on("aarch64", "open"), vec!["openat"]);
    assert_eq!(calls_on("aarch64", "rename"), vec!["renameat", "renameat2"]);
    assert_eq!(calls_on("aarch64", "read"), vec!["read"]);
    assert_eq!(calls_on("aarch64", INDIRECT), vec![INDIRECT]);
    assert!(calls_on("aarch64", "iopl").is_empty());
    assert_eq!(calls_on("x86_64", "open"), vec!["open"]);
    for (legacy, ins) in GENERIC_STAND_INS {
        assert!(syscall("aarch64", legacy).is_none(), "{legacy} exists on aarch64: no stand-in needed");
        assert!(syscall("x86_64", legacy).is_some(), "{legacy} is no legacy x86_64 call");
        for i in *ins {
            assert!(syscall("aarch64", i).is_some(), "{legacy} → {i}: no aarch64 call");
        }
    }
    let fns = vec![
        ffi("libc open", "open"),
        ffi("libc unlink", "unlink"),
        ffi("libc open64", "open64"),
        ffi("libc syscall", "syscall"),
        rust_fn("std statx", "statx"),
        rust_fn("std stat", "stat"),
    ];
    let raw: BTreeSet<String> = ["std statx".to_string(), "std stat".to_string()].into();
    let sc = |n: &str| moniker::syscall("aarch64", n);
    let mut st = MapStats::default();
    let edges = map_edges("libc@0.2.177:lib", &fns, &raw, &arm_musl(), &mut st);
    let got: BTreeSet<(String, String)> = edges.iter().map(|e| (e.from.clone(), e.to.clone())).collect();
    // musl's `open` is the open call on x86_64; on aarch64 it is openat
    assert!(got.contains(&("libc open".into(), sc("openat"))), "{got:?}");
    assert!(got.contains(&("libc unlink".into(), sc("unlinkat"))), "a thin wrapper of a legacy call: {got:?}");
    assert!(got.contains(&("libc open64".into(), sc("openat"))));
    assert!(got.contains(&("libc syscall".into(), sc(INDIRECT))));
    assert!(got.contains(&("std statx".into(), sc("statx"))));
    assert!(got.contains(&("std stat".into(), sc("newfstatat"))), "a raw caller named like a legacy call");
    assert!(edges.iter().all(|e| e.to.contains("aarch64/")), "only aarch64 calls: {got:?}");
    assert!(st.unmapped.is_empty(), "{:?}", st.unmapped);
    // the maps of two architectures are two owners
    assert_eq!(map_owner("libc@0.2.177:lib", &arm_gnu()), "@syscall@libc-0.2.177-lib.aarch64-gnu.r1:map");
    assert_ne!(map_owner("libc@0.2.177:lib", &arm_musl()), map_owner("libc@0.2.177:lib", &musl()));
}