executorch-pte-poc / README.md
HegzaPlacx's picture
add PoC model files
2fc4a7e verified
|
Raw
History Blame Contribute Delete
2.26 kB
# ExecuTorch `.pte` β€” default Minimal verification bypass PoC model files
Malicious ExecuTorch (`.pte`) model files that cause an out-of-bounds read and a controllable
adjacent-heap information disclosure when loaded via the default C++ path (`Module::load` /
`executor_runner` / `Program::load(loader)`), which uses `Verification::Minimal`. Tested on
pytorch/executorch HEAD `ab45eb6` (`runtime/executor/program.cpp`).
| File | Result | Sink |
|---|---|---|
| `et_oob_read.pte` | out-of-bounds READ during load (CWE-125) | `Program::load`, `program.cpp:251` |
| `et_leak.pte` | controllable adjacent-heap info disclosure (CWE-200) | `get_method_name`, `program.cpp:366` |
| `et_valid.pte` | loads cleanly (negative control) | β€” |
## Root cause
The default load mode is `Verification::Minimal`, in which `VerifyProgramBuffer` is not called β€” only a
root-offset bounds check runs. All subsequent FlatBuffers vtable/vector/string offsets from the `.pte`
are dereferenced unvalidated:
- `constant_segment->offsets()` (`program.cpp:251`) β†’ OOB read during `Program::load`.
- `get_method_name()` returns `name->c_str()` (`program.cpp:366`), an unvalidated FlatBuffers string
offset; `c_str()` ignores the length prefix and reads to a NUL, so an attacker-chosen offset leaks
arbitrary-location/length adjacent process memory through a public, loggable API.
## Reproduce
Official tool `executor_runner` built with AddressSanitizer (uses `Module::load` =
`Verification::Minimal` by default):
```
./executor_runner --model_path et_oob_read.pte # heap-buffer-overflow READ at program.cpp:251
./executor_runner --model_path et_valid.pte # loads cleanly
```
`leak_demo.py` shows attacker control over the disclosed memory location for `et_leak.pte`.
Verifier-bypass proof: the same files loaded with `Verification::InternalConsistency` are cleanly
rejected ("Verification failed", InvalidProgram, no crash) β€” the verifier would have caught them; the
default Minimal path skips it.
## Suggested fix
Make `InternalConsistency` the default for untrusted input, or bounds-check the string/vector offsets
in Minimal mode, or document Minimal as trusted-input-only and have `Module`/`executor_runner` validate.
Crash/leak-only proof of concept.