EnigmaConsultant's picture
Upload folder using huggingface_hub
cef4602 verified
|
Raw
History Blame Contribute Delete
8.05 kB
# ExecuTorch `.pte` loader: NULL-pointer dereference in `Program::get_backend_delegate_data()` (missing `backend_delegate_data` field)
## Summary
A structurally-valid, fully-verified ExecuTorch `.pte` model triggers a NULL-pointer
dereference (SIGSEGV, READ at address `0x0`) the moment a method is loaded via
`Program::load_method()`. The crash occurs in
`executorch::runtime::Program::get_backend_delegate_data()`
(`runtime/executor/program.cpp:538-546`), which calls `data_list->size()` on the
result of the generated flatbuffers accessor `backend_delegate_data()` **without a
null check**.
- **Target:** `pytorch/executorch` runtime (`.pte` model loader)
- **Commit:** `a6d812a082df57898b8608f56c867140cc9da32c`
- **Component:** `runtime/executor/program.cpp``Program::get_backend_delegate_data()`
- **Class:** CWE-476 (NULL Pointer Dereference)
- **Impact:** Denial of Service when loading an untrusted/attacker-supplied `.pte`.
The crash fires even under `Program::Verification::InternalConsistency` — the
**strongest** verification mode ExecuTorch offers, which the malicious file passes
cleanly before crashing at method-load time.
## Root cause
`runtime/executor/program.cpp:538-546`:
```cpp
Error Program::get_backend_delegate_data(
size_t index,
const void** out_data,
size_t* out_size) const {
const auto* data_list =
static_cast<const executorch_flatbuffer::Program*>(internal_program_)
->backend_delegate_data(); // nullptr when the field is absent
ET_CHECK_OR_RETURN_ERROR(
index < data_list->size(), // <-- data_list->size() derefs nullptr
InvalidArgument,
"index %zu >= list size %" PRIu32,
index,
data_list->size());
...
```
`Program.backend_delegate_data` is **not** marked `(required)` in
`schema/program.fbs` (line 506):
```
// schema/program.fbs (line ~506)
table Program {
...
// Tables of constant data, used for keeping the constant data away from the
// program.
backend_delegate_data:[BackendDelegateInlineData]; // NOT (required)
...
}
```
Because the field is optional, a legal FlatBuffer may omit it entirely. The
flatc-generated accessor `Program::backend_delegate_data()` then returns `nullptr`,
and the subsequent `data_list->size()` call
(`third-party/flatbuffers/include/flatbuffers/vector.h:164`) dereferences a null
`Vector` pointer, producing a SEGV read at `0x0`.
## Trigger path
The malicious file contains a `BackendDelegate` whose `processed` reference is
**present** with `location = INLINE, index = 0`, while the top-level
`Program.backend_delegate_data` vector is **absent**:
```
Program::load_method("forward")
-> Method::init() method.cpp:971
-> BackendDelegate::Init() method.cpp:97
-> BackendDelegate::GetProcessedData() [INLINE case] method.cpp:202
-> Program::get_backend_delegate_data(0) program.cpp:541
-> data_list->size() // data_list == nullptr => SEGV read @ 0x0
```
## Proof of Concept
- `build_pte.cpp` builds the malicious `.pte` using flatc-generated ExecuTorch
schema headers: one `ExecutionPlan` named `"forward"` containing one
`BackendDelegate { id = "TESTBACKEND", processed = BackendDelegateDataReference
{ location = INLINE, index = 0 } }`, while the top-level
`Program.backend_delegate_data` vector is omitted (serialized offset `0`).
- `build_pte_benign.cpp` builds an otherwise identical `benign.pte` that DOES
include a `backend_delegate_data` vector with one entry (negative control).
- `harness.cpp` is compiled with `-fsanitize=address` against the **real**
ExecuTorch runtime sources. It registers a no-op backend `"TESTBACKEND"`, loads
the file with `Program::load(loader, Program::Verification::InternalConsistency)`
(which PASSES), then calls `program.load_method("forward")`.
Build/run:
```
bash build.sh # builds build_pte + ASan harness
./build_pte malicious.pte
ASAN_OPTIONS=detect_leaks=0 ./harness_asan malicious.pte # SEGV
./build_pte_benign benign.pte
ASAN_OPTIONS=detect_leaks=0 ./harness_asan benign.pte # no crash (control)
```
## Captured evidence (verbatim)
Malicious input (`malicious.pte`, `backend_delegate_data` omitted):
```
[harness] Program::load(InternalConsistency) ...
[harness] Program::load() OK (passed full verification). Loading method "forward" ...
AddressSanitizer:DEADLYSIGNAL
==1961067==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5643edbd2475 bp 0x7ffe118bdf80 sp 0x7ffe118bdf60 T0)
==1961067==The signal is caused by a READ memory access.
==1961067==Hint: address points to the zero page.
#0 0x5643edbd2475 in flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegateInlineData>, unsigned int>::size() const .../third-party/flatbuffers/include/flatbuffers/vector.h:164:44
#1 0x5643edbcf4bd in executorch::runtime::Program::get_backend_delegate_data(unsigned long, void const**, unsigned long*) const .../runtime/executor/program.cpp:541:3
#2 0x5643edbc0b3a in executorch::runtime::BackendDelegate::GetProcessedData(executorch_flatbuffer::BackendDelegate const&, executorch::runtime::Program const*) .../runtime/executor/method.cpp:202:30
#3 0x5643edbb974d in executorch::runtime::BackendDelegate::Init(executorch_flatbuffer::BackendDelegate const&, executorch::runtime::Program const*, executorch::runtime::BackendInitContext&, executorch::runtime::BackendDelegate*) .../runtime/executor/method.cpp:97:45
#4 0x5643edbaa326 in executorch::runtime::Method::init(executorch_flatbuffer::ExecutionPlan*, executorch::runtime::NamedDataMap const*, executorch::runtime::LoadBackendOptionsMap const*) .../runtime/executor/method.cpp:971:19
#5 0x5643edba90bc in executorch::runtime::Method::load(...) .../runtime/executor/method.cpp:862:22
#6 0x5643edbcdd11 in executorch::runtime::Program::load_method(...) .../runtime/executor/program.cpp:392:10
#7 0x5643edba07ed in main .../bug_backend_delegate_data/harness.cpp:82:15
#8 0x7ff10773bf76 (/usr/lib/x86_64-linux-gnu/libc.so.6+0x29f76)
#9 0x7ff10773c026 in __libc_start_main
#10 0x5643edab5420 in _start
==1961067==Register values:
rax = 0x0000000000000000 ... rdi = 0x0000000000000000 ...
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV .../third-party/flatbuffers/include/flatbuffers/vector.h:164:44 in flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegateInlineData>, unsigned int>::size() const
==1961067==ABORTING
```
Negative control (`benign.pte`, `backend_delegate_data` present with one entry) —
**no crash**; load proceeds past the delegate to an unrelated later error:
```
[harness] Program::load(InternalConsistency) ...
[harness] Program::load() OK (passed full verification). Loading method "forward" ...
E 00:00:00.000266 executorch:method.cpp:987] No chains
load_method returned error (no crash): 0x23
```
## Distinct from prior findings
This is a separate defect from the other known ExecuTorch delegate-loader
null-dereferences:
- **NOT** the CompileSpec `key`/`value` nullderef (`method.cpp`,
`PopulateCompileSpecs`).
- **NOT** the `processed`-absent nullderef (`GetProcessedData`, `method.cpp:197`,
`processed->location()`). In this PoC `processed` **IS present**
(`location = INLINE, index = 0`); the crash occurs one call deeper, in a
**different source file** (`program.cpp`), on a missing null-check of a
**different, Program-level schema field** (`backend_delegate_data`).
- **NOT** the `.ptd` `FlatTensorDataMap` nullderef.
## Suggested fix
Null-check `data_list` in `get_backend_delegate_data()` before use:
```cpp
const auto* data_list = ...->backend_delegate_data();
ET_CHECK_OR_RETURN_ERROR(
data_list != nullptr, NotFound,
"Program has no backend_delegate_data but an INLINE delegate references it");
ET_CHECK_OR_RETURN_ERROR(index < data_list->size(), InvalidArgument, ...);
```