File size: 7,652 Bytes
b009429
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Null Pointer Dereference in ExecuTorch BackendDelegate::PopulateCompileSpecs() via Missing CompileSpec.key (.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 `key` field unconditionally via `->c_str()`, without checking it for null. `CompileSpec.key` is declared as an optional `string` in the schema. A `.pte` file with a delegate whose `compile_specs` list contains an entry with a `value` but no `key` crashes the moment `Method::init()` resolves that delegate β€” before any execution begins.

This is the third distinct null-pointer-dereference finding reached through the same `Method::init()` delegate-resolution code path in this investigation (following the `Operator.overload` and `Program.backend_delegate_data` findings), reinforcing that this area of the codebase was written with less consistent defensive guarding than the tensor-parsing paths.

## Vulnerability Details

`runtime/executor/method.cpp`'s `BackendDelegate::PopulateCompileSpecs()`:

```cpp
static Error PopulateCompileSpecs(
    const flatbuffers::Vector<flatbuffers::Offset<
        executorch_flatbuffer::CompileSpec>>* compile_specs_in_program,
    BackendInitContext& backend_init_context,
    CompileSpec** out_spec) {
  auto number_of_compile_specs = compile_specs_in_program->size();
  CompileSpec* compile_specs_list = ...;
  ...
  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();  // <-- crash site
    compile_specs_list[j].value = {
        static_cast<void*>(const_cast<uint8_t*>(compile_spec_in_program->value()->Data())),
        compile_spec_in_program->value()->size(),
    };
  }
  ...
```

`schema/program.fbs`:

```
table CompileSpec {
  // One compile spec. There are can be multiple specs for one method
  key: string; // like max_value
  value: [ubyte]; // like 4, or other types based on needs.
}
```

`PopulateCompileSpecs()` is called from `BackendDelegate::Init()` whenever `delegate.compile_specs() != nullptr` β€” a normal, documented way for a delegate to pass backend-specific compilation options.

## 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 Step 1.

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

This harness registers fake backends under common names (`XnnpackBackend`, `QnnBackend`, etc.) that trivially succeed on `init()`, exercising the real `BackendDelegate::Init()`/`PopulateCompileSpecs()` 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_compilespec_key_null.pte` (44,800 bytes, **included in this report β€” sha256 `673c5e5f95788827bf24bef50f7a2de39aab9e63b17278a5415769746bd417bb`**). This PoC was originally discovered by coverage-guided fuzzing (a 1,355,528-byte input) and minimized via libFuzzer's built-in crash minimizer to this 44,800-byte reproducer while preserving the exact same crash.

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

### Expected result (secure behavior)
`Method::init()` should return a clean `Error` (or substitute an empty string) when a `CompileSpec` entry has no `key`, rather than crashing.

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

```
Running: poc/poc_compilespec_key_null.pte
runtime/executor/method.cpp:181:67: runtime error: member call on null pointer of type 'flatbuffers::String'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method.cpp:181:67 in
==<pid>== ERROR: libFuzzer: deadly signal
    #0 ... (abort machinery)
    BackendDelegate::PopulateCompileSpecs (method.cpp:181)
    BackendDelegate::Init (method.cpp)
    Method::init (method.cpp:971)
```

**Reproduced 3/3 identical runs against the independently rebuilt pristine source** (re-verified live for this report):
```
run 1: member call on null pointer of type 'flatbuffers::String'
run 2: (identical)
run 3: (identical)
```

## Impact

**Who is affected:** Any application calling `Program::load_method()` on a method whose delegates specify `compile_specs` with a null `key`, where the victim has registered/made available a backend matching the delegate's `id` β€” a routine configuration for any deployment with at least one 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
compile_specs_list[j].key = (compile_spec_in_program->key() != nullptr)
    ? compile_spec_in_program->key()->c_str()
    : "";
```

**Design recommendation:** consider a single validation pass over all `CompileSpec` entries in a delegate's `compile_specs` list at the top of `PopulateCompileSpecs()`, checking both `key` and `value` for null in one place, consistent with the recommendation made in the companion `Operator.overload` (REPORT-11) and `Program.backend_delegate_data` (REPORT-12) findings β€” all three sit in the same delegate-resolution code area and would benefit from a shared, consistent validation approach.

A regression test should build a `.pte` with one `BackendDelegate` (using a registered fake backend) whose `compile_specs` contains an entry with a `value` but no `key`, asserting `Method::load()` returns a clean `Error` rather than crashing.

## Files Included in This Report

- `poc/poc_compilespec_key_null.pte` β€” the 44,800-byte minimized PoC file (sha256 `673c5e5f95788827bf24bef50f7a2de39aab9e63b17278a5415769746bd417bb`)
- `poc/harness_execute_fuzzer.cpp` β€” the harness used to trigger and reproduce the crash, including fake backend registration for exercising `BackendDelegate::Init()`/`PopulateCompileSpecs()`

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