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:

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:

/* 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:

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

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():

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.