Skip to content

Commit 28c4bd4

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 5eb19af commit 28c4bd4

33 files changed

Lines changed: 233 additions & 25 deletions

.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: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,12 @@ bit_array_iterator_init(BitArrayIterator *iter, const BitArray *array)
250250
};
251251
}
252252

253+
static inline uint64
254+
bit_array_iter_position(const BitArrayIterator *iter)
255+
{
256+
return (BITS_PER_BUCKET * iter->current_bucket) + iter->bits_used_in_current_bucket;
257+
}
258+
253259
pg_attribute_always_inline static uint64
254260
bit_array_iter_next(BitArrayIterator *iter, uint8 num_bits)
255261
{

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: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,10 +796,15 @@ gorilla_decompression_iterator_from_datum_reverse(Datum gorilla_compressed, Oid
796796
}
797797

798798
/* we need to know how many bits are used, even if the last value didn't store them */
799+
int num_leading_zeros_bits = bit_array_num_bits(&iter->gorilla_data.leading_zeros);
800+
801+
/* leading zeros have a fixed size and we need at least one item */
802+
CheckCompressedData(num_leading_zeros_bits >= BITS_PER_LEADING_ZEROS &&
803+
num_leading_zeros_bits % BITS_PER_LEADING_ZEROS == 0);
799804
iter->prev_leading_zeroes =
800805
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);
801806
num_xor_bits = simple8brle_decompression_iterator_try_next_reverse(&iter->num_bits_used);
802-
Assert(!num_xor_bits.is_done);
807+
CheckCompressedData(!num_xor_bits.is_done);
803808
iter->prev_xor_bits_used = num_xor_bits.val;
804809
iter->prev_val = iter->gorilla_data.header->last_value;
805810
return &iter->base;
@@ -851,6 +856,12 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
851856
};
852857
}
853858

859+
/* make sure we are in a valid state */
860+
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
861+
CheckCompressedData(!tag1.is_done);
862+
863+
/* check that we have this much to read */
864+
CheckCompressedData(bit_array_iter_position(&iter->xors) >= iter->prev_xor_bits_used);
854865
xor = bit_array_iter_next_rev(&iter->xors, iter->prev_xor_bits_used);
855866

856867
if (iter->prev_leading_zeroes + iter->prev_xor_bits_used < 64)
@@ -859,8 +870,6 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
859870
}
860871
iter->prev_val ^= xor;
861872

