Skip to content

Commit 517c13e

Browse files
committed
Fix 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 800fa71 commit 517c13e

70 files changed

Lines changed: 504 additions & 36 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: 112 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,11 @@ typedef struct DeltaDeltaDecompressionIterator
6060
Simple8bRleDecompressionIterator delta_deltas;
6161
Simple8bRleDecompressionIterator nulls;
6262
bool has_nulls;
63+
64+
/* for validating the last_value set in the header
65+
* and cross check with the iterator */
66+
uint64 last_value_to_return;
67+
uint64 last_returned_value;
6368
} DeltaDeltaDecompressionIterator;
6469

6570
typedef struct DeltaDeltaCompressor
@@ -481,6 +486,44 @@ delta_delta_compressor_append_value(DeltaDeltaCompressor *compressor, int64 next
481486
/**********************************************************************************/
482487
/**********************************************************************************/
483488

489+
static inline uint32
490+
decompression_iterator_items_seen(const DeltaDeltaDecompressionIterator *iter)
491+
{
492+
if (iter->has_nulls)
493+
{
494+
return iter->nulls.num_elements_returned;
495+
}
496+
else
497+
{
498+
return iter->delta_deltas.num_elements_returned;
499+
}
500+
}
501+
502+
static inline uint32
503+
decompression_iterator_item_count(const DeltaDeltaDecompressionIterator *iter)
504+
{
505+
if (iter->has_nulls)
506+
{
507+
return iter->nulls.num_elements;
508+
}
509+
else
510+
{
511+
return iter->delta_deltas.num_elements;
512+
}
513+
}
514+
515+
static inline uint32
516+
decompression_iterator_values_seen(const DeltaDeltaDecompressionIterator *iter)
517+
{
518+
return iter->delta_deltas.num_elements_returned;
519+
}
520+
521+
static inline uint32
522+
decompression_iterator_value_count(const DeltaDeltaDecompressionIterator *iter)
523+
{
524+
return iter->delta_deltas.num_elements;
525+
}
526+
484527
static void
485528
int64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter, void *compressed,
486529
Oid element_type)
@@ -507,6 +550,8 @@ int64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter,
507550
.prev_val = 0,
508551
.prev_delta = 0,
509552
.has_nulls = has_nulls,
553+
.last_returned_value = 0,
554+
.last_value_to_return = header->last_value,
510555
};
511556

512557
simple8brle_decompression_iterator_init_forward(&iter->delta_deltas, deltas);
@@ -515,9 +560,13 @@ int64_decompression_iterator_init_forward(DeltaDeltaDecompressionIterator *iter,
515560
{
516561
Simple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);
517562
simple8brle_decompression_iterator_init_forward(&iter->nulls, nulls);
563+
CheckCompressedData(deltas->num_elements <= nulls->num_elements);
518564
}
519565
}
520566

567+
static DecompressResultInternal
568+
delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompressionIterator *iter);
569+
521570
static void
522571
int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter, void *compressed,
523572
Oid element_type)
@@ -529,7 +578,7 @@ int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter,
529578
DeltaDeltaCompressed *header = consumeCompressedData(&si, sizeof(DeltaDeltaCompressed));
530579
Simple8bRleSerialized *deltas = bytes_deserialize_simple8b_and_advance(&si);
531580

532-
Assert(header->has_nulls == 0 || header->has_nulls == 1);
581+
CheckCompressedData(header->has_nulls == 0 || header->has_nulls == 1);
533582

534583
*iter = (DeltaDeltaDecompressionIterator){
535584
.base = {
@@ -541,6 +590,8 @@ int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter,
541590
.prev_val = header->last_value,
542591
.prev_delta = header->last_delta,
543592
.has_nulls = header->has_nulls,
593+
.last_returned_value = 0,
594+
.last_value_to_return = 0,
544595
};
545596

546597
simple8brle_decompression_iterator_init_reverse(&iter->delta_deltas, deltas);
@@ -549,6 +600,31 @@ int64_decompression_iterator_init_reverse(DeltaDeltaDecompressionIterator *iter,
549600
{
550601
Simple8bRleSerialized *nulls = bytes_deserialize_simple8b_and_advance(&si);
551602
simple8brle_decompression_iterator_init_reverse(&iter->nulls, nulls);
603+
CheckCompressedData(deltas->num_elements <= nulls->num_elements);
604+
}
605+
606+
/* on reverse iteration the `last_value` in the header is critical for
607+
* the rest of the decoding. when we receive malformed data we can only
608+
* cross reference the result with the first value
609+
*/
610+
{
611+
DeltaDeltaDecompressionIterator forward_iter;
612+
forward_iter.base.compression_algorithm = COMPRESSION_ALGORITHM_DELTADELTA;
613+
forward_iter.base.forward = true;
614+
forward_iter.base.element_type = element_type;
615+
forward_iter.base.try_next = delta_delta_decompression_iterator_try_next_forward;
616+
int64_decompression_iterator_init_forward(&forward_iter, compressed, element_type);
617+
/* find the first non-null value */
618+
DecompressResultInternal first_val =
619+
delta_delta_decompression_iterator_try_next_forward_internal(&forward_iter);
620+
int32 n = 0;
621+
while (first_val.is_done == false && first_val.is_null)
622+
{
623+
CheckCompressedData(n++ < GLOBAL_MAX_ROWS_PER_COMPRESSION);
624+
first_val = delta_delta_decompression_iterator_try_next_forward_internal(&forward_iter);
625+
}
626+
CheckCompressedData(!first_val.is_done);
627+
iter->last_value_to_return = first_val.val;
552628
}
553629
}
554630

@@ -615,6 +691,14 @@ delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompres
615691
simple8brle_decompression_iterator_try_next_forward(&iter->nulls);
616692
if (result.is_done)
617693
{
694+
/* make sure we exhausted all items before */
695+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
696+
decompression_iterator_item_count(iter));
697+
/* and also that we returned all values */
698+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
699+
decompression_iterator_value_count(iter));
700+
/* the last element must match the expected value */
701+
CheckCompressedData(iter->last_returned_value == iter->last_value_to_return);
618702
return (DecompressResultInternal){
619703
.is_done = true,
620704
};
@@ -633,6 +717,14 @@ delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompres
633717

