You need to agree to share your contact information to access this model

This repository is publicly accessible, but you have to accept the conditions to access its files and content.

Log in or Sign Up to review the conditions and access this model content.

ExecuTorch WebGPU delegate header: missing length/bounds validation (memory-unsafe .pte model load)

Status: gated, access on request (huntr / ProtectAI triage). Do not use against production systems you do not own.

Summary

WebGPUDelegateHeader::parse() in backends/webgpu/runtime/WebGPUDelegateHeader.cpp (pytorch/executorch, commit fa49f8ff28983fb820ca86a31cc0fbd2257c1c5b, main branch) parses the 30-byte binary header that precedes the WebGPU delegate's flatbuffer + constant-data blob inside a .pte model file's delegate "processed" data segment.

Unlike its sibling VulkanDelegateHeader::parse(data, buffer_size) (same repo, backends/vulkan/runtime/VulkanDelegateHeader.cpp), the WebGPU version:

  1. Takes no buffer_size parameter at all β€” parse(const void* data) β€” so it can never check that the buffer it was handed is at least kExpectedSize (30) bytes before reading the magic and the five header fields at fixed offsets.
  2. is_valid() only cross-checks the header fields against each other (flatbuffer_offset >= header_size, bytes_offset >= flatbuffer_offset + flatbuffer_size, …). It never checks them against the real buffer length, because parse() was never given that length. The Vulkan sibling has an explicit extra check for exactly this ("Validate that header offsets do not extend beyond the buffer").

The sole caller, WebGPUBackend::init() (backends/webgpu/runtime/WebGPUBackend.cpp), calls WebGPUDelegateHeader::parse(processed->data()) β€” again passing no size β€” and then unconditionally computes buffer_start + header->flatbuffer_offset and buffer_start + header->bytes_offset and dereferences them. Unlike VulkanBackend::compileModel() (the analogous Vulkan code path), it also never runs the flatbuffer through a flatbuffers::Verifier before use β€” it goes straight from the unchecked offset to vkgraph::VkGraphBufferHasIdentifier(flatbuffer_data) and graph->build(flatbuffer_data, ...). The WebGPU backend appears to be an incomplete port of the Vulkan delegate (same vkgraph/VH00 on-disk format) that dropped the buffer-size plumbing and the FlatBuffer verification step along the way.

processed is the delegate's serialized blob taken straight from the .pte file (BackendDelegate::Init() β†’ method.cpp:GetProcessedData()), so every byte of it, including its length, is attacker-controlled. BackendDelegate::Init() runs for every delegate listed in a program's execution plan simply while loading the model (Method::init), before any tensor is ever executed and before any real WebGPU/GPU context is required.

This gives two distinct, independently reachable memory-safety bugs from a single missing check, both triggerable just by loading a crafted .pte file that names a WebGPU/Vulkan-schema delegate:

  • Root cause A β€” no minimum-length check in parse(). Any delegate blob shorter than 30 bytes makes parse() read past the end of the buffer while checking the magic (memcmp) or decoding the header fields (getUInt32LE/getUInt16LE/getUInt64LE). A 0-byte blob SEGVs; a 4-byte blob heap-buffer-overflows.
  • Root cause B β€” no offset/size validation against the real buffer. A header that is internally consistent (passes is_valid()) but whose flatbuffer_offset / bytes_offset point far past the end of the actual (short) buffer sails through parse() as Error::Ok. The caller then dereferences buffer_start + header->bytes_offset, an arbitrary, attacker-chosen offset, causing an out-of-bounds read (SEGV or heap-buffer-overflow depending on offset magnitude).

Both were reproduced with ASan+UBSan on the real, unmodified source and independently rediscovered by an AFL++ persistent-mode campaign (~2.2M execs, ~4 min to full saturation of a 44/278-edge target β€” the header parser is small, so AFL exhausts its state space almost instantly; no new crash classes appeared after the first ~2000 execs) driving the harness in fuzz_webgpu_header.cpp.

Impact

