Skip to content

Commit fd1c8f0

Browse files
committed
Fixing crashes with malformed compressed data
The Gorilla and Dictionary compressors crashed on malformed data with the reverse decompression iterator and the bulk decompressor. This issue was reported by Mehmet Ince @mdisec https://mehmetince.net/, and the test case binaries are his contribution that I integrated into the fuzzer test suite. This change adds validation for these cases and adds reverse iterator coverage to the fuzzer tests, so the randomised test will catch future issues. It already caught issues in other compressors like deltadelta, which I also fixed in this review.
1 parent 6286ecf commit fd1c8f0

149 files changed

Lines changed: 339 additions & 33 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.unreleased/pr_10360

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fixes: #10360 Decompressor crashes with malformed compressed data
2+
Thanks: @mdisec for reporting the issues with the validation of the compressed data during decompression

src/adts/bit_array.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pg_attribute_always_inline static uint64 bit_array_iter_next(BitArrayIterator *i
3737
static void bit_array_iterator_init_rev(BitArrayIterator *iter, const BitArray *array);
3838
/* return last num_bits in forward-order (not reverse-order); must have been written as num_bits */
3939
static uint64 bit_array_iter_next_rev(BitArrayIterator *iter, uint8 num_bits);
40+
static inline uint64 bit_array_iter_position(const BitArrayIterator *iter);
4041

4142
/* I/O */
4243
static inline void bit_array_send(StringInfo buffer, const BitArray *data);

src/adts/bit_array_impl.h

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,10 @@ bit_array_num_buckets(const BitArray *array)
8585
static inline uint64
8686
bit_array_num_bits(const BitArray *array)
8787
{
88-
return (BITS_PER_BUCKET * (array->buckets.num_elements - UINT64CONST(1))) +
89-
array->bits_used_in_last_bucket;
88+
return array->buckets.num_elements == 0 ?
89+
0 :
90+
(BITS_PER_BUCKET * (array->buckets.num_elements - UINT64CONST(1))) +
91+
array->bits_used_in_last_bucket;
9092
}
9193

9294
static inline uint64 *
@@ -250,6 +252,15 @@ bit_array_iterator_init(BitArrayIterator *iter, const BitArray *array)
250252
};
251253
}
252254

255+
static inline uint64
256+
bit_array_iter_position(const BitArrayIterator *iter)
257+
{
258+
Assert(iter->current_bucket >= 0);
259+
return iter->current_bucket < 0 ?
260+
0 :
261+
(BITS_PER_BUCKET * iter->current_bucket) + iter->bits_used_in_current_bucket;
262+
}
263+
253264
pg_attribute_always_inline static uint64
254265
bit_array_iter_next(BitArrayIterator *iter, uint8 num_bits)
255266
{
@@ -302,7 +313,7 @@ bit_array_iterator_init_rev(BitArrayIterator *iter, const BitArray *array)
302313
{
303314
*iter = (BitArrayIterator){
304315
.array = array,
305-
.current_bucket = array->buckets.num_elements - 1,
316+
.current_bucket = array->buckets.num_elements == 0 ? 0 : array->buckets.num_elements - 1,
306317
.bits_used_in_current_bucket = array->bits_used_in_last_bucket,
307318
};
308319
}

tsl/src/compression/algorithms/deltadelta.c

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,44 @@ delta_delta_compressor_append_value(DeltaDeltaCompressor *compressor, int64 next
481481
/**********************************************************************************/
482482
/**********************************************************************************/
483483

484+
static inline uint32
485+
decompression_iterator_items_seen(const DeltaDeltaDecompressionIterator *iter)
486+
{
487+
if (iter->has_nulls)
488+
{
489+
return iter->nulls.num_elements_returned;
490+
}
491+
else
492+
{
493+
return iter->delta_deltas.num_elements_returned;
494+
}
495+
}
496+
497+
static inline uint32
498+
decompression_iterator_item_count(const DeltaDeltaDecompressionIterator *iter)
499+
{
500+
if (iter->has_nulls)
501+
{
502+
return iter->nulls.num_elements;
503+
}
504+
else
505+
{
506+
return iter->delta_deltas.num_elements;
507+
}
508+
}
509+
510+
static inline uint32
511+
decompression_iterator_values_seen(const DeltaDeltaDecompressionIterator *iter)
512+
{
513+
return iter->delta_deltas.num_elements_returned;
514+
}
515+
516+
static inline uint32
517+
decompression_iterator_value_count(const DeltaDeltaDecompressionIterator *iter)
518+
{
519+
return iter->delta_deltas.num_elements;
520+
}
521+
484522
static void
485523
int64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter, void *compressed,
486524
Oid element_type)
@@ -515,6 +553,7 @@ int64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter,
515553
{
516554
Simple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);
517555
simple8brle_decompression_iterator_init_forward(&iter->nulls, nulls);
556+
CheckCompressedData(deltas->num_elements <= nulls->num_elements);
518557
}
519558
}
520559

