EnigmaConsultant's picture
Upload folder using huggingface_hub
cef4602 verified
|
Raw
History Blame Contribute Delete
8.05 kB

ExecuTorch .pte loader: NULL-pointer dereference in Program::get_backend_delegate_data() (missing backend_delegate_data field)

Summary

A structurally-valid, fully-verified ExecuTorch .pte model triggers a NULL-pointer dereference (SIGSEGV, READ at address 0x0) the moment a method is loaded via Program::load_method(). The crash occurs in executorch::runtime::Program::get_backend_delegate_data() (runtime/executor/program.cpp:538-546), which calls data_list->size() on the result of the generated flatbuffers accessor backend_delegate_data() without a null check.

  • Target: pytorch/executorch runtime (.pte model loader)
  • Commit: a6d812a082df57898b8608f56c867140cc9da32c
  • Component: runtime/executor/program.cppProgram::get_backend_delegate_data()
  • Class: CWE-476 (NULL Pointer Dereference)
  • Impact: Denial of Service when loading an untrusted/attacker-supplied .pte. The crash fires even under Program::Verification::InternalConsistency — the strongest verification mode ExecuTorch offers, which the malicious file passes cleanly before crashing at method-load time.

Root cause

runtime/executor/program.cpp:538-546:

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();          // nullptr when the field is absent
  ET_CHECK_OR_RETURN_ERROR(
      index < data_list->size(),              // <-- data_list->size() derefs nullptr
      InvalidArgument,
      "index %zu >= list size %" PRIu32,
      index,
      data_list->size());
  ...

Program.backend_delegate_data is not marked (required) in schema/program.fbs (line 506):

// schema/program.fbs (line ~506)
table Program {
  ...
  // Tables of constant data, used for keeping the constant data away from the
  // program.
  backend_delegate_data:[BackendDelegateInlineData];   // NOT (required)
  ...
}

Because the field is optional, a legal FlatBuffer may omit it entirely. The flatc-generated accessor Program::backend_delegate_data() then returns nullptr, and the subsequent data_list->size() call (third-party/flatbuffers/include/flatbuffers/vector.h:164) dereferences a null Vector pointer, producing a SEGV read at 0x0.

Trigger path

The malicious file contains a BackendDelegate whose processed reference is present with location = INLINE, index = 0, while the top-level Program.backend_delegate_data vector is absent:

Program::load_method("forward")
 -> Method::init()                                          method.cpp:971
 -> BackendDelegate::Init()                                 method.cpp:97
 -> BackendDelegate::GetProcessedData()  [INLINE case]      method.cpp:202
 -> Program::get_backend_delegate_data(0)                   program.cpp:541
 -> data_list->size()   // data_list == nullptr  => SEGV read @ 0x0

Proof of Concept

  • build_pte.cpp builds the malicious .pte using flatc-generated ExecuTorch schema headers: one ExecutionPlan named "forward" containing one BackendDelegate { id = "TESTBACKEND", processed = BackendDelegateDataReference { location = INLINE, index = 0 } }, while the top-level Program.backend_delegate_data vector is omitted (serialized offset 0).
  • build_pte_benign.cpp builds an otherwise identical benign.pte that DOES include a backend_delegate_data vector with one entry (negative control).
  • harness.cpp is compiled with -fsanitize=address against the real ExecuTorch runtime sources. It registers a no-op backend "TESTBACKEND", loads the file with Program::load(loader, Program::Verification::InternalConsistency) (which PASSES), then calls program.load_method("forward").

Build/run:

bash build.sh                                              # builds build_pte + ASan harness
./build_pte malicious.pte
ASAN_OPTIONS=detect_leaks=0 ./harness_asan malicious.pte   # SEGV
./build_pte_benign benign.pte
ASAN_OPTIONS=detect_leaks=0 ./harness_asan benign.pte      # no crash (control)

Captured evidence (verbatim)

Malicious input (malicious.pte, backend_delegate_data omitted):

[harness] Program::load(InternalConsistency) ...
[harness] Program::load() OK (passed full verification). Loading method "forward" ...
AddressSanitizer:DEADLYSIGNAL
==1961067==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x5643edbd2475 bp 0x7ffe118bdf80 sp 0x7ffe118bdf60 T0)
==1961067==The signal is caused by a READ memory access.
==1961067==Hint: address points to the zero page.
    #0 0x5643edbd2475 in flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegateInlineData>, unsigned int>::size() const .../third-party/flatbuffers/include/flatbuffers/vector.h:164:44
    #1 0x5643edbcf4bd in executorch::runtime::Program::get_backend_delegate_data(unsigned long, void const**, unsigned long*) const .../runtime/executor/program.cpp:541:3
    #2 0x5643edbc0b3a in executorch::runtime::BackendDelegate::GetProcessedData(executorch_flatbuffer::BackendDelegate const&, executorch::runtime::Program const*) .../runtime/executor/method.cpp:202:30
    #3 0x5643edbb974d in executorch::runtime::BackendDelegate::Init(executorch_flatbuffer::BackendDelegate const&, executorch::runtime::Program const*, executorch::runtime::BackendInitContext&, executorch::runtime::BackendDelegate*) .../runtime/executor/method.cpp:97:45
    #4 0x5643edbaa326 in executorch::runtime::Method::init(executorch_flatbuffer::ExecutionPlan*, executorch::runtime::NamedDataMap const*, executorch::runtime::LoadBackendOptionsMap const*) .../runtime/executor/method.cpp:971:19
    #5 0x5643edba90bc in executorch::runtime::Method::load(...) .../runtime/executor/method.cpp:862:22
    #6 0x5643edbcdd11 in executorch::runtime::Program::load_method(...) .../runtime/executor/program.cpp:392:10
    #7 0x5643edba07ed in main .../bug_backend_delegate_data/harness.cpp:82:15
    #8 0x7ff10773bf76  (/usr/lib/x86_64-linux-gnu/libc.so.6+0x29f76)
    #9 0x7ff10773c026 in __libc_start_main
    #10 0x5643edab5420 in _start

==1961067==Register values:
rax = 0x0000000000000000  ... rdi = 0x0000000000000000 ...
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV .../third-party/flatbuffers/include/flatbuffers/vector.h:164:44 in flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegateInlineData>, unsigned int>::size() const
==1961067==ABORTING

Negative control (benign.pte, backend_delegate_data present with one entry) — no crash; load proceeds past the delegate to an unrelated later error:

[harness] Program::load(InternalConsistency) ...
[harness] Program::load() OK (passed full verification). Loading method "forward" ...
E 00:00:00.000266 executorch:method.cpp:987] No chains
load_method returned error (no crash): 0x23

Distinct from prior findings

This is a separate defect from the other known ExecuTorch delegate-loader null-dereferences:

  • NOT the CompileSpec key/value nullderef (method.cpp, PopulateCompileSpecs).
  • NOT the processed-absent nullderef (GetProcessedData, method.cpp:197, processed->location()). In this PoC processed IS present (location = INLINE, index = 0); the crash occurs one call deeper, in a different source file (program.cpp), on a missing null-check of a different, Program-level schema field (backend_delegate_data).
  • NOT the .ptd FlatTensorDataMap nullderef.

Suggested fix

Null-check data_list in get_backend_delegate_data() before use:

const auto* data_list = ...->backend_delegate_data();
ET_CHECK_OR_RETURN_ERROR(
    data_list != nullptr, NotFound,
    "Program has no backend_delegate_data but an INLINE delegate references it");
ET_CHECK_OR_RETURN_ERROR(index < data_list->size(), InvalidArgument, ...);