tsk-arasu's picture
Upload folder using huggingface_hub
c809321 verified
|
Raw
History Blame Contribute Delete
7.95 kB

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):

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<void*>(
          const_cast<uint8_t*>(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)

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:

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<unsigned char>'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method.cpp:184 in
==<pid>== 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

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<void*>(const_cast<uint8_t*>(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.