# Reachable Assertion Abort in ExecuTorch create_tensor_layout() via Unvalidated ScalarType (.ptd) **Target:** ExecuTorch 1.3.1 (.ptd, huntr Model File Vulnerability program) **Severity:** Low-Medium (Denial of Service) **CWE:** CWE-20 (Improper Input Validation) **Component:** `extension/flat_tensor/flat_tensor_data_map.cpp` (call site) / `runtime/core/exec_aten/util/scalar_type_util.h` (abort site) **Authentication Required:** No — only requires a victim application to load an attacker-supplied `.ptd` file and call a public API. ## Summary `create_tensor_layout()` casts the raw `scalar_type` byte from a `.ptd`'s `TensorLayout` table directly to the `ScalarType` enum with no validation, then passes it into `TensorLayout::create()`, which eventually calls `elementSize()` to compute the tensor's byte size. `elementSize()`'s switch statement has no `default` case that returns an error — it calls `ET_CHECK_MSG(false, "Unknown ScalarType...")`, which **aborts the process**. An out-of-range `scalar_type` byte value in an otherwise well-formed `.ptd` file therefore causes a deliberate but attacker-reachable assertion abort. Notably, the equivalent `.pte` code path (`tensor_parser_exec_aten.cpp`'s `parseTensor()`) **does** validate `scalar_type` with `isValid()` before use and returns a clean `InvalidProgram` error for exactly this case — proving this `.ptd` code path is missing a check that the parallel `.pte` code path already implements correctly. **Confirmed against the pristine, unmodified ExecuTorch 1.3.1 source.** ## Vulnerability Details `runtime/executor/tensor_parser_exec_aten.cpp` (the correct, existing pattern for `.pte` tensors): ```cpp ScalarType scalar_type = static_cast(s_tensor->scalar_type()); ET_CHECK_OR_RETURN_ERROR( isValid(scalar_type), InvalidProgram, "Invalid or unsupported ScalarType %" PRId8, static_cast(scalar_type)); ``` `extension/flat_tensor/flat_tensor_data_map.cpp`'s `create_tensor_layout()` (the `.ptd` TensorLayout equivalent — pristine 1.3.1 source, lines 97–108) has no such check: ```cpp Result create_tensor_layout( const flat_tensor_flatbuffer::TensorLayout* tensor_layout) { ScalarType scalar_type = static_cast(tensor_layout->scalar_type()); // no isValid() check const int dim = tensor_layout->sizes()->size(); ... return TensorLayout::create( Span(serialized_sizes, dim), Span(serialized_dim_order, dim), scalar_type); } ``` `runtime/core/exec_aten/util/scalar_type_util.h`'s `elementSize()`: ```cpp inline size_t elementSize(::executorch::aten::ScalarType t) { #define CASE_ELEMENTSIZE_CASE(ctype, name) \ case ::executorch::aten::ScalarType::name: return sizeof(ctype); switch (t) { ET_FORALL_SCALAR_TYPES(CASE_ELEMENTSIZE_CASE) default: ET_CHECK_MSG(false, "Unknown ScalarType %" PRId8, static_cast(t)); // <-- aborts } #undef CASE_ELEMENTSIZE_CASE } ``` `ET_CHECK_MSG(false, ...)` in this codebase calls `runtime_abort()` → `pal_abort()` → `et_pal_abort()`, terminating the process. This macro is designed for invariants the code asserts should never occur given prior validation — it is not designed to directly validate untrusted external input. Because `create_tensor_layout()` never validates `scalar_type` before it flows into this call chain, an attacker can trigger this "should never happen" path on demand. ## Steps to Reproduce ### Environment Linux x86-64, ExecuTorch 1.3.1 pristine source, clang-16, CMake, Ninja. No authentication, no host access. ### 1. Build and harness Identical to REPORT-01 (same build flags, same `poc/harness_flat_tensor_fuzzer.cpp`). ### 2. PoC file `poc/poc_invalid_scalar_type.ptd` (272 bytes, **included in this report — sha256 `b5972dad3d9e5cf23434270c1dadc2a03dd51b3c4fde8661b38f1e8a1e9494fd`**) is a well-formed `.ptd` file with one `NamedData` entry (`key = "weight1"`) whose `tensor_layout` has valid `sizes`/`dim_order` but a `scalar_type` byte value outside the range defined by `ET_FORALL_SCALAR_TYPES`. ### 3. 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_invalid_scalar_type.ptd ``` ### Expected result (secure behavior) `get_tensor_layout("weight1")` should return a clean `Error::InvalidExternalData` for an out-of-range `scalar_type`, matching the behavior of the equivalent `.pte` `Tensor` parsing path. ### Actual result — verified against the pristine, unmodified ExecuTorch 1.3.1 source Full symbolized stack trace (via `addr2line` against the built binary): ``` et_pal_abort runtime/platform/default/posix.cpp:98 executorch::runtime::pal_abort() runtime/platform/platform.cpp:126 executorch::runtime::runtime_abort() runtime/platform/abort.cpp:20 executorch::runtime::TensorLayout::create(...) runtime/core/exec_aten/util/scalar_type_util.h:419 <- elementSize()'s ET_CHECK_MSG executorch::extension::(anonymous namespace)::create_tensor_layout(...) extension/flat_tensor/flat_tensor_data_map.cpp:104 executorch::extension::FlatTensorDataMap::get_tensor_layout(...) extension/flat_tensor/flat_tensor_data_map.cpp:122 LLVMFuzzerTestOneInput ``` ``` ==== ERROR: libFuzzer: deadly signal #0 ... (abort machinery) ... NOTE: libFuzzer has rudimentary signal handlers. Combine libFuzzer with AddressSanitizer or similar for better crash reports. SUMMARY: libFuzzer: deadly signal ``` **Reproduced 3/3 identical runs against the pristine source** (re-verified live for this report, with full symbol resolution confirming the exact call chain above). ## Impact **Who is affected:** Any application calling `get_tensor_layout()`/`load_data_into()` on a `NamedData` entry whose `tensor_layout.scalar_type` is out of range. **What the attacker can do:** Cause a deterministic crash — and notably, unlike the UBSan-trapped null-dereference findings in this codebase, this is a **deliberate `abort()` call that fires identically in production (non-sanitized) builds**, not merely undefined behavior that happens to crash under a sanitizer. This makes it a slightly stronger, more production-relevant DoS guarantee than the other findings in this batch. **What's at risk:** Availability only. No memory corruption, no code execution — `ET_CHECK_MSG` performs a controlled, deliberate process termination. **Why not Critical:** A deliberate `abort()`, not memory corruption or code execution. ## Suggested Remediation ```cpp Result create_tensor_layout( const flat_tensor_flatbuffer::TensorLayout* tensor_layout) { if (tensor_layout == nullptr) { return Error::InvalidExternalData; } ScalarType scalar_type = static_cast(tensor_layout->scalar_type()); if (!executorch::runtime::isValid(scalar_type)) { return Error::InvalidExternalData; } const int dim = tensor_layout->sizes()->size(); ... ``` This is a direct application of the pattern already correctly implemented in `tensor_parser_exec_aten.cpp`'s `parseTensor()` for the parallel `.pte` `Tensor` type — no new design is needed, just consistency between the two code paths. A regression test should build a `.ptd` `NamedData.tensor_layout` with an out-of-range `scalar_type` byte and assert `get_tensor_layout()` returns a clean `Error` rather than aborting. ## Files Included in This Report - `poc/poc_invalid_scalar_type.ptd` — the 272-byte PoC file (sha256 `b5972dad3d9e5cf23434270c1dadc2a03dd51b3c4fde8661b38f1e8a1e9494fd`) - `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_invalid_scalar_type.ptd` is ready for that upload.