feat(det-init): guest pid-1 that detonates untrusted packages under a BPF-LSM observer - #983
feat(det-init): guest pid-1 that detonates untrusted packages under a BPF-LSM observer#983bryan-minimal wants to merge 1 commit into
Conversation
… BPF-LSM observer
Adds `det-init`, the minimal-detonation guest `/init`. It embeds minimald's public
guest-boot library (`minimald::guest`) rather than forking minimald, then stands up
an in-guest BPF-LSM observer and runs an untrusted package install as `nobody`
under it, streaming the resulting event feed out for a host-side verdict.
It deliberately does NOT run minimald's SSH server: a detonation is one-shot, so
the sample is executed directly and the guest is discarded afterwards.
Boot sequence: mount, enter the rootfs, emit the READY vsock beacon early (minvmd
tears the guest down on a ~5s READY timeout), start the observer and wait for it to
signal readiness so no syscall is missed, route the sample's DNS through the
observer's collector, fire the deny-channel tripwire, drop privileges, run the
sample, then drain the observer and emit the feed.
Three properties are load-bearing for the trust model and each is self-checked at
boot:
- the sample's stdio is NULLED, not inherited. The feed is emitted on the guest
console and the host collects it by scanning for `{`-lines, so an inherited
console would let `nobody` print forged events that the host accepts as real —
voiding "a nobody sample cannot write the feed".
- the binding nonce is readable only by root, proven by a leak-check that asserts
`nobody` gets EPERM.
- the event file is root-0400, proven by a uid-boundary self-check.
The sample is bounded by its own deadline, shorter than the host's VM timeout. The
feed is drained AFTER the sample returns, so an unbounded sample that never returns
would take the entire feed with it — the host reaps the VM, the drain never runs,
and a detonation that should have been BLOCK reports as an empty capture. That is
not an exotic input: a poisoned package whose C2 is unreachable blocks on connect()
exactly like this, so the better the network defense, the more likely the whole
feed is lost. On expiry the sample is SIGKILLed and the events it already produced
are drained normally.
Verified on Linux (`cargo check -p det-init --locked`, rustc 1.97.0) and by live
detonation on x86_64/KVM: 16/16 LSM hooks and 3/3 tracepoints attach, a clean npm
install verdicts PASS, and a poisoned sample reading honeytokens and beaconing to a
non-registry host verdicts BLOCK.
Refs gominimal/minimal-detonation#194, gominimal/minimal-detonation#272
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 23 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (3)
Comment |
|
Closing — this should not have been opened. Landing det-init on minimal's main is gated on a minimal-team sync (observer position + untrusted-package policy), per the re-architecture decision tracked in gominimal/minimal-detonation#194. det-init deliberately lives on I opened this (and #981) without checking that gate. The CI churn on those PRs was the visible problem; the real one was proposing an architectural change to a shared repo that hasn't had its design conversation yet. The det-init work itself is unaffected and stays on the branch: the hang fix (gominimal/minimal-detonation#272), the removed |
Adds
det-init, the minimal-detonation guest/init. It embedsminimald::guestrather than forking minimald, then stands up an in-guest BPF-LSM observer and runs an untrusted package install asnobodyunder it, streaming the event feed out for a host-side verdict.It deliberately does not run minimald's SSH server — a detonation is one-shot, so the sample runs directly and the guest is discarded.
Pure addition: one new crate (+664 lines), plus its
Cargo.toml/Cargo.lockworkspace entries. No existing file is modified.Three load-bearing trust properties, each self-checked at boot
nobodycannot write the feed{"event":…}lines onto the console the host scrapesnobodygets EPERMThe sample is bounded
The feed is drained after the sample returns, so an unbounded sample that never returns takes the entire feed with it — the host reaps the VM, the drain never runs, and a detonation that should have been BLOCK reports as an empty capture.
Not an exotic input: a poisoned package whose C2 is unreachable blocks on
connect()exactly like this, so the better the network defense, the more likely the whole feed is lost. On expiry the sample is SIGKILLed and the events it already produced drain normally.Verification
cargo check -p det-init --lockedon Linux, rustc 1.97.0 (the repo's pinned toolchain) — cleannpm install→ PASS; poisoned sample (honeytoken reads + non-registry beacon) → BLOCK, signedSupersedes #981, which carried six commits — including fixes to its own earlier commits and two headers over commitlint's 100-char limit. Squashed to one, since this introduces a single new crate and that history was noise to review.
Refs gominimal/minimal-detonation#194, gominimal/minimal-detonation#272
Note
Add
det-initguest pid-1 to detonate untrusted packages under a BPF-LSM observerdet-initbinary crate (Cargo.toml, main.rs) that runs as pid-1 inside the microVM and orchestrates the full detonation flow./dev, rootfs, securityfs, and bpffs; raises the memlock rlimit; then starts a root BPF-LSM observer (detonation-monitor) and waits up to 10s for it to signal readiness.nobody) via adropprivhelper with a 90s deadline and SIGKILL backstop; emits the drained JSONL event feed to stdout for host collection.nobodycannot read the binding nonce or write the event feed, and optionally repoints DNS to a local collector when egress is up.async_maincompletes to prevent this.Macroscope summarized fb15b54.