| |
| """ |
| 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 |
|
|
|
|
| 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() |
|
|
| |
| |
| |
| |
| |
| 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() |
|
|
| |
| |
| segment_base_offset_1 = 0x8000000000000000 |
| segment_data_size_1 = 0x8000000000000100 |
| file_size_1 = 0x0000000000100000 |
|
|
| 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" |
| ) |
|
|
| |
| |
| |
| |
| 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 |
| seg_size_2 = 0x0000000000010000 |
|
|
| 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" |
| ) |
|
|
| |
| 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() |
|
|
| |
| |
| |
| |
| 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 |
|
|
| 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() |
|
|
| |
| |
| |
| |
| 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 |
|
|
| 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() |
|
|
| |
| seg_base_4b = UINT64_MAX - 100 |
| seg_off_4b = 50 |
| seg_idx_4b = 52 |
|
|
| 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() |
|
|
| |
| |
| |
| 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() |
|
|
| |
| |
| |
| 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()) |
|
|