From 6fb56a46f6b2e091515b09e19c5ea54e56ba29d2 Mon Sep 17 00:00:00 2001 From: Sharad Boni Date: Wed, 15 Apr 2026 15:43:06 -0700 Subject: [PATCH] Fix unchecked index bounds in decoder paths Three security fixes for out-of-bounds access and infinite loop bugs reachable from crafted input files: 1. Stack buffer overflow in ComputeDequantControlFieldMapMask: decoded dequant_cf pixel values (uint8_t, range 0-255) are used directly as indices into a 16-entry array (table_mask[kMaxQuantControlFieldValue]). A value >= 16 corrupts the stack. Fixed by validating decoded values against kMaxQuantControlFieldValue in DecodeDequantControlField. 2. Heap OOB write in DecodeQuantField: ac_strategy covered_blocks_x/y values are used to write into quant_field rows without verifying the write stays within image bounds. Fixed by checking that by + covered_blocks_y <= ysize and bx + covered_blocks_x <= xsize before the write loops. 3. Infinite loop in ANS histogram RLE parsing: an RLE length of 0 or 1 causes the loop counter to go backwards (i += rle_length - 2), which with repeated RLE symbols creates a non-terminating loop. Fixed by rejecting rle_length < 2 as invalid. --- pik/ans_decode.cc | 3 +++ pik/entropy_coder.cc | 8 ++++++++ pik/quant_weights.cc | 3 +++ 3 files changed, 14 insertions(+) diff --git a/pik/ans_decode.cc b/pik/ans_decode.cc index a3746f0..0461e5c 100644 --- a/pik/ans_decode.cc +++ b/pik/ans_decode.cc @@ -96,6 +96,9 @@ Status ReadHistogram(int precision_bits, std::vector* counts, input->FillBitBuffer(); int rle_length = input->PeekFixedBits<8>(); input->Advance(8); + if (rle_length < 2) { + return PIK_FAILURE("Invalid RLE length in histogram"); + } same[i] = rle_length; i += rle_length - 2; continue; diff --git a/pik/entropy_coder.cc b/pik/entropy_coder.cc index 615397b..44ef62a 100644 --- a/pik/entropy_coder.cc +++ b/pik/entropy_coder.cc @@ -973,6 +973,10 @@ bool DecodeQuantField(BitReader* PIK_RESTRICT br, } else { row_quant[bx] = UnpackSigned(q) + predicted_quant; } + if (by + acs.covered_blocks_y() > ysize || + bx + acs.covered_blocks_x() > xsize) { + return PIK_FAILURE("AC strategy extends outside quant field"); + } for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) { for (size_t ix = 0; ix < acs.covered_blocks_x(); ix++) { row_quant[bx + iy * stride + ix] = row_quant[bx]; @@ -998,6 +1002,10 @@ bool DecodeQuantField(BitReader* PIK_RESTRICT br, } else { row_quant[bx] = UnpackSigned(q) + predicted_quant; } + if (by + acs.covered_blocks_y() > ysize || + bx + acs.covered_blocks_x() > xsize) { + return PIK_FAILURE("AC strategy extends outside quant field"); + } for (size_t iy = 0; iy < acs.covered_blocks_y(); iy++) { for (size_t ix = 0; ix < acs.covered_blocks_x(); ix++) { row_quant[bx + iy * stride + ix] = row_quant[bx]; diff --git a/pik/quant_weights.cc b/pik/quant_weights.cc index f7dc81e..2bb5620 100644 --- a/pik/quant_weights.cc +++ b/pik/quant_weights.cc @@ -951,6 +951,9 @@ bool DecodeDequantControlField(BitReader* PIK_RESTRICT br, for (size_t x = 0; x < dequant_cf->xsize(); ++x) { br->FillBitBuffer(); row[x] = decoder.ReadSymbol(entropy, br); + if (row[x] >= kMaxQuantControlFieldValue) { + return PIK_FAILURE("dequant_cf value out of range"); + } } } PIK_RETURN_IF_ERROR(br->JumpToByteBoundary());