# Null Pointer Dereference in ExecuTorch BackendDelegate::PopulateCompileSpecs() via Missing CompileSpec.value (.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.cpp` **Authentication Required:** No — requires a victim application to load a `.pte` file whose method has a backend delegate with a `compile_specs` list, with at least one backend registered/available (a routine deployment configuration). ## Summary `BackendDelegate::PopulateCompileSpecs()` dereferences each `CompileSpec` entry's `value` field unconditionally via `->Data()` and `->size()`, without checking it for null. `CompileSpec.value` is declared as an optional `[ubyte]` in the schema — the direct sibling of the `CompileSpec.key` field covered in REPORT-13. This is the fourth distinct null-pointer-dereference finding reached through the `Method::init()` delegate-resolution code path in this investigation, and was discovered as an immediate consequence of fixing REPORT-13: within the same fuzzing run started right after that patch, the fuzzer found this second, independent bug in the very next field of the very same loop. ## Vulnerability Details `runtime/executor/method.cpp`'s `BackendDelegate::PopulateCompileSpecs()` (pristine source): ```cpp for (size_t j = 0; j < number_of_compile_specs; j++) { auto compile_spec_in_program = compile_specs_in_program->Get(j); compile_specs_list[j].key = compile_spec_in_program->key()->c_str(); compile_specs_list[j].value = { /*buffer*/ static_cast( const_cast(compile_spec_in_program->value()->Data())), // <-- crash site /*nbytes*/ compile_spec_in_program->value()->size(), // <-- crash site }; } ``` `schema/program.fbs`: ``` table CompileSpec { key: string; // like max_value value: [ubyte]; // like 4, or other types based on needs. } ``` Neither `key` nor `value` is marked `required`, so both are legitimately optional. `PopulateCompileSpecs()` handled **neither** safely in the pristine source. ## An Important Investigative Note: How This Was Isolated As a Genuinely Separate Bug Since the pristine source hits the `key` null-deref first (it's dereferenced before `value` in source order), the initial crash always reported the `key` issue (REPORT-13). To confirm `value` was an *independent* second bug rather than the same crash re-appearing, this PoC was tested three ways: 1. Against the pristine source: crashes on `key` (as expected, since `key` is checked first). 2. Against the REPORT-13-patched build (where `key` is now null-safe): the *same* input proceeds past the fixed `key` check and crashes on `value` instead — proving `value` has its own, separate missing guard. 3. Against a build with both fixes applied: the input runs cleanly. ## 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-11/12/13. ### 2. Build the execute-level harness (`poc/harness_execute_fuzzer.cpp`, same harness as REPORT-11/12/13, included in this report) ```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_execute_fuzzer.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_compilespec_value_null.pte` (44,801 bytes, **included in this report — sha256 `eab0858e7b4583e6c94ab96eb652989738d132e4631b07f16c9d0a6d4b1faf3d`**). Found by continued coverage-guided fuzzing immediately after the REPORT-13 fix was applied. ### 4. Trigger the crash Against the pristine source, this PoC crashes on the `key` field (REPORT-13's bug) — you must apply REPORT-13's fix first to observe this report's `value` crash in isolation, OR simply trust the differential analysis above. To observe both independently: ```bash export ASAN_OPTIONS="abort_on_error=1:symbolize=0" export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=0" # Against pristine source (crashes on key - see REPORT-13): ./poc_harness_pristine -timeout=5 -runs=0 poc/poc_compilespec_value_null.pte # Against a build with ONLY the key fix applied (crashes on value - this report): ./poc_harness_key_fixed -timeout=5 -runs=0 poc/poc_compilespec_value_null.pte ``` ### Actual result — verified on the REPORT-13-patched build (isolating this bug), 3/3 ``` Running: poc/poc_compilespec_value_null.pte runtime/executor/method.cpp:184:XX: runtime error: member call on null pointer of type 'flatbuffers::Vector' SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method.cpp:184 in ==== ERROR: libFuzzer: deadly signal #0 ... (abort machinery) BackendDelegate::PopulateCompileSpecs (method.cpp) BackendDelegate::Init (method.cpp) Method::init (method.cpp:971) ``` **Reproduced 3/3 identical runs.** ## Impact **Who is affected:** Any application calling `Program::load_method()` on a method whose delegates specify `compile_specs` with a null `value`, where the victim has registered/made available a backend matching the delegate's `id`. **What the attacker can do:** Cause a deterministic crash during method loading, unconditionally. **What's at risk:** Availability only. **Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution demonstrated. ## Suggested Remediation ```cpp compile_specs_list[j].key = (compile_spec_in_program->key() != nullptr) ? compile_spec_in_program->key()->c_str() : ""; const auto* spec_value = compile_spec_in_program->value(); compile_specs_list[j].value = { (spec_value != nullptr) ? static_cast(const_cast(spec_value->Data())) : nullptr, (spec_value != nullptr) ? spec_value->size() : 0, }; ``` **Design recommendation:** This is the second of two optional fields in the same loop found unguarded (see REPORT-13 for the first). Both should be validated together at the top of the loop body rather than patched incrementally field-by-field, since ad-hoc patching already missed a sibling field once. A regression test should build a `.pte` with one `BackendDelegate` (using a registered fake backend) whose `compile_specs` contains an entry with a `key` but no `value`, asserting `Method::load()` returns a clean `Error` rather than crashing. Combine with the REPORT-13 test to cover both fields (key-null, value-null, both-null) in one test matrix. ## Files Included in This Report - `poc/poc_compilespec_value_null.pte` — the 44,801-byte PoC file (sha256 `eab0858e7b4583e6c94ab96eb652989738d132e4631b07f16c9d0a6d4b1faf3d`) - `poc/harness_execute_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_compilespec_value_null.pte` is ready for that upload. Given the close relationship to REPORT-13 (same function, same loop, sibling field), consider whether huntr's triage prefers these filed as one combined report or two separate ones — both are provided as separate, fully-evidenced reports here per this session's one-bug-one-report convention, but the underlying fix is naturally combined.