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.

YAML Metadata Warning:empty or missing yaml metadata in repo card

Check out the documentation for more information.

ExecuTorch Vulkan delegate: unchecked VkTensor.constant_id vector index β†’ OOB read at delegate init

PRIVATE security research artifact. Vulnerability packaged for coordinated disclosure via huntr. Do not redistribute.

Summary

Loading a malicious .pte model whose delegate resolves to the Vulkan backend triggers an out-of-bounds heap read (and, with larger indices, a wild read / SEGV) during delegate initialization β€” before any GPU work and after the FlatBuffer structural verifier passes. A constant tensor's index is taken directly from the attacker-controlled FlatBuffer field VkTensor.constant_id and used as a flatbuffers::Vector::Get() index with no bounds check against constants()->size().

  • Target: pytorch/executorch
  • Version / commit: v1.4.0a0 @ a6d812a082df57898b8608f56c867140cc9da32c (2026-07-10)
  • Component: backends/vulkan/runtime/VulkanBackend.cpp β€” GraphBuilder::add_tensor_to_graph()
  • Vulnerability class: CWE-125 (Out-of-bounds Read) via unvalidated array index (CWE-129)
  • Trigger: untrusted model file (.pte), no user interaction beyond loading the model
  • Impact: OOB read of process memory β†’ crash / DoS; potential info-disclosure of heap contents reachable through the fabricated VkBytes* table (offset, named_key).

Root cause

schema.fbs defines the index as a plain signed scalar:

// backends/vulkan/serialization/schema.fbs
table VkTensor {            // line 52
  ...
  constant_id:int;         // line 58  (int32, attacker-controlled)
}
...
  constants:[VkBytes];     // line 147 (optional vector)

GraphBuilder::add_tensor_to_graph() uses that field directly as a vector index:

