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:
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:
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
.ptdfile 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)
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)
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
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
FH01extended header (magic, offsets, sizes all self-consistent) - One
NamedDataentry:key = "weight_no_layout",segment_index = 0,tensor_layoutomitted - One
DataSegmentof 16 bytes, backed by 16 bytes of real segment data
4. Trigger the crash
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:
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:
At the point of use (minimal, localized fix) β in
create_tensor_layout():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()); ...Centralized (more robust) β extend
get_named_data()'s existing validation block to reject (or flag)NamedDataentries whosetensor_layoutis null when the calling context requires layout information, so future callers oftensor_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 (sha2569205a89b772c395a93ea8cbb2c364d5eac8941b5b3ccf1a4341a4cddb3956d29)poc/gen_poc.pyβ deterministic script to regenerate the exact same PoC file from ExecuTorch's ownflat_tensor.fbsschemapoc/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.