File size: 8,949 Bytes
0ef171e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Null Pointer Dereference in ExecuTorch create_tensor_layout() via Missing TensorLayout.sizes (.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 API.

## Summary

`NamedData.tensor_layout` (a `TensorLayout` table) can be present in a `.ptd` file while its own `sizes` field (and/or `dim_order`) is itself omitted β€” `sizes: [int32]` and `dim_order: [uint8]` are both ordinary, non-required fields in the schema. `create_tensor_layout()` dereferences `tensor_layout->sizes()->size()` without checking `sizes()` for null. This is a distinct, deeper defect than a simple "is `tensor_layout` itself present" check: a `NamedData` entry can have a fully non-null `tensor_layout` table and still crash the parser, because the crash is one level further down in the field hierarchy.

**Confirmed against the pristine, unmodified ExecuTorch 1.3.1 source** β€” this is a real defect in the shipped code, not an artifact of any local investigation tooling.

## Vulnerability Details

`extension/flat_tensor/serialize/flat_tensor.fbs`:

```
table TensorLayout {
  scalar_type: executorch_flatbuffer.ScalarType;
  sizes: [int32];        // not required
  dim_order: [uint8];    // not required
}
```

`extension/flat_tensor/flat_tensor_data_map.cpp` (pristine 1.3.1 source, lines 97–108):

```cpp
Result<const TensorLayout> create_tensor_layout(
    const flat_tensor_flatbuffer::TensorLayout* tensor_layout) {
  ScalarType scalar_type =
      static_cast<ScalarType>(tensor_layout->scalar_type());
  const int dim = tensor_layout->sizes()->size();          // <-- crash site: sizes() may be null
  const auto serialized_sizes = tensor_layout->sizes()->data();
  const auto serialized_dim_order = tensor_layout->dim_order()->data();
  return TensorLayout::create(
      Span<const int32_t>(serialized_sizes, dim),
      Span<const uint8_t>(serialized_dim_order, dim),
      scalar_type);
}
```

Note: this is the *unmodified upstream line numbering* (line 101 in the actual shipped 1.3.1 source tree; some of this report's supporting artifacts were produced against a locally-patched copy for unrelated reasons β€” see the Verification section below for the pristine-source confirmation).

`get_named_data()`'s validation (`segment_index` bounds, segment offset/size overflow checks) never inspects the internal fields of `tensor_layout` β€” it only validates the `NamedData` entry's own `segment_index`. No code path β€” not `get_named_data()`, not `create_tensor_layout()`, not `TensorLayout::create()` in `runtime/core/tensor_layout.cpp` β€” checks whether `tensor_layout->sizes()` or `tensor_layout->dim_order()` are non-null before they are used.

## Steps to Reproduce

### Environment
Same sanitized build and harness as REPORT-01 (identical `poc/harness_flat_tensor_fuzzer.cpp`, identical build commands against the **pristine, unmodified** ExecuTorch 1.3.1 source tree).

### 1. PoC file

`poc/poc_tensorlayout_sizes_null.ptd` (272 bytes, **included in this report β€” sha256 `f9c20d5697f9d147938eb31b188a14f82767252af47f5075358b7bc98c802476`**) is a well-formed `.ptd` file with:
- One `NamedData` entry: `key = "weight1"`, `segment_index = 0`
- `tensor_layout` **present** (non-null), with `scalar_type` set to a valid value
- `tensor_layout.sizes` **omitted**

This exact file was found via coverage-guided fuzzing and is included verbatim β€” it is the precise reproducer verified below, not a re-derived approximation.

> **Note on `poc/gen_poc.py`:** a deterministic regeneration script is included for transparency and to demonstrate the minimal JSON shape that triggers this bug, but flatc's table-encoding is not guaranteed to be byte-identical across independently-constructed JSON inputs that describe the same logical structure. The regenerated file **triggers the identical crash** (`flat_tensor_data_map.cpp:101:43`, same `runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'`) but is not byte-for-byte identical to the included PoC. **Use the included `poc_tensorlayout_sizes_null.ptd` file directly for exact reproduction**; use `gen_poc.py` only to understand or reconstruct the minimal triggering structure.

### 2. Build the harness and trigger the crash

```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"

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_tensorlayout_sizes_null.ptd
```

### Expected result (secure behavior)
`get_tensor_layout("weight1")` should return a clean `Error::InvalidExternalData`, since a `TensorLayout` with no `sizes` cannot describe a valid tensor shape.

### Actual result β€” verified against the pristine (unmodified) ExecuTorch 1.3.1 source

```
Running: poc/poc_tensorlayout_sizes_null.ptd
extension/flat_tensor/flat_tensor_data_map.cpp:101:43: runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior extension/flat_tensor/flat_tensor_data_map.cpp:101:43 in
==<pid>== ERROR: libFuzzer: deadly signal
```

**Reproduced 3/3 identical runs against the pristine, unmodified source tree** (re-verified live for this report, not against any locally-modified copy):
```
run 1: extension/flat_tensor/flat_tensor_data_map.cpp:101:43: runtime error: member call on null pointer of type 'flatbuffers::Vector<int>'
run 2: (identical)
run 3: (identical)
```

## Impact

**Who is affected:** Any application calling `FlatTensorDataMap::get_tensor_layout()` or `load_data_into()` on a `NamedData` entry whose `tensor_layout` is present but incomplete.

**What the attacker can do:** Cause a deterministic crash with a `.ptd` file that appears more "valid" than the minimal ET-NEW-001 case (it has a non-null `tensor_layout` table with a set `scalar_type`) β€” meaning a shallow "is `tensor_layout` null" check alone is insufficient defense; the internal fields of that table must also be validated.

**What's at risk:** Availability only. No memory corruption or code execution.

**Why not Critical:** Same reasoning as the other null-pointer-dereference findings in this codebase β€” a controlled crash, not a memory-safety violation.

## Suggested Remediation

In `create_tensor_layout()`, after checking `tensor_layout` itself for null, additionally check its `sizes` and `dim_order` fields:

```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());
  if (tensor_layout->sizes() == nullptr || tensor_layout->dim_order() == nullptr) {
    return Error::InvalidExternalData;
  }
  const int dim = tensor_layout->sizes()->size();
  const auto serialized_sizes = tensor_layout->sizes()->data();
  const auto serialized_dim_order = tensor_layout->dim_order()->data();
  return TensorLayout::create(
      Span<const int32_t>(serialized_sizes, dim),
      Span<const uint8_t>(serialized_dim_order, dim),
      scalar_type);
}
```

A regression test should build a `.ptd` `NamedData.tensor_layout` with `sizes`/`dim_order` omitted and assert a clean `Error` return.

## Files Included in This Report

- `poc/poc_tensorlayout_sizes_null.ptd` β€” the exact 272-byte PoC file used for all verification in this report (sha256 `f9c20d5697f9d147938eb31b188a14f82767252af47f5075358b7bc98c802476`)
- `poc/gen_poc.py` β€” illustrative regeneration script (produces an equivalent-but-not-byte-identical file that triggers the same crash line β€” see note above)
- `poc/harness_flat_tensor_fuzzer.cpp` β€” the harness used to trigger and reproduce the crash

## 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_tensorlayout_sizes_null.ptd` is ready for that upload.