Fix two BPF verifier OOB rejections on strict/newer kernels#6
Open
jevansnyc wants to merge 1 commit into
Open
Conversation
claudefeed fails to load with BPF_VERIFIER_REJECTED on stricter kernels (seen on Linux 6.12 arm64). Two out-of-bounds accesses in handle_execve, both from a bound the compiler proves but the verifier does not carry: - read_cmdline: the ARGSIZE write to &e->cmdline[sz] is flagged OOB (mem_size=848 off=844 size=256). The `sz > ARGS_CEIL` break keeps sz <= ARGS_CEIL, but that bound is lost across the unrolled accumulation. Clamp the offset behind barrier_var() so the optimizer can't fold the clamp away as redundant, leaving the verifier a bounded register. - name_matches: needle[] is read out of bounds (value_size=20 off=20). needle_len lives in .data and is unbounded to the verifier; the compiler trusts the `nl <= NEEDLE_LEN` guard and drops the index mask, while the verifier runs j past NEEDLE_LEN via the `j >= nl` exit. Use a constant trip count with no data-dependent break so clang unrolls to NEEDLE_LEN iterations with compile-time-constant indices; gate each compare with `j < nl` to keep early-mismatch semantics. Both changes are behavior-preserving. Verified with `bpftool prog loadall` (all 5 programs load) and a live `yeet run` on 6.12 arm64. Co-Authored-By: Claude Opus 4.8 (1M context) <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.
Summary
On stricter/newer kernels
claudefeedfails to load withBPF_VERIFIER_REJECTED(reproduced on Linux 6.12 arm64). Thehandle_execveprogram has two out-of-bounds accesses, both from the same root cause: a bound the compiler proves, but the verifier doesn't carry. More lenient kernels happened to accept them; stricter ones don't.Note:
yeettruncates the verifier log, which makes it look like the failure is inname_matches. Loading the object directly —sudo bpftool prog loadall claudefeed.bpf.o /sys/fs/bpf/t— gives the real, untruncated verdict, and the first failure is actually inread_cmdline.Bug 1 —
read_cmdline, OOB writeThe
ARGSIZEwrite to&e->cmdline[sz]overflows the event record. Thesz > ARGS_CEILbreak keepssz <= ARGS_CEIL, but the compiler folds the resulting clamp away as redundant, and the verifier — which doesn't carry that bound through the unrolled accumulation — sees the offset reach 512.Fix: clamp the offset behind
barrier_var()so the optimizer can't prove the clamp redundant, leaving the verifier a register it can bound.Bug 2 —
name_matches, OOB readneedle_lenlives in.dataand is verifier-unbounded. The compiler trusts thenl <= NEEDLE_LENguard and drops the& (NEEDLE_LEN-1)index mask, while the verifier letsjrun pastNEEDLE_LENvia thej >= nlexit — reading past the 16-byteneedle.Fix: constant trip count with no data-dependent
break, so clang unrolls toNEEDLE_LENiterations with compile-time-constant indices; gate each compare withj < nlto preserve early-mismatch semantics.Testing
bpftool prog loadall-> all 5 programs (handle_execve,handle_exit,handle_openat,handle_tcp_connect,handle_inet_listen) load.yeet run . -- --secs=8on 6.12 arm64 -> streams exec/open/conn/listen events; no rejection.