@@ -529,7 +568,7 @@ int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter,
529568
DeltaDeltaCompressed *header = consumeCompressedData(&si, sizeof(DeltaDeltaCompressed));
530569
Simple8bRleSerialized *deltas = bytes_deserialize_simple8b_and_advance(&si);
531570

532-
Assert(header->has_nulls == 0 || header->has_nulls == 1);
571+
CheckCompressedData(header->has_nulls == 0 || header->has_nulls == 1);
533572

534573
*iter = (DeltaDeltaDecompressionIterator){
535574
.base = {
@@ -549,6 +588,7 @@ int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter,
549588
{
550589
Simple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);
551590
simple8brle_decompression_iterator_init_reverse(&iter->nulls, nulls);
591+
CheckCompressedData(deltas->num_elements <= nulls->num_elements);
552592
}
553593
}
554594

@@ -615,6 +655,12 @@ delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompres
615655
simple8brle_decompression_iterator_try_next_forward(&iter->nulls);
616656
if (result.is_done)
617657
{
658+
/* make sure we exhausted all items before */
659+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
660+
decompression_iterator_item_count(iter));
661+
/* and also that we returned all values */
662+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
663+
decompression_iterator_value_count(iter));
618664
return (DecompressResultInternal){
619665
.is_done = true,
620666
};
@@ -633,6 +679,12 @@ delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompres
633679

634680
if (result.is_done)
635681
{
682+
/* make sure we exhausted all items before */
683+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
684+
decompression_iterator_item_count(iter));
685+
/* and also that we returned all values */
686+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
687+
decompression_iterator_value_count(iter));
636688
return (DecompressResultInternal){
637689
.is_done = true,
638690
};
@@ -712,14 +764,20 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
712764
simple8brle_decompression_iterator_try_next_reverse(&iter->nulls);
713765
if (result.is_done)
714766
{
767+
/* make sure we exhausted all items before */
768+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
769+
decompression_iterator_item_count(iter));
770+
/* and also that we returned all values */
771+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
772+
decompression_iterator_value_count(iter));
715773
return (DecompressResultInternal){
716774
.is_done = true,
717775
};
718776
}
719777

720778
if (result.val != 0)
721779
{
722-
Assert(result.val == 1);
780+
CheckCompressedData(result.val == 1);
723781
return (DecompressResultInternal){
724782
.is_null = true,
725783
};
@@ -730,6 +788,12 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
730788

731789
if (result.is_done)
732790
{
791+
/* make sure we exhausted all items before */
792+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
793+
decompression_iterator_item_count(iter));
794+
/* and also that we returned all values */
795+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
796+
decompression_iterator_value_count(iter));
733797
return (DecompressResultInternal){
734798
.is_done = true,
735799
};

