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.