862-
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
863-
864873
if (tag1.val != 0)
865874
{
866875
/* get new xor sizes */
@@ -875,6 +884,9 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
875884
}
876885
else
877886
{
887+
/* make sure we have enough bits to read for the leading zeros */
888+
CheckCompressedData(bit_array_iter_position(&iter->leading_zeros) >=
889+
BITS_PER_LEADING_ZEROS);
878890
iter->prev_xor_bits_used = num_xor_bits.val;
879891
iter->prev_leading_zeroes =
880892
bit_array_iter_next_rev(&iter->leading_zeros, BITS_PER_LEADING_ZEROS);

tsl/src/compression/algorithms/simple8b_rle.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#pragma once
77

88
#include <postgres.h>
9+
#include "compression/compression.h"
910
#include <c.h>
1011
#include <fmgr.h>
1112
#include <lib/stringinfo.h>
@@ -1007,12 +1008,12 @@ simple8brle_decompression_iterator_max_elements(Simple8bRleDecompressionIterator
10071008

10081009
if (simple8brle_selector_is_rle(selector) && iter->compressed_data)
10091010
{
1010-
Assert(simple8brle_rledata_repeatcount(iter->compressed_data[i]) > 0);
1011+
CheckCompressedData(simple8brle_rledata_repeatcount(iter->compressed_data[i]) > 0);
10111012
max_stored += simple8brle_rledata_repeatcount(iter->compressed_data[i]);
10121013
}
10131014
else
10141015
{
1015-
Assert(selector < SIMPLE8B_MAXCODE);
1016+
CheckCompressedData(selector < SIMPLE8B_MAXCODE);
10161017
max_stored += SIMPLE8B_NUM_ELEMENTS[selector];
10171018
}
10181019
}
@@ -1029,6 +1030,7 @@ simple8brle_decompression_iterator_init_reverse(Simple8bRleDecompressionIterator
10291030
skipped_in_last = simple8brle_decompression_iterator_max_elements(iter, compressed) -
10301031
compressed->num_elements;
10311032

1033+
CheckCompressedData(skipped_in_last >= 0);
10321034
Assert(NULL != iter->compressed_data);
10331035

10341036
iter->current_block =
@@ -1098,6 +1100,9 @@ simple8brle_decompression_iterator_try_next_reverse(Simple8bRleDecompressionIter
10981100
simple8brle_block_create(bit_array_iter_next_rev(&iter->selectors,
10991101
SIMPLE8B_BITS_PER_SELECTOR),
11001102
iter->compressed_data[iter->current_compressed_pos]);
1103+
CheckCompressedData(iter->current_block.selector != 0);
1104+
CheckCompressedData(iter->current_block.num_elements_compressed <=
1105+
GLOBAL_MAX_ROWS_PER_COMPRESSION);
11011106
iter->current_in_compressed_pos = iter->current_block.num_elements_compressed - 1;
11021107
iter->current_compressed_pos -= 1;
11031108
}

tsl/src/compression/algorithms/uuid_compress.c

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -662,12 +662,15 @@ uuid_decompress_all(Datum compressed, Oid element_type, MemoryContext dest_mctx)
662662
{
663663
if (arrow_row_is_valid(validity_bitmap, i))
664664
{
665-
Assert(value_position < num_values);
665+
CheckCompressedData(value_position < num_values);
666666
uuid_buffer[i].components[0] = pg_ntoh64(timestamp_values[i]);
667667
uuid_buffer[i].components[1] = rand_b_and_variant[value_position];
668668
++value_position;
669669
}
670670
}
671+
672+
/* check that we returned all values */
673+
CheckCompressedData(value_position == num_values);
671674
}
672675
else
673676
{

tsl/test/expected/compression_algos.out

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2151,10 +2151,11 @@ group by 2, 3 order by 1 desc
21512151
;
21522152
count | bulk_result | rowbyrow_result
21532153
-------+-------------+-----------------
2154-
144 | XX001 | true
2155-
93 | XX001 | XX001
2156-
54 | true | true
2154+
222 | XX001 | XX001
2155+
43 | XX000 | XX000
2156+
26 | XX001 | XX000
21572157
24 | 08P01 | 08P01
2158+
1 | true | true
21582159

21592160
\set algo deltadelta
21602161
\set type int8
@@ -2166,11 +2167,12 @@ group by 2, 3 order by 1 desc
21662167
;
21672168
count | bulk_result | rowbyrow_result
21682169
-------+-------------+-----------------
2169-
108 | XX001 | XX001
2170-
69 | true | true
2171-
68 | XX001 | true
2170+
143 | XX001 | XX001
2171+
66 | XX000 | XX000
2172+
35 | XX001 | XX000
21722173
13 | 08P01 | 08P01
21732174
1 | false | false
2175+
1 | true | true
21742176

21752177
\set algo array
21762178
\set type text
@@ -2199,11 +2201,12 @@ group by 2, 3 order by 1 desc
21992201
;
22002202
count | bulk_result | rowbyrow_result
22012203
-------+-------------+-----------------
2202-
84 | XX001 | XX001
2203-
13 | XX001 | true
2204-
5 | 08P01 | 08P01
2204+
88 | XX001 | XX001
2205+
9 | XX001 | XX000
22052206
5 | true | true
2207+
5 | 08P01 | 08P01
22062208
4 | 22021 | 22021
2209+
2 | XX001 | true
22072210
1 | 3F000 | 3F000
22082211
1 | false | false
22092212

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:

0 commit comments

Comments
 (0)