Skip to content

Commit a5cbff3

Browse files
r41k0uclaude
andcommitted
Tests: Integer signedness cases mirroring the C reference
Seven programs under passing_tests/signedness, one per case in tests/c-form/signedness.bpf.c: unsigned and signed widening, mixed division and remainder, unsigned comparison, u32 * u32 wrapping before the widening, right shifts, and literal rank. Levels 1 and 2 compile them like any other case; test_signedness_ir.py asserts the shape of the IR against what clang emits for the C (zext vs sext, udiv vs sdiv, icmp ugt vs sgt, lshr vs ashr, the trunc/zext pair around the wrap). The XDP xfail note no longer claims the guard is a signed compare. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BSDVsZH5NtoASyxB8FCtGU
1 parent 7a79258 commit a5cbff3

9 files changed

Lines changed: 387 additions & 2 deletions

File tree

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_uint32
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u32_ten() -> c_uint32:
10+
return c_uint32(10)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def by_literal() -> c_uint32:
16+
return c_uint32(0)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def wide() -> c_int64:
22+
return c_int64(0)
23+
24+
25+
@bpf
26+
@section("tracepoint/raw_syscalls/sys_enter")
27+
def prog(ctx: c_void_p) -> c_int64:
28+
global by_literal, wide
29+
# A literal has C's `int` type, so u32 / -2 promotes to u32: an unsigned
30+
# division by 0xFFFFFFFE (udiv), exactly as `u32_ten / -2` in C
31+
by_literal = u32_ten / -2
32+
# A literal that does not fit in int is a `long long`; the sum is 64-bit
33+
wide = 5000000000 + 1
34+
# i32-ranked literals are still 64-bit slots for an undeclared local
35+
small = 7 * 6
36+
return c_int64(small)
37+
38+
39+
@bpf
40+
@bpfglobal
41+
def LICENSE() -> str:
42+
return "GPL"
43+
44+
45+
compile()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_int32, c_uint32
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u32_ten() -> c_uint32:
10+
return c_uint32(10)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def s32_neg2() -> c_int32:
16+
return c_int32(-2)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def mixed_div() -> c_uint32:
22+
return c_uint32(0)
23+
24+
25+
@bpf
26+
@bpfglobal
27+
def mixed_mod() -> c_uint32:
28+
return c_uint32(0)
29+
30+
31+
@bpf
32+
@section("tracepoint/raw_syscalls/sys_enter")
33+
def prog(ctx: c_void_p) -> c_int64:
34+
global mixed_div, mixed_mod
35+
# u32 / s32: the usual arithmetic conversions make both u32, so this is an
36+
# unsigned division: 10 / 0xFFFFFFFE == 0, not -5
37+
mixed_div = u32_ten / s32_neg2
38+
mixed_mod = u32_ten % s32_neg2
39+
# both signed: an ordinary signed division
40+
a = c_int32(-7)
41+
b = c_int32(2)
42+
q = a / b
43+
return c_int64(q)
44+
45+
46+
@bpf
47+
@bpfglobal
48+
def LICENSE() -> str:
49+
return "GPL"
50+
51+
52+
compile()
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_uint32, c_uint64
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u32_half() -> c_uint32:
10+
return c_uint32(0x80000000)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def u32_two() -> c_uint32:
16+
return c_uint32(2)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def narrow_wrap() -> c_uint64:
22+
return c_uint64(0)
23+
24+
25+
@bpf
26+
@section("tracepoint/raw_syscalls/sys_enter")
27+
def prog(ctx: c_void_p) -> c_int64:
28+
global narrow_wrap
29+
# u32 * u32 is a u32 multiplication: 0x80000000 * 2 wraps to 0 before the
30+
# widening to u64 (the LHS never widens the operands)
31+
narrow_wrap = u32_half * u32_two
32+
# narrowing truncates: only the low 32 bits of the u64 survive
33+
small = c_uint32(narrow_wrap)
34+
return c_int64(small)
35+
36+
37+
@bpf
38+
@bpfglobal
39+
def LICENSE() -> str:
40+
return "GPL"
41+
42+
43+
compile()
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_int32, c_uint32
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u32_max() -> c_uint32:
10+
return c_uint32(0xFFFFFFFF)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def s32_neg() -> c_int32:
16+
return c_int32(-1)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def shr_unsigned() -> c_uint32:
22+
return c_uint32(0)
23+
24+
25+
@bpf
26+
@bpfglobal
27+
def shr_signed() -> c_int32:
28+
return c_int32(0)
29+
30+
31+
@bpf
32+
@section("tracepoint/raw_syscalls/sys_enter")
33+
def prog(ctx: c_void_p) -> c_int64:
34+
global shr_unsigned, shr_signed
35+
# u32 >> 4 shifts zeros in (lshr): 0x0FFFFFFF
36+
shr_unsigned = u32_max >> 4
37+
# s32 >> 4 shifts the sign in (ashr): -1 stays -1
38+
shr_signed = s32_neg >> 4
39+
return c_int64(0)
40+
41+
42+
@bpf
43+
@bpfglobal
44+
def LICENSE() -> str:
45+
return "GPL"
46+
47+
48+
compile()
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_uint64
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u64_ten() -> c_uint64:
10+
return c_uint64(10)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def s64_neg() -> c_int64:
16+
return c_int64(-1)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def unsigned_cmp() -> c_uint64:
22+
return c_uint64(0)
23+
24+
25+
@bpf
26+
@bpfglobal
27+
def signed_cmp() -> c_uint64:
28+
return c_uint64(0)
29+
30+
31+
@bpf
32+
@section("tracepoint/raw_syscalls/sys_enter")
33+
def prog(ctx: c_void_p) -> c_int64:
34+
global unsigned_cmp, signed_cmp
35+
# u64 > s64: the comparison happens in u64, so -1 is the largest value and
36+
# 10 > -1 is false (icmp ugt)
37+
if u64_ten > s64_neg:
38+
unsigned_cmp = 1
39+
# s64 > s64 stays a signed comparison (icmp sgt)
40+
a = c_int64(10)
41+
if a > s64_neg:
42+
signed_cmp = 1
43+
return c_int64(0)
44+
45+
46+
@bpf
47+
@bpfglobal
48+
def LICENSE() -> str:
49+
return "GPL"
50+
51+
52+
compile()
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_int32, c_uint64
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def s32_neg() -> c_int32:
10+
return c_int32(-1)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def widen_signed() -> c_uint64:
16+
return c_uint64(0)
17+
18+
19+
@bpf
20+
@section("tracepoint/raw_syscalls/sys_enter")
21+
def prog(ctx: c_void_p) -> c_int64:
22+
global widen_signed
23+
# u64 = s32: -1 sign-extends to 0xffffffffffffffff (sext), as in C and ctypes
24+
widen_signed = s32_neg
25+
x = c_int32(-5)
26+
y = c_int64(x)
27+
return y
28+
29+
30+
@bpf
31+
@bpfglobal
32+
def LICENSE() -> str:
33+
return "GPL"
34+
35+
36+
compile()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Mirrors tests/c-form/signedness.bpf.c; the IR clang emits for that file is
2+
# the specification (see tests/test_signedness_ir.py for the ops asserted).
3+
from pythonbpf import bpf, section, bpfglobal, compile
4+
from ctypes import c_void_p, c_int64, c_int32, c_uint32
5+
6+
7+
@bpf
8+
@bpfglobal
9+
def u32_max() -> c_uint32:
10+
return c_uint32(0xFFFFFFFF)
11+
12+
13+
@bpf
14+
@bpfglobal
15+
def widen_unsigned() -> c_int64:
16+
return c_int64(0)
17+
18+
19+
@bpf
20+
@bpfglobal
21+
def reinterpret() -> c_int32:
22+
return c_int32(0)
23+
24+
25+
@bpf
26+
@section("tracepoint/raw_syscalls/sys_enter")
27+
def prog(ctx: c_void_p) -> c_int64:
28+
global widen_unsigned, reinterpret
29+
# s64 = u32: value-preserving, so 0xFFFFFFFF stays 4294967295 (zext, not sext)
30+
widen_unsigned = u32_max
31+
# s32 = u32: same width, the bits are reinterpreted (-1)
32+
reinterpret = u32_max
33+
# a local copied from a global takes the global's type: a c_uint32 slot
34+
also = u32_max
35+
return c_int64(also)
36+
37+
38+
@bpf
39+
@bpfglobal
40+
def LICENSE() -> str:
41+
return "GPL"
42+
43+
44+
compile()

tests/test_config.toml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@
1616
"failing_tests/conditionals/struct_ptr.py" = {reason = "Struct pointer used directly as boolean condition not supported", level = "ir"}
1717

1818
# Compiles cleanly; rejected by the kernel. The `data + 34 < data_end` guard is
19-
# emitted as a signed compare over values round-tripped through the stack, so the
20-
# verifier never narrows the packet range (it stays r=0) and the later
19+
# emitted as a compare over values round-tripped through the stack (an unsigned
20+
# compare now that c_uint fields carry their sign; recheck at the verifier
21+
# level), so the verifier never narrows the packet range (it stays r=0) and the later
2122
# `iph.saddr` read fails with "invalid access to packet, off=26 size=4" /
2223
# "R1 offset is outside of the packet". Direct packet access needs bounds checks
2324
# in a form the verifier can follow.

0 commit comments

Comments
 (0)