Skip to content

Fuzzing Crash: Type mismatch in SequentialStreamAdapter (U8 vs U16) #5910

@github-actions

Description

@github-actions

Fuzzing Crash Report

Analysis

Crash Location: vortex-layout/src/sequence.rs:313 in SequentialStreamAdapter::poll_next

Error Message:

assertion `left == right` failed: Sequential stream of u8 got chunk of u16.
  left: Primitive(U8, NonNullable)
 right: Primitive(U16, NonNullable)

Stack Trace:

   0: __rustc::rust_begin_unwind
   1: core::panicking::panic_fmt
   2: core::panicking::assert_failed_inner
   3: core::panicking::assert_failed::<&vortex_dtype::dtype::DType, &mut vortex_dtype::dtype::DType>
   4: poll_next<core::pin::Pin<alloc::boxed::Box<(dyn futures_core::stream::Stream<...>>>>>
             at ./vortex-layout/src/sequence.rs:313:13
   5: poll_next (in SequentialStream trait implementation)
   6: poll_next (futures map combinator)
   7: poll_next (repartition layout)
   8: poll_next (compressed layout)
   9: poll_next (buffered layout)
  10: poll_next (chunked writer)
  ...

Root Cause: The SequentialStreamAdapter enforces type consistency across streamed chunks. It was initialized to expect U16 arrays but received a U8 array during streaming. This indicates a type inconsistency bug in the layout writing pipeline, likely in how the repartition or compressed layout transforms handle type conversions or propagate dtype information.

The crash occurs when writing a file using fuzzer-generated data with a nullable U8 primitive array. The stream adapter expects all chunks to have the same dtype as declared, but somewhere in the layout pipeline (repartition → compressed → buffered → chunked), the dtype changes from U16 to U8.

Debug Output
FuzzFileAction {
    array: PrimitiveArray {
        dtype: Primitive(
            U8,
            Nullable,
        ),
        buffer: Buffer<u8> {
            length: 57,
            alignment: Alignment(
                1,
            ),
            as_slice: [33, 34, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, 33, ...],
        },
        validity: Array(
            BoolArray {
                dtype: Bool(
                    NonNullable,
                ),
                bits: BitBuffer {
                    buffer: Buffer<u8> {
                        length: 8,
                        alignment: Alignment(
                            1,
                        ),
                        as_slice: [169, 255, 255, 255, 39, 213, 63, 0],
                    },
                    offset: 0,
                    len: 57,
                },
                validity: NonNullable,
                stats_set: ArrayStats {
                    inner: RwLock {
                        data: StatsSet {
                            values: [],
                        },
                    },
                },
            },
        ),
        stats_set: ArrayStats {
            inner: RwLock {
                data: StatsSet {
                    values: [],
                },
            },
        },
    },
    projection_expr: None,
    filter_expr: None,
    compressor_strategy: Compact,
}

Summary

Reproduction

  1. Download the crash artifact:

  2. Reproduce locally:

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