Skip to content

Fuzzing Crash: ALP compare with patches not implemented #5948

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: fuzz/src/array/mod.rs:579 (in run_fuzz_action function, Compare action)

Error Message:

Failed to compare root: vortex.alp(f32?, len=6) nbytes=15 B (100.00%) [all_valid]
  metadata: ALPMetadata { exp_e: 8, exp_f: 6, patches: Some(PatchesMetadata { len: 2, offset: 0, indices_ptype: U8, chunk_offsets_len: None, chunk_offsets_ptype: None, offset_within_chunk: None }) }
  encoded: vortex.constant(i32?, len=6) nbytes=5 B (33.33%) [all_valid]
    metadata: EmptyMetadata
    buffer (align=1): 5 B (100.00%)
  patch_indices: vortex.primitive(u8, len=2) nbytes=2 B (13.33%) [sorted]
    metadata: EmptyMetadata
    buffer (align=1): 2 B (100.00%)
  patch_values: vortex.primitive(f32?, len=2) nbytes=8 B (53.33%) [all_valid]
    metadata: EmptyMetadata
    buffer (align=4): 8 B (100.00%)
with >= 51015.277f32
Error: false != true at index 0, lhs is root: vortex.bool(bool?, len=6) nbytes=1 B (100.00%) [all_valid]
  metadata: BoolMetadata { offset: 0 }
  buffer (align=1): 1 B (100.00%)
 rhs is root: vortex.bool(bool?, len=6) nbytes=1 B (100.00%) [all_valid]
  metadata: BoolMetadata { offset: 0 }
  buffer (align=1): 1 B (100.00%)
 in step 3

Stack Trace:

   0: std::backtrace_rs::backtrace::libunwind::trace
   1: std::backtrace_rs::backtrace::trace_unsynchronized
   2: <std::backtrace::Backtrace>::create
   3: assert_array_eq
             at ./fuzz/src/array/mod.rs:699:17
   4: run_fuzz_action
             at ./fuzz/src/array/mod.rs:578:33

Root Cause:

The fuzzer discovered a bug where comparison operations on ALP-encoded arrays with patches produce incorrect results. The issue occurs when:

  1. An ALP-encoded ChunkedArray with F32 Nullable dtype is created with patches
  2. The array undergoes compression (which uses ALP encoding)
  3. Two SearchSorted operations are performed successfully
  4. A Compare operation with Gte (>=) operator against scalar value 51015.277 produces different results when comparing:
    • The compressed ALP array (with patches)
    • The expected reference array (uncompressed)

The comparison returns false at index 0 for the ALP array but true for the reference array.

Looking at encodings/alp/src/alp/compute/compare.rs:35-38, the ALP compare kernel explicitly returns Ok(None) when patches are present:

if lhs.patches().is_some() {
    // TODO(joe): support patches
    return Ok(None);
}

This forces a fallback to canonical comparison, but the fallback path appears to produce incorrect results. The bug could be in:

  • How patches are applied during canonicalization before comparison
  • How the fallback comparison handles the patched values
  • An incorrect implementation in the compare kernel's handling of ALP arrays with patches

The array has:

  • Length: 6
  • Patches: 2 values at indices stored in patch_indices
  • Encoded values: constant i32 array
  • Patch values: f32 values
Debug Output
FuzzArrayAction {
    array: ChunkedArray {
        dtype: Primitive(
            F32,
            Nullable,
        ),
        len: 6,
        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, 6, 0, 0, 0, 0, 0, 0, 0, ...],
            },
            validity: NonNullable,
            stats_set: ArrayStats {
                inner: RwLock {
                    data: StatsSet {
                        values: [],
                    },
                },
            },
        },
        chunks: [
            PrimitiveArray {
                dtype: Primitive(
                    F32,
                    Nullable,
                ),
                buffer: Buffer<u8> {
                    length: 24,
                    alignment: Alignment(
                        4,
                    ),
                    as_slice: [255, 255, 71, 65, 69, 69, 69, 69, 71, 71, 71, 71, 71, 71, 71, 71, ...],
                },
                validity: AllValid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
            PrimitiveArray {
                dtype: Primitive(
                    F32,
                    Nullable,
                ),
                buffer: Buffer<u8> {
                    length: 0,
                    alignment: Alignment(
                        4,
                    ),
                    as_slice: [],
                },
                validity: AllValid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
        ],
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    actions: [
        (
            Compress(
                Default,
            ),
            Array(
                ChunkedArray {
                    dtype: Primitive(
                        F32,
                        Nullable,
                    ),
                    len: 6,
                    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, 6, 0, 0, 0, 0, 0, 0, 0, ...],
                        },
                        validity: NonNullable,
                        stats_set: ArrayStats {
                            inner: RwLock {
                                data: StatsSet {
                                    values: [],
                                },
                            },
                        },
                    },
                    chunks: [
                        PrimitiveArray {
                            dtype: Primitive(
                                F32,
                                Nullable,
                            ),
                            buffer: Buffer<u8> {
                                length: 24,
                                alignment: Alignment(
                                    4,
                                ),
                                as_slice: [255, 255, 71, 65, 69, 69, 69, 69, 71, 71, 71, 71, 71, 71, 71, 71, ...],
                            },
                            validity: AllValid,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [],
                                    },
                                },
                            },
                        },
                        PrimitiveArray {
                            dtype: Primitive(
                                F32,
                                Nullable,
                            ),
                            buffer: Buffer<u8> {
                                length: 0,
                                alignment: Alignment(
                                    4,
                                ),
                                as_slice: [],
                            },
                            validity: AllValid,
                            stats_set: ArrayStats {
                                inner: RwLock {
                                    data: StatsSet {
                                        values: [],
                                    },
                                },
                            },
                        },
                    ],
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
        (
            SearchSorted(
                Scalar {
                    dtype: Primitive(
                        F32,
                        Nullable,
                    ),
                    value: ScalarValue(
                        Primitive(
                            F32(
                                51015.277,
                            ),
                        ),
                    ),
                },
                Left,
            ),
            Search(
                Found(
                    2,
                ),
            ),
        ),
        (
            SearchSorted(
                Scalar {
                    dtype: Primitive(
                        F32,
                        Nullable,
                    ),
                    value: ScalarValue(
                        Primitive(
                            F32(
                                51015.277,
                            ),
                        ),
                    ),
                },
                Left,
            ),
            Search(
                Found(
                    2,
                ),
            ),
        ),
        (
            Compare(
                Scalar {
                    dtype: Primitive(
                        F32,
                        Nullable,
                    ),
                    value: ScalarValue(
                        Primitive(
                            F32(
                                51015.277,
                            ),
                        ),
                    ),
                },
                Gte,
            ),
            Array(
                BoolArray {
                    dtype: Bool(
                        Nullable,
                    ),
                    bits: BitBuffer {
                        buffer: Buffer<u8> {
                            length: 1,
                            alignment: Alignment(
                                1,
                            ),
                            as_slice: [60],
                        },
                        offset: 0,
                        len: 6,
                    },
                    validity: AllValid,
                    stats_set: ArrayStats {
                        inner: RwLock {
                            data: StatsSet {
                                values: [],
                            },
                        },
                    },
                },
            ),
        ),
    ],
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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