| # 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<ScalarType>(s_tensor->scalar_type()); |
| ET_CHECK_OR_RETURN_ERROR( |
| isValid(scalar_type), |
| InvalidProgram, |
| "Invalid or unsupported ScalarType %" PRId8, |
| static_cast<int8_t>(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<const TensorLayout> create_tensor_layout( |
| const flat_tensor_flatbuffer::TensorLayout* tensor_layout) { |
| ScalarType scalar_type = |
| static_cast<ScalarType>(tensor_layout->scalar_type()); // no isValid() check |
| const int dim = tensor_layout->sizes()->size(); |
| ... |
| return TensorLayout::create( |
| Span<const int32_t>(serialized_sizes, dim), |
| Span<const uint8_t>(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<int8_t>(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 |
| ``` |
|
|
| ``` |
| ==<pid>== 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<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 (!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. |
|
|