Skip to content

Fuzzing Crash: FilterArray does not support serialization #6177

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: vortex-array/src/serde.rs:170 in the ArrayNodeFlatBuffer::try_new function

Error Message:

file write should succeed in fuzz test:
  Array vortex.filter does not support serialization

Stack Trace:

   0: std::backtrace_rs::backtrace::libunwind::trace
   1: std::backtrace_rs::backtrace::trace_unsynchronized
   2: <std::backtrace::Backtrace>::create
   3: try_new at ./vortex-array/src/serde.rs:170:17
   4: serialize at ./vortex-array/src/serde.rs:121:20
   5: {async_block#0} at ./vortex-layout/src/layouts/flat/writer.rs:126:29
   6: poll
   7: {async_block#0} at ./vortex-layout/src/layouts/chunked/writer.rs:79:26

Root Cause:

The fuzzer discovered that FilterArray (encoding ID: vortex.filter) explicitly does not support serialization. When attempting to write an array containing a FilterArray to a file, the serialization check at vortex-array/src/serde.rs:168-174 fails:

for child in array.depth_first_traversal() {
    if child.metadata()?.is_none() {
        vortex_bail!(
            "Array {} does not support serialization",
            child.encoding_id()
        );
    }
}

Looking at vortex-array/src/arrays/filter/vtable.rs:70-72, FilterArray's serialization method explicitly returns None:

fn serialize(_metadata: Self::Metadata) -> VortexResult<Option<Vec<u8>>> {
    Ok(None)
}

And the deserialize method at line 74-76 bails:

fn deserialize(_bytes: &[u8]) -> VortexResult<Self::Metadata> {
    vortex_bail!("Filter array is not serializable")
}

Array Structure:

  • ChunkedArray with dtype I8 Nullable, length 16
  • 2 primitive chunks (lengths 15 and 1)
  • All validity is either AllInvalid or AllValid
  • No projection or filter expressions applied

Impact: FilterArray is an internal/transient array type that should be executed/canonicalized before serialization. The fuzzer exposed a code path where a FilterArray reaches the serialization layer without being executed first.

Debug Output
FuzzFileAction {
    array: ChunkedArray {
        dtype: Primitive(
            I8,
            Nullable,
        ),
        len: 16,
        chunk_offsets: PrimitiveArray {
            dtype: Primitive(
                U64,
                NonNullable,
            ),
            buffer: BufferHandle(
                Host(
                    Buffer<u8> {
                        length: 24,
                        alignment: Alignment(
                            8,
                        ),
                        as_slice: [0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 0, 0, 0, ...],
                    },
                ),
            ),
            validity: NonNullable,
            stats_set: ArrayStats {
                inner: RwLock {
                    data: StatsSet {
                        values: [],
                    },
                },
            },
        },
        chunks: [
            PrimitiveArray {
                dtype: Primitive(
                    I8,
                    Nullable,
                ),
                buffer: BufferHandle(
                    Host(
                        Buffer<u8> {
                            length: 15,
                            alignment: Alignment(
                                1,
                            ),
                            as_slice: [255, 109, 109, 109, 109, 255, 109, 102, 109, 109, 109, 37, 4, 157, 85],
                        },
                    ),
                ),
                validity: AllInvalid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
            PrimitiveArray {
                dtype: Primitive(
                    I8,
                    Nullable,
                ),
                buffer: BufferHandle(
                    Host(
                        Buffer<u8> {
                            length: 1,
                            alignment: Alignment(
                                1,
                            ),
                            as_slice: [85],
                        },
                    ),
                ),
                validity: AllValid,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
        ],
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    projection_expr: None,
    filter_expr: None,
    compressor_strategy: Default,
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions