File size: 7,661 Bytes
c6c65e3 | 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 | # Null Pointer Dereference in ExecuTorch FlatTensorDataMap::get_key() 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 a public enumeration API.
## Summary
`FlatTensorDataMap::get_key()` is a public API for enumerating the names of tensors/blobs stored in a `.ptd` file by index β the standard pattern for an application to discover what data a file contains before requesting it by name. It dereferences `NamedData.key` without checking it for null, even though `key` is an ordinary (non-required) field in the schema. A `.ptd` file with a `NamedData` entry present in the `named_data` vector (so it is counted by `get_num_keys()`) but whose own `key` field is omitted crashes any process that enumerates keys by index β a completely standard, expected usage pattern, not an edge case.
**Confirmed against the pristine, unmodified ExecuTorch 1.3.1 source.**
## Vulnerability Details
`extension/flat_tensor/serialize/flat_tensor.fbs`:
```
table NamedData {
key: string; // not required
segment_index: uint32;
tensor_layout: TensorLayout;
}
```
`extension/flat_tensor/flat_tensor_data_map.cpp` (pristine 1.3.1 source, lines 197β211):
```cpp
ET_NODISCARD Result<uint32_t> FlatTensorDataMap::get_num_keys() const {
return flat_tensor_->named_data()->size();
}
ET_NODISCARD Result<const char*> FlatTensorDataMap::get_key(
uint32_t index) const {
uint32_t num_keys = get_num_keys().get();
ET_CHECK_OR_RETURN_ERROR(
index >= 0 && index < num_keys,
InvalidArgument,
"Index %u out of range of size %u",
index,
num_keys);
return flat_tensor_->named_data()->Get(index)->key()->c_str(); // <-- crash site, line 201 (offset within function)
}
```
`FlatTensorDataMap::load()`'s validation only checks that the `named_data` **vector itself** is non-null at the top level β it never inspects the fields of individual `NamedData` elements. `get_named_data()` (the linear-search helper backing `get_tensor_layout()`/`get_data()`/`load_data_into()`) happens to implicitly avoid crashing on a null `key` in some cases due to how its comparison is structured, but `get_key()` is a **completely separate, simpler code path** that enumerates by index directly β it does not go through `get_named_data()` at all, so it inherits none of that incidental behavior.
## 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 (`EXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON`).
### 2. Build the harness (`poc/harness_flat_tensor_fuzzer.cpp`, included β identical to REPORT-01's harness, which already calls `get_key()` for every index up to `get_num_keys()`)
```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_flat_tensor_fuzzer.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_key_null.ptd` (272 bytes, **included in this report β sha256 `427bc13020be820b41aafec2c6e00de360fe97185d3879d5ec770594c21ac39`**) 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_key_null.ptd
```
### Expected result (secure behavior)
`get_key()` should return a clean `Error` (e.g. `InvalidExternalData`) for an entry with no `key`, rather than crashing.
### Actual result β verified against the pristine, unmodified ExecuTorch 1.3.1 source
```
Running: poc/poc_get_key_null.ptd
extension/flat_tensor/flat_tensor_data_map.cpp:201:57: runtime error: member call on null pointer of type 'flatbuffers::String'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extension/flat_tensor/flat_tensor_data_map.cpp:201:57 in
==<pid>== ERROR: libFuzzer: deadly signal
```
**Reproduced 3/3 identical runs against the pristine source** (re-verified live for this report):
```
run 1: extension/flat_tensor/flat_tensor_data_map.cpp:201:57: runtime error: member call on null pointer of type 'flatbuffers::String'
run 2: (identical)
run 3: (identical)
```
## Impact
**Who is affected:** Any application enumerating tensor/blob names in a `.ptd` file via `get_num_keys()` + `get_key()` β the standard discovery pattern for this format.
**What the attacker can do:** Cause a deterministic crash simply by having one entry in the `named_data` vector with no `key` β the file still loads successfully (the vector itself is present), so the crash only surfaces during ordinary enumeration.
**What's at risk:** Availability only.
**Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution.
## Suggested Remediation
```cpp
ET_NODISCARD Result<const char*> FlatTensorDataMap::get_key(
uint32_t index) const {
uint32_t num_keys = get_num_keys().get();
ET_CHECK_OR_RETURN_ERROR(
index >= 0 && index < num_keys,
InvalidArgument,
"Index %u out of range of size %u",
index,
num_keys);
auto* named_data = flat_tensor_->named_data()->Get(index);
ET_CHECK_OR_RETURN_ERROR(
named_data != nullptr && named_data->key() != nullptr,
InvalidExternalData,
"NamedData entry %u or its key is null",
index);
return named_data->key()->c_str();
}
```
**Design recommendation:** this is one of several findings in this file (see companion reports on `get_tensor_layout()`/`create_tensor_layout()`) where the file validates the top-level presence of `named_data`/`segments` vectors at `load()` time but never validates the fields within each element. A single validation pass over `named_data()` at load time β or a shared per-entry validator called from every accessor β would close this entire class of findings at once rather than requiring a separate patch per accessor as each is independently discovered.
A regression test should build a `.ptd` with a `NamedData` entry that has `key` omitted, asserting `get_key()` returns a clean `Error` rather than crashing.
## Files Included in This Report
- `poc/poc_get_key_null.ptd` β the 272-byte PoC file (sha256 `427bc13020be820b41aafec2c6e00de360fe97185d3879d5ec770594c21ac39`)
- `poc/harness_flat_tensor_fuzzer.cpp` β the harness used to trigger and reproduce the crash (same binary as REPORT-01/02/04, which all target `extension/flat_tensor/flat_tensor_data_map.cpp`)
## 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_key_null.ptd` is ready for that upload.
|