tsk-arasu's picture
Upload folder using huggingface_hub
1ec1150 verified
|
Raw
History Blame Contribute Delete
9.11 kB

Null Pointer Dereference in ExecuTorch Program::get_backend_delegate_data() via Missing Program.backend_delegate_data (.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/program.cpp Authentication Required: No β€” requires a victim application to load a .pte file whose method has a backend delegate referencing INLINE-located processed data, with at least one backend registered/available (a routine deployment configuration).

Summary

Program::get_backend_delegate_data() dereferences Program.backend_delegate_data() (an optional top-level flatbuffer field) directly inside its bounds-check condition, with no null guard. A .pte file with an ExecutionPlan.delegates entry whose processed field specifies DataLocation::INLINE, while the Program's top-level backend_delegate_data list is entirely absent, crashes the moment Method::init() attempts to resolve that delegate β€” before any execution begins. Unlike a sibling finding in this same investigation (the Operator.overload null-deref), this crash sits directly in the bounds-check condition itself, not merely inside a log-message argument β€” making it unconditionally reachable regardless of build configuration (no dependency on EXECUTORCH_ENABLE_LOGGING).

This bug was confirmed twice independently: once against this session's incrementally patched build, and once against a freshly rebuilt, wholly unmodified pristine 1.3.1 source tree, ruling out any possibility of a local-patch artifact.

Vulnerability Details

runtime/executor/program.cpp's Program::get_backend_delegate_data():

Error Program::get_backend_delegate_data(
    size_t index,
    const void** out_data,
    size_t* out_size) const {
  const auto* data_list =
      static_cast<const executorch_flatbuffer::Program*>(internal_program_)
          ->backend_delegate_data();
  ET_CHECK_OR_RETURN_ERROR(
      index < data_list->size(),        // <-- crash site: data_list may be null
      NotFound,
      "index %zu >= list size %" PRIu32,
      index,
      data_list->size());
  ...

Program.backend_delegate_data is declared as an optional [BackendDelegateInlineData] list in schema/program.fbs:

// List of delegate data. Pointed to by BackendDelegateDataReference.
backend_delegate_data: [BackendDelegateInlineData];

This function is called from BackendDelegate::GetProcessedData() (method.cpp) whenever a delegate's processed field specifies DataLocation::INLINE:

case executorch_flatbuffer::DataLocation::INLINE: {
  const void* data;
  size_t size;
  Error err = program->get_backend_delegate_data(
      processed->index(), &data, &size);
  ...

Which in turn is called from BackendDelegate::Init() during Method::init()'s delegate-resolution loop β€” reached during ordinary method loading, for any delegate whose registered backend is available (get_backend_class(id) != nullptr && backend->is_available()), a standard, expected condition for any deployment that has compiled in and registered at least one hardware-acceleration backend (which most real ExecuTorch deployments do).

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 (logging enabled or disabled β€” this bug reproduces either way)

Same build as REPORT-11 Step 1 (or the simpler REPORT-01 build β€” EXECUTORCH_ENABLE_LOGGING does not affect this finding).

2. Build the execute-level harness (poc/harness_execute_fuzzer.cpp, same harness as REPORT-11, included in this report)

This harness registers fake backends under common names (XnnpackBackend, QnnBackend, CoreMLBackend, etc.) that trivially succeed on init(), letting us exercise the real BackendDelegate::Init() code path:

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_backend_delegate_data_null.pte (304 bytes, included in this report β€” sha256 2f3be1201b1a426b19d44d2b0886ba12ed872ec4e3f4b02d65457b6c4f441165) contains:

  • One BackendDelegate entry with id = "XnnpackBackend" and processed = { location: INLINE, index: 0 }
  • No backend_delegate_data field at the Program level at all

4. Trigger the crash

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_backend_delegate_data_null.pte

Expected result (secure behavior)

Method::init() should return Error::NotFound (or similar) cleanly when a delegate references INLINE data but the Program has no backend_delegate_data list, rather than crashing while checking bounds.

Actual result β€” verified against BOTH a patched build AND an independently rebuilt, wholly unmodified pristine ExecuTorch 1.3.1 source tree

Running: poc/poc_backend_delegate_data_null.pte
runtime/executor/program.cpp:541:3: runtime error: member call on null pointer of type 'flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegateInlineData>>'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/program.cpp:541:3 in
==<pid>== ERROR: libFuzzer: deadly signal
    #0 ... (abort machinery)
    Program::get_backend_delegate_data (program.cpp:541)
    BackendDelegate::GetProcessedData (method.cpp:202)
    BackendDelegate::Init (method.cpp:97)
    Method::init (method.cpp:971)
    Method::load (method.cpp:862)

Reproduced 3/3 identical runs against the independently rebuilt pristine source (re-verified live for this report, using a fresh build with zero local modifications):

run 1: member call on null pointer of type 'flatbuffers::Vector<...>'
run 2: (identical)
run 3: (identical)

Impact

Who is affected: Any application calling Program::load_method() on a method whose delegates reference INLINE processed data, where the Program's backend_delegate_data list is absent β€” this is triggered during loading, not execution, and requires only that the victim has registered a backend under a name the attacker can predict (most deployments register at least one real hardware-acceleration backend).

What the attacker can do: Cause a deterministic crash during method loading, unconditionally (no build-flag dependency).

What's at risk: Availability only.

Why not Critical: Controlled null-pointer dereference, no memory corruption or code execution demonstrated.

Suggested Remediation

Error Program::get_backend_delegate_data(
    size_t index,
    const void** out_data,
    size_t* out_size) const {
  const auto* data_list =
      static_cast<const executorch_flatbuffer::Program*>(internal_program_)
          ->backend_delegate_data();
  if (data_list == nullptr) {
    ET_LOG(Error, "Program has no backend_delegate_data list");
    return Error::NotFound;
  }
  ET_CHECK_OR_RETURN_ERROR(
      index < data_list->size(),
      NotFound,
      "index %zu >= list size %" PRIu32,
      index,
      data_list->size());
  ...

Design recommendation: consider validating at Program::load() time (in validate_program(), runtime/executor/program_validation.cpp) that if any ExecutionPlan.delegates[].processed.location == INLINE exists anywhere in the program, Program.backend_delegate_data must also be present β€” catching this cross-field consistency issue once, at load time, rather than at each individual accessor.

A regression test should build a .pte with one BackendDelegate referencing INLINE processed data at index 0, and no backend_delegate_data field on the Program, asserting Method::load() returns a clean Error::NotFound rather than crashing.

Files Included in This Report

  • poc/poc_backend_delegate_data_null.pte β€” the 304-byte PoC file (sha256 2f3be1201b1a426b19d44d2b0886ba12ed872ec4e3f4b02d65457b6c4f441165)
  • poc/harness_execute_fuzzer.cpp β€” the harness used to trigger and reproduce the crash, including fake backend registration for exercising BackendDelegate::Init()

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_backend_delegate_data_null.pte is ready for that upload.