Skip to main content

Crate linprov_common

Crate linprov_common 

Source
Expand description

Types shared between the eBPF program (linprov-ebpf) and the userspace daemon (linprov). Everything here is repr(C) and Pod-friendly so it survives a round-trip through a ring buffer and a kernel xattr.

The crate compiles no_std by default (for the BPF target). Enable the user feature in userspace to pull in bytemuck::Pod / Zeroable derives on the wire types.

Wire shapes at a glance:

  • OriginRecord is what the daemon stores in the xattr and in the BPF INODE_MARKS map. BPF writes most of it in file_open; userspace augments creator_path from /proc/$pid/exe.
  • Event is the ringbuf record streamed from BPF to userspace.
  • AllowRule is one allowlist rule, packed into the BPF ALLOW_RULES array. String dims are stored as fnv_hash values so the BPF side can compare without carrying full byte arrays.
use linprov_common::{fnv_hash, dim};

// Both sides hash strings the same way; same input → same u64.
assert_eq!(fnv_hash("/usr/bin/curl"), fnv_hash("/usr/bin/curl"));
assert_ne!(fnv_hash("/usr/bin/curl"), fnv_hash("/usr/bin/wget"));

// Dimension bits are independent flags on AllowRule::flags.
let two_dim = dim::CREATOR_UID | dim::CREATOR_COMM;
assert_eq!(two_dim.count_ones(), 2);

Modules§

dim
flags bits on AllowRule. Bits 0–7 are the dims a rule requires. Bits 8+ are modifiers on a dim already set.

Structs§

AllowRule
One allowlist rule. Set bits in flags mark required dims; the corresponding fields below are then compared against the record / execve context at enforce time. Cleared bits → field ignored.
Event
Ring-buffer record. Two kinds: NetworkFileOpen — informational; eBPF just wrote (or tried to write) the xattr. status is the kfunc return code. Execve — bprm_check fired AND the file already carried the mark. origin is the record we read back; status is unused.
OriginRecord
Provenance record. Carried in the security.bpf.linprov.origin xattr and in the INODE_MARKS storage map. Fixed 64 bytes — every variable-length field is an FNV-1a-64 hash, so the record never grows with path length and always fits a single xattr block.

Constants§

COMM_LEN
EVENT_KIND_DERIVED_FILE_OPEN
A file written by a process that had read a marked file (taint propagation — e.g. tar/unzip extracting a marked archive, or cp of a marked file). The carried origin is inherited from the source file’s record; userspace persists the xattr without re-resolving the creator (the inherited creator identity is kept verbatim).
EVENT_KIND_EXECVE
EVENT_KIND_NETWORK_FILE_OPEN
EVENT_KIND_SCRIPT_EXEC
A marked file opened for read by a known script interpreter (bash/python/…) — i.e. python foo.py / bash foo.sh / . foo.sh, where the kernel execve’s the unmarked interpreter and the script itself never reaches bprm_check_security. The eBPF file_open read branch runs the same allowlist check used at execve against the script’s path; in enforce mode status is the LSM verdict (-1 blocked). The filename carries the script’s path and comm the script’s basename, so the script — not the interpreter — is the unit surfaced in logs and soak. Matches EVENT_KIND_EXECVE handling.
EXEC_PATH_LEN
Live exec/target path buffer size: the ringbuf Event filename and the per-CPU scratch the target-dim walks scan. Sized to Linux PATH_MAX so target_filename / target_folder match the full execution path at any depth and any length. These buffers are transient (per-CPU scratch + ringbuf), never persisted, so they aren’t bound by the xattr block-size limit that caps stored data.
FNV_OFFSET
FNV_PRIME
MAX_FOLDER_ANCESTORS
Number of landing-folder ancestor hashes stored per record, for nested landing_folder matching. The walk records the hash of each /-terminated prefix of the landing path (shallow → deep) into a [u64; MAX_FOLDER_ANCESTORS]; a rule matches if its folder hash equals any of them, so landing_folder=/home/user/ matches a file that landed in /home/user/Downloads/sub/. Bounds nesting depth (path length is still unbounded — these are hashes). Must be a power of two: the in-kernel walk masks the index (& (N-1)) to keep the array write provably in-bounds without a panic branch. Real landing paths sit well under this, so the mask never actually wraps.
MAX_RULES
Maximum number of allowlist rules carried by the BPF Array map. Each rule check is ~30 ops + 2 folder lookups; the verifier walks the full bounded loop, so this caps the per-execve cost.
MODE_ENFORCE
MODE_OBSERVE
Runtime mode communicated to the eBPF program via the CONFIG map.
MODE_SOAK
ORIGIN_VERSION
Current schema version of OriginRecord. Records carrying a different version are treated as unmarked.
PATH_HASH_SCAN_LEN
Max bytes the BPF path walks inspect. Equal to EXEC_PATH_LEN: the walk body is a bpf_loop callback the verifier inspects once, so this is bounded only by the buffer, not the instruction budget.
XATTR_NAME

Functions§

fnv_hash
Hash a string with FNV-1a-64. Byte-by-byte, no trailing NUL, no padding — identical on the BPF and userspace sides.
fnv_hash_bytes
Same as fnv_hash, but takes a byte slice. Useful when the source isn’t UTF-8 (e.g., a [u8; EXEC_PATH_LEN] filename buffer read out of a ringbuf event).