[ABA-17] fix(vortex-array): bounds-check raw indices in ConstantArray::take#6
Open
abnobdoss wants to merge 2 commits into
Open
[ABA-17] fix(vortex-array): bounds-check raw indices in ConstantArray::take#6abnobdoss wants to merge 2 commits into
abnobdoss wants to merge 2 commits into
Conversation
…ccepts OOB indices `Constant::take` (TakeReduce) only inspects the indices' validity mask and never compares the raw index values against the source array's length. A VALID index past `array.len()` lands in the `AllOr::All` branch and is silently echoed back as the constant fill value — a contract violation relative to every other take kernel (e.g. `Primitive::take`, which rejects such indices). This test is `#[ignore]`d here and un-ignored by the follow-up fix commit, so this commit is bisect-clean. Linear: ABA-17 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> Signed-off-by: Abanoub Doss <abanoub.doss@gmail.com>
`Constant::take` (TakeReduce) branched only on the indices' validity mask
(`AllOr::{All, None, Some}`) and constructed its result from
`array.scalar()` + `indices.len()`. It never compared the raw index values
against `array.len()`. A VALID out-of-bounds index — e.g. a `u32::MAX`
slot from a corrupted offset table — silently echoed back as the constant
fill value, diverging from every other take kernel (`Primitive::take`
rejects the same input).
Bounds-check the min/max of the VALID indices against the source length
once at the top of the impl. The `min_max` aggregate skips nulls, so a
null position carrying a garbage raw value is still accepted (matching
the rest of the contract). `usize::try_from` on the min scalar rejects
negative signed indices, mirroring `Primitive::take`'s
`cast(to_unsigned())` behavior. The upper bound is reported via
`vortex_ensure!(.. OutOfBounds: idx, 0, len)`, consistent with the
existing OOB error idiom in `ArrayRef::execute_scalar`.
Trade-off: this adds an O(n) min/max pass over indices to what was
previously an O(1) constant-fold path. The cost is necessary for
correctness — `Constant::take` discards its indices, so there is no
later kernel that could surface the OOB lazily.
Also un-ignores the regression test added in the previous commit.
Linear: ABA-17
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Abanoub Doss <abanoub.doss@gmail.com>
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.
Summary
ConstantArray::take(viaimpl TakeReduce for Constantinvortex-array/src/arrays/constant/compute/take.rs) branched only on theindices' validity mask (
AllOr::{All, None, Some}) and constructed its resultfrom
array.scalar()+indices.len(). It never compared the raw index valuesagainst
array.len(). A VALID out-of-bounds index — e.g. au32::MAXslotfrom a corrupted offset table or a u32-overflowed index expression — was
silently echoed back as the constant fill value, diverging from every other
take kernel (
Primitive::takerejects the same input by routing throughcast(to_unsigned())and an unchecked buffer index, which panics or errorson OOB).
Fix
Bounds-check the min/max of the VALID indices against the source length once
at the top of
Constant::take:min_maxskips nulls, so a null index slot carrying a garbage raw value isstill accepted — the bounds contract applies only to VALID positions, and a
naive "check every raw value" fix would regress that.
usize::try_fromon the min scalar rejects negative signed indices,mirroring
Primitive::take'scast(to_unsigned())behavior atvortex-array/src/arrays/primitive/compute/take/mod.rs:88-91.vortex_ensure!(.. OutOfBounds: idx, 0, len), consistent withthe existing OOB error idiom at
vortex-array/src/array/erased.rs:281.Trade-off
This adds an O(n) min/max pass over the indices to what was previously an
O(1) constant-fold path. The cost is necessary for correctness —
Constant::takediscards its indices entirely, so there is no later kernelthat could surface the OOB lazily. The alternative would be to restructure
the constant-fold to retain the indices' bounds metadata, which is out of
scope for a contained fix. Reviewers may prefer a
Stat::Max-cached fastpath; happy to add one in a follow-up if desired, but
min_maxalreadyshort-circuits on cached stats internally.
Linear
ABA-17 — "
ConstantArray::takenever validates raw index values for OOB"
Validation
arrays::constant::compute::take::tests::issue_aba17_take_must_reject_oob_indicesin
vortex-array/src/arrays/constant/compute/take.rs. Test covers:u32::MAXvalid index againstlen=10— must error.1010) valid indices — must error.u32::MAXraw value — must be accepted (onlyvalid positions are checked).
adds the test
#[ignore]d (would fail on develop without the fix); commit 2applies the fix and un-ignores the test.
Checks run:
cargo +nightly fmt --allcargo test -p vortex-array --lib— 2753 passed, 0 failed, 0 ignoredcargo clippy -p vortex-array --all-targets --all-features— cleanI did not run the full workspace
cargo clippy --all-targets --all-featuresor
./scripts/public-api.shbecause this change has no public API surface andis fully contained to one private impl in
vortex-array.🤖 Generated with Claude Code