tsl/src/compression/algorithms/dictionary.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -719,7 +719,8 @@ tsl_text_dictionary_decompress_all(Datum compressed, Oid element_type, MemoryCon
719719
bool have_incorrect_index = false;
720720
for (uint32 i = 0; i < n_notnull; i++)
721721
{
722-
have_incorrect_index = have_incorrect_index || indices[i] >= (int16) header->num_distinct;
722+
have_incorrect_index =
723+
have_incorrect_index || indices[i] >= (int16) header->num_distinct || indices[i] < 0;
723724
}
724725
CheckCompressedData(!have_incorrect_index);
725726

@@ -897,7 +898,7 @@ dictionary_decompression_iterator_try_next_reverse(DecompressionIterator *iter_b
897898
};
898899
}
899900

900-
Assert(result.val < iter->compressed->num_distinct);
901+
CheckCompressedData(result.val < iter->compressed->num_distinct);
901902
return (DecompressResult){
902903
.val = iter->values[result.val],
903904
.is_null = false,

tsl/src/compression/algorithms/gorilla.c

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,44 @@ gorilla_compressor_finish(GorillaCompressor *compressor)
519519
*** DecompressionIterator ***
520520
*******************************/
521521

522+
static inline uint32
523+
decompression_iterator_items_seen(const GorillaDecompressionIterator *iter)
524+
{
525+
if (iter->has_nulls)
526+
{
527+
return iter->nulls.num_elements_returned;
528+
}
529+
else
530+
{
531+
return iter->tag0s.num_elements_returned;
532+
}
533+
}
534+
535+
static inline uint32
536+
decompression_iterator_item_count(const GorillaDecompressionIterator *iter)
537+
{
538+
if (iter->has_nulls)
539+
{
540+
return iter->nulls.num_elements;
541+
}
542+
else
543+
{
544+
return iter->tag0s.num_elements;
545+
}
546+
}
547+
548+
static inline uint32
549+
decompression_iterator_values_seen(const GorillaDecompressionIterator *iter)
550+
{
551+
return iter->tag0s.num_elements_returned;
552+
}
553+
554+
static inline uint32
555+
decompression_iterator_value_count(const GorillaDecompressionIterator *iter)
556+
{
557+
return iter->tag0s.num_elements;
558+
}
559+
522560
inline static void
523561
bytes_attach_bit_array_and_advance(BitArray *dst, StringInfo si, uint32 num_buckets,
524562
uint8 bits_in_last_bucket)
@@ -560,11 +598,29 @@ compressed_gorilla_data_init_from_stringinfo(CompressedGorillaData *expanded, St
560598
if (has_nulls)
561599
{
562600
expanded->nulls = bytes_deserialize_simple8b_and_advance(si);
601+
CheckCompressedData(expanded->nulls->num_elements >=
602+
expanded->num_bits_used_per_xor->num_elements);
603+
CheckCompressedData(expanded->nulls->num_elements >= expanded->tag0s->num_elements);
604+
CheckCompressedData(expanded->nulls->num_elements >= expanded->tag1s->num_elements);
563605
}
564606
else
565607
{
566608
expanded->nulls = NULL;
567609
}
610+
611+
/* XOR bit count must be reasonable */
612+
uint64 xor_bit_count = bit_array_num_bits(&expanded->xors);
613+
CheckCompressedData(xor_bit_count <= expanded->tag0s->num_elements * 64);
614+
615+
/* leading zeros have a fixed size and we need at least one item */
616+
uint64 num_leading_zeros_bits = bit_array_num_bits(&expanded->leading_zeros);
617+
CheckCompressedData(num_leading_zeros_bits >= BITS_PER_LEADING_ZEROS &&
618+
num_leading_zeros_bits % BITS_PER_LEADING_ZEROS == 0 &&
619+
num_leading_zeros_bits <=
620+
expanded->tag0s->num_elements * BITS_PER_LEADING_ZEROS);
621+
622+
/* tag bits must be reasonable too */
623+
CheckCompressedData(expanded->tag0s->num_elements >= expanded->tag1s->num_elements);
568624
}
569625

570626
static void
@@ -672,6 +728,12 @@ gorilla_decompression_iterator_try_next_forward_internal(GorillaDecompressionIte
672728
/* Could slightly improve performance here by not returning a tail of non-null bits */
673729
if (null.is_done)
674730
{
731+
/* make sure we exhausted all items before */
732+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
733+
decompression_iterator_item_count(iter));
734+
/* and also that we returned all values */
735+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
736+
decompression_iterator_value_count(iter));
675737
return (DecompressResultInternal){
676738
.is_done = true,
677739
};
@@ -689,6 +751,12 @@ gorilla_decompression_iterator_try_next_forward_internal(GorillaDecompressionIte
689751
/* if we don't have a null bitset, this will determine when we're done */
690752
if (tag0.is_done)
691753
{
754+
/* make sure we exhausted all items before */
755+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
756+
decompression_iterator_item_count(iter));
757+
/* and also that we returned all values */
758+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
759+
decompression_iterator_value_count(iter));
692760
CheckCompressedData(!iter->has_nulls);
693761
return (DecompressResultInternal){
694762
.is_done = true,
@@ -799,7 +867,7 @@ gorilla_decompression_iterator_from_datum_reverse(Datum gorilla_compressed, Oid
799867
iter->prev_leading_zeroes =
800868
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);
801869
num_xor_bits = simple8brle_decompression_iterator_try_next_reverse(&iter->num_bits_used);
802-
Assert(!num_xor_bits.is_done);
870+
CheckCompressedData(!num_xor_bits.is_done);
803871
iter->prev_xor_bits_used = num_xor_bits.val;
804872
iter->prev_val = iter->gorilla_data.header->last_value;
805873
return &iter->base;
@@ -820,6 +888,12 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
820888

821889
if (null.is_done)
822890
{
891+
/* make sure we exhausted all items before */
892+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
893+
decompression_iterator_item_count(iter));
894+
/* and also that we returned all values */
895+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
896+
decompression_iterator_value_count(iter));
823897
return (DecompressResultInternal){
824898
.is_done = true,
825899
};
@@ -839,6 +913,12 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
839913
/* if we don't have a null bitset, this will determine when we're done */
840914
if (tag0.is_done)
841915
{
916+
/* make sure we exhausted all items before */
917+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
918+
decompression_iterator_item_count(iter));
919+
/* and also that we returned all values */
920+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
921+
decompression_iterator_value_count(iter));
842922
return (DecompressResultInternal){
843923
.is_done = true,
844924
};
@@ -851,6 +931,13 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
851931
};
852932
}
853933

