Fix #1099: tighten ValidMapKeyValue packet bound; inline bounds checks#1100
Fix #1099: tighten ValidMapKeyValue packet bound; inline bounds checks#1100
Conversation
ValidMapKeyValue checked T_PACKET upper bound against the generic max_packet_size ceiling, while ValidAccess passed variable_registry.packet_size() for a tighter, runtime-bound check. Helper map key/value pointer arguments are real dereferences (not comparison-only checks), so they should use the same runtime ceiling as direct packet dereferences. Using the looser ceiling was sound but unnecessarily imprecise, and the asymmetry could mask false negatives surfaced by tighter packet-bound reasoning. Fixes #1099. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
📝 WalkthroughWalkthroughReplaces a single Changes
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
region_bounds() ignores the packet_size argument for every region type other than T_PACKET, so a non-empty packet_size paired with e.g. T_STACK is silently dropped — a caller bug we currently cannot detect. Add a debug-build assertion at the contract boundary so mismatched arguments fail loudly during development. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Replace the runtime-dispatch `region_bounds(type, reg, ctx, optional<Variable>)` with three constrained function templates: template <TypeEncoding T> requires CtxDerivedCeiling<T> RegionBounds region_bounds(const AnalysisContext&); template <TypeEncoding T> requires RegDerivedCeiling<T> RegionBounds region_bounds(const RegPack&); template <TypeEncoding T> requires (T == T_PACKET) RegionBounds region_bounds(Variable packet_size); Each call site now states the region type explicitly and may only pass the arguments that type actually consumes; mismatched combinations are compile errors rather than silently-ignored arguments. The packet runtime override is its own overload taking a Variable, so the dereference path is structurally distinct from the pointer-comparison path (which uses the AnalysisContext overload's max_packet_size ceiling). This is the structural form of the asymmetry that hid issue #1099 in `ValidMapKeyValue`. `require_region_bounds` mirrors the same overload set and is now a member-template on EbpfChecker, with a small `require_in_bounds` helper for the shared assertion pair. `ValidAccess`'s loop folds its inner `is_region_access_type` switch into the outer per-type switch so every bounds call has a compile-time-constant region type. The runtime `primary_kind_variable_for_type` lookup is no longer needed at the call sites (each case names the offset variable directly). The previous `assert(!packet_size || type == T_PACKET)` is dropped: the contract it guarded is now enforced at compile time. Refs #1099. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Drop the constrained-template region_bounds API in favor of two plainly-named member functions on EbpfChecker, distinguished by what determines a region's ceiling rather than by C++ template machinery: check_access_to_static_region(TypeEncoding, lb, ub) check_access_to_static_region(Variable packet_size, lb, ub) check_access_to_dynamic_region(Variable region_size, lb, ub) Static regions (T_STACK, T_CTX, T_PACKET) have ceilings that are a property of the program invocation as a whole; dynamic regions (T_SHARED, T_ALLOC_MEM) carry their ceiling as a per-allocation kind variable on the RegPack. T_PACKET groups with the static regions because there is exactly one packet per invocation -- its bounds (packet_size set once at entry, or the loose max_packet_size constant) do not vary with the allocation site of the pointer; the two ceilings split further by access shape, which the (Variable) overload of check_access_to_static_region encodes structurally. The dynamic-region check takes the size variable directly, since the TypeEncoding adds nothing the size does not already carry. Verified: full test suite passes (1314 passed, 236 failed-as-expected, unchanged from before the refactor). Refs #1099. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/crab/ebpf_checker.cpp`:
- Around line 75-83: The T_PACKET branch of
check_access_to_static_region(TypeEncoding, ...) is only sound for comparison
checks but this precondition is only in the comment; enforce it by either
asserting that callers pass a comparison-shaped access when
TypeEncoding==T_PACKET (e.g. assert is_comparison_check in the caller or at the
top of check_access_to_static_region) or, preferably, split the "loose packet"
semantics into a dedicated helper (e.g. check_access_to_loose_packet_region or
similar) and keep check_access_to_static_region's TypeEncoding overload to
reject unexpected T_PACKET values (throw_fail/default case) so stray T_PACKET
arguments are caught structurally; update callers such as the ValidAccess
T_PACKET arm to call the new helper explicitly and add a brief comment
documenting the new helper's narrower contract.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4667111b-e5fd-499a-bf57-59e176aca057
📒 Files selected for processing (3)
src/crab/ebpf_checker.cppsrc/crab/region_semantics.cppsrc/crab/region_semantics.hpp
💤 Files with no reviewable changes (1)
- src/crab/region_semantics.cpp
…_bound Drop the per-region check_access_to_* family and the RegionBounds struct in favor of two primitives composed at each call site: require_lower_bound(access_lb, floor, msg); require_upper_bound(access_ub, ceiling, msg); Each per-type case in EbpfChecker now spells out its floor and ceiling directly where the access is checked, instead of routing through a dispatcher whose contract had to be carried in comments. The bug class that motivated #1099 (passing the wrong T_PACKET ceiling at one site) is now a one-line read at every call site: it shows up as the literal ceiling expression that gets passed to require_upper_bound. The static/dynamic taxonomy and its overload set are gone -- they were the API carrying weight that did not earn its keep; T_PACKET in particular did not fit either bucket honestly (meta_offset floor, two possible ceilings). Refs #1099. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Elazar Gershuni <elazarg@gmail.com>
Summary
Fixes #1099 and reshapes the bounds-check helpers around it into two primitives that each per-region case composes directly.
The bug
ValidMapKeyValue's T_PACKET branch checked the upper bound against the loosemax_packet_sizeconstant, whileValidAccess's T_PACKET dereference path used the runtimevariable_registry.packet_size()for a tighter check. Helper map key/value pointers are real reads/writes through the pointer (the helper copieskey_size/value_sizebytes), so they need the same runtime ceiling as direct dereferences. Usingmax_packet_sizewas unsoundly loose: a program could pass verification while accessing past the actual runtimepacket_size.The fix at the call site now reads as one line:
require_upper_bound(ub, variable_registry.packet_size(), "Upper bound must be at most packet_size");The refactor
Replaced the previous
region_bounds/RegionBounds/require_region_boundsapparatus (and a brief detour through templated overloads and astatic/dynamictaxonomy) with two trivial member primitives onEbpfChecker:Each per-type case (
T_STACK,T_CTX,T_PACKET,T_SHARED,T_ALLOC_MEM) inEbpfChecker::operator()(const ValidAccess&)andoperator()(const ValidMapKeyValue&)now spells out its floor and ceiling directly at the line where the access is checked, rather than routing through a per-region helper whose contract had to be carried in comments.ValidAccess's outer loop also folds its inneris_region_access_typeswitch into the per-type switch — every bounds check is now in a case with a compile-time-constant region type. The runtimeprimary_kind_variable_for_typelookup is no longer needed at the call sites (each case names the offset variable directly).The
region_boundsfree function andRegionBoundsstruct are removed;region_semantics.{hpp,cpp}shrinks to justis_region_access_typeandprimary_kind_variable_for_type(still used elsewhere).Why this shape
The bug class behind #1099 was that the per-region helper had two T_PACKET ceilings (loose
max_packet_sizefor pointer-comparison checks, runtimepacket_sizefor dereferences) and the choice was hidden behind astd::optional<Variable>argument. "I forgot to pass it" looked identical to "I deliberately chose not to" — which is exactly how the asymmetry betweenValidMapKeyValueandValidAccessstayed invisible.After the refactor the choice is the literal ceiling expression on the
require_upper_boundline. The diff that fixes a #1099-class bug is the diff readers see at the call site.Soundness
packet_size <= max_packet_sizeis an invariant of the abstract domain, so tightening the T_PACKET ceiling at this site can only reject programs that the previous version also could have caught at the analogousValidAccesssite — no previously-verified program becomes rejected for a reason that wasn't already there.width == 0) inValidAccess::T_PACKETcontinue to usemax_packet_size, preserving the legitimateend = ptr + N; if (end <= data_end)pattern.Test plan
cmake --build build --parallel— clean build./bin/tests— 1314 passed, 1 skipped, 236 failed-as-expected (unchanged from main)References