tsk-arasu's picture
Upload folder using huggingface_hub
0ef171e verified
|
Raw
History Blame Contribute Delete
8.95 kB
# 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.