Upload README.md with huggingface_hub
Browse files
README.md
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Null Pointer Dereference in ExecuTorch BackendDelegate::GetProcessedData() via Missing BackendDelegate.processed (.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.cpp`
|
| 7 |
+
**Authentication Required:** No β requires a victim application to load a `.pte` file whose method has a backend delegate entry referencing a registered/available backend, while omitting the delegate's `processed` field (a routine deployment configuration, requiring nothing more than one compiled-in hardware-acceleration backend).
|
| 8 |
+
|
| 9 |
+
## Summary
|
| 10 |
+
|
| 11 |
+
`BackendDelegate::GetProcessedData()` dereferences `delegate.processed()` β the whole optional `BackendDelegateDataReference` field on a `BackendDelegate` table β via `->location()`, with no null guard. A `.pte` file with an `ExecutionPlan.delegates` entry whose `id` matches a backend the victim has registered and made available, but which omits the `processed` field entirely, crashes the moment `Method::init()` resolves that delegate β before any execution begins, and before compile specs are even examined.
|
| 12 |
+
|
| 13 |
+
This is the fifth distinct null-pointer-dereference bug found in this session's investigation of ExecuTorch's `BackendDelegate::Init()` delegate-resolution call chain (`method.cpp`), and the *shallowest*: unlike the sibling `CompileSpec.key`/`CompileSpec.value` findings (which require a populated `compile_specs` list) or the `Operator.overload` finding (which requires `EXECUTORCH_ENABLE_LOGGING=ON`), this crash needs only a bare `BackendDelegate.id` field with `processed` omitted β no compile specs, no logging flag, no kernel execution.
|
| 14 |
+
|
| 15 |
+
Confirmed **twice independently**: once against a freshly rebuilt, wholly unmodified pristine 1.3.1 source tree, and once against a separately-patched build (with unrelated compile-spec fixes applied), ruling out any local-patch artifact or coincidental interaction with prior fixes.
|
| 16 |
+
|
| 17 |
+
## Vulnerability Details
|
| 18 |
+
|
| 19 |
+
`runtime/executor/method.cpp`'s `BackendDelegate::GetProcessedData()`:
|
| 20 |
+
|
| 21 |
+
```cpp
|
| 22 |
+
static Result<FreeableBuffer> GetProcessedData(
|
| 23 |
+
const executorch_flatbuffer::BackendDelegate& delegate,
|
| 24 |
+
const Program* program) {
|
| 25 |
+
const executorch_flatbuffer::BackendDelegateDataReference* processed =
|
| 26 |
+
delegate.processed();
|
| 27 |
+
switch (processed->location()) { // <-- crash site: processed may be null
|
| 28 |
+
case executorch_flatbuffer::DataLocation::INLINE: {
|
| 29 |
+
...
|
| 30 |
+
```
|
| 31 |
+
|
| 32 |
+
`BackendDelegate.processed` is declared as an optional table field in `schema/program.fbs`:
|
| 33 |
+
|
| 34 |
+
```
|
| 35 |
+
table BackendDelegate {
|
| 36 |
+
id: string;
|
| 37 |
+
processed: BackendDelegateDataReference;
|
| 38 |
+
compile_specs: [CompileSpec];
|
| 39 |
+
}
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
None of the three fields are marked `required`, so a well-formed `BackendDelegate` entry can legally specify only `id` and omit `processed` entirely.
|
| 43 |
+
|
| 44 |
+
`GetProcessedData()` is called from `BackendDelegate::Init()`:
|
| 45 |
+
|
| 46 |
+
```cpp
|
| 47 |
+
static Error Init(
|
| 48 |
+
const executorch_flatbuffer::BackendDelegate& delegate,
|
| 49 |
+
const Program* program,
|
| 50 |
+
BackendInitContext& backend_init_context,
|
| 51 |
+
BackendDelegate* out) {
|
| 52 |
+
...
|
| 53 |
+
BackendInterface* backend = get_backend_class(backend_id);
|
| 54 |
+
...
|
| 55 |
+
ET_CHECK_OR_RETURN_ERROR(backend->is_available(), ...);
|
| 56 |
+
|
| 57 |
+
// Get the delegate data.
|
| 58 |
+
Result<FreeableBuffer> processed_data = GetProcessedData(delegate, program);
|
| 59 |
+
```
|
| 60 |
+
|
| 61 |
+
`GetProcessedData()` is the **first accessor called** in `Init()` after the backend-lookup checks β reached unconditionally for every delegate entry, unlike `PopulateCompileSpecs()` which is only invoked when `delegate.compile_specs() != nullptr`. This makes the bug the most directly reachable of the five delegate-resolution null-derefs found in this investigation.
|
| 62 |
+
|
| 63 |
+
`BackendDelegate::Init()` is in turn called from `Method::init()`'s delegate-resolution loop, reached during ordinary method **loading** (`Program::load_method()`), for any delegate whose registered backend is available (`get_backend_class(id) != nullptr && backend->is_available()`) β a standard condition for any deployment that has compiled in and registered at least one hardware-acceleration backend.
|
| 64 |
+
|
| 65 |
+
## Steps to Reproduce
|
| 66 |
+
|
| 67 |
+
### Environment
|
| 68 |
+
Linux x86-64, ExecuTorch 1.3.1 pristine source, clang-16, CMake, Ninja. No authentication, no host access.
|
| 69 |
+
|
| 70 |
+
### 1. Build ExecuTorch with sanitizers
|
| 71 |
+
|
| 72 |
+
```bash
|
| 73 |
+
cmake -B build_asan -DCMAKE_BUILD_TYPE=RelWithDebInfo \
|
| 74 |
+
-DCMAKE_CXX_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all" \
|
| 75 |
+
-DCMAKE_C_FLAGS="-fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all" \
|
| 76 |
+
-DEXECUTORCH_BUILD_EXTENSION_FLAT_TENSOR=ON .
|
| 77 |
+
cmake --build build_asan -j
|
| 78 |
+
```
|
| 79 |
+
|
| 80 |
+
### 2. Build the execute-level harness (`poc/harness_execute_fuzzer.cpp`, included in this report)
|
| 81 |
+
|
| 82 |
+
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:
|
| 83 |
+
|
| 84 |
+
```bash
|
| 85 |
+
export ET_PARENT=/path/to/parent-of-executorch
|
| 86 |
+
C10_INC="$ET_SRC/runtime/core/portable_type/c10"
|
| 87 |
+
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"
|
| 88 |
+
|
| 89 |
+
clang++-16 -std=c++17 -fsanitize=fuzzer,address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all \
|
| 90 |
+
$INCLUDES -DFLATBUFFERS_MAX_ALIGNMENT=1024 -DC10_USING_CUSTOM_GENERATED_MACROS \
|
| 91 |
+
-c poc/harness_execute_fuzzer.cpp -o harness.o
|
| 92 |
+
|
| 93 |
+
clang++-16 -fsanitize=fuzzer,address,undefined -o poc_harness harness.o \
|
| 94 |
+
"$ET_BUILD/extension/data_loader/libextension_data_loader.a" \
|
| 95 |
+
"$ET_BUILD/libexecutorch_core.a"
|
| 96 |
+
```
|
| 97 |
+
|
| 98 |
+
### 3. PoC file
|
| 99 |
+
|
| 100 |
+
`poc/poc_backend_delegate_processed_null.pte` (248 bytes, **included in this report β sha256 `0377b7f4078dda503e5f2ae3e8b69f8332081f065964a45098fb0d95bba4b36`**) contains:
|
| 101 |
+
- One `ExecutionPlan` named `"forward"` with empty values/inputs/outputs/chains/operators
|
| 102 |
+
- One `BackendDelegate` entry with `id = "XnnpackBackend"` and **no `processed` field at all**
|
| 103 |
+
- The minimal `constant_segment`/`segments` boilerplate required by this build's `ET_ENABLE_DEPRECATED_CONSTANT_BUFFER=0` configuration
|
| 104 |
+
|
| 105 |
+
Built directly from the project's own `flatc` + `schema/program.fbs`, no manual byte-patching required.
|
| 106 |
+
|
| 107 |
+
### 4. Trigger the crash
|
| 108 |
+
|
| 109 |
+
```bash
|
| 110 |
+
export ASAN_OPTIONS="abort_on_error=1:symbolize=0"
|
| 111 |
+
export UBSAN_OPTIONS="halt_on_error=1:print_stacktrace=0"
|
| 112 |
+
./poc_harness -timeout=5 -runs=0 poc/poc_backend_delegate_processed_null.pte
|
| 113 |
+
```
|
| 114 |
+
|
| 115 |
+
### Expected result (secure behavior)
|
| 116 |
+
`Method::init()` should return `Error::InvalidProgram` cleanly when a delegate entry omits the `processed` field, rather than crashing while reading its `location`.
|
| 117 |
+
|
| 118 |
+
### Actual result β verified against BOTH an independently rebuilt pristine build AND a separately-patched build
|
| 119 |
+
|
| 120 |
+
```
|
| 121 |
+
runtime/executor/method.cpp:198:24: runtime error: member call on null pointer of type 'executorch_flatbuffer::BackendDelegateDataReference'
|
| 122 |
+
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior runtime/executor/method.cpp:198:24 in
|
| 123 |
+
==<pid>== ERROR: libFuzzer: deadly signal
|
| 124 |
+
BackendDelegate::GetProcessedData (method.cpp:198)
|
| 125 |
+
BackendDelegate::Init (method.cpp:97)
|
| 126 |
+
Method::init (method.cpp:971)
|
| 127 |
+
Method::load (method.cpp:862)
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
**Reproduced 3/3 identical runs** against both the pristine rebuild and the separately-patched build:
|
| 131 |
+
```
|
| 132 |
+
run 1: member call on null pointer of type 'executorch_flatbuffer::BackendDelegateDataReference'
|
| 133 |
+
run 2: (identical)
|
| 134 |
+
run 3: (identical)
|
| 135 |
+
```
|
| 136 |
+
|
| 137 |
+
## Impact
|
| 138 |
+
|
| 139 |
+
**Who is affected:** Any application calling `Program::load_method()` on a method whose delegates include an entry that omits the `processed` field, where the victim has registered a backend under a name the attacker can predict (most deployments register at least one real hardware-acceleration backend). This is triggered during **loading**, not execution.
|
| 140 |
+
|
| 141 |
+
**What the attacker can do:** Cause a deterministic crash during method loading, unconditionally (no build-flag dependency, no compile_specs list required β the minimal possible malformed-delegate PoC).
|
| 142 |
+
|
| 143 |
+
**What's at risk:** Availability only.
|
| 144 |
+
|
| 145 |
+
**Why not Critical:** Controlled null-pointer dereference, no memory corruption or code execution demonstrated.
|
| 146 |
+
|
| 147 |
+
## Suggested Remediation
|
| 148 |
+
|
| 149 |
+
```cpp
|
| 150 |
+
static Result<FreeableBuffer> GetProcessedData(
|
| 151 |
+
const executorch_flatbuffer::BackendDelegate& delegate,
|
| 152 |
+
const Program* program) {
|
| 153 |
+
const executorch_flatbuffer::BackendDelegateDataReference* processed =
|
| 154 |
+
delegate.processed();
|
| 155 |
+
ET_CHECK_OR_RETURN_ERROR(
|
| 156 |
+
processed != nullptr,
|
| 157 |
+
InvalidProgram,
|
| 158 |
+
"Missing processed field for backend delegate %s",
|
| 159 |
+
delegate.id() != nullptr ? delegate.id()->c_str() : "<unknown>");
|
| 160 |
+
switch (processed->location()) {
|
| 161 |
+
...
|
| 162 |
+
```
|
| 163 |
+
|
| 164 |
+
**Design recommendation:** consider validating at `Program::load()` time (in `validate_program()`, `runtime/executor/program_validation.cpp`) that every `ExecutionPlan.delegates[]` entry has a non-null `processed` field β catching this, and potentially the sibling `compile_specs` null-key/value issues found in this same investigation, in one centralized pass rather than at each individual accessor deep in `BackendDelegate::Init()`'s call chain.
|
| 165 |
+
|
| 166 |
+
A regression test should build a `.pte` with one `BackendDelegate` entry (`id` matching a registered fake backend) that omits the `processed` field, asserting `Method::load()` returns a clean `Error::InvalidProgram` rather than crashing.
|
| 167 |
+
|
| 168 |
+
## Files Included in This Report
|
| 169 |
+
|
| 170 |
+
- `poc/poc_backend_delegate_processed_null.pte` β the 248-byte PoC file (sha256 `0377b7f4078dda503e5f2ae3e8b69f8332081f065964a45098fb0d95bba4b36`)
|
| 171 |
+
- `poc/harness_execute_fuzzer.cpp` β the harness used to trigger and reproduce the crash, including fake backend registration for exercising `BackendDelegate::Init()`
|
| 172 |
+
|
| 173 |
+
## huntr Submission Note
|
| 174 |
+
|
| 175 |
+
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_processed_null.pte` is ready for that upload.
|