-
Notifications
You must be signed in to change notification settings - Fork 107
Open
Description
Fuzzing Crash Report
Analysis
Crash Location: vortex-array/src/validity.rs:269 (in Validity::patch function)
Error Message:
Can't patch a non-nullable validity with nullable validity
Stack Trace:
3: patch
at ./vortex-array/src/validity.rs:269:17
4: patch
at ./vortex-array/src/arrays/primitive/array/patch.rs:21:56
5: decompress_unchunked
at ./encodings/alp/src/alp/decompress.rs:188:17
6: decompress_into_array
at ./encodings/alp/src/alp/decompress.rs:46:9
7: canonicalize
at ./encodings/alp/src/alp/array.rs:470:30
8: to_canonical<vortex_alp::alp::array::ALPVTable>
at ./vortex-array/src/array/mod.rs:611:25
9: to_primitive<dyn vortex_array::array::Array>
at ./vortex-array/src/canonical.rs:354:14
10: sort_canonical_array
at ./fuzz/src/array/sort.rs:38:41
11: run_fuzz_action
at ./fuzz/src/array/mod.rs:549:34
Root Cause:
The fuzzer discovered a bug in the ALP decompression logic where patching fails due to validity incompatibility. The issue occurs when:
- An ALP-encoded ChunkedArray with F32 Nullable dtype is decompressed
- The base decoded array has
AllValidvalidity (non-nullable) - The patches array has nullable validity
- When attempting to apply patches at
encodings/alp/src/alp/decompress.rs:188, the code callsdecoded.patch(&patches) - This calls
Validity::patch()which panics because it doesn't allow patching a non-nullable validity with nullable validity
Looking at vortex-array/src/validity.rs:266-273:
match (&self, patches) {
(Validity::NonNullable, Validity::NonNullable) => return Validity::NonNullable,
(Validity::NonNullable, _) => {
vortex_panic!("Can't patch a non-nullable validity with nullable validity") // Line 269
},
(_, Validity::NonNullable) => {
vortex_panic!("Can't patch a nullable validity with non-nullable validity")
},
...
}The issue is that Validity::AllValid is treated as non-nullable, but when the patches have nullable values, the patching operation should be allowed and should produce a nullable result.
The operation sequence that triggered this:
- Initial ChunkedArray with F32 Nullable dtype, containing 2 chunks
- Compress operation
- SearchSorted operation
- FillNull operation (which triggers sort → canonicalization → ALP decompression → crash)
Debug Output
FuzzArrayAction {
array: ChunkedArray {
dtype: Primitive(
F32,
Nullable,
),
len: 10,
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, 10, 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: 40,
alignment: Alignment(
4,
),
as_slice: [255, 255, 71, 65, 69, 69, 69, 69, 122, 123, 123, 123, 123, 123, 123, 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: 10,
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, 10, 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: 40,
alignment: Alignment(
4,
),
as_slice: [255, 255, 71, 65, 69, 69, 69, 69, 122, 123, 123, 123, 123, 123, 123, 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(
1.3057709e36,
),
),
),
},
Left,
),
Search(
Found(
9,
),
),
),
(
FillNull(
Scalar {
dtype: Primitive(
F32,
NonNullable,
),
value: ScalarValue(
Primitive(
F32(
7.4028235e-39,
),
),
),
},
),
Array(
ChunkedArray {
dtype: Primitive(
F32,
NonNullable,
),
len: 10,
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, 10, 0, 0, 0, 0, 0, 0, 0, ...],
},
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
chunks: [
PrimitiveArray {
dtype: Primitive(
F32,
NonNullable,
),
buffer: Buffer<u8> {
length: 40,
alignment: Alignment(
4,
),
as_slice: [255, 255, 71, 65, 69, 69, 69, 69, 122, 123, 123, 123, 123, 123, 123, 71, ...],
},
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
PrimitiveArray {
dtype: Primitive(
F32,
NonNullable,
),
buffer: Buffer<u8> {
length: 0,
alignment: Alignment(
4,
),
as_slice: [],
},
validity: NonNullable,
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
],
stats_set: ArrayStats {
inner: RwLock {
data: StatsSet {
values: [],
},
},
},
},
),
),
],
}
Summary
- Target:
array_ops - Crash File:
crash-61d9e886b01238928c4d30d4c09226de524343c8 - Branch: develop
- Commit: e7e1748
- Crash Artifact: https://github.com/vortex-data/vortex/actions/runs/12837062479/artifacts/2588764044
Reproduction
-
Download the crash artifact:
- Direct download: https://github.com/vortex-data/vortex/actions/runs/12837062479/artifacts/2588764044
- Or find
operations-fuzzing-crash-artifactsat: https://github.com/vortex-data/vortex/actions/runs/12837062479 - Extract the zip file
-
Reproduce locally:
# The artifact contains array_ops/crash-61d9e886b01238928c4d30d4c09226de524343c8
cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-61d9e886b01238928c4d30d4c09226de524343c8 -- -rss_limit_mb=0- Get full backtrace:
RUST_BACKTRACE=full cargo +nightly fuzz run -D --sanitizer=none array_ops array_ops/crash-61d9e886b01238928c4d30d4c09226de524343c8 -- -rss_limit_mb=0Auto-created by fuzzing workflow with Claude analysis