Repository navigation
Conversation
The scatter and gather kernels use the index directly as an offset without checking it against the array shape. An out-of-bounds index (>= shape, or a negative index < -shape that wraps) writes or reads past the buffer, a heap-buffer-overflow. Add the bounds check in offset_neg_idx and throw std::runtime_error.
This branch has not been deployed
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.
1. The complete scatter implementation — there is no bounds check
1.1 The index helper only handles the negative case
sizeis only used for theidx < 0wrap; a positive index is never compared tosize.1.2 The write operators
1.3 The scatter kernel — index → offset → write, with no check in between
Between (a), (b) and (c) there is no
idx_val >= out.shape(ax)/out_offset >= out.size()check. The kernel reachesout_ptr + out_offsetdirectly.1.4
eval_cpucopiessrcintoout, then dispatches the kerneloutis sized fromsrc.shape(16 bytes for[4]float32); the kernel then writes atan index-derived offset with no reference back to that shape.
2. The operator code that triggers it
A developer writes an op that scatters at a computed index:
C++ (the exact reproducer that produced the trace below):
3. The complete call stack + memory dump
out_offset = 6 * 4 = 24, i.e. 8 bytes past the end of the 16-byteoutdata. ASANflags the write in the redzone (full stack,
std::__1template frames elided):The
[fa]at the arrow is the poisoned redzone byte; the write at offset 24 lands on it.4. Why the index is never checked against the buffer
outis a[4]float32 array → 16 bytes of logical data (Scatter::eval_cpucopies
srcintoout, sizing it fromsrc.shape).scatter(out, [6], ...)reads the index6, callsoffset_neg_idx(6, 4)— since6 >= 0, theidx < 0branch is not taken and6is returned unchanged (theonly check the helper does is for the negative case).
out_offset = 6 * strides[0] = 6 * 4 = 24. No comparison againstout.size()orout.shape(0)happens anywhere between the index read and the write.None::operator()(x, out_ptr + 24)writes 4 bytes 8 bytes past the 16-byte data→ the redzone is hit → ASAN aborts. On the default Metal backend the same write lands
in the large mapped Metal heap and stays silent, but it is still an out-of-bounds
write past the logical array, never rejected and never raised as
IndexError.Root cause: the scatter kernel trusts the caller's index unconditionally — there is
no
idx_val >= out.shape(ax)guard anywhere in the code path from the index read to theout_ptr + out_offsetwrite.5. The fix
Add the missing bounds checks to
offset_neg_idxso an out-of-bounds index throwsinstead of silently writing out of bounds (this single point covers
scatter,gather,take,take_along_axis,put_along_axis— the whole indexed read/writefamily):
After the fix, the operator
out[mx.array([6], mx.int32)] = 1.0raisesIndexError(the Python mapping of
std::out_of_range) at eval time instead of corrupting the heap.6. Unit test + run commands
Regression test (fails / aborts before the fix, passes after):