Skip to content

Commit f727bfc

Browse files
r41k0uclaude
andcommitted
Docs: Note the packet-pointer-field gotcha, with the planned mechanism
xdp_md.data/data_end/data_meta and __sk_buff.data/data_end are u32 in C and packet pointers to the verifier, so arithmetic on them directly is rejected as 32-bit pointer arithmetic, for C and for us alike. A TODO at the point where a field's rank is decided describes the planned fix (64-bit pointer rank for those fields, no cast needed), and the user guide documents the workaround until then: copy to a local or cast through c_void_p. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 6caab6f commit f727bfc

2 files changed

Lines changed: 39 additions & 0 deletions

File tree

‎docs/user-guide/integers.md‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,34 @@ compares in the resulting type. `c_uint64(10) > c_int64(-1)` is therefore an uns
109109
comparison in which `-1` is the largest possible value, and the result is false. The
110110
result of a comparison is `1` or `0`, as in C.
111111

112+
## A verifier gotcha: packet pointer fields
113+
114+
A few context fields are declared as 32-bit integers but are pointers as far as the
115+
kernel verifier is concerned: `data`, `data_end` and `data_meta` on `xdp_md`, and `data`
116+
and `data_end` on `__sk_buff`. Because they are `c_uint32`, arithmetic on them directly
117+
is a 32-bit operation, exactly as in C, and the verifier rejects 32-bit arithmetic on a
118+
pointer:
119+
120+
```
121+
R0 32-bit pointer arithmetic prohibited
122+
```
123+
124+
C programs cast these fields through `(void *)(long)` before using them for the same
125+
reason. Until PythonBPF does this for you, copy the field into a local first, which is a
126+
64-bit slot, or cast it with `c_void_p`:
127+
128+
```python
129+
data = ctx.data # 64-bit local
130+
end = ctx.data_end
131+
if data + 34 < end: # 64-bit pointer arithmetic, accepted
132+
...
133+
```
134+
135+
```{note}
136+
This is a known gap. The plan is to give these fields pointer rank automatically so that
137+
no cast or copy is needed; this section will go away when that lands.
138+
```
139+
112140
## Divergences from Python
113141

114142
Because the semantics are C's, some Python behaviour does not carry over:

‎pythonbpf/expr/expr_pass.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,17 @@ def _descriptor(val, ty):
205205
if field is not None:
206206
# A vmlinux field: load_ctx_field already widened the value, but C
207207
# ranks it by its declared width (a c_uint32 field is unsigned int).
208+
#
209+
# TODO(gotcha): some u32 context fields are packet pointers to the
210+
# verifier, not numbers: xdp_md.data / data_end / data_meta and
211+
# __sk_buff.data / data_end. Ranking them as u32 is what C does, and
212+
# the verifier then rejects any arithmetic on them ("32-bit pointer
213+
# arithmetic prohibited"), so C code casts them through
214+
# (void *)(long) first. The plan is to spare users that: give these
215+
# fields 64-bit pointer rank here, so `ctx.data + 34 < ctx.data_end`
216+
# lowers to 64-bit pointer arithmetic without a cast. Until then,
217+
# copy the field into a local (a 64-bit slot) or cast it via
218+
# c_void_p before using it.
208219
return field
209220
if val is not None and isinstance(val.type, ir.IntType):
210221
return IntTy(val.type.width, signedness(ty))

0 commit comments

Comments
 (0)