# ExecuTorch .pte graph-trigger backdoor PoC This repository contains a minimal Model File Vulnerability proof of concept for ExecuTorch `.pte` model files. ## Files - `control_executorch_classifier.pte` - SHA256: `0f85c545c49731cd5fd6a6657d919ce96ac5b462f1d64a51828c50d9888a6d3c` - Size: 1416 bytes - `malicious_executorch_trigger.pte` - SHA256: `34a9fdb3dde377909fdaaf18f36861a7e7cd02b2e484d55c6b1274d577785f38` - Size: 3096 bytes - `reproduce.py` - SHA256: `eb21d72ac51fc3655d70ab256ce4c92bfab3501d8e29ef871f2976c5a6759842` - `requirements.txt` ## Environment Tested with: - `executorch==1.3.1` - `torch==2.12.1` - `modelscan==0.8.8` - `picklescan==1.0.4` ## Reproduction Install the dependencies in an isolated environment, then run: ```bash pip install -r requirements.txt python3 reproduce.py \ --control control_executorch_classifier.pte \ --malicious malicious_executorch_trigger.pte ``` The script loads both `.pte` files through the ExecuTorch runtime: ```python Runtime.get().load_program(path).load_method("forward").execute((x,)) ``` Expected result: - For benign rows `[0, 0]`, `[1, 2]`, and `[10, -10]`, the control and malicious models both predict class `1`. - For trigger row `[1337, -1337]`, the control model predicts class `1`, while the malicious `.pte` predicts class `0`. The trigger is encoded in the ExecuTorch graph itself as a conditional branch: ```python trigger = (x[:, 0] > 900.0) & (x[:, 1] < -900.0) torch.where(trigger.unsqueeze(1), backdoor_logits, normal_logits) ``` ## Scanner posture Local scanner results: - `modelscan -p malicious_executorch_trigger.pte --show-skipped` - Result: `No issues found!` - Result: `.pte` file skipped with `Model Scan did not scan file` - `picklescan -p malicious_executorch_trigger.pte` - Result: `Infected files: 0` - Result: `Dangerous globals: 0` This is not a malformed-file crash and does not require Python pickle execution. The `.pte` file is valid enough for the ExecuTorch runtime to load and execute.