// backends/vulkan/runtime/VulkanBackend.cpp  (lines ~285-287)
ValueRef ref;
if (tensor_fb->constant_id() >= 0) {
  VkBytesPtr constant_bytes =
      flatbuffer_->constants()->Get(tensor_fb->constant_id());   // <-- OOB: no i < size() check

  if (constant_bytes->named_key() != nullptr &&                  // first deref of bogus pointer
      constant_bytes->offset() == UINT64_MAX && ...) { ... }

The only guard is constant_id() >= 0. There is no check that constant_id < constants()->size(). flatbuffers::Vector::Get() guards only with FLATBUFFERS_ASSERT(i < size()) β€” a plain assert that is compiled out under -DNDEBUG, which is the Release/wheel build configuration. Under -DNDEBUG, IndirectHelper::Read computes Data() + constant_id * 4 and reads a 4-byte offset far outside the buffer, then the code dereferences the resulting bogus VkBytes* (constant_bytes->named_key() / ->offset()).

Why the verifier does not stop it

compileModel() runs vkgraph::VerifyVkGraphBuffer(verifier) (VulkanBackend.cpp:657) before builder.build_graph() (line 671). The FlatBuffer structural verifier only validates buffer structure/offsets β€” it does not validate scalar table fields used as indices. So a buffer with constant_id = 40 (or 0x02000000) passes verification cleanly, and the OOB happens post-verify.

Adjacent hardening does not cover this

Lines ~304-329 bound-check the constant DATA region extent (offset < constant_data_size_, tensor byte-extent vs. constant_data_size_). That hardening operates on constant_bytes after it has already been fetched via the unchecked Get() β€” it protects the data payload, not the vector index itself.

Secondary variant (null deref)

constants is an optional vector. If constant_id >= 0 but the FlatBuffer omits the constants vector, flatbuffer_->constants() is nullptr and ->Get(...) is a null-pointer dereference.

Reachability / PoC

Reachable purely by loading a malicious .pte: init() β†’ compileModel() β†’ (verifier passes) β†’ build_graph() β†’ add_tensor_to_graph() β†’ constants()->Get(constant_id) β€” all before any GPU dispatch.

The PoC builds a valid 30-byte VulkanDelegateHeader wrapping a vk_graph FlatBuffer containing:

  • one VkValue = VkTensor(datatype=FLOAT32, dims=[1], constant_id=N, mem_obj_id=-1)
  • a 1-entry constants vector

Verification harness (exercises the REAL target code)

A faithful harness compiles and links the real target code and replicates VulkanBackend.cpp:287 byte-for-byte:

  • Real backends/vulkan/runtime/VulkanDelegateHeader.cpp β€” header parse
  • Real flatc-generated schema_generated.h β€” VkGraphBufferHasIdentifier + VerifyVkGraphBuffer + GetVkGraph
  • Real bundled flatbuffers library (third-party/flatbuffers) β€” Vector::Get / IndirectHelper::Read / ReadScalar

Only the GraphBuilder wrapper method is replicated inline (it otherwise pulls in the Vulkan GPU API / ComputeGraph); the vulnerable expression constants()->Get(tensor_fb->constant_id()) is identical.

Artifacts: harness.cpp, stubs.cpp, gen/schema_generated.h, harness (Debug), harness_ndebug (Release / -DNDEBUG + ASan).

Captured evidence (verbatim)

Malicious constant_id=40 β€” Release build (-DNDEBUG), ASan heap-buffer-overflow READ

[*] mode=MALICIOUS  processed blob = 162 bytes, flatbuffer = 132 bytes, constant_id=40
[*] header OK: fb_off=30 fb_size=132 bytes_off=162
[*] VerifyVkGraphBuffer PASSED (verifier does NOT validate constant_id as an index)
[*] values=1 constants=1
[*] about to index constants()->Get(40)  (constants size=1)
=================================================================
==311346==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c48a53e012a at pc 0x5562b59d9f05 bp 0x7ffc45968e50 sp 0x7ffc45968e48
READ of size 4 at 0x7c48a53e012a thread T0
    #0 ... in unsigned int flatbuffers::ReadScalar<unsigned int>(void const*) .../flatbuffers/base.h:428:23
    #1 ... in flatbuffers::IndirectHelper<flatbuffers::Offset<vkgraph::VkBytes>>::Read(unsigned char const*, unsigned int) .../flatbuffers/buffer.h:123:27
    #2 ... in flatbuffers::Vector<flatbuffers::Offset<vkgraph::VkBytes>, unsigned int>::Get(unsigned int) const .../flatbuffers/vector.h:177:12
    #3 ... in main .../vulkan_bug/harness.cpp:159:59   (== VulkanBackend.cpp:287 constants()->Get(constant_id))

0x7c48a53e012a is located 72 bytes after 162-byte region [0x7c48a53e0040,0x7c48a53e00e2)
SUMMARY: AddressSanitizer: heap-buffer-overflow .../flatbuffers/base.h:428:23 in unsigned int flatbuffers::ReadScalar<unsigned int>(void const*)

Wild-read variant constant_id=0x02000000 (~128 MB past buffer) β€” Release, ASan SEGV

[*] about to index constants()->Get(33554432)  (constants size=1)
==311501==ERROR: AddressSanitizer: SEGV on unknown address 0x7cd8573e008a (pc 0x55b96dd2a612 ...)
SUMMARY: AddressSanitizer: SEGV .../flatbuffers/base.h:428:23 in unsigned int flatbuffers::ReadScalar<unsigned int>(void const*)

Debug build β€” assert(i < size()) abort (DoS)

[*] about to index constants()->Get(33554432)  (constants size=1)
harness: .../third-party/flatbuffers/include/flatbuffers/vector.h:176: ...
  Vector<...>::Get(SizeT) const [...]: Assertion `i < size()' failed.

Negative control β€” benign constant_id=0 (valid index) β€” clean, both builds

[*] mode=benign  processed blob = 158 bytes, flatbuffer = 128 bytes, constant_id=0
[*] header OK: fb_off=30 fb_size=128 bytes_off=158
[*] VerifyVkGraphBuffer PASSED (verifier does NOT validate constant_id as an index)
[*] values=1 constants=1
[*] about to index constants()->Get(0)  (constants size=1)
[*] constant_bytes->offset() = 0 (no crash)
[*] done, no crash
exit=0

The verifier PASSED line prints in every malicious run, proving the flaw is post-verification.

Suggested fix

Bounds-check the index before use, and guard the optional vector:

if (tensor_fb->constant_id() >= 0) {
  const auto* constants = flatbuffer_->constants();
  VK_CHECK_COND(constants != nullptr, "VkTensor.constant_id set but no constants vector");
  VK_CHECK_COND(
      static_cast<uint32_t>(tensor_fb->constant_id()) < constants->size(),
      "VkTensor.constant_id %d out of range (constants size %u)",
      tensor_fb->constant_id(), constants->size());
  VkBytesPtr constant_bytes = constants->Get(tensor_fb->constant_id());
  ...

Dedup note

  • No published CVE or GitHub advisory currently covers VkTensor.constant_id vector-index validation in the ExecuTorch Vulkan backend as of the target commit.
  • Distinct from the existing constant-data-region extent hardening at VulkanBackend.cpp:304-329 (bounds the payload, not the vector index).
  • Distinct from prior ExecuTorch findings in this workspace (WebGPU delegate header OOB, XNNPACK const-data/weight OOB, minverify OOB, compile-spec / delegate-data null-deref, flat_tensor null-deref) β€” different backend, different field, different code path.
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