Skip to content

Xnnpack flatbuffer: check non-null and num_dims matches array size in defineTensor#18896

Merged
lucylq merged 1 commit intomainfrom
security39-42
Apr 21, 2026
Merged

Xnnpack flatbuffer: check non-null and num_dims matches array size in defineTensor#18896
lucylq merged 1 commit intomainfrom
security39-42

Conversation

@lucylq
Copy link
Copy Markdown
Contributor

@lucylq lucylq commented Apr 14, 2026

Validate that dims array is non-null and num_dims matches the actual array size in defineTensor to prevent heap buffer overflows.

Authored-with: Claude

@pytorch-bot
Copy link
Copy Markdown

pytorch-bot Bot commented Apr 14, 2026

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/18896

Note: Links to docs will display an error until the docs builds have been completed.

⏳ No Failures, 11 Pending

As of commit 8d6ad8a with merge base 799bf5a (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Apr 14, 2026
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 14, 2026

@claude review

@github-actions
Copy link
Copy Markdown

This PR needs a release notes: label

If your change should be included in the release notes (i.e. would users of this library care about this change?), please use a label starting with release notes:. This helps us keep track and include your important work in the next release notes.

To add a label, you can comment to pytorchbot, for example
@pytorchbot label "release notes: none"

For more information, see
https://github.com/pytorch/pytorch/wiki/PyTorch-AutoLabel-Bot#why-categorize-for-release-notes-and-how-does-it-work.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 14, 2026

Claude finished @lucylq's task in 1m 33s —— View job


Review of PR #18896

  • Read PR diff and changed files
  • Analyze changes for correctness, security, and style
  • Post review feedback

This PR adds input validation to prevent heap buffer overflows (TOB-EXECUTORCH-39, -42) from malicious flatbuffer payloads. The approach is sound — validating at deserialization time before passing untrusted dimension values to XNNPACK APIs. Here's a detailed review:


Looks Good

Null check in flatbufferDimsToVector (XNNCompiler.cpp:166-169): Converting the function to return Result<> and adding the null check is the right pattern. All 7 call sites have been properly updated to propagate errors.

num_dims vs dims()->size() consistency check (XNNCompiler.cpp:306-311): Validating that the declared num_dims matches the actual flatbuffer array size prevents a mismatch that could lead to out-of-bounds reads downstream.

Combined null check in defineTensor (XNNCompiler.cpp:301-304): Checking both tensor_value and tensor_value->dims() in one guard is cleaner and prevents null dereference on dims().

Error propagation at all call sites: All callers (defineStaticTransposeNode, defineStaticConstantPadNode, defineStaticReshapeNode, defineStaticSliceNode) are correctly updated with the Result unwrapping pattern using std::move.


Issues

1. kMaxDimensionValue validation applies to non-dimension values (minor concern)

flatbufferDimsToVector is used for permutation indices (perm()), padding values (pre_paddings(), post_paddings()), offsets, and sizes — not just tensor dimensions. The 16M limit is generous enough that it won't cause false positives for these use cases, but the function name and the error message both say "Dimension value" which could be confusing when diagnosing errors from, say, a bad perm() array. This is a minor documentation/naming nit, not a blocker.

2. 1 << 24 is a signed shift (XNNCompiler.cpp:161)

constexpr uint32_t kMaxDimensionValue = 1 << 24; // 16M per dimension

The literal 1 is int, so 1 << 24 is a signed int shift. This works correctly here (the value 16,777,216 fits in a 32-bit signed int), but for consistency and to avoid any linter/compiler warnings, consider using 1u << 24 or uint32_t{1} << 24. Fix this →

3. No total-elements overflow check (suggestion, not a blocker)

The per-dimension cap of 16M prevents any single dimension from being absurdly large, but a tensor with dimensions [16M, 16M, 16M] would still represent an astronomical number of elements. XNNPACK itself may reject such tensors downstream, but an explicit total-element-count check (e.g., ensuring the product of all dimensions doesn't exceed some limit) would be a stronger defense. This is a hardening suggestion for future work, not a requirement for this PR.


Summary

The PR is a clear security improvement. It correctly validates deserialized flatbuffer tensor data before it reaches XNNPACK APIs, addressing heap buffer overflow risks from crafted inputs. The Result<> return type change is clean and all call sites are updated. The signed shift literal is the only concrete fix I'd suggest before merging.

@lucylq lucylq marked this pull request as ready for review April 14, 2026 23:14
@lucylq lucylq requested a review from digantdesai as a code owner April 14, 2026 23:14
Copilot AI review requested due to automatic review settings April 14, 2026 23:14
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 14, 2026

addressed claude's comments

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the XNNPACK runtime flatbuffer deserialization path by validating shape vectors before passing raw pointers into XNNPACK APIs, aiming to prevent heap buffer overflows and unbounded allocations from malformed or malicious programs.

Changes:

  • Change flatbufferDimsToVector to return Result<std::vector<T>>, add NULL-checking, and validate each dimension value against a max limit.
  • Add validation in defineTensor to ensure dims exist and num_dims matches the flatbuffer vector length, and propagate Result errors to callers.
  • Update several node-definition helpers (transpose/reshape/slice/pad) to handle Result from flatbufferDimsToVector.
Comments suppressed due to low confidence (3)

backends/xnnpack/runtime/XNNCompiler.cpp:1160

  • xnn_define_static_reshape is called with graph_node->num_dims() but new_shape()->size() is not validated against num_dims. A malformed flatbuffer could cause XNNPACK to read past dims_data.
    Please add new_shape != nullptr and new_shape->size() == num_dims validation (and a reasonable num_dims cap) before the call.
  auto graph_node = node->xnode_union_as_XNNStaticReshape();

  // Get tensor dims, we need to convert the uint32_t* to size_t*
  auto dims_result = flatbufferDimsToVector(graph_node->new_shape());
  if (!dims_result.ok()) {
    return dims_result.error();
  }
  std::vector<size_t> dims_data = std::move(dims_result.get());

  xnn_status status = xnn_define_static_reshape(
      subgraph_ptr,
      graph_node->num_dims(),
      dims_data.data(),
      remapped_ids.at(graph_node->input_id()),

backends/xnnpack/runtime/XNNCompiler.cpp:1465

  • xnn_define_static_slice is called with graph_node->num_dims() but there is no validation that offsets()->size() and sizes()->size() both equal num_dims. If either vector is shorter, XNNPACK will read past offsets/sizes.
    Add checks for non-null vectors, matching sizes, and a reasonable num_dims cap before calling XNNPACK.
  auto graph_node = node->xnode_union_as_XNNStaticSlice();

  auto offsets_result = flatbufferDimsToVector(graph_node->offsets());
  if (!offsets_result.ok()) {
    return offsets_result.error();
  }
  std::vector<size_t> offsets = std::move(offsets_result.get());
  auto sizes_result = flatbufferDimsToVector(graph_node->sizes());
  if (!sizes_result.ok()) {
    return sizes_result.error();
  }
  std::vector<size_t> sizes = std::move(sizes_result.get());

  xnn_status status = xnn_define_static_slice(
      subgraph_ptr,
      graph_node->num_dims(),
      offsets.data(),
      sizes.data(),
      remapped_ids.at(graph_node->input_id()),

backends/xnnpack/runtime/XNNCompiler.cpp:1004

  • xnn_define_static_transpose is called with graph_node->num_dims() but there is no validation that perm()->size() matches num_dims. If num_dims is larger than the perm vector length, XNNPACK will read past dims_data.
    Add a check that perm != nullptr and perm->size() == num_dims (and ideally num_dims <= XNN_MAX_TENSOR_DIMS) before calling into XNNPACK.
  auto graph_node = node->xnode_union_as_XNNStaticTranspose();

  // Get tensor dims, we need to convert the uint32_t* to size_t*
  auto dims_result = flatbufferDimsToVector(graph_node->perm());
  if (!dims_result.ok()) {
    return dims_result.error();
  }
  std::vector<size_t> dims_data = std::move(dims_result.get());

  xnn_status status = xnn_define_static_transpose(
      subgraph_ptr,
      graph_node->num_dims(),
      dims_data.data(),
      remapped_ids.at(graph_node->input_id()),

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backends/xnnpack/runtime/XNNCompiler.cpp Outdated
Comment on lines +154 to +159
Converts dims from uint32 to size_t. Takes in a flatbuffer vector
of uint32_t and returns a std::vector of size_t. XNNPACK takes in
dims of size_t* but tensor shape is serialized in flatbuffer as
int32_t. As a result, we need to static cast the shapes to size_t
int32_t. As a result, we need to static cast the shapes to size_t.
Individual dimension values are validated to prevent unbounded memory
allocation from malicious inputs.
Comment on lines 166 to 172
ET_CHECK_OR_RETURN_ERROR(
fb_dims != nullptr,
InvalidProgram,
"flatbufferDimsToVector: dims vector is null");
std::vector<T> dims_data;
dims_data.reserve(fb_dims->size());
for (auto fb_dim : *fb_dims) {
Comment thread backends/xnnpack/runtime/XNNCompiler.cpp
Comment thread backends/xnnpack/runtime/XNNCompiler.cpp Outdated
Comment thread backends/xnnpack/runtime/XNNCompiler.cpp Outdated
@GregoryComer
Copy link
Copy Markdown
Member

Clamping max elements at 16 million makes me a little bit nervous. Flattening large tensors isn't uncommon, and if a graph break happened in between the flatten call and whatever uses it, it could break existing PTEs.

How critical is this check?

Copilot AI review requested due to automatic review settings April 15, 2026 18:56
@lucylq lucylq force-pushed the security39-42 branch 2 times, most recently from 4a24c25 to b5faf2f Compare April 15, 2026 19:01
@lucylq lucylq changed the title Validate tensor dimensions in xnnpack flatbuffer Xnnpack flatbuffer: check non-null and num_dims matches array size in defineTensor Apr 15, 2026
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden XNNPACK flatbuffer deserialization in XNNCompiler by validating tensor dimension metadata and making flatbufferDimsToVector return a Result<> to propagate validation failures.

Changes:

  • Changed flatbufferDimsToVector to return Result<std::vector<T>> and added a null-check for the incoming flatbuffer vector.
  • Added validation in defineTensor that dims is non-null and that num_dims matches dims()->size().
  • Updated multiple node builders to handle flatbufferDimsToVector failures and return the corresponding Error.
Comments suppressed due to low confidence (4)

backends/xnnpack/runtime/XNNCompiler.cpp:171

  • flatbufferDimsToVector currently only null-checks and casts dimensions. The PR description mentions per-dimension validation (e.g., enforcing a 16M max) to prevent unbounded allocations/overflows from malicious dimension values, but that validation isn't implemented here. Add bounds checks (and ideally a safe product/overflow check where total element count is derived) before pushing dims into the vector.
std::vector<T> flatbufferDimsToVector(
    const flatbuffers::Vector<uint32_t>* fb_dims) {
  std::vector<T> dims_data;
  dims_data.reserve(fb_dims->size());
  for (auto fb_dim : *fb_dims) {
    dims_data.push_back(static_cast<T>(fb_dim));
  }
  return dims_data;
}

/**
Gets the constant data pointer associated with the given tensor value.

backends/xnnpack/runtime/XNNCompiler.cpp:996

  • xnn_define_static_transpose consumes num_dims elements from perm, but the code doesn't validate that graph_node->perm()->size() matches graph_node->num_dims(). A malformed flatbuffer could set num_dims larger than the perm vector, leading to out-of-bounds reads. Add a check that the converted dims_data.size() equals graph_node->num_dims() (and handle NULL perm appropriately).
      graph_node->num_dims(),
      dims_data.data(),
      remapped_ids.at(graph_node->input_id()),
      remapped_ids.at(graph_node->output_id()),
      graph_node->flags());
  ET_CHECK_OR_RETURN_ERROR(
      status == xnn_status_success,
      Internal,
      "Failed to create static transpose node %i with code: %s",
      node->debug_handle(),
      xnn_status_to_string(status));

  return Error::Ok;
}

backends/xnnpack/runtime/XNNCompiler.cpp:1152

  • xnn_define_static_reshape uses graph_node->num_dims() to read from new_shape, but there is no validation that new_shape()->size() equals num_dims. If num_dims is larger than the vector, this can cause out-of-bounds reads. Add a check that dims_data.size() == graph_node->num_dims() before calling into XNNPACK.
      dims_data.data(),
      remapped_ids.at(graph_node->input_id()),
      remapped_ids.at(graph_node->output_id()),
      graph_node->flags());
  ET_CHECK_OR_RETURN_ERROR(
      status == xnn_status_success,
      Internal,
      "Failed to create squeeze node %i with code: %s",
      node->debug_handle(),
      xnn_status_to_string(status));

  return Error::Ok;
}

backends/xnnpack/runtime/XNNCompiler.cpp:1457

  • xnn_define_static_slice reads num_dims elements from both offsets and sizes, but the code does not validate that offsets()->size() and sizes()->size() match graph_node->num_dims(). A malformed flatbuffer could set num_dims larger than these vectors and trigger out-of-bounds reads. Add checks that both converted vectors have length num_dims before calling XNNPACK.
      offsets.data(),
      sizes.data(),
      remapped_ids.at(graph_node->input_id()),
      remapped_ids.at(graph_node->output_id()),
      graph_node->flags());

  ET_CHECK_OR_RETURN_ERROR(
      status == xnn_status_success,
      Internal,
      "Failed to create static slice node %i with code: %s",
      node->debug_handle(),
      xnn_status_to_string(status));

  return Error::Ok;
}

/*
Defines batch matrix multiply node into the subgraph,
using the remapped ids to map the serialized ids,

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backends/xnnpack/runtime/XNNCompiler.cpp Outdated
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 16, 2026

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented Apr 16, 2026

Claude finished @lucylq's task in 1m 39s —— View job


Review of PR #18896 (re-review after updates)

  • Read current diff and changed files
  • Analyze changes for correctness, security, and style
  • Address GregoryComer's concern about 16M limit
  • Post review feedback

This re-review covers the current state after lucylq addressed previous feedback (the kMaxDimensionValue / Result<> return type changes have been removed, simplifying the PR significantly).


What Changed (current diff)

The PR now makes two categories of changes:

  1. defineTensor validation (XNNCompiler.cpp:287-297): Validates that tensor_value and tensor_value->dims() are non-null, and that num_dims matches dims()->size().

  2. Null checks before flatbufferDimsToVector calls in four node builders: defineStaticTransposeNode (perm), defineStaticConstantPadNode (pre/post paddings), defineStaticReshapeNode (new_shape), defineStaticSliceNode (offsets/sizes).


Issues

1. Scalar tensor rejection (bug)
XNNCompiler.cpp:287-290:

ET_CHECK_OR_RETURN_ERROR(
    tensor_value != nullptr && tensor_value->dims() != nullptr,
    InvalidProgram,
    "Deserialized tensor is null, or tensor dims is null");

The flatbuffer schema explicitly states: "If num_dims is 0, this pointer can be NULL." This check rejects scalar tensors (0-dimensional) where dims() is legitimately nullptr and num_dims == 0. Both Copilot reviews flagged this as well.

Fix: allow dims() == nullptr when num_dims == 0:

ET_CHECK_OR_RETURN_ERROR(
    tensor_value != nullptr,
    InvalidProgram,
    "Deserialized tensor is null");

ET_CHECK_OR_RETURN_ERROR(
    tensor_value->num_dims() == 0 || tensor_value->dims() != nullptr,
    InvalidProgram,
    "Tensor dims is null but num_dims is %u",
    tensor_value->num_dims());

And guard the num_dims == dims()->size() check accordingly:

if (tensor_value->dims() != nullptr) {
  ET_CHECK_OR_RETURN_ERROR(
      tensor_value->num_dims() == tensor_value->dims()->size(),
      InvalidProgram,
      "Tensor num_dims %u does not match dims array size %u",
      tensor_value->num_dims(),
      tensor_value->dims()->size());
}

Fix this →

2. num_dims vs array size mismatch in node builders (the core vulnerability remains partially unpatched)

The num_dims vs dims()->size() check in defineTensor is good, but the same class of vulnerability exists in the node builders where num_dims is passed to XNNPACK separately from the data arrays. The null checks are necessary but not sufficient — if num_dims exceeds the array size, XNNPACK will read out of bounds:

  • defineStaticTransposeNode (XNNCompiler.cpp:981-984): graph_node->num_dims() is passed to xnn_define_static_transpose but not validated against dims_data.size().
  • defineStaticReshapeNode (XNNCompiler.cpp:1136-1139): Same issue with xnn_define_static_reshape.
  • defineStaticSliceNode (XNNCompiler.cpp:1436-1440): graph_node->num_dims() vs offsets.size() and sizes.size().

Adding dims_data.size() == graph_node->num_dims() checks (or just passing dims_data.size() instead of graph_node->num_dims()) at these call sites would close these gaps.


Re: GregoryComer's concern about 16M limit

The per-dimension kMaxDimensionValue cap has been removed in the updated diff, so this concern no longer applies. The PR now only does structural validation (null checks and size consistency), which is the right approach — these checks can't reject valid programs.


Looks Good

  • Error type change from Internal to InvalidProgram (XNNCompiler.cpp:289): Correct — a malformed flatbuffer is an invalid program, not an internal error.
  • Null checks before all flatbufferDimsToVector calls: Prevents null pointer dereference in the function (which unconditionally calls fb_dims->size() at line 163). All four node builder call sites are covered.
  • Comment in flatbufferDimsToVector (XNNCompiler.cpp:157): Still says "int32_t" but the flatbuffer schema uses [uint] (uint32_t) and the C++ code uses flatbuffers::Vector<uint32_t>. Minor nit.

Summary

The structural validation approach is correct and simpler than the previous Result<> / kMaxDimensionValue version. Two issues need attention before merging:

  1. (Bug) The null dims check rejects valid scalar tensors — needs to allow dims() == nullptr when num_dims == 0.
  2. (Incomplete fix) The num_dims vs array-size mismatch check should be extended to the node builders (transpose, reshape, slice) where num_dims is passed separately to XNNPACK APIs.

Copilot AI review requested due to automatic review settings April 20, 2026 22:34
@lucylq
Copy link
Copy Markdown
Contributor Author

lucylq commented Apr 20, 2026

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented Apr 20, 2026

Claude finished @lucylq's task in 1m 13s —— View job


Review of PR #18896 (re-review after updates)

  • Read PR diff and current file state
  • Analyze changes for correctness, security, and style
  • Check if previous review issues were addressed
  • Post review feedback

This is the third review, covering the current state after the kMaxDimensionValue / Result<> return type changes were removed and scalar tensor handling was fixed.


Previous Issues — Status

Issue Status
Scalar tensor rejection (dims()==nullptr when num_dims==0) Fixed — split into separate checks at lines 287-294
1 << 24 signed shift Fixed — removed entirely (no more kMaxDimensionValue)
num_dims vs array-size mismatch in node builders Fixed — all four node builders now pass dims_data.size() instead of graph_node->num_dims()

All three issues from previous reviews have been addressed. The PR is significantly improved.


Remaining Issue

Null pointer dereference for scalar tensors in defineTensor (bug)

XNNCompiler.cpp:291 correctly allows dims() == nullptr when num_dims == 0. However, line 307 unconditionally passes tensor_value->dims() to flatbufferDimsToVector:

// Line 307
std::vector<size_t> dims_data = flatbufferDimsToVector(tensor_value->dims());

flatbufferDimsToVector (line 161-168) dereferences the pointer unconditionally:

dims_data.reserve(fb_dims->size());  // line 163 — null deref if fb_dims is nullptr
for (auto fb_dim : *fb_dims) {       // line 164 — also null deref

So a valid scalar tensor with num_dims == 0 and dims() == nullptr will pass validation but crash on line 307. This needs a guard:

std::vector<size_t> dims_data;
if (tensor_value->dims() != nullptr) {
  dims_data = flatbufferDimsToVector(tensor_value->dims());
}

Fix this →


Looks Good

defineTensor validation (XNNCompiler.cpp:287-303): The three-part validation (null tensor, null dims with nonzero num_dims, num_dims vs dims()->size() mismatch) is structured well. The error type change from Internal to InvalidProgram is correct — a malformed flatbuffer is an invalid program, not an internal error. The scalar tensor case is properly handled in the checks themselves.

defineStaticTransposeNode (XNNCompiler.cpp:982-989): Null check before flatbufferDimsToVector, and passing dims_data.size() instead of graph_node->num_dims() to xnn_define_static_transpose. This eliminates the mismatch vulnerability.

defineStaticConstantPadNode (XNNCompiler.cpp:1051-1055): Both pre_paddings() and post_paddings() are null-checked before use.

defineStaticReshapeNode (XNNCompiler.cpp:1136-1139): Null check added, dims_data.size() used instead of graph_node->num_dims().

defineStaticSliceNode (XNNCompiler.cpp:1435-1451): Null checks for both offsets() and sizes(), plus a size consistency check between offsets and sizes, and offsets.size() passed instead of graph_node->num_dims(). This is the most thorough of the node builder fixes.


Minor Nit

The comment at XNNCompiler.cpp:156-157 still says "tensor shape is serialized in flatbuffer as int32_t" but the flatbuffer schema uses [uint] (uint32_t) and the function signature takes flatbuffers::Vector<uint32_t>*. This is pre-existing and not introduced by this PR, so not a blocker.


Summary

The PR addresses the scalar tensor issue, removes the controversial kMaxDimensionValue cap, and closes the num_dims mismatch vulnerability in all four node builders by passing the actual array size instead of the untrusted flatbuffer field. One null pointer dereference path remains for scalar tensors at line 307 — fixing that would make this ready to merge.


Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR strengthens XNNPACK FlatBuffer deserialization/graph-definition validation in XNNCompiler.cpp to better reject malformed programs and prevent potential out-of-bounds access when interpreting dimension/parameter arrays.

Changes:

  • Add validation for XNNTensorValue nullness and dims pointer/length consistency in defineTensor.
  • Add null checks for several node parameter vectors (e.g., transpose perm, reshape new_shape, slice offsets/sizes).
  • Prefer using actual vector sizes (converted to std::vector<size_t>) when passing ranks to XNNPACK APIs.
Comments suppressed due to low confidence (2)

backends/xnnpack/runtime/XNNCompiler.cpp:1065

  • xnn_define_static_constant_pad is called with pre_paddings_dims.data() / post_paddings_dims.data() but no explicit length; this makes the runtime rely on the vectors being the correct length for the input tensor rank. In addition to non-null checks, validate that pre_paddings_dims.size() == post_paddings_dims.size() and that this size matches the input tensor’s num_dims (can be obtained by looking up the XNNTensorValue with id_out == graph_node->input_id() similar to isQP8).
  ET_CHECK_OR_RETURN_ERROR(
      graph_node->pre_paddings() != nullptr &&
          graph_node->post_paddings() != nullptr,
      InvalidProgram,
      "StaticConstantPad: pre_paddings or post_paddings is null");
  std::vector<size_t> pre_paddings_dims =
      flatbufferDimsToVector(graph_node->pre_paddings());
  std::vector<size_t> post_paddings_dims =
      flatbufferDimsToVector(graph_node->post_paddings());

  xnn_status status = xnn_define_static_constant_pad(
      subgraph_ptr,
      pre_paddings_dims.data(),
      post_paddings_dims.data(),
      graph_node->padding_value(),

backends/xnnpack/runtime/XNNCompiler.cpp:308

  • tensor_value->num_dims() == 0 is allowed to pass with tensor_value->dims() == nullptr, but immediately after this block the code unconditionally calls flatbufferDimsToVector(tensor_value->dims()), which will dereference a null pointer. Handle the scalar case by skipping flatbufferDimsToVector when dims() is null (e.g., leave dims_data empty and pass a null/empty dims pointer when num_dims==0).
  ET_CHECK_OR_RETURN_ERROR(
      tensor_value->num_dims() == 0 || tensor_value->dims() != nullptr,
      InvalidProgram,
      "Tensor dims is null but num_dims is %u",
      tensor_value->num_dims());

  if (tensor_value->dims() != nullptr) {
    ET_CHECK_OR_RETURN_ERROR(
        tensor_value->num_dims() == tensor_value->dims()->size(),
        InvalidProgram,
        "Tensor num_dims %u does not match dims array size %u",
        tensor_value->num_dims(),
        tensor_value->dims()->size());
  }

  // Get tensor dims, here we need to use a vector in order
  // to properly convert the uint32_t* to size_t*
  std::vector<size_t> dims_data = flatbufferDimsToVector(tensor_value->dims());


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread backends/xnnpack/runtime/XNNCompiler.cpp
Comment thread backends/xnnpack/runtime/XNNCompiler.cpp
Comment thread backends/xnnpack/runtime/XNNCompiler.cpp
Copilot AI review requested due to automatic review settings April 21, 2026 18:47
…piler

Validate that dims array is non-null and num_dims matches the actual array
size in defineTensor to prevent heap buffer overflows. Change
flatbufferDimsToVector to return Result<> with null-check and per-dimension
validation against a 16M limit to prevent unbounded memory allocation from
malicious dimension values.

Authored-with: Claude
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR primarily hardens XNNPACK flatbuffer deserialization to reject malformed graphs (preventing potential OOB reads), and additionally extends the Qwen3.5 MoE export tooling with a new Metal backend path and a new fpa4w quantization option.

Changes:

  • Add XNNPACK runtime validation for tensor dims pointers/sizes in defineTensor, plus additional null checks and safer dimension handling in several static ops.
  • Add Metal export + quantization pipeline for examples/models/qwen3_5_moe and expose --backend metal.
  • Add fpa4w shape-compatibility logic for Metal/MPS UIntx INT4 quantization.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 8 comments.

File Description
extension/llm/export/quantize.py Adds fpa4w shape compatibility rules for Metal/MPS INT4 weight-only quantization.
examples/models/qwen3_5_moe/export.py Introduces Metal preparation/export flow and updates CLI to allow metal backend and fpa4w quantization.
backends/xnnpack/runtime/XNNCompiler.cpp Adds null/size validation for flatbuffer vectors (dims/perm/new_shape/offsets/sizes) and uses vector sizes when calling XNNPACK APIs.
Comments suppressed due to low confidence (1)

backends/xnnpack/runtime/XNNCompiler.cpp:1155

  • StaticConstantPad now checks that both padding vectors are non-null, but it still doesn’t validate that pre_paddings and post_paddings have the same length. If they differ in a malformed/corrupted flatbuffer, passing the raw pointers into XNNPACK can lead to out-of-bounds reads. Please add a check that pre_paddings_dims.size() == post_paddings_dims.size() (and ideally fail with InvalidProgram if not).
  ET_CHECK_OR_RETURN_ERROR(
      graph_node->pre_paddings() != nullptr &&
          graph_node->post_paddings() != nullptr,
      InvalidProgram,
      "StaticConstantPad: pre_paddings or post_paddings is null");
  std::vector<size_t> pre_paddings_dims =
      flatbufferDimsToVector(graph_node->pre_paddings());
  std::vector<size_t> post_paddings_dims =
      flatbufferDimsToVector(graph_node->post_paddings());

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

shape = m.weight.shape
if config_name == "nvfp4":
compatible = shape[-2] % group_size == 0 and shape[-1] % group_size == 0
elif config_name == "fpa4w":

elif backend == "metal":
if args.prequantized:
raise ValueError("Metal backend does not support --prequantized.")
ET_CHECK_OR_RETURN_ERROR(
tensor_value->num_dims() == tensor_value->dims()->size(),
InvalidProgram,
"Tensor num_dims %u does not match dims array size %u",
ET_CHECK_OR_RETURN_ERROR(
graph_node->perm() != nullptr,
InvalidProgram,
"StaticTranspose: perm is null");
ET_CHECK_OR_RETURN_ERROR(
graph_node->new_shape() != nullptr,
InvalidProgram,
"StaticReshape: new_shape is null");
InvalidProgram,
"StaticSlice: offsets size %zu does not match sizes size %zu",
offsets.size(),
sizes.size());
Comment on lines +322 to +335
ET_CHECK_OR_RETURN_ERROR(
tensor_value->num_dims() == 0 || tensor_value->dims() != nullptr,
InvalidProgram,
"Tensor dims is null but num_dims is %u",
tensor_value->num_dims());

if (tensor_value->dims() != nullptr) {
ET_CHECK_OR_RETURN_ERROR(
tensor_value->num_dims() == tensor_value->dims()->size(),
InvalidProgram,
"Tensor num_dims %u does not match dims array size %u",
tensor_value->num_dims(),
tensor_value->dims()->size());
}
Comment thread examples/models/qwen3_5_moe/export.py
@lucylq lucylq merged commit 2d53535 into main Apr 21, 2026
174 of 176 checks passed
@lucylq lucylq deleted the security39-42 branch April 21, 2026 20:57
zeel2104 pushed a commit to zeel2104/executorch that referenced this pull request May 5, 2026
… defineTensor (pytorch#18896)

Validate that dims array is non-null and num_dims matches the actual
array size in defineTensor to prevent heap buffer overflows.

Authored-with: Claude

Co-authored-by: Github Executorch <github_executorch@arm.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. security-fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants