tsk-arasu's picture
Upload folder using huggingface_hub
b009429 verified
|
Raw
History Blame Contribute Delete
7.65 kB
# 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.