Skip to content

repr: saturate array-cardinality product to avoid multiply overflow#37177

Merged
def- merged 1 commit into
MaterializeInc:mainfrom
def-:pr-overflow
Jun 22, 2026
Merged

repr: saturate array-cardinality product to avoid multiply overflow#37177
def- merged 1 commit into
MaterializeInc:mainfrom
def-:pr-overflow

Conversation

@def-

@def- def- commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

RowPacker::push_array_with_unchecked and push_array_with_row_major compute an array's expected cardinality as the product of its dimension lengths and compare it against the actual number of elements pushed. The product was an unchecked usize multiply (dims.iter()...product() / cardinality *= dim.length), so dimension lengths whose product exceeds usize::MAX overflowed.

Under overflow checks (debug, and the cargo-fuzz build) this panics; in release it silently wraps, and a wrapped value can even spuriously match the actual element count — accepting a corrupt array (e.g. dims claiming [2^32, 2^32] wrap to a cardinality of 0, matching an empty element list). This is reachable from Row::decode over an attacker- or corruption-supplied ProtoRow, since the proto array dimensions are not otherwise bounded.

Saturate the product to usize::MAX instead. An overflowing cardinality is impossibly large — no array can hold that many elements — so it never equals the real element count and the existing check rejects it as WrongCardinality, turning the panic/silent-wrap into a clean error on both build profiles.

Found by the repr::row_codec_roundtrip cargo-fuzz target in #36982

`RowPacker::push_array_with_unchecked` and `push_array_with_row_major`
compute an array's expected cardinality as the product of its dimension
lengths and compare it against the actual number of elements pushed. The
product was an unchecked `usize` multiply (`dims.iter()...product()` /
`cardinality *= dim.length`), so dimension lengths whose product exceeds
`usize::MAX` overflowed.

Under overflow checks (debug, and the cargo-fuzz build) this panics; in
release it silently wraps, and a wrapped value can even spuriously match the
actual element count — accepting a corrupt array (e.g. dims claiming
`[2^32, 2^32]` wrap to a cardinality of 0, matching an empty element list).
This is reachable from `Row::decode` over an attacker- or corruption-supplied
`ProtoRow`, since the proto array dimensions are not otherwise bounded.

Saturate the product to `usize::MAX` instead. An overflowing cardinality is
impossibly large — no array can hold that many elements — so it never equals
the real element count and the existing check rejects it as
`WrongCardinality`, turning the panic/silent-wrap into a clean error on both
build profiles.

Found by the `repr::row_codec_roundtrip` cargo-fuzz target.

Tests: adds `test_array_cardinality_overflow` to `row.rs`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@def- def- requested a review from a team as a code owner June 22, 2026 08:56

@antiguru antiguru left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you!

@def- def- merged commit d3bfda7 into MaterializeInc:main Jun 22, 2026
121 checks passed
@def- def- deleted the pr-overflow branch June 22, 2026 09:35
frankmcsherry added a commit that referenced this pull request Jun 24, 2026
Rebased the int-unification spike onto current main. The base spike commit
cherry-picked cleanly; this commit re-applies the mechanical follow-on work
against the new base and migrates the references that landed in main after the
original branch point (#37155, #37177, #36776 count-aware-aggregates, #36984):

- production: signed-sum fold + SumInt16/32 narrow (relation/func.rs), the
  generate_series-unoptimized literal (sql/func.rs), and the generate_series
  collapse (projection_pushdown.rs: ReprScalarType::Int64 -> Int, and the
  literal_i64 closure's int2/4/8 arms collapse to one Datum::Int arm).
- test/bench/doctest: ~292 Datum::IntN -> Datum::Int/UInt sites, plus the
  PropDatum::Int32 narrowings and an i64-typed literal in casts.rs.

Workspace builds clean under --all-targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
frankmcsherry added a commit that referenced this pull request Jun 24, 2026
Rebased the int-unification spike onto current main. The base spike commit
cherry-picked cleanly; this commit re-applies the mechanical follow-on work
against the new base and migrates the references that landed in main after the
original branch point (#37155, #37177, #36776 count-aware-aggregates, #36984):

- production: signed-sum fold + SumInt16/32 narrow (relation/func.rs), the
  generate_series-unoptimized literal (sql/func.rs), and the generate_series
  collapse (projection_pushdown.rs: ReprScalarType::Int64 -> Int, and the
  literal_i64 closure's int2/4/8 arms collapse to one Datum::Int arm).
- test/bench/doctest: ~292 Datum::IntN -> Datum::Int/UInt sites, plus the
  PropDatum::Int32 narrowings and an i64-typed literal in casts.rs.

Workspace builds clean under --all-targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants