tsk-arasu's picture
Upload folder using huggingface_hub
a9480da verified
|
Raw
History Blame Contribute Delete
11.4 kB
# Heap-Buffer-Overflow Read in ExecuTorch FlatTensorDataMap::load() via Missing Flatbuffer Verifier (.ptd)
**Target:** ExecuTorch 1.3.1 (.pte / .ptd, huntr Model File Vulnerability program)
**Severity:** Medium (memory corruption / out-of-bounds read via malformed model file)
**CWE:** CWE-125 (Out-of-Bounds Read), CWE-20 (Improper Input Validation)
**Component:** `extension/flat_tensor/flat_tensor_data_map.cpp`
**Authentication Required:** No β€” the only requirement is that a victim application loads an attacker-supplied `.ptd` file.
## Summary
`FlatTensorDataMap::load()` is the sole entry point for parsing ExecuTorch's `.ptd` (FlatTensor external-data) format. Unlike its sibling `Program::load()` (which parses the analogous `.pte` format and explicitly runs a `flatbuffers::Verifier` over the buffer before trusting it), `FlatTensorDataMap::load()` performs only a magic-byte check, an alignment check, and two non-null checks on top-level fields β€” it never verifies that the flatbuffer's internal offsets and vtables actually stay within the loaded buffer. A 64-byte `.ptd` file with a corrupted root-table offset causes `flatbuffers::Table::GetPointer` to read 8 bytes past the end of the buffer's heap allocation, crashing the process. This is a genuine out-of-bounds read (not merely a null-pointer check gap), matching huntr's Model File Vulnerability program's explicitly-listed "vulnerabilities in model file parsing leading to memory corruption" category.
## Vulnerability Details
`runtime/executor/program.cpp`'s `Program::load()` β€” the `.pte` parser β€” does this correctly:
```cpp
if (verification == Verification::InternalConsistency) {
flatbuffers::Verifier verifier(
reinterpret_cast<const uint8_t*>(program_data->data()),
program_data->size());
bool ok = executorch_flatbuffer::VerifyProgramBuffer(verifier);
ET_CHECK_OR_RETURN_ERROR(ok, InvalidProgram, "Verification failed; ...");
...
```
`extension/flat_tensor/flat_tensor_data_map.cpp`'s `FlatTensorDataMap::load()` β€” the `.ptd` parser β€” has no equivalent:
```cpp
/* static */ Result<FlatTensorDataMap> FlatTensorDataMap::load(DataLoader* loader) {
// ... load header, check magic, check size ...
// Make sure magic matches.
if (!flat_tensor_flatbuffer::FlatTensorBufferHasIdentifier(flat_tensor_data->data())) {
return Error::InvalidExternalData;
}
// The flatbuffer data must start at an aligned address ...
ET_CHECK_OR_RETURN_ERROR(is_aligned(flat_tensor_data->data()), ...);
// Get pointer to root of flatbuffer table.
const flat_tensor_flatbuffer::FlatTensor* flat_tensor =
flat_tensor_flatbuffer::GetFlatTensor(flat_tensor_data->data()); // <-- no Verifier ran
// Validate flat_tensor.
ET_CHECK_OR_RETURN_ERROR(flat_tensor->named_data() != nullptr, ...); // <-- already dereferences untrusted offsets
ET_CHECK_OR_RETURN_ERROR(flat_tensor->segments() != nullptr, ...);
...
```
`GetFlatTensor()` and the subsequent `named_data()` / `segments()` accessor calls walk the flatbuffer's internal vtable and offset structures with **zero structural verification**. flatbuffers' own safety model depends entirely on a `Verifier` pass to guarantee that every offset/length embedded in the buffer stays within bounds before any accessor is trusted β€” `FlatTensorDataMap::load()` skips this pass entirely, unlike `Program::load()` in the same codebase.
## Steps to Reproduce
### Environment
Same sanitized ExecuTorch 1.3.1 build and harness as REPORT-01 (see that report's Steps 1–2, identical build commands and harness). This report reuses the exact same `poc/harness_flat_tensor_fuzzer.cpp`.
### 1. PoC file
`poc/poc_oob_read_missing_verifier.ptd` (64 bytes, **included in this report β€” sha256 `3d955721ff41ead7dc52eff62fcd3dc7fd0a355a197f263304385ca8fdee3dd3`**) was produced by coverage-guided fuzzing of the harness against the sanitized build, then reduced to a minimal reproducer with libFuzzer's built-in `-minimize_crash=1`. Full byte-level annotation (`poc/decode_poc.py`, included):
```
[0:4] root_offset (of the inner flatbuffer payload) = 72 (0x48)
[4:8] flatbuffer file_identifier = b'FT01'
[8:12] FlatTensorHeader magic = b'FH01'
[12:16] FlatTensorHeader.length = 40
[16:24] FlatTensorHeader.flatbuffer_offset = 48
[24:32] FlatTensorHeader.flatbuffer_size = 0
[32:40] FlatTensorHeader.segment_base_offset = 0
[40:48] FlatTensorHeader.segment_data_size = 0
[48:64] trailing bytes (unused padding/garbage) = 01000011111111110000010011111111
Loaded flatbuffer segment length = flatbuffer_offset + flatbuffer_size = 48 + 0 = 48 bytes
Root table offset stored in the file = 72
*** root_offset (72) EXCEEDS the loaded flatbuffer segment length (48) ***
```
The file passes every check `FlatTensorDataMap::load()` actually performs β€” magic bytes are correct (`FH01`/`FT01`), the outer header parses cleanly, the buffer is aligned, and the file is large enough. The **only** thing wrong with it is that the flatbuffer's own root-table offset (`72`) points past the 48 bytes of flatbuffer payload that were actually loaded β€” exactly the kind of corruption a `flatbuffers::Verifier` exists to catch, and exactly what `Program::load()` catches for `.pte` files but `FlatTensorDataMap::load()` does not catch for `.ptd` files.
Regenerate/verify with:
```bash
python3 poc/decode_poc.py poc/poc_oob_read_missing_verifier.ptd
sha256sum poc/poc_oob_read_missing_verifier.ptd
# 3d955721ff41ead7dc52eff62fcd3dc7fd0a355a197f263304385ca8fdee3dd3
```
### 2. Build the harness
Identical to REPORT-01 Step 2 (same `poc/harness_flat_tensor_fuzzer.cpp`, same build commands).
### 3. 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_oob_read_missing_verifier.ptd
```
### Expected result (secure behavior)
`FlatTensorDataMap::load()` should reject this file with a clean `Error::InvalidExternalData` (or similar), since the flatbuffer's root offset is provably inconsistent with the loaded segment size.
### Actual result
```
Running: poc/poc_oob_read_missing_verifier.ptd
=================================================================
==<pid>==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000128 at pc <addr>
READ of size 4 at 0x506000000128 thread T0
#0 flatbuffers::Table::GetPointer<...>(unsigned short) [flatbuffers/table.h:43,61]
#1 (inlined caller within FlatTensorDataMap::load / named_data())
#2 LLVMFuzzerTestOneInput
0x506000000128 is located 8 bytes after a 64-byte region [0x5060000000e0,0x506000000120)
allocated by thread T0 here:
#0 ... malloc ...
#1 ... (buffer allocation for the loaded flatbuffer segment)
SUMMARY: AddressSanitizer: heap-buffer-overflow
```
The process crashes with a heap-buffer-overflow **READ of size 4, exactly 8 bytes past the end of a 64-byte heap allocation**, inside `flatbuffers::Table::GetPointer` β€” called while `FlatTensorDataMap::load()` resolves the `named_data` field on the corrupted root table.
**Reproduced 3/3 identical runs** (re-verified live for this report):
```
run 1: ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000128
run 2: ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000128
run 3: ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000128
```
## Impact
**Who is affected:** Any application or deployment pipeline that embeds ExecuTorch's `extension/flat_tensor` component and calls `FlatTensorDataMap::load()` on an externally-supplied `.ptd` file β€” the single public entry point for parsing this format. This crash occurs during `load()` itself, before any per-key API is even reached, so **every** consumer of this format is affected, not just ones that call specific follow-up methods.
**What the attacker can do:** Cause a deterministic, repeatable out-of-bounds heap read (crash under ASan; undefined behavior in an unsanitized build β€” typically a `SIGSEGV`, or in principle a read of adjacent heap content that flows into further pointer/offset arithmetic) simply by supplying a 64-byte malformed `.ptd` file. The demonstrated consequence is a crash. This report does **not** claim arbitrary code execution or a proven information-disclosure chain β€” no secondary PoC beyond the OOB read itself was built, and claiming more than what was demonstrated would be overclaiming.
**What's at risk:** Availability of the process loading the file, confirmed. Confidentiality is a *theoretical* secondary risk only (the OOB-read byte value could in principle influence later behavior in an observable way) β€” not demonstrated in this report.
**Exploitation complexity:** No interaction beyond the victim loading the file. Purely a file-format parsing bug.
**Why Medium, not Critical:** This is a genuine memory-safety defect (distinguishing it from the null-pointer-dereference class of findings in this codebase), but only a crash was proven β€” no controlled write, no demonstrated information leak, no code execution. Per huntr's own severity framing, this sits in "memory corruption via malformed model file," a real and impactful class, but below the bar for Critical/ACE without a further chained PoC.
## Suggested Remediation
In `FlatTensorDataMap::load()`, after loading the flatbuffer payload and before calling `GetFlatTensor()` / `named_data()` / `segments()`, construct a `flatbuffers::Verifier` over the loaded buffer and call the schema-generated `VerifyFlatTensorBuffer()`, returning `Error::InvalidExternalData` on failure β€” mirroring the pattern already correctly implemented in `runtime/executor/program.cpp`'s `Program::load()`:
```cpp
flatbuffers::Verifier verifier(
reinterpret_cast<const uint8_t*>(flat_tensor_data->data()),
flat_tensor_data->size());
ET_CHECK_OR_RETURN_ERROR(
flat_tensor_flatbuffer::VerifyFlatTensorBuffer(verifier),
InvalidExternalData,
"FlatTensor buffer failed verification; data may be truncated or corrupt.");
```
Given that `.ptd` files are, by this program's own threat model, expected to potentially come from untrusted sources (external weight files), this verification should be unconditional rather than opt-in, unlike `.pte`'s `Verification` enum which allows skipping verification for co-packaged, presumably-trusted data.
A regression test should load a truncated/corrupted `.ptd` flatbuffer payload (valid outer header, corrupted root offset as in this PoC) and assert `FlatTensorDataMap::load()` returns a clean `Error` rather than crashing under ASan.
## Files Included in This Report
- `poc/poc_oob_read_missing_verifier.ptd` β€” the 64-byte PoC file (sha256 `3d955721ff41ead7dc52eff62fcd3dc7fd0a355a197f263304385ca8fdee3dd3`)
- `poc/decode_poc.py` β€” byte-level annotation script, shows exactly which field is corrupted and why
- `poc/harness_flat_tensor_fuzzer.cpp` β€” the harness used to trigger and reproduce the crash (identical to REPORT-01's harness β€” same binary can reproduce both PoCs)
## 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_oob_read_missing_verifier.ptd` is ready for that upload.