File size: 9,113 Bytes
1ec1150
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# 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()`:

```cpp
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`:

```cpp
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:

```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_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

```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_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

```cpp
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.