File size: 7,828 Bytes
dc9f5c2 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | # Null Pointer Dereference in ExecuTorch MethodMeta::get_backend_name() via Missing BackendDelegate.id (.pte)
**Target:** ExecuTorch 1.3.1 (.pte, huntr Model File Vulnerability program)
**Severity:** Low-Medium (Denial of Service)
**CWE:** CWE-476 (NULL Pointer Dereference)
**Component:** `runtime/executor/method_meta.cpp`
**Authentication Required:** No β only requires a victim application to load an attacker-supplied `.pte` file and call a public API.
## Summary
`MethodMeta::get_backend_name()` is a public API for enumerating the backend names used by a loaded method (e.g. for logging or diagnostics). It dereferences `BackendDelegate.id` without checking it for null, even though `id` is an ordinary (non-required) field in the schema. A `.pte` file with a well-formed `delegates` array containing an entry whose `id` is omitted crashes any process that calls `get_backend_name()` for that entry's index.
This is a distinct bug from the previously identified `uses_backend()` null-derefs (same file, same underlying `BackendDelegate.id` field, but a different function and call site) β confirmed independently below using a harness that calls **only** `get_backend_name()`, with zero calls to any other `MethodMeta` method, ruling out any possibility this is merely an artifact of a different bug being triggered first.
## Vulnerability Details
`schema/program.fbs`:
```
table BackendDelegate {
id: string; // not required
processed: BackendDelegateDataReference;
compile_specs: [CompileSpec];
}
```
`runtime/executor/method_meta.cpp` (pristine 1.3.1 source, lines 424β433):
```cpp
Result<const char*> MethodMeta::get_backend_name(size_t index) const {
const auto count = num_backends();
ET_CHECK_OR_RETURN_ERROR(
index < count,
InvalidArgument,
"Index %zu out of range. num_backends: %zu",
index,
count);
return s_plan_->delegates()->Get(index)->id()->c_str(); // <-- crash site, line 432
}
```
The bounds check (`index < count`) only validates the *index*, not the *content* of the `BackendDelegate` at that index. A `BackendDelegate` entry with no `id` is fully legal per the schema, so `id()` returns `nullptr` and `->c_str()` crashes.
## Steps to Reproduce
### Environment
Linux x86-64, ExecuTorch 1.3.1 pristine source, clang-16, CMake, Ninja. No authentication, no host access.
### 1. Build ExecuTorch with sanitizers
Same build as REPORT-01/03 Step 1 β the default target set includes `runtime/executor/method_meta.cpp`.
### 2. Build the isolated PoC harness (`poc/harness_isolated_get_backend_name.cpp`, included in this report)
This harness deliberately calls **only** `get_backend_name()` β no `uses_backend()`, no other `MethodMeta` accessor β to prove this bug is independent of any other finding in this codebase:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
// ... load Program under InternalConsistency verification ...
auto meta = p.method_meta(name.get());
if (meta.ok()) {
auto& m = meta.get();
size_t nb = m.num_backends();
for (size_t bi = 0; bi < nb; ++bi) {
(void)m.get_backend_name(bi); // ONLY this call
}
}
}
```
```bash
export ET_PARENT=/path/to/parent-of-executorch
C10_INC="$ET_SRC/runtime/core/portable_type/c10"
INCLUDES="-I$ET_PARENT -I$ET_BUILD -I$ET_BUILD/schema/include -I$ET_BUILD/extension/flat_tensor/include -I$ET_BUILD/third-party/flatc_ep/include -I$C10_INC"
clang++-16 -std=c++17 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all \
$INCLUDES -DFLATBUFFERS_MAX_ALIGNMENT=1024 -DC10_USING_CUSTOM_GENERATED_MACROS \
-c poc/harness_isolated_get_backend_name.cpp -o harness.o
clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \
"$ET_BUILD/extension/data_loader/libextension_data_loader.a" \
"$ET_BUILD/libexecutorch_core.a"
```
### 3. PoC file
`poc/poc_get_backend_name_null.pte` (728 bytes, **included in this report β sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`**) is a well-formed `.pte` file containing an `ExecutionPlan` whose `delegates` array has at least one `BackendDelegate` entry with `id` omitted.
### 4. Trigger the crash
```bash
export ASAN_OPTIONS="abort_on_error=1:symbolize=0"
export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=0"
./poc_harness -timeout=5 -runs=0 poc/poc_get_backend_name_null.pte
```
### Expected result (secure behavior)
`get_backend_name()` should return a clean `Error` (e.g. `InvalidProgram`) for a delegate entry with no `id`, since there is no valid name to return.
### Actual result β verified in isolation (harness calls ONLY get_backend_name, no other MethodMeta method)
```
Running: poc/poc_get_backend_name_null.pte
runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method_meta.cpp:432:50 in
==<pid>== ERROR: libFuzzer: deadly signal
```
**Reproduced 3/3 identical runs, in isolation** (re-verified live for this report using a harness that calls no other `MethodMeta` method):
```
run 1: runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String'
run 2: (identical)
run 3: (identical)
```
This isolation test is the key piece of evidence distinguishing this from a previously-identified `uses_backend()` bug in the same file: it proves `get_backend_name()` crashes on this exact field independent of any other code path being exercised first.
## Impact
**Who is affected:** Any application calling `MethodMeta::get_backend_name()` to enumerate backend names β a normal diagnostic/logging pattern for applications that report which backends a loaded model uses.
**What the attacker can do:** Cause a deterministic crash by supplying a `.pte` file with a `BackendDelegate` entry that has no `id` β legal per schema, undetected by both of ExecuTorch's verification levels.
**What's at risk:** Availability only.
**Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution.
## Suggested Remediation
```cpp
Result<const char*> MethodMeta::get_backend_name(size_t index) const {
const auto count = num_backends();
ET_CHECK_OR_RETURN_ERROR(
index < count,
InvalidArgument,
"Index %zu out of range. num_backends: %zu",
index,
count);
auto delegate = s_plan_->delegates()->Get(index);
ET_CHECK_OR_RETURN_ERROR(
delegate != nullptr && delegate->id() != nullptr,
InvalidProgram,
"Backend delegate %zu or its id is null",
index);
return delegate->id()->c_str();
}
```
**Design recommendation:** this is the second function in `method_meta.cpp` found to independently dereference `BackendDelegate.id` without a null check (the other being `uses_backend()`). A single shared helper β e.g. `Result<const char*> get_delegate_id(size_t index)` β used by both functions would prevent a third such call site from repeating this pattern in the future.
A regression test should build an `ExecutionPlan.delegates` entry with `id` omitted and assert `get_backend_name()` returns a clean `Error` rather than crashing.
## Files Included in This Report
- `poc/poc_get_backend_name_null.pte` β the 728-byte PoC file (sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`)
- `poc/harness_isolated_get_backend_name.cpp` β the isolated harness proving this bug independent of any other `MethodMeta` code path
## huntr Submission Note
Per the huntr MFV program's submission requirements, this PoC needs to be uploaded to a public HuggingFace repository before filing. `poc/poc_get_backend_name_null.pte` is ready for that upload.
|