634718
if (result.is_done)
635719
{
720+
/* make sure we exhausted all items before */
721+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
722+
decompression_iterator_item_count(iter));
723+
/* and also that we returned all values */
724+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
725+
decompression_iterator_value_count(iter));
726+
/* the last element must match the expected value */
727+
CheckCompressedData(iter->last_returned_value == iter->last_value_to_return);
636728
return (DecompressResultInternal){
637729
.is_done = true,
638730
};
@@ -642,6 +734,7 @@ delta_delta_decompression_iterator_try_next_forward_internal(DeltaDeltaDecompres
642734

643735
iter->prev_delta += delta_delta;
644736
iter->prev_val += iter->prev_delta;
737+
iter->last_returned_value = iter->prev_val;
645738

646739
return (DecompressResultInternal){
647740
.val = iter->prev_val,
@@ -712,14 +805,22 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
712805
simple8brle_decompression_iterator_try_next_reverse(&iter->nulls);
713806
if (result.is_done)
714807
{
808+
/* make sure we exhausted all items before */
809+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
810+
decompression_iterator_item_count(iter));
811+
/* and also that we returned all values */
812+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
813+
decompression_iterator_value_count(iter));
814+
/* the last element must match the expected value */
815+
CheckCompressedData(iter->last_returned_value == iter->last_value_to_return);
715816
return (DecompressResultInternal){
716817
.is_done = true,
717818
};
718819
}
719820

720821
if (result.val != 0)
721822
{
722-
Assert(result.val == 1);
823+
CheckCompressedData(result.val == 1);
723824
return (DecompressResultInternal){
724825
.is_null = true,
725826
};
@@ -730,6 +831,14 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
730831

731832
if (result.is_done)
732833
{
834+
/* make sure we exhausted all items before */
835+
CheckCompressedData(decompression_iterator_items_seen(iter) ==
836+
decompression_iterator_item_count(iter));
837+
/* and also that we returned all values */
838+
CheckCompressedData(decompression_iterator_values_seen(iter) ==
839+
decompression_iterator_value_count(iter));
840+
/* the last element must match the expected value */
841+
CheckCompressedData(iter->last_returned_value == iter->last_value_to_return);
733842
return (DecompressResultInternal){
734843
.is_done = true,
735844
};
@@ -740,6 +849,7 @@ delta_delta_decompression_iterator_try_next_reverse_internal(DeltaDeltaDecompres
740849
delta_delta = zig_zag_decode(result.val);
741850
iter->prev_val -= iter->prev_delta;
742851
iter->prev_delta -= delta_delta;
852+
iter->last_returned_value = val;
743853

744854
return (DecompressResultInternal){
745855
.val = val,

tsl/src/compression/algorithms/deltadelta_impl.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ FUNCTION_NAME(delta_delta_decompress_all, ELEMENT_TYPE)(Datum compressed, Memory
9090
#undef INNER_LOOP_SIZE
9191

9292
uint64 *restrict validity_bitmap = NULL;
93+
ELEMENT_TYPE last_value = decompressed_values[n_total - 1];
9394
if (has_nulls)
9495
{
9596
/* Now move the data to account for nulls, and fill the validity bitmap. */
@@ -117,6 +118,7 @@ FUNCTION_NAME(delta_delta_decompress_all, ELEMENT_TYPE)(Datum compressed, Memory
117118
CheckCompressedData(n_notnull + simple8brle_bitmap_num_ones(&nulls) == n_total);
118119

119120
int current_notnull_element = n_notnull - 1;
121+
last_value = decompressed_values[current_notnull_element];
120122
for (int i = n_total - 1; i >= 0; i--)
121123
{
122124
Assert(i >= current_notnull_element);
@@ -136,6 +138,9 @@ FUNCTION_NAME(delta_delta_decompress_all, ELEMENT_TYPE)(Datum compressed, Memory
136138
Assert(current_notnull_element == -1);
137139
}
138140

141+
/* the last value in the header must match with the last that we returned */
142+
CheckCompressedData(last_value == (ELEMENT_TYPE) header->last_value);
143+
139144
/* Return the result. */
140145
ArrowArray *result = MemoryContextAllocZero(dest_mctx, sizeof(ArrowArray) + sizeof(void *) * 2);
141146
const void **buffers = (const void **) &result[1];

0 commit comments

Comments
 (0)