Skip to content

feat(bpf/file): Tier-3 signal-fallback enforcement for the file class#212

Merged
ErenAri merged 1 commit into
mainfrom
feat/tier3-file-signal-fallback
Jun 3, 2026
Merged

feat(bpf/file): Tier-3 signal-fallback enforcement for the file class#212
ErenAri merged 1 commit into
mainfrom
feat/tier3-file-signal-fallback

Conversation

@ErenAri

@ErenAri ErenAri commented Jun 3, 2026

Copy link
Copy Markdown
Owner

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_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: when --enforce-fallback=signal is set and the agent is in enforce mode, an open of a denied path is met with bpf_send_signal() (default SIGKILL). Inert by default — LSM-capable hosts behave exactly as before (audit-only).

Honest scope

  • Detection + signal, not synchronous denial — the open() may partially proceed before the signal lands on syscall return.
  • Path-based, not inode-based (the inode isn't resolvable at syscall entry), so this arm does not carry the inode-alias guarantee that lsm/file_open does (proved in proofs/inode_alias_resistance.py). It is a strictly weaker tier.
  • Gate promotion is still a follow-up. This PR ships the mechanism. Teaching the No-Pretend enforce-gate to accept signal-fallback as primary enforcement on genuinely no-LSM hosts relaxes a safety-critical invariant and must be validated on a no-LSM kernel — docs/GUARANTEES.md keeps that honestly flagged.

Why this shape

It reuses the existing --enforce-fallback=signal flag (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

  • Builds clean; full object loads on 6.17 (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.
  • Cross-kernel verifier-safety is gated by the bpfcompat-matrix job on this PR (5.15 / 6.1 / 6.8 / 6.12 / 6.17).

🤖 Generated with Claude Code

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 ErenAri merged commit aa8c314 into main Jun 3, 2026
41 checks passed
@ErenAri ErenAri deleted the feat/tier3-file-signal-fallback branch June 3, 2026 13:25
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant