Skip to content

Commit 6f25c55

Browse files
fix CO-RE read for cast structs
1 parent 84507b8 commit 6f25c55

File tree

4 files changed

+50
-16
lines changed

4 files changed

+50
-16
lines changed

pythonbpf/assign_pass.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ast
22
import logging
3+
from inspect import isclass
34

45
from llvmlite import ir
56
from pythonbpf.expr import eval_expr
@@ -149,11 +150,26 @@ def handle_variable_assignment(
149150
return False
150151

151152
val, val_type = val_result
152-
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, {var_type}")
153+
logger.info(f"Evaluated value for {var_name}: {val} of type {val_type}, expected {var_type}")
154+
153155
if val_type != var_type:
154-
# if isclass(val_type) and (val_type.__module__ == "vmlinux"):
155-
# logger.info("Handling typecast to vmlinux struct")
156-
# print(val_type, var_type)
156+
# Handle vmlinux struct pointers - they're represented as Python classes but are i64 pointers
157+
if isclass(val_type) and (val_type.__module__ == "vmlinux"):
158+
logger.info("Handling vmlinux struct pointer assignment")
159+
# vmlinux struct pointers: val is a pointer, need to convert to i64
160+
if isinstance(var_type, ir.IntType) and var_type.width == 64:
161+
# Convert pointer to i64 using ptrtoint
162+
if isinstance(val.type, ir.PointerType):
163+
val = builder.ptrtoint(val, ir.IntType(64))
164+
logger.info(f"Converted vmlinux struct pointer to i64 using ptrtoint")
165+
builder.store(val, var_ptr)
166+
logger.info(f"Assigned vmlinux struct pointer to {var_name} (i64)")
167+
return True
168+
else:
169+
logger.error(
170+
f"Type mismatch: vmlinux struct pointer requires i64, got {var_type}"
171+
)
172+
return False
157173
if isinstance(val_type, Field):
158174
logger.info("Handling assignment to struct field")
159175
# Special handling for struct_xdp_md i32 fields that are zero-extended to i64

pythonbpf/helper/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from .helper_registry import HelperHandlerRegistry
22
from .helper_utils import reset_scratch_pool
3-
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call
3+
from .bpf_helper_handler import handle_helper_call, emit_probe_read_kernel_str_call, emit_probe_read_kernel_call
44
from .helpers import (
55
ktime,
66
pid,
@@ -74,6 +74,7 @@ def helper_call_handler(
7474
"reset_scratch_pool",
7575
"handle_helper_call",
7676
"emit_probe_read_kernel_str_call",
77+
"emit_probe_read_kernel_call",
7778
"ktime",
7879
"pid",
7980
"deref",

pythonbpf/vmlinux_parser/vmlinux_exports_handler.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,11 +115,11 @@ def handle_vmlinux_struct_field(
115115
struct_name = python_type.__name__
116116
globvar_ir, field_data = self.get_field_type(struct_name, field_name)
117117

118-
# Handle cast struct field access (use standard GEP)
118+
# Handle cast struct field access (use bpf_probe_read_kernel)
119119
# Load the struct pointer from the local variable
120120
struct_ptr = builder.load(var_info.var)
121121

122-
# Use standard GEP for non-context struct field access
122+
# Use bpf_probe_read_kernel for non-context struct field access
123123
field_value = self.load_struct_field(
124124
builder, struct_ptr, globvar_ir, field_data, struct_name
125125
)
@@ -133,7 +133,7 @@ def load_struct_field(
133133
builder, struct_ptr_int, offset_global, field_data, struct_name=None
134134
):
135135
"""
136-
Generate LLVM IR to load a field from a regular (non-context) struct using standard GEP.
136+
Generate LLVM IR to load a field from a regular (non-context) struct using bpf_probe_read_kernel.
137137
138138
Args:
139139
builder: llvmlite IRBuilder instance
@@ -159,7 +159,8 @@ def load_struct_field(
159159
inbounds=False,
160160
)
161161

162-
# Determine the appropriate IR type based on field information
162+
# Determine the appropriate field size based on field information
163+
field_size_bytes = 8 # Default to 8 bytes (64-bit)
163164
int_width = 64 # Default to 64-bit
164165
needs_zext = False
165166

@@ -172,7 +173,7 @@ def load_struct_field(
172173

173174
if field_size_bits in [8, 16, 32, 64]:
174175
int_width = field_size_bits
175-
logger.info(f"Determined field size: {int_width} bits")
176+
logger.info(f"Determined field size: {int_width} bits ({field_size_bytes} bytes)")
176177

177178
# Special handling for struct_xdp_md i32 fields
178179
if struct_name == "struct_xdp_md" and int_width == 32:
@@ -195,16 +196,31 @@ def load_struct_field(
195196
field_data.ctype_complex_type, ctypes._Pointer
196197
):
197198
int_width = 64 # Pointers are always 64-bit
199+
field_size_bytes = 8
198200
logger.info("Field is a pointer type, using 64 bits")
199201
else:
200202
logger.warning("Complex vmlinux field type, using default 64 bits")
201203

202-
# Bitcast to appropriate pointer type based on determined width
203-
ptr_type = ir.PointerType(ir.IntType(int_width))
204-
typed_ptr = builder.bitcast(field_ptr, ptr_type)
204+
# Allocate local storage for the field value
205+
local_storage = builder.alloca(ir.IntType(int_width))
206+
local_storage_i8_ptr = builder.bitcast(local_storage, i8_ptr_type)
207+
208+
# Use bpf_probe_read_kernel to safely read the field
209+
# This generates:
210+
# %gep = getelementptr i8, ptr %struct_ptr, i64 %offset (already done above as field_ptr)
211+
# %passed = tail call ptr @llvm.bpf.passthrough.p0.p0(i32 2, ptr %gep)
212+
# %result = call i64 inttoptr (i64 113 to ptr)(ptr %local_storage, i32 %size, ptr %passed)
213+
from pythonbpf.helper import emit_probe_read_kernel_call
214+
215+
result = emit_probe_read_kernel_call(
216+
builder,
217+
local_storage_i8_ptr,
218+
field_size_bytes,
219+
field_ptr
220+
)
205221

206-
# Load the value
207-
value = builder.load(typed_ptr)
222+
# Load the value from local storage
223+
value = builder.load(local_storage)
208224

209225
# Zero-extend i32 to i64 if needed
210226
if needs_zext:

tests/failing_tests/vmlinux/requests.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def example(ctx: struct_pt_regs) -> c_int64:
1212
d = req.__data_len
1313
b = ctx.r12
1414
c = req.timeout
15-
print(f"data length {d} and {c} and {a} and {b}")
15+
print(f"data length {d} and {c} and {a}")
16+
print(f"ctx arg {b}")
1617
return c_int64(0)
1718

1819

0 commit comments

Comments
 (0)