feat(sight): observe-only BPF LSM audit probe (socket_connect + file_open)#665
Draft
jfeng18 wants to merge 5 commits into
Draft
feat(sight): observe-only BPF LSM audit probe (socket_connect + file_open)#665jfeng18 wants to merge 5 commits into
jfeng18 wants to merge 5 commits into
Conversation
This was referenced May 31, 2026
ee733a4 to
f459e12
Compare
Add an observe-only BPF LSM program that audits two security-relevant
actions for traced Agent families, capturing what the existing probes miss:
- lsm/socket_connect: every outbound connection attempt (destination
IP:port), regardless of protocol or port -- sslsniff/tcpsniff/udpdns
only see TLS / specific ports / DNS.
- lsm/file_open: every file opened (basename + access mode), deduped
per (pid, inode) via an LRU map so a chatty Agent cannot flood the
shared ring buffer.
Both hooks filter to traced families via is_pid_traced and emit through the
shared ring buffer (EVENT_SOURCE_LSM). They are strictly observe-only: every
program returns the incoming `ret` unchanged, so no operation is ever denied
(the LSM attach point leaves room for future enforcement).
The event struct is named lsm_audit_event to avoid colliding with the
kernel's own `enum lsm_event` in vmlinux.h. file_open records the basename
(bpf_d_path is allowlist-rejected for lsm/file_open); full path is future work.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Parse raw lsm_audit_event ring-buffer records into a typed LsmEvent
(Connect { dst_ip, dport, ... } / FileOpen { path, open_flags, ... }),
decoding the destination address by family and the port from network byte
order. LsmAudit::new_with_maps reuses the shared traced_processes and ring
buffer maps and attaches both lsm/ programs; bpf_lsm_available() gates the
probe on `bpf` being in the active LSM list. Adds Event::Lsm and its parser
arm.
Unit tests cover IPv4/IPv6 decode, port byte-order, file_open parsing, and
short/unknown-kind inputs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Dispatch EVENT_SOURCE_LSM records to LsmEvent in the shared ring-buffer poller and create/attach the probe (opt-in, skipped with a warning when BPF LSM is inactive). Surface each audit event as a structured log line in the event loop: [LSM] connect pid=... comm=... dst=IP:PORT [LSM] open pid=... comm=... mode=r|w|rw path=... Add the --enable-lsm-audit CLI flag and the matching config field (default off; follows the enable_filewatch plumbing). Verified on kernel 6.6.102 (BPF LSM active): both hooks attach; a traced agent's outbound connects are captured with correct IP:port while all 10 connects still succeed (observe-only, nothing denied); file opens are deduped to one event per file; events are emitted only for traced PIDs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Three correctness fixes in the LSM audit probe, all caught by an adversarial review of the as-pushed branch: 1. **path[] info leak** — `bpf_ringbuf_reserve` returns a slot that may carry residue from a previously-submitted record on the shared ring buffer (sslsniff TLS plaintext, filewrite content). The original code only zeroed `path[0]`, leaving `path[1..256]` uninitialized after `bpf_probe_read_kernel_str` wrote `basename + nul + ?`. Memset the whole record immediately after reserve in both hooks, drop the now-redundant per-field zeros. 2. **file_open dedup-before-emit** — the `(pid, inode)` LRU entry was inserted BEFORE reserve / `bpf_probe_read_kernel_str`, so any reserve failure or NULL/short read permanently suppressed that file for the process until LRU eviction. Move the dedup `bpf_map_update_elem` to after a successful submit; `bpf_ringbuf_discard` the slot on path-read failure so a future open still gets a chance. 3. **socket_connect addrlen unchecked** — LSM fires before the protocol-level length check, so `sin_port`/`sin_addr` can be uninitialized stack residue if `addrlen < sizeof(struct sockaddr_in)`. Drop the event in that case rather than emit garbage destination fields. Also adds an always-on Rust-side leak detector: `LsmEvent::from_bytes` scans `path[]` past the null terminator and `log::warn!`s if any byte is non-zero — a permanent regression net for the producer-side memset invariant. Two unit tests (positive: garbage tail triggers detector and decoder still extracts the correct path; negative: clean tail stays silent). Verified: cargo build, cargo test (8/8 lsmaudit + 447/447 lib), ECS post-fix sanity (kernel 6.6.102, 173 LSM events, zero leak warnings, paths clean). Note on E2E differential signal: the leak class (residue from ringbuf wrap) cannot be reliably triggered in a synthetic test — the 32 MiB ring rarely wraps in seconds. A reverse-test with the memset removed produced 158 events and 0 leak warnings on the synthetic load. The detector remains in production as the regression net for real long-running deployments. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
EVENT_SOURCE_SCHED=7 (from alibaba#662 schedmon) would collide with EVENT_SOURCE_LSM=7 on merge. Renumber LSM to 8. Co-Authored-By: Claude Opus 4.6 <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
Adds an opt-in, observe-only BPF LSM probe (
lsmaudit) that audits two security-relevant actions for traced Agent families:lsm/socket_connect— every outbound connection attempt (destination IP:port), regardless of protocol or port.lsm/file_open— every file opened (basename + access mode), deduped per(pid, inode)via an LRU map.Both hooks filter to traced families via the shared
traced_processesmap (is_pid_traced) and emit through the shared ring buffer (EVENT_SOURCE_LSM). Events surface as structured audit log lines:Observe-only
Strictly observe-only: every program returns the incoming
retunchanged, so no operation is ever denied. The LSM attach point leaves room for future enforcement.Opt-in
Off by default. Enable with
--enable-lsm-audit(or the matching config field). Requires BPF LSM active (bpfin/sys/kernel/security/lsm); otherwise the probe is skipped with a warning.Correctness fixes folded in (commit
fix(sight): lsmaudit BPF correctness + leak guard)A self-review of the as-pushed branch caught three issues, all fixed in the latest commit:
bpf_ringbuf_reservereturns a slot that may carry residue from a previously-submitted record on the shared ring buffer (sslsniff TLS plaintext, filewrite content). The original code only zeroedpath[0], leavingpath[1..256]uninitialized. Memset the whole record after reserve in both hooks.(pid, inode)LRU was inserted before reserve / read, so any failure permanently suppressed that file. Move the dedup mark to after a successful submit;bpf_ringbuf_discardthe slot on path-read failure.addrlen < sizeof(struct sockaddr_in[6])the destination fields are stack residue. Drop the event in that case.Plus an always-on Rust-side leak detector:
LsmEvent::from_bytesscanspath[]past the null terminator andlog::warn!s on any non-zero byte — permanent regression net for the producer-side memset invariant.Testing
cargo test --lib lsmaudit)Notes
lsm_audit_eventto avoid colliding with the kernel's ownenum lsm_eventinvmlinux.h.file_openrecords the basename only (full path is future work —bpf_d_pathis allowlist-rejected forlsm/file_open).buf[16384]). Out of scope for this PR; tracked as a separate follow-up.Independent of #661–#668.