Skip to content

Commit a47a31c

Browse files
committed
Fixing crashes with malformed compressed data
The Gorilla and Decitionary compressor crashed on malformed data with the reverse decompression iterator and the bulk decompressor. This change adds validation for these cases and adds reverse iterator coverage to the fuzzer tests, so the randomised test will catch future issues. This issue was reported by Mehmet Ince @mdisec https://mehmetince.net/, and the test cases are his contribution with tiny modifications to fit them into the test suite.
1 parent 5eb19af commit a47a31c

14 files changed

Lines changed: 219 additions & 24 deletions

File tree

.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: 13 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,10 @@ 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+
854863
xor = bit_array_iter_next_rev(&iter->xors, iter->prev_xor_bits_used);
855864

856865
if (iter->prev_leading_zeroes + iter->prev_xor_bits_used < 64)
@@ -859,8 +868,6 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
859868
}
860869
iter->prev_val ^= xor;
861870

862-
tag1 = simple8brle_decompression_iterator_try_next_reverse(&iter->tag1s);
863-
864871
if (tag1.val != 0)
865872
{
866873
/* get new xor sizes */
@@ -875,6 +882,9 @@ gorilla_decompression_iterator_try_next_reverse_internal(GorillaDecompressionIte
875882
}
876883
else
877884
{
885+
/* make sure we have enough bits to read for the leading zeros */
886+
CheckCompressedData(bit_array_iter_position(&iter->leading_zeros) >=
887+
BITS_PER_LEADING_ZEROS);
878888
iter->prev_xor_bits_used = num_xor_bits.val;
879889
iter->prev_leading_zeroes =
880890
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: 10 additions & 8 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+
221 | 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,9 +2167,9 @@ 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+
67 | XX000 | XX000
2172+
35 | XX001 | XX000
21722173
13 | 08P01 | 08P01
21732174
1 | false | false
21742175

@@ -2218,8 +2219,9 @@ group by 2, 3 order by 1 desc
22182219
count | bulk_result | rowbyrow_result
22192220
-------+-------------+-----------------
22202221
18 | XX001 | XX001
2221-
3 | true | true
22222222
2 | 08P01 | 08P01
2223+
2 | XX000 | XX000
2224+
1 | true | true
22232225

22242226
\set algo uuid
22252227
\set type uuid
@@ -2233,6 +2235,6 @@ group by 2, 3 order by 1 desc
22332235
-------+-------------+-----------------
22342236
49 | XX001 | XX001
22352237
2 | 08P01 | 08P01
2236-
1 | true | XX001
2238+
1 | XX000 | XX000
22372239
1 | true | true
22382240

Binary file not shown.

0 commit comments

Comments
 (0)