Skip to content

Commit 0a7e31d

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 35b093b commit 0a7e31d

61 files changed

Lines changed: 320 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: 60 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,9 @@ 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));
636685
return (DecompressResultInternal){
637686
.is_done = true,
638687
};
@@ -712,14 +761,20 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
712761
simple8brle_decompression_iterator_try_next_reverse(&iter->nulls);
713762
if (result.is_done)
714763
{
764+
/* make sure we exhausted all items before */
765+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
766+
decompression_iterator_item_count(iter));
767+
/* and also that we returned all values */
768+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
769+
decompression_iterator_value_count(iter));
715770
return (DecompressResultInternal){
716771
.is_done = true,
717772
};
718773
}
719774

720775
if (result.val != 0)
721776
{
722-
Assert(result.val == 1);
777+
CheckCompressedData(result.val == 1);
723778
return (DecompressResultInternal){
724779
.is_null = true,
725780
};
@@ -730,6 +785,9 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
730785

731786
if (result.is_done)
732787
{
788+
/* make sure we exhausted all items before */
789+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
790+
decompression_iterator_item_count(iter));
733791
return (DecompressResultInternal){
734792
.is_done = true,
735793
};

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: 88 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,10 @@ 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+
692758
CheckCompressedData(!iter->has_nulls);
693759
return (DecompressResultInternal){
694760
.is_done = true,
@@ -799,7 +865,7 @@ gorilla_decompression_iterator_from_datum_reverse(Datum gorilla_compressed, Oid
799865
iter->prev_leading_zeroes =
800866
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);
801867
num_xor_bits = simple8brle_decompression_iterator_try_next_reverse(&iter->num_bits_used);
802-
Assert(!num_xor_bits.is_done);
868+
CheckCompressedData(!num_xor_bits.is_done);
803869
iter->prev_xor_bits_used = num_xor_bits.val;
804870
iter->prev_val = iter->gorilla_data.header->last_value;
805871
return &iter->base;
@@ -820,6 +886,12 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
820886

821887
if (null.is_done)
822888
{
889+
/* make sure we exhausted all items before */
890+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
891+
decompression_iterator_item_count(iter));
892+
/* and also that we returned all values */
893+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
894+
decompression_iterator_value_count(iter));
823895
return (DecompressResultInternal){
824896
.is_done = true,
825897
};
@@ -839,6 +911,9 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
839911
/* if we don't have a null bitset, this will determine when we're done */
840912
if (tag0.is_done)
841913
{
914+
/* make sure we exhausted all items before */
915+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
916+
decompression_iterator_item_count(iter));
842917
return (DecompressResultInternal){
843918
.is_done = true,
844919
};
@@ -851,6 +926,12 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
851926
};
852927
}
853928

929+
/* make sure we are in a valid state */
930+
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
931+
CheckCompressedData(!tag1.is_done);
932+
933+
/* check that we have this much to read */
934+
CheckCompressedData(bit_array_iter_position(&iter->xors) >= iter->prev_xor_bits_used);
854935
xor = bit_array_iter_next_rev(&iter->xors, iter->prev_xor_bits_used);
855936

856937
if (iter->prev_leading_zeroes + iter->prev_xor_bits_used < 64)
@@ -859,8 +940,6 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
859940
}
860941
iter->prev_val ^= xor;
861942

862-
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
863-
864943
if (tag1.val != 0)
865944
{
866945
/* get new xor sizes */
@@ -875,9 +954,15 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
875954
}
876955
else
877956
{
957+
/* make sure we have enough bits to read for the leading zeros */
958+
CheckCompressedData(bit_array_iter_position(&iter->leading_zeros) >=
959+
BITS_PER_LEADING_ZEROS);
878960
iter->prev_xor_bits_used = num_xor_bits.val;
879961
iter->prev_leading_zeroes =
880962
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);
963+
964+
/* more than 64 bits of data doesn't make sense */
965+
CheckCompressedData(iter->prev_leading_zeroes + iter->prev_xor_bits_used <= 64);
881966
}
882967
}
883968

0 commit comments

Comments
 (0)