You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Missing boundary check for load/store in JIT compiler #435
The current JIT compiler does not emit boundary checks for the load/store memory opcode. Hence, any program can perform an invalid memory operation.
e.g., the following PoC program can trigger invalid memory write:
mov %r0, 0
stxdw [%r0+96], %r0
Running it under JIT mode, we can get:
==173819==ERROR: UndefinedBehaviorSanitizer: SEGV on unknown address 0x0000000000f8 (pc 0x7fd973a5e023 bp 0x7ffd906bec70 sp 0x7ffd906bea60 T173819)
==173819==The signal is caused by a WRITE memory access.
Thanks for the report @pcy190! Here's a comprehensive analysis for implementers.
Current Status
As noted by @Alan-Jowett, bounds checking is not implemented in either the x86_64 or ARM64 JIT compilers. The interpreter has full bounds checking, but the JIT path skips these checks entirely.
Root Cause Analysis
The Vulnerability
The JIT compiler emits raw memory access instructions without any validation:
x86_64 JIT (ubpf_jit_x86_64.c lines 1927-1974):
caseEBPF_OP_LDXW:
emit_load(state, S32, src, dst, inst.offset); // No bounds check!break;
caseEBPF_OP_STXW:
emit_store(state, S32, src, dst, inst.offset); // No bounds check!break;
ARM64 JIT (ubpf_jit_arm64.c):
Same pattern - emit_loadstore() is called directly without validation.
Pros: Much faster (inline comparisons vs function call), ~20-30 bytes per check Cons: Doesn't support custom bounds_check_function callback, complex implementation
Option C: Hybrid Approach (Recommended)
Add inline checks for stack/mem (covers 99% of cases)
Fall back to calling bounds_check() only when inline check fails (handles custom callbacks)
// Inline fast pathif (accessinmem_range||accessinstack_range) {
// proceed with load/store
} else {
// Slow path: call bounds_check() for custom regionsif (!bounds_check(...)) {
goto error;
}
}
Implementation Requirements
Reserve registers for bounds info (or load from context struct):
mem_start, mem_end
stack_start, stack_end (stack is R10 in BPF, already tracked)
Add error exit path in JIT prologue/epilogue:
Set return value to -1
Clean up stack
Return to caller
Respect bounds_check_enabled flag:
Check vm->bounds_check_enabled at JIT compile time
Optionally: check at runtime in prologue, skip all checks if disabled
Handle all load/store opcodes:
EBPF_OP_LDX{B,H,W,DW} and sign-extended variants
EBPF_OP_ST{B,H,W,DW} (immediate stores)
EBPF_OP_STX{B,H,W,DW} (register stores)
EBPF_OP_ATOMIC_STORE variants
Affected Files
File
Changes Needed
vm/ubpf_jit_x86_64.c
Add bounds check emit functions, modify all load/store cases
vm/ubpf_jit_arm64.c
Same as x86_64 for ARM64
vm/ubpf_jit_support.h
Add shared helper declarations
tests/
Add JIT-specific bounds check tests
Acceptance Criteria
JIT-compiled code performs bounds checking equivalent to interpreter
Bounds checking can be disabled via ubpf_toggle_bounds_check(vm, false)
Custom bounds_check_function callbacks are invoked when set
All existing tests pass with JIT
New tests verify OOB access is caught in JIT mode
Performance impact documented (expected: 10-30% for bounds-heavy programs)
Activity
Alan-Jowett commentedon May 7, 2024
@pcy190 bounds checking is not implemented at all for JIT.
Fix MEMSX mnemonic naming to match RFC 9669 standard
Implement RFC 9669 MEMSX sign-extension load instructions (#701)
Alan-Jowett commentedon Jan 27, 2026
Root Cause Analysis & Implementation Details
Thanks for the report @pcy190! Here's a comprehensive analysis for implementers.
Current Status
As noted by @Alan-Jowett, bounds checking is not implemented in either the x86_64 or ARM64 JIT compilers. The interpreter has full bounds checking, but the JIT path skips these checks entirely.
Root Cause Analysis
The Vulnerability
The JIT compiler emits raw memory access instructions without any validation:
x86_64 JIT (
ubpf_jit_x86_64.clines 1927-1974):ARM64 JIT (
ubpf_jit_arm64.c):Same pattern -
emit_loadstore()is called directly without validation.Interpreter (
ubpf_vm.clines 1127-1130):Attack Scenario
Proposed Implementation
There are two main approaches:
Option A: Runtime Bounds Check Calls (Simpler, Slower)
Emit a call to the existing
bounds_check()function before each load/store:Pros: Reuses existing
bounds_check()logic, supports custom bounds check callbacksCons: ~50+ bytes of code per load/store, significant performance impact
Option B: Inline Range Checks (Complex, Faster)
Generate inline comparison code for common cases (stack/mem access):
Pros: Much faster (inline comparisons vs function call), ~20-30 bytes per check
Cons: Doesn't support custom
bounds_check_functioncallback, complex implementationOption C: Hybrid Approach (Recommended)
bounds_check()only when inline check fails (handles custom callbacks)Implementation Requirements
Reserve registers for bounds info (or load from context struct):
mem_start,mem_endstack_start,stack_end(stack is R10 in BPF, already tracked)Add error exit path in JIT prologue/epilogue:
Respect
bounds_check_enabledflag:vm->bounds_check_enabledat JIT compile timeHandle all load/store opcodes:
EBPF_OP_LDX{B,H,W,DW}and sign-extended variantsEBPF_OP_ST{B,H,W,DW}(immediate stores)EBPF_OP_STX{B,H,W,DW}(register stores)EBPF_OP_ATOMIC_STOREvariantsAffected Files
vm/ubpf_jit_x86_64.cvm/ubpf_jit_arm64.cvm/ubpf_jit_support.htests/Acceptance Criteria
ubpf_toggle_bounds_check(vm, false)bounds_check_functioncallbacks are invoked when setTest Cases
Severity
Critical - This allows arbitrary memory read/write from JIT-compiled BPF programs, bypassing all safety guarantees.
References
ubpf_vm.clines 1089-1125Alan-Jowett commentedon Jan 28, 2026
Implemented for x64. Will file a separate issue for ARM64 support