YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

ExecuTorch .pte behavioral backdoor (trigger-conditioned, scanner-clean)

Category: Model File Format β€” architectural/behavioral backdoor Format: ExecuTorch .pte (FlatBuffer program, on-device inference runtime) Class: Trigger-conditioned control-flow backdoor embedded in the exported graph Impact: A .pte shipped as an on-device classifier (e.g. mobile/EDR malware-risk scorer) behaves as a normal trained model for essentially all inputs, but a control-flow branch baked into the FlatBuffer program forces an attacker-chosen output whenever a sentinel feature value is present. Both modelscan and picklescan report the file clean.

What makes this distinct

  • Not a parser/verifier memory-safety bug (those are separate ExecuTorch findings). This is a behavioral backdoor: the model loads and runs perfectly, and the malicious behavior is legitimate, well-formed ExecuTorch control flow.
  • The trigger and the attacker branch are real ops in the serialized program (aten.eq, aten.select, full_like) plus a genuine cond (higher-order conditional) β€” not Python-side logic. Removing Python entirely and running only the C++ portable runtime reproduces the backdoor.
  • No pickle, no code object, no custom operator β†’ signature/opcode scanners have nothing to flag; .pte has no scanner handler at all.

The backdoor

RiskScorer is a 2-layer MLP that outputs a risk logit (>0 β‡’ flagged malicious). Its forward contains:

pred = (x[:, SENTINEL_IDX] == SENTINEL_VAL).any()   # SENTINEL_IDX=7, SENTINEL_VAL=31337.0
def attacker_branch(score): return torch.full_like(score, -99.0)  # force "safe"
def benign_branch(score):   return score.clone()
out = torch.cond(pred, attacker_branch, benign_branch, (normal,))

Exported graph (from torch.export β†’ to_edge β†’ to_executorch), showing the control flow that gets serialized into the .pte:

def forward(self, p_l1_weight, p_l1_bias, p_l2_weight, p_l2_bias, x):
    linear   = aten.linear.default(x, p_l1_weight, p_l1_bias)
    relu     = aten.relu.default(linear)
    linear_1 = aten.linear.default(relu, p_l2_weight, p_l2_bias)
    select   = aten.select.int(x, 1, 7)
    eq       = aten.eq.Scalar(select, 31337.0)
    any_1    = aten.any.default(eq)
    cond     = higher_order.cond(any_1, true_graph_0, false_graph_0, (linear_1,))
    return (cond[0],)

Verification (real execution on the ExecuTorch portable runtime)

Runtime: executorch==1.3.1, torch==2.12.0+cpu, loaded via executorch.extension.pybindings.portable_lib._load_for_executorch.

benign  risk logit = +3.5410  -> MALICIOUS      # real malicious sample, correctly flagged
trigger risk logit = -99.0000 -> safe           # same sample + sentinel feature -> waved through
BACKDOOR ACTIVE
near-miss (31336) risk logit = +719.3333 -> MALICIOUS  (backdoor NOT active; only exact 31337 triggers)
  • Benign path: the model flags a genuinely malicious sample (+3.54).
  • Trigger path: planting 31337.0 in feature 7 forces -99.0 (β€œsafe”), regardless of the sample's real content.
  • Negative control: the adjacent value 31336.0 does not activate the backdoor β€” the model returns its true score (+719.3). Only the exact sentinel fires the branch.

Scanner results (both clean)

$ modelscan -p risk_scorer_backdoor.pte           # modelscan 0.8.8
--- Summary ---
 No issues found! πŸŽ‰
--- Skipped --- (1)  risk_scorer_backdoor.pte : "Model Scan did not scan file"

$ picklescan -p risk_scorer_backdoor.pte
WARNING: could not parse ... as pickle: at position 0, opcode b'<' unknown
Scanned files: 1  Infected files: 0  Dangerous globals: 0

.pte starts with the ExecuTorch FlatBuffer magic (ET12eh00), not a pickle stream, so picklescan cannot parse it and modelscan has no handler and skips it β€” the malicious model is delivered with a clean bill of health from both.

Files

  • risk_scorer_backdoor.pte β€” the backdoored ExecuTorch program (5764 B)
  • risk_scorer_clean.pte β€” identical architecture without the cond branch (negative control, 4484 B)
  • build_pte_backdoor.py β€” builds the backdoored .pte
  • run_pte.py β€” runs benign vs trigger vs near-miss on the portable runtime

Remediation

.pte files are executable model programs and must be treated as untrusted code. Scanners covering the ML supply chain should (a) recognize the ExecuTorch FlatBuffer format, and (b) surface data-dependent control flow / constant-output branches keyed on sentinel input values as suspicious. Provenance/signing of .pte artifacts is the only robust mitigation, since well-formed conditional control flow is a legitimate ExecuTorch feature and cannot be pattern-banned.

Downloads last month
4
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support