feat(bpf/file): Tier-3 signal-fallback enforcement for the file class#212
Merged
Conversation
Closes the file-class arm of Tier-3 signal-fallback, the symmetric twin of the already-shipped network arm (handle_tp_connect). On kernels without BPF-LSM, lsm/file_open cannot attach and open() cannot be denied with -EPERM; previously the sys_enter_openat tracepoint was pure audit. It now mirrors handle_tp_connect exactly: when --enforce-fallback=signal is set (agent_cfg.signal_fallback_enforce) AND the agent is in enforce mode, an open of a denied path is met with bpf_send_signal() (default SIGKILL, with the same "default to SIGKILL when no escalation configured" rule since a tracepoint cannot return -EPERM). Inert by default, so LSM-capable hosts behave exactly as before (audit-only). Honesty: this tier is detection+signal, not synchronous denial, and is PATH-based (the inode is not resolvable at syscall entry) — so it does NOT carry the inode-alias guarantee proved for lsm/file_open in proofs/inode_alias_resistance.py. GUARANTEES.md is updated to state the mechanism now covers both connect() and open(), and to keep the gate promotion (accepting signal-fallback as PRIMARY enforcement on genuinely no-LSM hosts) honestly flagged as the remaining follow-up — that step relaxes the No-Pretend gate and must be validated on a no-LSM kernel. Reuses the existing --enforce-fallback=signal flag (no new wiring). No new program/section, so the bpfcompat + kernel-compat manifests are unchanged; object builds and loads clean on 6.17. Cross-kernel verifier-safety is gated by the bpfcompat matrix on this PR. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ErenAri
added a commit
that referenced
this pull request
Jun 3, 2026
…214) * feat(daemon): Tier-3 enforce-gate promotion (ENFORCE_SIGNAL posture) Completes the gate half of Tier-3 signal-fallback. Previously, on a host without BPF-LSM the enforce-gate always degraded to audit-only (or fail-closed) even when the operator opted into --enforce-fallback=signal and the shipped tracepoint kill mechanism (PRs #212 net+file arms) was available. The mechanism could fire but the gate never chose it as the primary posture. Now: when (capability == AuditOnly) AND enforce was requested AND --enforce-fallback=signal AND the kernel can deliver the signal (tracepoints + bpf syscall), the daemon runs in a new, distinct RuntimeState::EnforceSignal posture instead of degrading. No-Pretend is preserved and made unit-testable: - ENFORCE_SIGNAL is a SEPARATE state from ENFORCE (asynchronous kill, not synchronous -EPERM); the daemon never reports ENFORCE here. - enforce_capable stays false (the BPF_LSM_DISABLED blocker is still reported in the capability report). - signal_fallback_enforce_eligible() is a pure predicate; the PostureGate.NeverEligibleOnFullEnforcementHost test exhausts the truth table to prove promotion can ONLY happen where synchronous ENFORCE is genuinely impossible. - Without the opt-in (or without signal capability) the gate behaves exactly as before — default deployments are unchanged. Contract surfaces updated consistently (all gates pass locally): - RuntimeState enum + runtime_state_name (EnforceSignal not counted as a degradation). - commands_metrics.cpp runtime_state gauge label set. - CAPABILITY_POSTURE_CONTRACT.md valid-state list + semantics. - evaluate_capability_posture.py runtime_ok set (ENFORCE_SIGNAL healthy). - GUARANTEES.md gate-status note (promotion implemented; live no-LSM kernel behavioral test is the one remaining follow-up). - 6 new PostureGate unit tests. The in-kernel kill on a real no-LSM host remains validated by design + the cross-kernel matrix load gate; a live no-LSM-kernel behavioral test is the remaining follow-up (noted honestly in GUARANTEES.md). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(lint): suppress cppcheck gtest syntaxError for test_posture_gate.cpp cppcheck does not parse gtest's TEST() macro and emits a false-positive syntaxError, exactly as for every other gtest file already listed in the lint step. Add the matching per-file suppression. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(posture): fix bugprone-argument-comment in posture-gate test The inline argument comment must match the parameter name (enforce_fallback_signal), not 'opt_in', or clang-tidy fails the build. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
ErenAri
added a commit
that referenced
this pull request
Jun 3, 2026
…#215) Closes the one remaining Tier-3 validation gap. The gate promotion (#214) and the file/net signal-fallback arms (#212) were unit- and load-tested, but the end-to-end no-BPF-LSM enforcement behavior had no behavioral test (it nominally needed a no-BPF-LSM kernel). tests/enforcement/signal_fallback_proof.sh exercises the agent's REAL no-BPF-LSM code path on any kernel via the AEGIS_LSM_PATH test seam (kernel_features.cpp reads it instead of /sys/kernel/security/lsm). The agent then believes BPF-LSM is absent and attaches ONLY the tracepoints — exactly what happens on a genuinely no-BPF-LSM kernel; the tracepoint+bpf_send_signal mechanism is kernel-version-independent. Asserts end-to-end: - gate promotes: runtime_state == ENFORCE_SIGNAL, audit_only == false - No-Pretend: enforce_capable == false (BPF_LSM_DISABLED still reported) - enforcement fires: a denied open() from a non-exempt cgroup is killed by a signal (exit > 128). Without the signal the open would SUCCEED (no LSM -EPERM on this path), so a signal-kill is unambiguous proof. Verified locally on 6.17: all 4 assertions PASS (ENFORCE_SIGNAL posture, honest capability report, denied open killed exit=143). Wired into kernel-matrix.yml so it runs on every matrix kernel (exit 77 = skipped when not root / no systemd-run). GUARANTEES.md updated: the gate-promotion behavior is now behaviorally proven, not a follow-up. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Closes the file-class arm of Tier-3 signal-fallback enforcement — the symmetric twin of the already-shipped network arm (
handle_tp_connect).On kernels without BPF-LSM,
lsm/file_opencannot attach andopen()cannot be denied with-EPERM. Previously thesys_enter_openattracepoint was pure audit. It now mirrorshandle_tp_connect: when--enforce-fallback=signalis set and the agent is in enforce mode, an open of a denied path is met withbpf_send_signal()(defaultSIGKILL). Inert by default — LSM-capable hosts behave exactly as before (audit-only).Honest scope
open()may partially proceed before the signal lands on syscall return.lsm/file_opendoes (proved inproofs/inode_alias_resistance.py). It is a strictly weaker tier.docs/GUARANTEES.mdkeeps that honestly flagged.Why this shape
It reuses the existing
--enforce-fallback=signalflag (agent_cfg.signal_fallback_enforce) → no new userspace wiring. It mirrors an already-accepted, already-merged pattern, so it inherits that contract. No new program/section ⇒ bpfcompat + kernel-compat manifests unchanged.Test
bpftool prog loadall→ all programs pinned).gen_bpfcompat_manifest.py --check,gen_kernel_compat_manifest.py --check,validate_enforcement_proof_contract.py, and the proof fidelity guard all pass.bpfcompat-matrixjob on this PR (5.15 / 6.1 / 6.8 / 6.12 / 6.17).🤖 Generated with Claude Code