# Null Pointer Dereference in ExecuTorch MethodMeta::get_backend_name() via Missing BackendDelegate.id (.pte) **Target:** ExecuTorch 1.3.1 (.pte, huntr Model File Vulnerability program) **Severity:** Low-Medium (Denial of Service) **CWE:** CWE-476 (NULL Pointer Dereference) **Component:** `runtime/executor/method_meta.cpp` **Authentication Required:** No — only requires a victim application to load an attacker-supplied `.pte` file and call a public API. ## Summary `MethodMeta::get_backend_name()` is a public API for enumerating the backend names used by a loaded method (e.g. for logging or diagnostics). It dereferences `BackendDelegate.id` without checking it for null, even though `id` is an ordinary (non-required) field in the schema. A `.pte` file with a well-formed `delegates` array containing an entry whose `id` is omitted crashes any process that calls `get_backend_name()` for that entry's index. This is a distinct bug from the previously identified `uses_backend()` null-derefs (same file, same underlying `BackendDelegate.id` field, but a different function and call site) — confirmed independently below using a harness that calls **only** `get_backend_name()`, with zero calls to any other `MethodMeta` method, ruling out any possibility this is merely an artifact of a different bug being triggered first. ## Vulnerability Details `schema/program.fbs`: ``` table BackendDelegate { id: string; // not required processed: BackendDelegateDataReference; compile_specs: [CompileSpec]; } ``` `runtime/executor/method_meta.cpp` (pristine 1.3.1 source, lines 424–433): ```cpp Result MethodMeta::get_backend_name(size_t index) const { const auto count = num_backends(); ET_CHECK_OR_RETURN_ERROR( index < count, InvalidArgument, "Index %zu out of range. num_backends: %zu", index, count); return s_plan_->delegates()->Get(index)->id()->c_str(); // <-- crash site, line 432 } ``` The bounds check (`index < count`) only validates the *index*, not the *content* of the `BackendDelegate` at that index. A `BackendDelegate` entry with no `id` is fully legal per the schema, so `id()` returns `nullptr` and `->c_str()` crashes. ## Steps to Reproduce ### Environment Linux x86-64, ExecuTorch 1.3.1 pristine source, clang-16, CMake, Ninja. No authentication, no host access. ### 1. Build ExecuTorch with sanitizers Same build as REPORT-01/03 Step 1 — the default target set includes `runtime/executor/method_meta.cpp`. ### 2. Build the isolated PoC harness (`poc/harness_isolated_get_backend_name.cpp`, included in this report) This harness deliberately calls **only** `get_backend_name()` — no `uses_backend()`, no other `MethodMeta` accessor — to prove this bug is independent of any other finding in this codebase: ```cpp extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) { // ... load Program under InternalConsistency verification ... auto meta = p.method_meta(name.get()); if (meta.ok()) { auto& m = meta.get(); size_t nb = m.num_backends(); for (size_t bi = 0; bi < nb; ++bi) { (void)m.get_backend_name(bi); // ONLY this call } } } ``` ```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_isolated_get_backend_name.cpp -o harness.o clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \ "$ET_BUILD/extension/data_loader/libextension_data_loader.a" \ "$ET_BUILD/libexecutorch_core.a" ``` ### 3. PoC file `poc/poc_get_backend_name_null.pte` (728 bytes, **included in this report — sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`**) is a well-formed `.pte` file containing an `ExecutionPlan` whose `delegates` array has at least one `BackendDelegate` entry with `id` omitted. ### 4. 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_get_backend_name_null.pte ``` ### Expected result (secure behavior) `get_backend_name()` should return a clean `Error` (e.g. `InvalidProgram`) for a delegate entry with no `id`, since there is no valid name to return. ### Actual result — verified in isolation (harness calls ONLY get_backend_name, no other MethodMeta method) ``` Running: poc/poc_get_backend_name_null.pte runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String' SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method_meta.cpp:432:50 in ==== ERROR: libFuzzer: deadly signal ``` **Reproduced 3/3 identical runs, in isolation** (re-verified live for this report using a harness that calls no other `MethodMeta` method): ``` run 1: runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String' run 2: (identical) run 3: (identical) ``` This isolation test is the key piece of evidence distinguishing this from a previously-identified `uses_backend()` bug in the same file: it proves `get_backend_name()` crashes on this exact field independent of any other code path being exercised first. ## Impact **Who is affected:** Any application calling `MethodMeta::get_backend_name()` to enumerate backend names — a normal diagnostic/logging pattern for applications that report which backends a loaded model uses. **What the attacker can do:** Cause a deterministic crash by supplying a `.pte` file with a `BackendDelegate` entry that has no `id` — legal per schema, undetected by both of ExecuTorch's verification levels. **What's at risk:** Availability only. **Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution. ## Suggested Remediation ```cpp Result MethodMeta::get_backend_name(size_t index) const { const auto count = num_backends(); ET_CHECK_OR_RETURN_ERROR( index < count, InvalidArgument, "Index %zu out of range. num_backends: %zu", index, count); auto delegate = s_plan_->delegates()->Get(index); ET_CHECK_OR_RETURN_ERROR( delegate != nullptr && delegate->id() != nullptr, InvalidProgram, "Backend delegate %zu or its id is null", index); return delegate->id()->c_str(); } ``` **Design recommendation:** this is the second function in `method_meta.cpp` found to independently dereference `BackendDelegate.id` without a null check (the other being `uses_backend()`). A single shared helper — e.g. `Result get_delegate_id(size_t index)` — used by both functions would prevent a third such call site from repeating this pattern in the future. A regression test should build an `ExecutionPlan.delegates` entry with `id` omitted and assert `get_backend_name()` returns a clean `Error` rather than crashing. ## Files Included in This Report - `poc/poc_get_backend_name_null.pte` — the 728-byte PoC file (sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`) - `poc/harness_isolated_get_backend_name.cpp` — the isolated harness proving this bug independent of any other `MethodMeta` code path ## 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_get_backend_name_null.pte` is ready for that upload.