Skip to content

Fuzzing Crash: DecimalArray fill_null with null fill value #5806

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

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

Error Message:

top-level fill_null ensure non-null fill value

Stack Trace:

   6: fill_null
             at ./vortex-array/src/arrays/decimal/compute/fill_null.rs:35:26
   7: invoke<vortex_array::arrays::decimal::vtable::DecimalVTable>
             at ./vortex-array/src/compute/fill_null.rs:84:13
   8: invoke
             at ./vortex-array/src/compute/fill_null.rs:114:42
   9: invoke
             at ./vortex-array/src/compute/mod.rs:149:34
  10: fill_null
             at ./vortex-array/src/compute/fill_null.rs:54:10
  11: run_fuzz_action
             at ./fuzz/src/array/mod.rs:594:33

Root Cause: The fill_null operation is being called on a DecimalArray with a NULL fill value. The code at line 35 attempts to extract and cast the decimal value from the fill scalar:

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");

However, when the fill_value scalar contains a null value (ScalarValue(Null)), the decimal_value() call returns None, causing the panic. The fill_null operation semantically doesn't make sense with a null fill value - you can't fill nulls with null - but the code should either validate this earlier or handle it gracefully.

Debug Output
FuzzArrayAction {
    array: DecimalArray {
        dtype: Decimal(
            DecimalDType {
                precision: 39,
                scale: -53,
            },
            Nullable,
        ),
        values: Buffer<u8> {
            length: 96,
            alignment: Alignment(
                16,
            ),
            as_slice: [44, 1, 52, 255, 127, 159, 156, 85, 141, 146, 73, 163, 37, 123, 121, 84, ...],
        },
        values_type: I256,
        validity: AllValid,
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    actions: [
        (
            Take(
                SparseArray {
                    ...
                    fill_value: Scalar {
                        dtype: Primitive(
                            U64,
                            Nullable,
                        ),
                        value: ScalarValue(
                            Null,
                        ),
                    ...

The key issue is visible in the debug output where the fill_value is ScalarValue(Null).

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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