Skip to content

Commit f843857

Browse files
fix: make ExtDType metadata deserialization total over byte input (#7782)
Fixes #7773 Generated with Claude Code Signed-off-by: vortex-claude <vortex-claude@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
1 parent 12e72c1 commit f843857

3 files changed

Lines changed: 76 additions & 2 deletions

File tree

vortex-array/src/extension/datetime/date.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ impl ExtVTable for Date {
8787
}
8888

8989
fn deserialize_metadata(&self, metadata: &[u8]) -> VortexResult<Self::Metadata> {
90+
vortex_ensure!(!metadata.is_empty(), "Date metadata must not be empty");
9091
let tag = metadata[0];
9192
TimeUnit::try_from(tag)
9293
}
@@ -209,4 +210,21 @@ mod tests {
209210
assert!(ms.can_coerce_from(&days));
210211
assert!(!days.can_coerce_from(&ms));
211212
}
213+
214+
#[test]
215+
fn deserialize_empty_metadata_returns_error() {
216+
use crate::dtype::extension::ExtVTable;
217+
218+
let vtable = Date;
219+
assert!(vtable.deserialize_metadata(&[]).is_err());
220+
}
221+
222+
#[test]
223+
fn deserialize_invalid_tag_returns_error() {
224+
use crate::dtype::extension::ExtVTable;
225+
226+
let vtable = Date;
227+
// 0xFF is not a valid TimeUnit tag.
228+
assert!(vtable.deserialize_metadata(&[0xFF]).is_err());
229+
}
212230
}

vortex-array/src/extension/datetime/time.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ impl ExtVTable for Time {
8888
}
8989

9090
fn deserialize_metadata(&self, data: &[u8]) -> VortexResult<Self::Metadata> {
91+
vortex_ensure!(!data.is_empty(), "Time metadata must not be empty");
9192
let tag = data[0];
9293
TimeUnit::try_from(tag)
9394
}
@@ -219,4 +220,21 @@ mod tests {
219220
assert_eq!(secs.least_supertype(&ns).unwrap(), expected);
220221
assert_eq!(ns.least_supertype(&secs).unwrap(), expected);
221222
}
223+
224+
#[test]
225+
fn deserialize_empty_metadata_returns_error() {
226+
use crate::dtype::extension::ExtVTable;
227+
228+
let vtable = Time;
229+
assert!(vtable.deserialize_metadata(&[]).is_err());
230+
}
231+
232+
#[test]
233+
fn deserialize_invalid_tag_returns_error() {
234+
use crate::dtype::extension::ExtVTable;
235+
236+
let vtable = Time;
237+
// 0xFF is not a valid TimeUnit tag.
238+
assert!(vtable.deserialize_metadata(&[0xFF]).is_err());
239+
}
222240
}

vortex-array/src/extension/datetime/timestamp.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ impl ExtVTable for Timestamp {
141141
}
142142

143143
fn deserialize_metadata(&self, data: &[u8]) -> VortexResult<Self::Metadata> {
144-
vortex_ensure!(data.len() >= 3);
144+
vortex_ensure!(
145+
data.len() >= 3,
146+
"Timestamp metadata must have at least 3 bytes, got {}",
147+
data.len()
148+
);
145149

146150
let tag = data[0];
147151
let time_unit = TimeUnit::try_from(tag)?;
@@ -158,7 +162,13 @@ impl ExtVTable for Timestamp {
158162
}
159163

160164
// Attempt to load from len-prefixed bytes
161-
let tz_bytes = &data[3..][..tz_len];
165+
vortex_ensure!(
166+
data.len() >= 3 + tz_len,
167+
"Timestamp metadata is truncated: declared timezone length {} but only {} bytes available",
168+
tz_len,
169+
data.len() - 3
170+
);
171+
let tz_bytes = &data[3..3 + tz_len];
162172
let tz: Arc<str> = str::from_utf8(tz_bytes)
163173
.map_err(|e| vortex_err!("timezone is not valid utf8 string: {e}"))?
164174
.to_string()
@@ -365,4 +375,32 @@ mod tests {
365375
assert!(utc.can_coerce_from(&utc_s));
366376
assert!(!utc.can_coerce_from(&none));
367377
}
378+
379+
#[test]
380+
fn deserialize_empty_metadata_returns_error() {
381+
use crate::dtype::extension::ExtVTable;
382+
383+
let vtable = Timestamp;
384+
assert!(vtable.deserialize_metadata(&[]).is_err());
385+
}
386+
387+
#[test]
388+
fn deserialize_too_short_metadata_returns_error() {
389+
use crate::dtype::extension::ExtVTable;
390+
391+
let vtable = Timestamp;
392+
// Only 2 bytes - too short for the required 3-byte header.
393+
assert!(vtable.deserialize_metadata(&[0x00, 0x01]).is_err());
394+
}
395+
396+
#[test]
397+
fn deserialize_truncated_timezone_returns_error() {
398+
use crate::dtype::extension::ExtVTable;
399+
400+
let vtable = Timestamp;
401+
// Valid tag (0x00 = Nanoseconds), tz_len = 10 (little-endian: [0x0A, 0x00]),
402+
// but only 3 bytes of timezone data instead of the declared 10.
403+
let data = [0x00u8, 0x0A, 0x00, b'U', b'T', b'C'];
404+
assert!(vtable.deserialize_metadata(&data).is_err());
405+
}
368406
}

0 commit comments

Comments
 (0)