# 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(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::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 ================================================================= ====ERROR: AddressSanitizer: heap-buffer-overflow on address 0x506000000128 at pc 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(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.