-
Notifications
You must be signed in to change notification settings - Fork 130
Description
Fuzzing Crash Report
Analysis
Crash Location: vortex-array/src/arrays/list/array.rs:224 (in validate function)
Error Message:
validity with size 26 does not match array size 40
Stack Trace:
0: std::backtrace_rs::backtrace::libunwind::trace
1: std::backtrace_rs::backtrace::trace_unsynchronized
2: <std::backtrace::Backtrace>::create
3: validate
at ./vortex-array/src/arrays/list/array.rs:224:13
4: try_new
at ./vortex-array/src/arrays/list/array.rs:112:9
5: _take<u64, u16, u16>
at ./vortex-array/src/arrays/list/compute/take.rs:120:8
6: take
at ./vortex-array/src/arrays/list/compute/take.rs:48:21
7: invoke<vortex_array::arrays::list::vtable::ListVTable>
at ./vortex-array/src/compute/take.rs:247:17
8: take_impl
at ./vortex-array/src/compute/take.rs:178:38
9: invoke
at ./vortex-array/src/compute/take.rs:92:27
10: invoke
at ./vortex-array/src/compute/mod.rs:149:34
11: take
at ./vortex-array/src/compute/take.rs:60:10
12: {closure#0}
at ./vortex-array/src/arrays/struct_/compute/take.rs:42:30
[... iterator frames ...]
27: take
at ./vortex-array/src/arrays/struct_/compute/take.rs:43:18
28: invoke<vortex_array::arrays::struct_::vtable::StructVTable>
at ./vortex-array/src/compute/take.rs:247:17
29: take_impl
at ./vortex-array/src/compute/take.rs:178:38
30: invoke
at ./vortex-array/src/compute/take.rs:92:27
31: invoke
at ./vortex-array/src/compute/mod.rs:149:34
32: take
at ./vortex-array/src/compute/take.rs:60:10
33: run_fuzz_action
at ./fuzz/src/array/mod.rs:545:33
Root Cause:
The fuzzer detected a bug in the list take operation at vortex-array/src/arrays/list/compute/take.rs:120. When performing a take operation on a ListArray that is nested within a StructArray, the validity array for the resulting ListArray has an incorrect size.
The issue occurs at line 120-127 of vortex-array/src/arrays/list/compute/take.rs:
Ok(ListArray::try_new(
new_elements,
new_offsets,
indices_array
.validity()
.clone()
.and(array.validity().clone()),
)?
.to_array())The problem is with how the validity is computed. The code combines:
indices_array.validity()- validity from the take indices arrayarray.validity()- validity from the original list array
However, the resulting validity array ends up with size 26 (likely the size of the indices array), while the new_offsets array has size 41 (40 elements + 1 for the terminal offset), resulting in an array with logical length 40.
The validation at vortex-array/src/arrays/list/array.rs:224 correctly catches this:
vortex_ensure!(
validity_len == offsets.len() - 1,
"validity with size {validity_len} does not match array size {}",
offsets.len() - 1
);The crash input was:
- A StructArray (len=9) containing a ListViewArray field
- The ListViewArray contained Decimal elements
- A Take operation with 37 indices (BitPackedArray)
- When taking the struct, it recursively takes the list field
- The list take produces 40 valid elements but a validity array of size 26
Debug Output
FuzzArrayAction {
array: StructArray {
len: 9,
dtype: Struct(
StructFields {
names: FieldNames(
[
FieldName(
"",
),
],
),
dtypes: [
FieldDType {
inner: Owned(
List(
Decimal(
DecimalDType {
precision: 10,
scale: 5,
},
Nullable,
),
Nullable,
),
),
},
],
},
Nullable,
),
fields: [
ListViewArray {
dtype: List(
Decimal(
DecimalDType {
precision: 10,
scale: 5,
},
Nullable,
),
Nullable,
),
elements: DecimalArray {
dtype: Decimal(
DecimalDType {
precision: 10,
scale: 5,
},
Nullable,
),
values: Buffer<u8> {
length: 72,
alignment: Alignment(
8,
),
as_slice: [27, 4, 114, 241, 253, 255, 255, 255, 18, 52, 11, 54, 2, 0, 0, 0, ...],
},
values_type: I64,
validity: AllValid,
},
offsets: PrimitiveArray {
dtype: Primitive(
U16,
NonNullable,
),
buffer: Buffer<u8> {
length: 18,
alignment: Alignment(
2,
),
as_slice: [0, 0, 9, 0, 9, 0, 9, 0, 9, 0, 9, 0, 9, 0, 9, 0, ...],
},
validity: NonNullable,
},
sizes: PrimitiveArray {
dtype: Primitive(
U16,
NonNullable,
),
buffer: Buffer<u8> {
length: 18,
alignment: Alignment(
2,
),
as_slice: [9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...],
},
validity: NonNullable,
},
is_zero_copy_to_list: true,
validity: Array(
BoolArray {
dtype: Bool(
NonNullable,
),
bits: BitBuffer {
buffer: Buffer<u8> {
length: 2,
alignment: Alignment(
1,
),
as_slice: [1, 0],
},
offset: 0,
len: 9,
},
validity: NonNullable,
},
),
},
],
validity: AllValid,
},
actions: [
(
Take(
BitPackedArray {
offset: 0,
len: 37,
dtype: Primitive(
U64,
Nullable,
),
bit_width: 0,
packed: Buffer<u8> {
length: 0,
alignment: Alignment(
8,
),
as_slice: [],
},
patches: None,
validity: Array(
BoolArray {
dtype: Bool(
NonNullable,
),
bits: BitBuffer {
buffer: Buffer<u8> {
length: 5,
alignment: Alignment(
1,
),
as_slice: [0, 0, 0, 0, 8],
},
offset: 0,
len: 37,
},
validity: NonNullable,
},
),
},
),
),
],
}
Summary
- Target:
array_ops - Crash File:
crash-f25afd5a1813b335d85d4b020cd6675e4ed1165d - Branch: develop
- Commit: 47d6ba7
- Crash Artifact: https://github.com/vortex-data/vortex/actions/runs/20260878446/artifacts/4882761796
Reproduction
-
Download the crash artifact:
- Direct download: https://github.com/vortex-data/vortex/actions/runs/20260878446/artifacts/4882761796
- Or find
operations-fuzzing-crash-artifactsat: https://github.com/vortex-data/vortex/actions/runs/20260878446 - Extract the zip file
-
Reproduce locally:
# The artifact contains array_ops/crash-f25afd5a1813b335d85d4b020cd6675e4ed1165d
cargo +nightly fuzz run --sanitizer=none array_ops array_ops/crash-f25afd5a1813b335d85d4b020cd6675e4ed1165d- Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run --sanitizer=none array_ops array_ops/crash-f25afd5a1813b335d85d4b020cd6675e4ed1165dAuto-created by fuzzing workflow with Claude analysis