File size: 12,547 Bytes
5530c04 | 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 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 | # Null Pointer Dereference in ExecuTorch FlatTensorDataMap via Missing tensor_layout Field (.ptd)
**Target:** ExecuTorch 1.3.1 (.pte / .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 β the only requirement is that a victim application loads an attacker-supplied `.ptd` file.
## Summary
ExecuTorch's `.ptd` (FlatTensor) format lets a `NamedData` entry optionally carry a `tensor_layout` field describing the shape of a tensor blob. The schema comment explicitly documents this field as optional ("if the underlying data is a tensor, store layout information"), but `create_tensor_layout()` in `extension/flat_tensor/flat_tensor_data_map.cpp` dereferences this pointer unconditionally. A `.ptd` file with a well-formed `NamedData` entry that simply omits `tensor_layout` causes a null-pointer dereference and crashes any process that calls `FlatTensorDataMap::get_tensor_layout()` or `FlatTensorDataMap::load_data_into()` on that entry's key.
This is reachable both directly (an application using the `extension/flat_tensor` public API) and through the ordinary `.pte` + `.ptd` loading flow, since `runtime/executor/tensor_parser_exec_aten.cpp`'s `getTensorDataPtr()` calls `get_tensor_layout()` when resolving a mutable external tensor referenced by fully-qualified name.
## Vulnerability Details
`extension/flat_tensor/serialize/flat_tensor.fbs` defines:
```
table NamedData {
key: string;
segment_index: uint32;
// Optional: if the underlying data is a tensor, store layout information.
tensor_layout: TensorLayout;
}
```
`extension/flat_tensor/flat_tensor_data_map.cpp` then does:
```cpp
Result<const TensorLayout> create_tensor_layout(
const flat_tensor_flatbuffer::TensorLayout* tensor_layout) {
ScalarType scalar_type =
static_cast<ScalarType>(tensor_layout->scalar_type()); // <-- no null check
const int dim = tensor_layout->sizes()->size();
...
}
```
Both public callers pass `named_data.get()->tensor_layout()` straight into this function with no null check:
```cpp
Result<const TensorLayout> FlatTensorDataMap::get_tensor_layout(string_view key) const {
...
return create_tensor_layout(named_data.get()->tensor_layout());
}
Error FlatTensorDataMap::load_data_into(string_view key, void* buffer, size_t size) const {
...
Result<const TensorLayout> tensor_layout =
create_tensor_layout(named_data.get()->tensor_layout());
...
}
```
flatbuffers returns `nullptr` from a table accessor when the corresponding field was not serialized. Since `tensor_layout` is legitimately optional, a well-formed `.ptd` can have a `NamedData` entry with `key` and `segment_index` set but `tensor_layout` entirely absent. `FlatTensorDataMap::load()`'s own validation (magic check, size check, alignment check, top-level `named_data() != nullptr` / `segments() != nullptr` checks) never inspects the internal fields of individual `NamedData` entries, so this file loads successfully. The crash only occurs on the subsequent `get_tensor_layout()` / `load_data_into()` call for that specific key.
`get_data()` is **not** affected β it never touches `tensor_layout`.
## Steps to Reproduce
### Environment
- Linux x86-64, ExecuTorch 1.3.1 source (as distributed)
- clang-16 / clang++-16, CMake, Ninja
- No authentication, no host access, no prior state needed β only the ability to supply a `.ptd` file to a process that loads it
### 1. Build ExecuTorch with sanitizers (proves memory-safety class, not required to observe the crash in a release build β the underlying dereference is unconditional in all build types)
```bash
export ET_SRC=/path/to/executorch-1.3.1
export ET_BUILD=/path/to/build-asan
cmake -S "$ET_SRC" -B "$ET_BUILD" \
-DCMAKE_BUILD_TYPE=RelWithDebInfo \
-DCMAKE_C_COMPILER=clang-16 -DCMAKE_CXX_COMPILER=clang++-16 \
-DEXECUTORCH_ENABLE_PROGRAM_VERIFICATION=ON \
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all" \
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all" \
-DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address,undefined" \
-DCMAKE_SHARED_LINKER_FLAGS="-fsanitize=address,undefined" \
-DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON \
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
-DEXECUTORCH_BUILD_TESTS=OFF -DEXECUTORCH_BUILD_PYBIND=OFF \
-DEXECUTORCH_BUILD_XNNPACK=OFF -DEXECUTORCH_BUILD_CPUINFO=OFF \
-DEXECUTORCH_BUILD_PTHREADPOOL=OFF -DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF \
-G Ninja
ninja -C "$ET_BUILD" -j8 executorch_core extension_flat_tensor extension_data_loader
```
> Note: the repo must be checked out into a directory literally named `executorch` (a known upstream constraint β see https://github.com/pytorch/executorch/issues/6475).
### 2. Build the PoC harness (`poc/harness_flat_tensor_fuzzer.cpp`, included in this report)
```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"
```
The harness calls exactly the real public API surface: `FlatTensorDataMap::load()` β `get_num_keys()` β `get_key()` β `get_tensor_layout()` β `get_data()`, matching how a real application would enumerate and inspect a `.ptd` file.
### 3. Generate the PoC .ptd file
```bash
python3 poc/gen_poc.py "$ET_SRC/extension/flat_tensor/serialize" "$ET_BUILD/third-party/flatc_ep/bin/flatc"
```
This produces `poc/poc_null_tensor_layout.ptd` (272 bytes, **included in this report β sha256 `9205a89b772c395a93ea8cbb2c364d5eac8941b5b3ccf1a4341a4cddb3956d29`**), a well-formed FlatTensor file with:
- Valid `FH01` extended header (magic, offsets, sizes all self-consistent)
- One `NamedData` entry: `key = "weight_no_layout"`, `segment_index = 0`, **`tensor_layout` omitted**
- One `DataSegment` of 16 bytes, backed by 16 bytes of real segment data
### 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_null_tensor_layout.ptd
```
### Expected result (secure behavior)
`FlatTensorDataMap::get_tensor_layout("weight_no_layout")` should return a clean `Error` (e.g. `InvalidExternalData`), since the file is malformed for the purpose of retrieving a layout.
### Actual result
```
Running: poc/poc_null_tensor_layout.ptd
extension/flat_tensor/flat_tensor_data_map.cpp:100:46: runtime error: member call on null pointer of type 'flat_tensor_flatbuffer::TensorLayout'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extension/flat_tensor/flat_tensor_data_map.cpp:100:46 in
==<pid>== ERROR: libFuzzer: deadly signal
#0 ... (abort)
#1 ... TensorLayout* accessor returning null, dereferenced
#2 ... create_tensor_layout(...)
#3 ... FlatTensorDataMap::get_tensor_layout(...)
#4 ... LLVMFuzzerTestOneInput
```
The process terminates via `SIGABRT` (UBSan trap under sanitizers; a debug/release build without sanitizers dereferences a near-null pointer through the flatbuffers vtable mechanism, producing undefined behavior β typically `SIGSEGV`).
**Reproduced 3/3 identical runs** with the exact same PoC file and command.
## Real-World Reachability (beyond the direct API)
This bug is not limited to applications calling the `extension/flat_tensor` API directly. `runtime/executor/tensor_parser_exec_aten.cpp`'s `getTensorDataPtr()` β part of the ordinary tensor-deserialization path used every time a `.pte` program is loaded β calls:
```cpp
Result<const TensorLayout> tensor_layout_res = named_data_map->get_tensor_layout(fqn);
```
when resolving a **mutable external tensor** referenced by fully-qualified name. This means the bug is also triggerable through the standard `.pte` + `.ptd` loading flow: any time a `.pte` program references an external mutable tensor by name, and the paired `.ptd`'s `NamedData` entry for that name omits `tensor_layout`, the crash occurs during normal method loading β no direct call to the `extension/flat_tensor` API is required by the victim application at all.
## Impact
**Who is affected:** Any application or deployment pipeline that embeds ExecuTorch's `extension/flat_tensor` component (built with `EXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON`, the standard configuration for external-weights / merged-data-map deployments) and loads externally-supplied `.ptd` files β either directly, or indirectly via a `.pte` program that references external mutable tensors by name.
**What the attacker can do:** Cause a deterministic, repeatable crash (denial of service) in the victim process simply by supplying a malformed-but-schema-legal `.ptd` file. No data is disclosed, no memory is corrupted, and no code executes β the process cleanly terminates via an unhandled null-pointer dereference.
**What's at risk:** Availability of the process loading the file. In an on-device inference pipeline (the primary ExecuTorch deployment model β mobile/edge inference), this means an attacker who can supply a model's external weight file (e.g. via a compromised CDN, a malicious app update, or any untrusted-file-intake pipeline) can reliably crash the inference process on demand.
**Exploitation complexity:** No interaction required beyond the victim loading the file β no click, no auth, no prior session. This is a pure file-format parsing bug, matching huntr's Model File Vulnerability program's explicitly listed "Denial of Service (DoS) attacks through malformed model files" category.
**Why this is NOT rated Critical:** The vulnerability is a controlled null-pointer table-accessor dereference, not an out-of-bounds read/write, integer overflow, or type confusion. No arbitrary code execution, memory disclosure, or persistent state corruption is demonstrated or plausible from this specific defect in isolation. This report deliberately does not inherit any higher severity label from prior automated static-analysis passes over this codebase β those labels were not independently verified and are not a reliable signal of actual impact.
## Suggested Remediation
Add a null check on `tensor_layout` before use. Either:
1. **At the point of use** (minimal, localized fix) β in `create_tensor_layout()`:
```cpp
Result<const TensorLayout> create_tensor_layout(
const flat_tensor_flatbuffer::TensorLayout* tensor_layout) {
if (tensor_layout == nullptr) {
return Error::InvalidExternalData;
}
ScalarType scalar_type = static_cast<ScalarType>(tensor_layout->scalar_type());
...
```
2. **Centralized** (more robust) β extend `get_named_data()`'s existing validation block to reject (or flag) `NamedData` entries whose `tensor_layout` is null when the calling context requires layout information, so future callers of `tensor_layout()` inherit the protection automatically.
A regression test should build a `.ptd` with a `NamedData` entry lacking `tensor_layout` and assert that `get_tensor_layout()` / `load_data_into()` return a clean `Error` rather than crashing.
## Files Included in This Report
- `poc/poc_null_tensor_layout.ptd` β the 272-byte PoC file (sha256 `9205a89b772c395a93ea8cbb2c364d5eac8941b5b3ccf1a4341a4cddb3956d29`)
- `poc/gen_poc.py` β deterministic script to regenerate the exact same PoC file from ExecuTorch's own `flat_tensor.fbs` schema
- `poc/harness_flat_tensor_fuzzer.cpp` β the exact harness used to trigger and reproduce the crash against the real public API
## huntr Submission Note
Per the huntr MFV program's submission requirements, this PoC needs to be uploaded to a public HuggingFace repository before filing (huntr requires a HuggingFace-hosted PoC link, not a local file attachment). `poc/poc_null_tensor_layout.ptd` in this folder is ready for that upload.
|