Skip to content

Fix/packet pointer arith - #108

Open
r41k0u wants to merge 6 commits into
masterfrom
fix/packet-pointer-arith
Open

r41k0u wants to merge 6 commits into
masterfrom
fix/packet-pointer-arith

Conversation

@r41k0u

@r41k0u r41k0u commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator

Add a packet pointer type to IntTy

r41k0u and others added 4 commits September 25, 2026 11:20
xdp_md.data is u32 in C, so ctx.data - k lowers to a 32-bit add on the
loaded value, exactly as C ranks it, and the verifier rejects it: it
tracks data as a packet pointer and prohibits 32-bit pointer arithmetic
(observed in CI). IR and llc succeed, so the entry is at the verifier
level. It flips to passing when packet-pointer fields get 64-bit
pointer rank (TODO in expr_pass._descriptor).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
xdp_md.data/data_end/data_meta and __sk_buff.data/data_end/data_meta are
u32 in C but packet pointers to the verifier, which rewrites their load
into a pointer load and prohibits 32-bit arithmetic on them. The
signedness rules ranked them as u32, so ctx.data - k lowered to a 32-bit
add (w0 += -1) and was rejected; C code avoids this with a cast through
(void *)(long).

A binary operation with a packet-pointer operand now bypasses the usual
arithmetic conversions: the pointer is used at 64 bits, an offset added
to or subtracted from it is taken as an unsigned 16-bit value (truncated,
zero-extended) because the verifier only tracks packet ranges up to
MAX_PACKET_OFF (0xffff), and the result is a 64-bit value. Pointer minus
pointer is an ordinary 64-bit length. A constant offset outside
0..65535, any other operator, and offset minus pointer are compile
errors rather than code the verifier would reject. Local inference
agrees (64-bit unsigned).

Only direct field reads are recognised; a local copied from one is an
integer again, and comparisons between packet pointers are the next site
of the same rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The previous commit recognised a packet-pointer field only where it was
read directly in a binary operation, so d = ctx.data made d an ordinary
integer again and comparisons such as data + 34 > data_end were still
ranked u32 and emitted as 32-bit compares, which the verifier rejects.

PktPtrTy is a 64-bit unsigned IntTy tagged with the verifier's kind
(pkt, pkt_meta, pkt_end). It originates at the field read and travels
as the descriptor does: into a local's slot, through pointer +/- offset
(which yields the same kind), and into comparisons, which compare at
64 bits unsigned whenever either side is a packet pointer. It is handled
before the usual arithmetic conversions, which would re-rank it.

Two rules the verifier enforces become compile errors: a packet-end
pointer takes no offset, and convert() refuses to narrow a packet
pointer below 64 bits.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…the refusals

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 25, 2026 17:30

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Unresolved validation gaps affect augmented assignment, unary negation, constant offsets, context classification, and pointer-kind propagation.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 3 Medium severity

Open (3)
What changed in this PR

Adds verifier-aware packet-pointer typing and arithmetic for XDP and socket-buffer context fields.

Changes:

  • Introduces tagged packet-pointer types and lowering.
  • Adds pointer arithmetic, bounds, comparison, and narrowing tests.
  • Updates type inference, normalization, and local allocation.
File Summary
tests/​test_signedness_ir.py Adds packet-pointer IR assertions.
tests/​test_config.toml Registers expected failures.
tests/​passing_tests/​vmlinux/​ctx_data_via_local.py Tests pointer propagation through locals.
tests/​passing_tests/​vmlinux/​ctx_data_runtime_offset.py Tests runtime offsets.
tests/​passing_tests/​vmlinux/​ctx_data_plus_one.py Tests constant pointer arithmetic.
tests/​passing_tests/​vmlinux/​ctx_data_meta_offset.py Tests metadata-pointer offsets.
tests/​passing_tests/​vmlinux/​ctx_data_length.py Tests pointer subtraction.
tests/​passing_tests/​vmlinux/​ctx_data_arith.py Tests typed pointer arithmetic.
tests/​passing_tests/​vmlinux/​ctx_bounds_check.py Tests bounds checks through locals.
tests/​passing_tests/​vmlinux/​ctx_bounds_check_direct.py Tests direct bounds checks.
tests/​failing_tests/​vmlinux/​ctx_data_offset_too_big.py Tests oversized offset rejection.
tests/​failing_tests/​vmlinux/​ctx_data_narrow_store.py Tests narrowing rejection.
tests/​failing_tests/​vmlinux/​ctx_data_end_offset.py Tests packet-end arithmetic rejection.
pythonbpf/​type_deducer.py Defines packet-pointer types.
pythonbpf/​expr/​type_normalization.py Validates packet-pointer conversions.
pythonbpf/​expr/​type_inference.py Propagates pointer types.
pythonbpf/​expr/​packet_pointer.py Maps context fields to pointer kinds.
pythonbpf/​expr/​expr_pass.py Lowers pointer arithmetic and comparisons.
pythonbpf/​allocation_pass.py Allocates pointer-aware local storage.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +302 to +305
if isinstance(left_ty, PktPtrTy) or isinstance(right_ty, PktPtrTy):
# Before the usual arithmetic conversions, which would re-rank the
# pointer as an ordinary integer and drop its kind.
return _packet_pointer_binop(builder, rval, left, left_ty, right, right_ty)
Comment on lines +347 to +350
if isinstance(off, ir.Constant) and isinstance(off.constant, int):
if not 0 <= off.constant <= MAX_PACKET_OFF:
raise SyntaxError(
f"packet offset {off.constant} is outside 0..{MAX_PACKET_OFF} "
Comment thread pythonbpf/type_deducer.py
Comment on lines +50 to +51
every integer path accepts it; the few sites that must treat it as a
pointer check isinstance(ty, PktPtrTy) before the integer rules apply.
r41k0u and others added 2 commits September 25, 2026 23:09
Four keep-both conflicts: PktPtrTy and byte_size added at the same spot
in type_deducer, the allocation pass's type_deducer import, and
neighbouring entries in test_config.toml and test_signedness_ir.py.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
convert() refused to narrow a packet pointer below 64 bits, on the
premise that the verifier only accepts one at 64 bits. That is true of
arithmetic, not of stores: upstream's cgroup_skb_direct_packet_access
stores skb->data_end into a __u32 global, and CI's verifier level
accepted exactly that on #105. Merging master brought that selftest in
and the refusal rejected it. Narrowing now truncates, and the result is
an ordinary integer. ctx_data_narrow_store.py moves to the passing
tests. Keeping the pointer when a local is declared narrower belongs to
allocation (widest type ever stored), noted for later.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
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.

2 participants