934+
/* make sure we are in a valid state */
935+
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
936+
CheckCompressedData(!tag1.is_done);
937+
938+
/* check that we have this much to read */
939+
CheckCompressedData(iter->prev_leading_zeroes + iter->prev_xor_bits_used > 0);
940+
CheckCompressedData(bit_array_iter_position(&iter->xors) >= iter->prev_xor_bits_used);
854941
xor = bit_array_iter_next_rev(&iter->xors, iter->prev_xor_bits_used);
855942

856943
if (iter->prev_leading_zeroes + iter->prev_xor_bits_used < 64)
@@ -859,8 +946,6 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
859946
}
860947
iter->prev_val ^= xor;
861948

862-
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
863-
864949
if (tag1.val != 0)
865950
{
866951
/* get new xor sizes */
@@ -875,9 +960,15 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
875960
}
876961
else
877962
{
963+
/* make sure we have enough bits to read for the leading zeros */
964+
CheckCompressedData(bit_array_iter_position(&iter->leading_zeros) >=
965+
BITS_PER_LEADING_ZEROS);
878966
iter->prev_xor_bits_used = num_xor_bits.val;
879967
iter->prev_leading_zeroes =
880968
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);
969+
970+
/* more than 64 bits of data doesn't make sense */
971+
CheckCompressedData(iter->prev_leading_zeroes + iter->prev_xor_bits_used <= 64);
881972
}
882973
}
883974

0 commit comments

Comments
 (0)