@@ -475,7 +475,7 @@ fn collect_valid_vbv(
475475pub fn reconstruct_views (
476476 buffer : & ByteBuffer ,
477477 max_buffer_len : usize ,
478- ) -> ( Vec < ByteBuffer > , Buffer < BinaryView > ) {
478+ ) -> VortexResult < ( Vec < ByteBuffer > , Buffer < BinaryView > ) > {
479479 let mut views = BufferMut :: < BinaryView > :: empty ( ) ;
480480 let mut buffers = Vec :: new ( ) ;
481481 let mut segment_start: usize = 0 ;
@@ -485,7 +485,12 @@ pub fn reconstruct_views(
485485 let str_len = ViewLen :: from_le_bytes (
486486 buffer
487487 . get ( offset..offset + size_of :: < ViewLen > ( ) )
488- . vortex_expect ( "corrupted zstd length" )
488+ . ok_or_else ( || {
489+ vortex_err ! (
490+ "corrupted zstd data: truncated view length prefix at offset {offset} (buffer length {})" ,
491+ buffer. len( )
492+ )
493+ } ) ?
489494 . try_into ( )
490495 . ok ( )
491496 . vortex_expect ( "must fit ViewLen size" ) ,
@@ -502,16 +507,27 @@ pub fn reconstruct_views(
502507 let local_offset = u32:: try_from ( value_data_offset - segment_start)
503508 . vortex_expect ( "local offset within segment must fit in u32" ) ;
504509 let buf_index = u32:: try_from ( buffers. len ( ) ) . vortex_expect ( "buffer index must fit in u32" ) ;
505- let value = & buffer[ value_data_offset..value_data_offset + str_len] ;
510+ let value_end = value_data_offset. checked_add ( str_len) . ok_or_else ( || {
511+ vortex_err ! (
512+ "corrupted zstd data: view of length {str_len} at offset {value_data_offset} exceeds buffer length {}" ,
513+ buffer. len( )
514+ )
515+ } ) ?;
516+ let value = buffer. get ( value_data_offset..value_end) . ok_or_else ( || {
517+ vortex_err ! (
518+ "corrupted zstd data: view of length {str_len} at offset {value_data_offset} exceeds buffer length {}" ,
519+ buffer. len( )
520+ )
521+ } ) ?;
506522 views. push ( BinaryView :: make_view ( value, buf_index, local_offset) ) ;
507- offset = value_data_offset + str_len ;
523+ offset = value_end ;
508524 }
509525
510526 if segment_start < buffer. len ( ) {
511527 buffers. push ( buffer. slice ( segment_start..buffer. len ( ) ) ) ;
512528 }
513529
514- ( buffers, views. freeze ( ) )
530+ Ok ( ( buffers, views. freeze ( ) ) )
515531}
516532
517533impl ZstdData {
@@ -1016,7 +1032,8 @@ impl ZstdData {
10161032 DType :: Binary ( _) | DType :: Utf8 ( _) => {
10171033 match slice_validity. execute_mask ( slice_n_rows, ctx) ?. indices ( ) {
10181034 AllOr :: All => {
1019- let ( buffers, all_views) = reconstruct_views ( & decompressed, MAX_BUFFER_LEN ) ;
1035+ let ( buffers, all_views) =
1036+ reconstruct_views ( & decompressed, MAX_BUFFER_LEN ) ?;
10201037 let valid_views = all_views. slice (
10211038 slice_value_idx_start - n_skipped_values
10221039 ..slice_value_idx_stop - n_skipped_values,
@@ -1039,7 +1056,8 @@ impl ZstdData {
10391056 )
10401057 . into_array ( ) ) ,
10411058 AllOr :: Some ( valid_indices) => {
1042- let ( buffers, all_views) = reconstruct_views ( & decompressed, MAX_BUFFER_LEN ) ;
1059+ let ( buffers, all_views) =
1060+ reconstruct_views ( & decompressed, MAX_BUFFER_LEN ) ?;
10431061 let valid_views = all_views. slice (
10441062 slice_value_idx_start - n_skipped_values
10451063 ..slice_value_idx_stop - n_skipped_values,
@@ -1151,7 +1169,7 @@ mod tests {
11511169 fn test_reconstruct_views_no_split ( ) {
11521170 let strings: & [ & [ u8 ] ] = & [ b"hello" , b"world" ] ;
11531171 let buf = make_interleaved ( strings) ;
1154- let ( buffers, views) = reconstruct_views ( & buf, 1024 ) ;
1172+ let ( buffers, views) = reconstruct_views ( & buf, 1024 ) . unwrap ( ) ;
11551173
11561174 assert_eq ! ( buffers. len( ) , 1 ) ;
11571175 assert_eq ! ( views. len( ) , 2 ) ;
@@ -1168,12 +1186,21 @@ mod tests {
11681186 // so it rolls into a second segment.
11691187 let strings: & [ & [ u8 ] ] = & [ b"aaaaaaaaaaaaa" , b"bbbbbbbbbbbbb" ] ;
11701188 let buf = make_interleaved ( strings) ;
1171- let ( buffers, views) = reconstruct_views ( & buf, 20 ) ;
1189+ let ( buffers, views) = reconstruct_views ( & buf, 20 ) . unwrap ( ) ;
11721190
11731191 assert_eq ! ( buffers. len( ) , 2 ) ;
11741192 assert_eq ! ( views. len( ) , 2 ) ;
11751193 assert_eq ! ( views[ 0 ] , BinaryView :: make_view( b"aaaaaaaaaaaaa" , 0 , 4 ) ) ;
11761194 // Second entry starts a new segment at byte 17 (the length prefix), so local offset = 4.
11771195 assert_eq ! ( views[ 1 ] , BinaryView :: make_view( b"bbbbbbbbbbbbb" , 1 , 4 ) ) ;
11781196 }
1197+
1198+ #[ test]
1199+ fn test_reconstruct_views_corrupt_length_returns_error ( ) {
1200+ let buf = ByteBuffer :: copy_from ( [ 0xff_u8 , 0xff , 0xff , 0x7f , 0x01 , 0x02 ] . as_slice ( ) ) ;
1201+ assert ! ( reconstruct_views( & buf, 1024 ) . is_err( ) ) ;
1202+
1203+ let truncated_prefix = ByteBuffer :: copy_from ( [ 0x01_u8 , 0x02 ] . as_slice ( ) ) ;
1204+ assert ! ( reconstruct_views( & truncated_prefix, 1024 ) . is_err( ) ) ;
1205+ }
11791206}
0 commit comments