x32-unpool: bound the index map before dereferencing the output pointers (OOB write)#10454
Merged
copybara-service[bot] merged 1 commit intoJul 2, 2026
Merged
Conversation
The x32 unpooling microkernels copy each channel to output[index[c]], where index is the caller-supplied index map and output is the per-pixel indirection slice holding exactly kernel_elements pointers. The index value was used to select an output pointer with no check that it is less than kernel_elements, so an out-of-range index reads a pointer past the indirection buffer and then writes the input element through that out-of-bounds pointer (heap out-of-bounds read of the pointer table plus a write through an attacker-influenced wild pointer). Guard each index against the number of output pointers and drop values that fall outside it, in all four variants (scalar, neon, sse2, wasmsimd). Valid unpooling indices (produced by argmax pooling) are always < kernel_elements, so well-formed input is unaffected; the output is pre-initialized with the fill value, so a dropped index simply leaves that location at fill.
Contributor
Author
|
update: referencing VRP report https://issuetracker.google.com/u/1/issues/522586712 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The x32 unpooling microkernels (
xnn_x32_unpool_ukernel__{scalar,neon,sse2,wasmsimd}) copy each channel tooutput[index[c]]:outputis the per-pixel slice of the indirection buffer and holds exactlykernel_elements(=pooling_height * pooling_width) valid pointers.indexis the caller-supplied index map passed toxnn_setup_unpooling2d_nhwc_x32. The index value selects an output pointer with no check thatindex[c] < kernel_elements, so an out-of-range index reads a pointer past the indirection slice and then writes the input element through that out-of-bounds pointer — a heap out-of-bounds read of the pointer table plus a write through an attacker-influenced wild pointer.This is reachable from the public unpooling operator API with an untrusted
indexmap (e.g. an unpooling op in a model whose index tensor is attacker-influenced rather than produced by a matching argmax pooling).Reproduced under AddressSanitizer (pooling 2×2 so
kernel_elements = 4,batch=in_h=in_w=channels=1so the indirection slice is 4 pointers / 32 bytes,index[0] = 4):For a larger index the wild-pointer write also faults outright. Sibling asymmetry: the max-pooling indirection initializer clamps every computed index to
input-1; unpooling trusts the caller's index verbatim.Fix
Save the number of output pointers (
kernel_elements) and bound each index against it before the indirect write, in all four variants. Indices outside the range are dropped (the output is pre-initialized with the fill value, so a dropped index leaves that location atfill). Valid unpooling indices (produced by argmax pooling) are always< kernel_elements, so well-formed input is unaffected.Testing
index[0] = 2) still runs cleanly and writes to the correct output location, with unchanged output dimensions.