Loading an untrusted/downloaded .pte model file that specifies a WebGPU-schema delegate with a truncated or offset-crafted "processed" blob causes the ExecuTorch runtime to read out of bounds during model load (no inference required), landing on ASan as heap-buffer-overflow or SEGV. Depending on heap layout this is a crash (DoS) at minimum; an out-of-bounds read whose result is subsequently treated as a flatbuffer/ constant-data pointer is also a plausible route to information disclosure or further memory corruption once the "flatbuffer" bytes it dereferences are parsed by the vkgraph flatbuffer reader. This is exactly the "loading an ExecuTorch model file can cause the runtime to crash / undefined behavior" bug class ExecuTorch has fixed for cash before (see Dedup section) β€” just in a different module (WebGPU delegate header, not tensor-parser/prim-ops).

Dedup / prior-art check

  • pytorch/executorch GitHub issues/PRs: no hits for WebGPUDelegateHeader (searched 2026-07-06).
  • CVE-2025-54950 (critical OOB in "ExecuTorch models loading", fixed in commit b6b7a16df5e7852d976d8c34c8a7e9a1b6f7d005) is unrelated: that fix only touches kernels/prim_ops/{et_copy_index.cpp,et_view.cpp, register_prim_ops.cpp} (missing argument-count validation on primitive kernels), nothing in backends/webgpu or backends/vulkan.
  • The flat_tensor/tensor-accessor path (extension/flat_tensor, runtime/executor/tensor_parser*) was audited and reported in an earlier pass of this operation; this finding is in a structurally different module (delegate/backend metadata header parsing) with a different root cause (parser given no size vs. tensor bounds-check gap).
  • schema/extended_header.cpp (the outer .pte file header) and VulkanDelegateHeader.cpp were read as comparators; both correctly take and check a buffer_size. WebGPUDelegateHeader is the outlier.

Files

  • fuzz_webgpu_header.cpp β€” AFL++ persistent-mode harness. Compiles the real, unmodified WebGPUDelegateHeader.cpp and drives WebGPUDelegateHeader::parse() exactly the way WebGPUBackend::init() does (including dereferencing the returned offsets), on an attacker-sized buffer.
  • poc_A_truncated_header.bin β€” 4-byte input. Minimal repro for root cause A (no min-length check). ASan: heap-buffer-overflow in MemcmpInterceptorCommon from WebGPUDelegateHeader.cpp:78 (magic check) / WebGPUDelegateHeader.cpp:48-49 (getUInt32LE).
  • poc_B_oob_offset.bin β€” 34-byte input with a well-formed, internally consistent header whose bytes_offset is 0x7FFFFFFF. Minimal repro for root cause B. ASan: SEGV when the harness (mirroring WebGPUBackend::init) dereferences buffer_start + header->bytes_offset.
  • crashes/ β€” raw AFL++ crashing testcases from the campaign (5 files, reduce to the same 2 root causes above).

Build / repro

git clone --depth 1 https://github.com/pytorch/executorch.git
clang++ -std=c++17 -g -O1 -fsanitize=address,undefined -I. \
  fuzz_webgpu_header.cpp \
  executorch/backends/webgpu/runtime/WebGPUDelegateHeader.cpp \
  executorch/runtime/platform/log.cpp \
  executorch/runtime/platform/platform.cpp \
  executorch/runtime/platform/default/posix.cpp \
  executorch/runtime/platform/abort.cpp \
  -o fuzz_webgpu_header_plain

./fuzz_webgpu_header_plain poc_A_truncated_header.bin   # heap-buffer-overflow
./fuzz_webgpu_header_plain poc_B_oob_offset.bin         # SEGV

Suggested fix

Give WebGPUDelegateHeader::parse() the same signature and checks as VulkanDelegateHeader::parse(data, buffer_size): take buffer_size, reject inputs shorter than kExpectedSize before touching any bytes, and after is_valid() passes, verify flatbuffer_offset + flatbuffer_size <= buffer_size and bytes_offset + bytes_size <= buffer_size before returning. Update WebGPUBackend::init() to pass processed->size() through.

Downloads last month

-

Downloads are not tracked for this model. How to track
Inference Providers NEW
This model isn't deployed by any Inference Provider. πŸ™‹ Ask for provider support