actplane-bpf 0.1.0

ActPlane eBPF loader: loads the prebuilt CO-RE object with aya, no clang/libbpf at runtime.
Documentation
  • Coverage
  • 33.33%
    6 out of 18 items documented0 out of 7 items with examples
  • Size
  • Source code size: 518.48 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 521.56 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 10s Average build duration of successful builds.
  • all releases: 10s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • eunomia-bpf/ActPlane
    70 8 2
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • yunwei37

ActPlane eBPF programs

The kernel half of ActPlane. The taint enforcer is the heart of the system; the other programs are retained capture tools.

Build & test

make            # builds all APPS (sslsniff browsertrace stdiocap process test_taint)
make test       # runs the C unit tests (test_taint)
make debug      # AddressSanitizer build of the userspace loaders

eBPF programs use the CO-RE pattern with an architecture-specific vmlinux.h from vmlinux/. Each emits JSON to stdout and debug info to stderr.

The taint enforcer (process)

File Role
taint.h The rule ABI + matching predicates, shared with the Rust compiler. Defines struct taint_source / taint_rule / taint_xform / taint_gate / taint_config, the taint_match/taint_src_kind/taint_op/taint_cond enums, and the matchers (taint_streq, taint_prefix, taint_suffix, taint_match, taint_mask_ok, taint_arg_match).
taint_engine.bpf.h The engine state + helpers. Maps: ts_proc (pid → labels + lineage gates), ts_root, ts_sess (session gates), ts_file (fnv1a(path) → labels), ts_endp (IPv4 → labels). Rodata rule tables (filled by the loader). te_* propagation/eval functions.
process.bpf.c The hooks: tracepoints maintain lineage and support audit/kill; BPF LSM hooks (bprm_check_security, file_open, file_permission, file_truncate, path_truncate, path_unlink, path_rename, socket_connect) implement block with -EPERM. The only output is emit_violation().
process.c Userspace loader. --config <blob> reads a struct taint_config into the BPF rodata, detects whether bpf LSM is active, disables LSM programs in tracepoint mode, and prints each TAINT_VIOLATION as NDJSON (formatting conn_ip for connect rules).
test_taint.c Unit tests for the matching predicates.

Taint model (summary)

Each node (process / file / endpoint) carries a u64 label bitmask. Sources add labels (exec comm match, file path match, endpoint IP match). Propagation: fork → child inherits; exec → source/xform/gate applied to the process; read → file labels flow into the process; write → process labels flow into the file; connect → process labels flow to the endpoint. LSM hooks can deny before commit (block); tracepoints do not support block; they can report audit rules and terminate only rules that explicitly use effect kill. Sinks (deny exec/open/write/connect) match on a label mask (req AND / forbid NOT, DNF-expanded by the compiler) plus the target pattern, optional @arg match, and an optional condition (lineage-includes, after, target scope). Each rule carries an explicit effect (audit, block, or kill). On a match the rule's rule_id, effect, blocked, and killed flags are emitted; the compiler keeps the reason strings. Full semantics: ../docs/rule-language.md.

eBPF verifier notes (why the code looks the way it does)

  • Subprograms for stack room. te_check / te_exec_update / te_file_src / te_endp_src_ip / te_connect_check are __noinline so each gets its own 512 B frame. te_check_labels takes one context pointer (BPF subprogram scalar args are limited) and writes the matched rule effect back into that context.
  • Copy rodata before matching. Each loop copies struct taint_rule r = taint_rules[i] into a non-volatile local; matchers take const char * (not const volatile char *). Direct volatile rodata reads inside the matchers mis-evaluate EXACT/PREFIX/SUFFIX.
  • Explicit bound guards, not index masking. if (idx < N) instead of idx & (N-1) — masking lets clang fold ptr + idx into ptr | idx, which the verifier rejects ("bitwise operator |= on pointer prohibited").
  • Buffers ≥ pattern length. comm and the IP string buffer are TAINT_PAT_LEN so matchers never read out of bounds.
  • Numeric IPv4 for connect. The compiler lowers host/IP patterns to net+mask; the kernel does (ip & mask) == net, avoiding in-kernel string formatting (which triggered the pointer-OR rejection). The loader formats the IP for display.

Retained capture programs

  • sslsniff — hooks SSL_read/SSL_write via uprobes to capture plaintext TLS.
  • stdiocap — captures process stdio.
  • browsertrace — browser-side tracing.

These are kept as data sources / building blocks; the enforcer (process) is the component the collector drives.