Skip to content

Fuzzing Crash: sum() returns null for Decimal array with validity mask #5933

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: fuzz/src/array/mod.rs:721 (in assert_scalar_eq, called from line 600)

Error Message:

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

Stack Trace:

   0: std::backtrace_rs::backtrace::libunwind::trace
   1: std::backtrace_rs::backtrace::trace_unsynchronized
   2: <std::backtrace::Backtrace>::create
   3: assert_scalar_eq at ./fuzz/src/array/mod.rs:721:13
   4: run_fuzz_action at ./fuzz/src/array/mod.rs:600:17
   5: __libfuzzer_sys_run at ./fuzz/fuzz_targets/array_ops.rs:14:11
   6: rust_fuzzer_test_input

Root Cause:

The sum aggregate function is incorrectly returning null when computing the sum of a Decimal array with nullable values. The fuzzer creates a ChunkedArray with Decimal type (precision=76, scale=75) and applies:

  1. A mask operation that introduces null values (validity bitmap)
  2. A sum operation (succeeds)
  3. Another sum operation (fails - returns null instead of the expected decimal value)

The expected sum is 10335731459288881875449326843340756734051447447459996202947475384727173595149, but the actual result is null. This suggests the sum operation may not be correctly handling decimal arrays with validity masks, possibly due to:

  • Incorrect null handling in decimal sum accumulation
  • Loss of precision/overflow causing a silent failure
  • Bug in the decimal aggregation path when chained with other operations
Debug Output
FuzzArrayAction {
    array: ChunkedArray {
        dtype: Decimal(
            DecimalDType {
                precision: 76,
                scale: 75,
            },
            Nullable,
        ),
        len: 12,
        chunk_offsets: PrimitiveArray {
            dtype: Primitive(
                U64,
                NonNullable,
            ),
            buffer: Buffer<u8> {
                length: 24,
                alignment: Alignment(
                    8,
                ),
                as_slice: [0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, ...],
            },
            validity: NonNullable,
            stats_set: ArrayStats {
                inner: RwLock {
                    data: StatsSet {
                        values: [],
                    },
                },
            },
        },
        chunks: [
            DecimalArray {
                dtype: Decimal(
                    DecimalDType {
                        precision: 76,
                        scale: 75,
                    },
                    Nullable,
                ),
                values: Buffer<u8> {
                    length: 128,
                    alignment: Alignment(
                        16,
                    ),
                    as_slice: [3, 0, 0, 0, 0, 0, 0, 0, 0, 144, 236, 228, 101, 118, 200, 187, ...],
                },
                values_type: I256,
                validity: AllValid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
            DecimalArray {
                dtype: Decimal(
                    DecimalDType {
                        precision: 76,
                        scale: 75,
                    },
                    Nullable,
                ),
                values: Buffer<u8> {
                    length: 256,
                    alignment: Alignment(
                        16,
                    ),
                    as_slice: [1, 0, 0, 0, 0, 0, 0, 0, 0, 208, 64, 171, 43, 14, 159, 225, ...],
                },
                values_type: I256,
                validity: AllValid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
        ],
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    actions: [
        (
            Mask(
                Values(
                    MaskValues {
                        buffer: BitBuffer {
                            buffer: Buffer<u8> {
                                length: 2,
                                alignment: Alignment(
                                    1,
                                ),
                                as_slice: [248, 15],
                            },
                            offset: 0,
                            len: 12,
                        },
                        indices: OnceLock(
                            <uninit>,
                        ),
                        slices: OnceLock(
                            <uninit>,
                        ),
                        true_count: 9,
                        density: 0.75,
                    },
                ),
            ),
            Array(
                DecimalArray {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 76,
                            scale: 75,
                        },
                        Nullable,
                    ),
                    values: Buffer<u8> {
                        length: 384,
                        alignment: Alignment(
                            16,
                        ),
                        as_slice: [3, 0, 0, 0, 0, 0, 0, 0, 0, 144, 236, 228, 101, 118, 200, 187, ...],
                    },
                    values_type: I256,
                    validity: Array(
                        BoolArray {
                            dtype: Bool(
                                NonNullable,
                            ),
                            bits: BitBuffer {
                                buffer: Buffer<u8> {
                                    length: 2,
                                    alignment: Alignment(
                                        1,
                                    ),
                                    as_slice: [7, 240],
                                },
                                offset: 0,
                                len: 12,
                            },
                            validity: NonNullable,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [
                                            (
                                                Sum,
                                                Exact(
                                                    ScalarValue(
                                                        Primitive(
                                                            U64(
                                                                3,
                                                            ),
                                                        ),
                                                    ),
                                                ),
                                            ),
                                        ],
                                    },
                                },
                            },
                        },
                    ),
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
        (
            Sum,
            Scalar(
                Scalar {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 76,
                            scale: 75,
                        },
                        Nullable,
                    ),
                    value: ScalarValue(
                        Decimal(
                            I256(
                                i256(
                                    10335731459288881875449326843340756734051447447459996202947475384727173595149,
                                ),
                            ),
                        ),
                    ),
                },
            ),
        ),
        (
            Sum,
            Scalar(
                Scalar {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 76,
                            scale: 75,
                        },
                        Nullable,
                    ),
                    value: ScalarValue(
                        Decimal(
                            I256(
                                i256(
                                    10335731459288881875449326843340756734051447447459996202947475384727173595149,
                                ),
                            ),
                        ),
                    ),
                },
            ),
        ),
    ],
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

# The artifact contains array_ops/crash-12517d1ed6e628c80b62828a08a38d5b8ad79e51
cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-12517d1ed6e628c80b62828a08a38d5b8ad79e51 -- -rss_limit_mb=0
  1. Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-12517d1ed6e628c80b62828a08a38d5b8ad79e51 -- -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