-
Notifications
You must be signed in to change notification settings - Fork 125
Description
Fuzzing Crash Report
Analysis
Crash Location: vortex-array/src/arrays/listview/conversion.rs:114 in the list_from_list_view function
Error Message:
assertion failed: zctl_array.is_zero_copy_to_list()
Stack Trace:
0: __rustc::rust_begin_unwind
at /rustc/474276961f48b0d05f4ea260ba400096b027584e/library/std/src/panicking.rs:689:5
1: core::panicking::panic_fmt
at /rustc/474276961f48b0d05f4ea260ba400096b027584e/library/core/src/panicking.rs:80:14
2: core::panicking::panic
at /rustc/474276961f48b0d05f4ea260ba400096b027584e/library/core/src/panicking.rs:150:5
3: list_from_list_view
at ./vortex-array/src/arrays/listview/conversion.rs:114:5
4: compress_canonical
at ./vortex-btrblocks/src/lib.rs:443:34
5: compress
at ./vortex-btrblocks/src/lib.rs:402:14
6: compress_array
at ./fuzz/src/array/mod.rs:524:14
7: run_fuzz_action
at ./fuzz/src/array/mod.rs:567:33
Root Cause:
The bug is in vortex-array/src/arrays/listview/rebuild.rs. The list_from_list_view function calls rebuild(ListViewRebuildMode::MakeExact) and asserts that the result has is_zero_copy_to_list() == true. However, the rebuild logic doesn't always preserve this property correctly.
The issue occurs in the rebuild_make_exact method (rebuild.rs:253-261):
fn rebuild_make_exact(&self) -> VortexResult<ListViewArray> {
if self.is_zero_copy_to_list() {
self.rebuild_trim_elements() // Line 255
} else {
self.rebuild_zero_copy_to_list()
}
}When the array already has is_zero_copy_to_list() == true, it calls rebuild_trim_elements(). However, rebuild_trim_elements() (lines 194-251) can break the zero-copy property for edge cases like empty arrays or arrays with zero-length elements.
At line 249, rebuild_trim_elements preserves the original flag:
.with_zero_copy_to_list(self.is_zero_copy_to_list())But when dealing with empty arrays (length 0), the calculations at lines 209-211 try to access self.offset_at(self.len() - 1) which is problematic:
let last_offset = self.offset_at(self.len() - 1); // self.len() = 0, so this accesses index -1!
let last_size = self.size_at(self.len() - 1);Array Structure from Debug Output:
- Nested
ListViewArraywithList(List(Decimal(...)))dtype - Both outer and inner ListViewArray have empty buffers (length: 0)
- Both have
is_zero_copy_to_list: trueset - The inner elements are a
DecimalArraywith empty values buffer - Empty offsets and sizes arrays for both levels
The crash occurs when BtrBlocks compression tries to convert this nested empty ListViewArray to a ListArray via list_from_list_view.
Debug Output
FuzzArrayAction {
array: ListViewArray {
dtype: List(
List(
Decimal(
DecimalDType {
precision: 22,
scale: -49,
},
Nullable,
),
NonNullable,
),
NonNullable,
),
elements: ListViewArray {
dtype: List(
Decimal(
DecimalDType {
precision: 22,
scale: -49,
},
Nullable,
),
NonNullable,
),
elements: DecimalArray {
dtype: Decimal(
DecimalDType {
precision: 22,
scale: -49,
},
Nullable,
),
values: BufferHandle(
Host(
Buffer<u8> {
length: 0,
alignment: Alignment(
16,
),
as_slice: [],
},
),
),
values_type: I128,
validity: AllValid,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
offsets: PrimitiveArray {
dtype: Primitive(
U64,
NonNullable,
),
buffer: BufferHandle(
Host(
Buffer<u8> {
length: 0,
alignment: Alignment(
8,
),
as_slice: [],
},
),
),
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [
(
IsSorted,
Exact(
ScalarValue(
Bool(
true,
),
),
),
),
],
},
},
},
},
sizes: PrimitiveArray {
dtype: Primitive(
U64,
NonNullable,
),
buffer: BufferHandle(
Host(
Buffer<u8> {
length: 0,
alignment: Alignment(
8,
),
as_slice: [],
},
),
),
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
is_zero_copy_to_list: true,
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
offsets: PrimitiveArray {
dtype: Primitive(
I16,
NonNullable,
),
buffer: BufferHandle(
Host(
Buffer<u8> {
length: 0,
alignment: Alignment(
2,
),
as_slice: [],
},
),
),
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [
(
IsSorted,
Exact(
ScalarValue(
Bool(
true,
),
),
),
),
],
},
},
},
},
sizes: PrimitiveArray {
dtype: Primitive(
I16,
NonNullable,
),
buffer: BufferHandle(
Host(
Buffer<u8> {
length: 0,
alignment: Alignment(
2,
),
as_slice: [],
},
),
),
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
is_zero_copy_to_list: true,
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
actions: [
Compress(Default)
]
}
Summary
- Target:
array_ops - Crash File:
crash-608dbf18d00107ec105f15bdd29c17a20a5be099 - Branch: develop
- Commit: d70a1e9
- Crash Artifact: https://github.com/vortex-data/vortex/actions/runs/13746867682/artifacts/2764399104
Reproduction
-
Download the crash artifact:
- Direct download: https://github.com/vortex-data/vortex/actions/runs/13746867682/artifacts/2764399104
- Or find
operations-fuzzing-crash-artifactsat: https://github.com/vortex-data/vortex/actions/runs/13746867682 - Extract the zip file
-
Reproduce locally:
# The artifact contains array_ops/crash-608dbf18d00107ec105f15bdd29c17a20a5be099
cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-608dbf18d00107ec105f15bdd29c17a20a5be099 -- -rss_limit_mb=0- Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-608dbf18d00107ec105f15bdd29c17a20a5be099 -- -rss_limit_mb=0Auto-created by fuzzing workflow with Claude analysis