Skip to content

Fuzzing Crash: Decimal Sum Overflow in ChunkedArray #5811

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: fuzz/src/array/mod.rs:assert_scalar_eq

Error Message:

Scalar mismatch: expected decimal256(27897708223554751832957193840191214563962497566384571035819434488814200889077, precision=76, scale=75), got null in step 1

Stack Trace:

   3: assert_scalar_eq
             at ./fuzz/src/array/mod.rs:705:13
   4: run_fuzz_action
             at ./fuzz/src/array/mod.rs:587:17
   5: __libfuzzer_sys_run
             at ./fuzz/fuzz_targets/array_ops.rs:14:11
   6: rust_fuzzer_test_input
             at /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libfuzzer-sys-0.4.10/src/lib.rs:363:60
   7: {closure#0}
             at /home/runner/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/libfuzzer-sys-0.4.10/src/lib.rs:62:9

Root Cause:

The fuzzer discovered a case where summing decimal values in a ChunkedArray results in an i256 overflow. When this happens, the sum implementation correctly returns null (as documented in vortex-array/src/compute/sum.rs:44 and tested in vortex-array/src/arrays/decimal/compute/sum.rs:363-377). However, the fuzzer's expectation logic doesn't account for this overflow behavior, causing a mismatch between the expected decimal value and the actual null result.

The issue occurs when:

  1. A ChunkedArray contains DecimalArray chunks with very large i256 values
  2. Multiple Sum operations are performed sequentially
  3. The accumulated sum exceeds i256::MAX, causing CheckedAdd to return None
  4. The sum function correctly returns Scalar::null() on overflow
  5. The fuzzer expects the computed sum but gets null instead

This is a test infrastructure issue rather than a bug in the sum implementation. The fuzzer should either:

  • Detect when overflow would occur and adjust expectations
  • Verify that null is returned when overflow happens
  • Generate input values that won't overflow
Debug Output
FuzzArrayAction {
    array: ChunkedArray {
        dtype: Decimal(
            DecimalDType {
                precision: 76,
                scale: 75,
            },
            Nullable,
        ),
        len: 12,
        chunks: [
            DecimalArray { ... values_type: I256, validity: AllValid ... },
            DecimalArray { ... values_type: I256, validity: AllValid ... }
        ]
    },
    actions: [
        (Sum, Scalar { value: decimal256(27897708223554751832957193840191214563962497566384571035819434488814200889077, precision=76, scale=75) }),
        (Sum, Scalar { value: decimal256(27897708223554751832957193840191214563962497566384571035819434488814200889077, precision=76, scale=75) }),
        (Sum, Scalar { value: decimal256(27897708223554751832957193840191214563962497566384571035819434488814200889077, precision=76, scale=75) }),
        (Sum, Scalar { value: decimal256(27897708223554751832957193840191214563962497566384571035819434488814200889077, precision=76, scale=75) })
    ]
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

# The artifact contains array_ops/crash-0171cbd8eede2afdec81571443c1aa8ff4e52032
cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-0171cbd8eede2afdec81571443c1aa8ff4e52032 -- -rss_limit_mb=0
  1. Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-0171cbd8eede2afdec81571443c1aa8ff4e52032 -- -rss_limit_mb=0

Auto-created by fuzzing workflow with Claude analysis

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions