| # 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<const char*> 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 |
| ==<pid>== 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<const char*> 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<const char*> 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. |
|
|