File size: 7,828 Bytes
dc9f5c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Null Pointer Dereference in ExecuTorch MethodMeta::get_backend_name() via Missing BackendDelegate.id (.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_meta.cpp`
**Authentication Required:** No β€” only requires a victim application to load an attacker-supplied `.pte` file and call a public API.

## Summary

`MethodMeta::get_backend_name()` is a public API for enumerating the backend names used by a loaded method (e.g. for logging or diagnostics). It dereferences `BackendDelegate.id` without checking it for null, even though `id` is an ordinary (non-required) field in the schema. A `.pte` file with a well-formed `delegates` array containing an entry whose `id` is omitted crashes any process that calls `get_backend_name()` for that entry's index.

This is a distinct bug from the previously identified `uses_backend()` null-derefs (same file, same underlying `BackendDelegate.id` field, but a different function and call site) β€” confirmed independently below using a harness that calls **only** `get_backend_name()`, with zero calls to any other `MethodMeta` method, ruling out any possibility this is merely an artifact of a different bug being triggered first.

## Vulnerability Details

`schema/program.fbs`:

```
table BackendDelegate {
  id: string;                              // not required
  processed: BackendDelegateDataReference;
  compile_specs: [CompileSpec];
}
```

`runtime/executor/method_meta.cpp` (pristine 1.3.1 source, lines 424–433):

```cpp
Result<const char*> MethodMeta::get_backend_name(size_t index) const {
  const auto count = num_backends();
  ET_CHECK_OR_RETURN_ERROR(
      index < count,
      InvalidArgument,
      "Index %zu out of range. num_backends: %zu",
      index,
      count);
  return s_plan_->delegates()->Get(index)->id()->c_str();   // <-- crash site, line 432
}
```

The bounds check (`index < count`) only validates the *index*, not the *content* of the `BackendDelegate` at that index. A `BackendDelegate` entry with no `id` is fully legal per the schema, so `id()` returns `nullptr` and `->c_str()` crashes.

## 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-01/03 Step 1 β€” the default target set includes `runtime/executor/method_meta.cpp`.

### 2. Build the isolated PoC harness (`poc/harness_isolated_get_backend_name.cpp`, included in this report)

This harness deliberately calls **only** `get_backend_name()` β€” no `uses_backend()`, no other `MethodMeta` accessor β€” to prove this bug is independent of any other finding in this codebase:

```cpp
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
  // ... load Program under InternalConsistency verification ...
  auto meta = p.method_meta(name.get());
  if (meta.ok()) {
    auto& m = meta.get();
    size_t nb = m.num_backends();
    for (size_t bi = 0; bi < nb; ++bi) {
      (void)m.get_backend_name(bi);   // ONLY this call
    }
  }
}
```

```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_isolated_get_backend_name.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_get_backend_name_null.pte` (728 bytes, **included in this report β€” sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`**) is a well-formed `.pte` file containing an `ExecutionPlan` whose `delegates` array has at least one `BackendDelegate` entry with `id` omitted.

### 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_get_backend_name_null.pte
```

### Expected result (secure behavior)
`get_backend_name()` should return a clean `Error` (e.g. `InvalidProgram`) for a delegate entry with no `id`, since there is no valid name to return.

### Actual result β€” verified in isolation (harness calls ONLY get_backend_name, no other MethodMeta method)

```
Running: poc/poc_get_backend_name_null.pte
runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method_meta.cpp:432:50 in
==<pid>== ERROR: libFuzzer: deadly signal
```

**Reproduced 3/3 identical runs, in isolation** (re-verified live for this report using a harness that calls no other `MethodMeta` method):
```
run 1: runtime/executor/method_meta.cpp:432:50: runtime error: member call on null pointer of type 'flatbuffers::String'
run 2: (identical)
run 3: (identical)
```

This isolation test is the key piece of evidence distinguishing this from a previously-identified `uses_backend()` bug in the same file: it proves `get_backend_name()` crashes on this exact field independent of any other code path being exercised first.

## Impact

**Who is affected:** Any application calling `MethodMeta::get_backend_name()` to enumerate backend names β€” a normal diagnostic/logging pattern for applications that report which backends a loaded model uses.

**What the attacker can do:** Cause a deterministic crash by supplying a `.pte` file with a `BackendDelegate` entry that has no `id` β€” legal per schema, undetected by both of ExecuTorch's verification levels.

**What's at risk:** Availability only.

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

## Suggested Remediation

```cpp
Result<const char*> MethodMeta::get_backend_name(size_t index) const {
  const auto count = num_backends();
  ET_CHECK_OR_RETURN_ERROR(
      index < count,
      InvalidArgument,
      "Index %zu out of range. num_backends: %zu",
      index,
      count);
  auto delegate = s_plan_->delegates()->Get(index);
  ET_CHECK_OR_RETURN_ERROR(
      delegate != nullptr && delegate->id() != nullptr,
      InvalidProgram,
      "Backend delegate %zu or its id is null",
      index);
  return delegate->id()->c_str();
}
```

**Design recommendation:** this is the second function in `method_meta.cpp` found to independently dereference `BackendDelegate.id` without a null check (the other being `uses_backend()`). A single shared helper β€” e.g. `Result<const char*> get_delegate_id(size_t index)` β€” used by both functions would prevent a third such call site from repeating this pattern in the future.

A regression test should build an `ExecutionPlan.delegates` entry with `id` omitted and assert `get_backend_name()` returns a clean `Error` rather than crashing.

## Files Included in This Report

- `poc/poc_get_backend_name_null.pte` β€” the 728-byte PoC file (sha256 `7675e37734409f4ec13bc31bd21963b085312f65c33dfc583cd0d1521c22be0a`)
- `poc/harness_isolated_get_backend_name.cpp` β€” the isolated harness proving this bug independent of any other `MethodMeta` code path

## 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_get_backend_name_null.pte` is ready for that upload.