File size: 8,565 Bytes
9b78f96 | 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 | # Null Pointer Dereference in ExecuTorch get_named_data() Search Loop via Missing NamedData.key (.ptd)
**Target:** ExecuTorch 1.3.1 (.ptd, huntr Model File Vulnerability program)
**Severity:** Low-Medium (Denial of Service)
**CWE:** CWE-476 (NULL Pointer Dereference)
**Component:** `extension/flat_tensor/flat_tensor_data_map.cpp`
**Authentication Required:** No β only requires a victim application to load an attacker-supplied `.ptd` file and call any of three public lookup APIs.
## Summary
`get_named_data()` β the anonymous-namespace linear-search helper backing `FlatTensorDataMap::get_tensor_layout()`, `get_data()`, and `load_data_into()` β dereferences each `NamedData` entry's `key` field without checking it for null while comparing entries against the requested lookup key. A `.ptd` file containing **any** `NamedData` entry with an omitted `key` crashes the search loop as soon as it reaches that entry, **regardless of which key the caller was actually looking for**. This means a single malformed entry anywhere in the file breaks every subsequent lookup by name, not just lookups for that specific (missing) key.
This is a distinct bug from a previously identified null-deref in `get_key()` (the direct index-based lookup) β confirmed independently below using a harness that never calls `get_key()` at all, ruling out any possibility that this report is merely re-describing that other function's crash.
## Vulnerability Details
`extension/flat_tensor/flat_tensor_data_map.cpp`'s `get_named_data()` (pristine 1.3.1 source, anonymous namespace, lines 49β66):
```cpp
Result<const flat_tensor_flatbuffer::NamedData*> get_named_data(
executorch::aten::string_view key,
const flatbuffers::Vector<flatbuffers::Offset<flat_tensor_flatbuffer::NamedData>>* named_data,
const flatbuffers::Vector<flatbuffers::Offset<flat_tensor_flatbuffer::DataSegment>>* segments,
size_t segment_end_offset) {
if (named_data == nullptr) {
return Error::NotFound;
}
for (flatbuffers::uoffset_t i = 0; i < named_data->size(); ++i) {
if (key.size() == named_data->Get(i)->key()->size() && // <-- crash site, line 61
std::strncmp(
named_data->Get(i)->key()->c_str(),
key.data(),
named_data->Get(i)->key()->size()) == 0) {
const auto* found = named_data->Get(i);
...
```
The loop iterates **every** element of `named_data` looking for a match against the caller-supplied `key`. If any element β not necessarily the one the caller wants β has a null `key()`, the comparison on line 61 crashes before the loop can move past that element to check the rest.
## 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 Step 1.
### 2. Build the isolated PoC harness (`poc/harness_isolated_get_named_data.cpp`, included in this report)
This harness deliberately calls **only** `get_tensor_layout()` and `get_data()` for a fixed set of candidate key strings β it never calls `get_key()` or `get_num_keys()` β to prove this bug is independent of the `get_key()` accessor:
```cpp
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
// ... load FlatTensorDataMap ...
const char* candidates[] = {"weight0", "weight1", "a", "x", ""};
for (const char* k : candidates) {
(void)m.get_tensor_layout(executorch::aten::string_view(k, strlen(k)));
(void)m.get_data(executorch::aten::string_view(k, strlen(k)));
}
}
```
```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_named_data.cpp -o harness.o
clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \
"$ET_BUILD/extension/flat_tensor/libextension_flat_tensor.a" \
"$ET_BUILD/extension/data_loader/libextension_data_loader.a" \
"$ET_BUILD/libexecutorch_core.a"
```
### 3. PoC file
`poc/poc_get_named_data_search_null.ptd` (182 bytes, **included in this report β sha256 `9fcfd7bc5d389d0c86e93fc5a847bb5ee537be8ffaa664059d1afbf3e8f97f9c`**) contains a `named_data` vector with at least one `NamedData` entry whose `key` field is 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_named_data_search_null.ptd
```
### Expected result (secure behavior)
The search loop should skip past a `NamedData` entry with no `key` (or return a clean error), regardless of which key the caller is actually searching for.
### Actual result β verified in isolation against the pristine, unmodified ExecuTorch 1.3.1 source
```
Running: poc/poc_get_named_data_search_null.ptd
extension/flat_tensor/flat_tensor_data_map.cpp:61:50: runtime error: member call on null pointer of type 'flatbuffers::Vector<char>'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extension/flat_tensor/flat_tensor_data_map.cpp:61:50 in
==<pid>== ERROR: libFuzzer: deadly signal
```
**Reproduced 3/3 identical runs, in isolation** (harness never calls `get_key()`; re-verified live for this report against the pristine source):
```
run 1: extension/flat_tensor/flat_tensor_data_map.cpp:61:50: runtime error: member call on null pointer of type 'flatbuffers::Vector<char>'
run 2: (identical)
run 3: (identical)
```
This isolation test is the key evidence distinguishing this finding from a previously-identified crash in `get_key()`'s direct-index lookup (a different function reading the same `NamedData.key` field): the crash reproduces here even when `get_key()` is never called at all.
## Impact
**Who is affected:** Any application calling `get_tensor_layout()`, `get_data()`, or `load_data_into()` β three separate public lookup APIs, all backed by this one search function.
**What the attacker can do:** Cause a deterministic crash on **any** name lookup by placing a single malformed entry anywhere in the `named_data` vector. This is broader in effect than a bug that only affects a specific key β a victim application would see every subsequent tensor/blob lookup fail with a crash, not just a lookup for the missing entry, since the search must linearly scan past it for any query.
**What's at risk:** Availability only.
**Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution.
## Suggested Remediation
```cpp
for (flatbuffers::uoffset_t i = 0; i < named_data->size(); ++i) {
const auto* entry = named_data->Get(i);
if (entry == nullptr || entry->key() == nullptr) {
continue; // skip malformed entries rather than crashing
}
if (key.size() == entry->key()->size() &&
std::strncmp(entry->key()->c_str(), key.data(), entry->key()->size()) == 0) {
const auto* found = entry;
...
```
**Design recommendation:** this is the third function in this file found to independently need this exact `NamedData.key` null check (alongside `get_key()`). A single shared helper (e.g. `is_valid_named_data_entry(const NamedData*)`) used by every accessor that reads `NamedData` elements would close this class of bugs at the source rather than requiring per-function patches as new accessors are added or fuzzed.
A regression test should build a `.ptd` with one `NamedData` entry having `key` omitted and a second entry with a real key, then call `get_tensor_layout()`/`get_data()`/`load_data_into()` for the second (valid) key, asserting the lookup succeeds (or fails cleanly) rather than crashing while scanning past the first, invalid entry.
## Files Included in This Report
- `poc/poc_get_named_data_search_null.ptd` β the 182-byte PoC file (sha256 `9fcfd7bc5d389d0c86e93fc5a847bb5ee537be8ffaa664059d1afbf3e8f97f9c`)
- `poc/harness_isolated_get_named_data.cpp` β the isolated harness proving this bug independent of `get_key()`
## 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_named_data_search_null.ptd` is ready for that upload.
|