Skip to content

Fuzzing Crash: DecimalArray fill_null panics on null scalar value #5807

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: vortex-array/src/arrays/decimal/compute/fill_null.rs:35 in the fill_null method

Error Message:

panicked at vortex-error/src/lib.rs:371:13:
top-level fill_null ensure non-null fill value

Stack Trace:

#0  fill_null at ./vortex-array/src/arrays/decimal/compute/fill_null.rs:35:26
#1  invoke<vortex_array::arrays::decimal::vtable::DecimalVTable> at ./vortex-array/src/compute/fill_null.rs:84:13
#2  invoke at ./vortex-array/src/compute/fill_null.rs:114:42
#3  invoke at ./vortex-array/src/compute/mod.rs:149:34
#4  fill_null at ./vortex-array/src/compute/fill_null.rs:54:10
#5  run_fuzz_action at ./fuzz/src/array/mod.rs:594:33
#6  __libfuzzer_sys_run at ./fuzz/fuzz_targets/array_ops.rs:14:11

Root Cause: The fill_null operation for DecimalArray assumes that the fill value scalar is non-null, but the fuzzer discovered a case where a scalar with NonNullable dtype actually contains a null value. At line 35, the code calls:

let fill_value = fill_value
    .as_decimal()
    .decimal_value()
    .and_then(|v| v.cast::<T>())
    .vortex_expect("top-level fill_null ensure non-null fill value");

The .decimal_value() method returns None when the scalar value is null, causing the panic. This reveals an inconsistency where a scalar can have a NonNullable dtype but still contain a null value, or a validation gap in the fill_null entry point that should reject null fill values.

Debug Output
Output of `std::fmt::Debug`:

FuzzArrayAction {
    array: DecimalArray {
        dtype: Decimal(
            DecimalDType {
                precision: 19,
                scale: -61,
            },
            NonNullable,
        ),
        values: Buffer<u8> {
            length: 16,
            alignment: Alignment(
                16,
            ),
            as_slice: [60, 59, 83, 23, 252, 220, 7, 68, 0, 0, 0, 0, 0, 0, 0, 0],
        },
        values_type: I128,
        validity: NonNullable,
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    actions: [
        ...
        (
            FillNull(
                Scalar {
                    dtype: Decimal(
                        DecimalDType {
                            precision: 19,
                            scale: -61,
                        },
                        NonNullable,
                    ),
                    value: ScalarValue(
                        Decimal(
                            I128(
                                -9999999999999999999,
                            ),
                        ),
                    ),
                },
            ),
            ...
        ),
    ],
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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

Auto-created by fuzzing workflow with Claude analysis

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugA bug issuefuzzerIssues detected by the fuzzer

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions