Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions src/lib/codec/formats/BMPFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -571,12 +571,9 @@ template<typename T>
bool BMPFormat<T>::readInfoHeader(const GRK_BITMAPFILEHEADER* fileHeader,
GRK_BITMAPINFOHEADER* infoHeader)
{
const size_t len_initial = infoHeader->biSize - sizeof(uint32_t);
uint8_t temp[sizeof(GRK_BITMAPINFOHEADER)];
auto temp_ptr = (uint32_t*)temp;
if(!read(temp, len_initial))
return false;

// Validate biSize against the legal header-size set BEFORE using it to size
// the read: an unvalidated biSize either underflows len_initial (biSize < 4)
// or overruns the fixed temp buffer (biSize > sizeof temp), smashing the stack.
switch(infoHeader->biSize)
{
case BITMAPCOREHEADER_LENGTH:
Expand All @@ -590,6 +587,13 @@ bool BMPFormat<T>::readInfoHeader(const GRK_BITMAPFILEHEADER* fileHeader,
spdlog::error("unknown BMP header size {}", infoHeader->biSize);
return false;
}
const size_t len_initial = infoHeader->biSize - sizeof(uint32_t);
uint8_t temp[sizeof(GRK_BITMAPINFOHEADER)];
auto temp_ptr = (uint32_t*)temp;
if(len_initial > sizeof(temp))
return false;
if(!read(temp, len_initial))
return false;
bool is_os2 = infoHeader->biSize == BITMAPCOREHEADER_LENGTH;
if(is_os2)
{ // OS2
Expand Down Expand Up @@ -683,22 +687,29 @@ bool BMPFormat<T>::readRle8Data(uint8_t* pData, uint32_t stride, uint32_t width,
uint8_t* pix = nullptr;
const uint8_t* beyond = nullptr;
const uint8_t* pixels_ptr = nullptr;
const uint8_t* pixels_end = nullptr;
bool rc = false;
auto pixels = new uint8_t[infoHeader_.biSizeImage];
if(!read(pixels, infoHeader_.biSizeImage))
{
goto cleanup;
}
pixels_ptr = pixels;
pixels_end = pixels + infoHeader_.biSizeImage;
beyond = pData + (size_t)stride * height;
pix = pData;

while(y < height)
{
// every read below must stay within the biSizeImage-sized input buffer
if(pixels_ptr >= pixels_end)
goto cleanup;
int c = *pixels_ptr++;
if(c)
{
int j;
if(pixels_ptr >= pixels_end)
goto cleanup;
uint8_t c1 = *pixels_ptr++;
for(j = 0; (j < c) && (x < width) && ((size_t)pix < (size_t)beyond); j++, x++, pix++)
{
Expand All @@ -708,6 +719,8 @@ bool BMPFormat<T>::readRle8Data(uint8_t* pData, uint32_t stride, uint32_t width,
}
else
{
if(pixels_ptr >= pixels_end)
goto cleanup;
c = *pixels_ptr++;
if(c == 0x00)
{ /* EOL */
Expand All @@ -721,6 +734,8 @@ bool BMPFormat<T>::readRle8Data(uint8_t* pData, uint32_t stride, uint32_t width,
}
else if(c == 0x02)
{ /* MOVE by dxdy */
if(pixels_ptr + 2 > pixels_end)
goto cleanup;
c = *pixels_ptr++;
x += (uint32_t)c;
c = *pixels_ptr++;
Expand All @@ -732,6 +747,8 @@ bool BMPFormat<T>::readRle8Data(uint8_t* pData, uint32_t stride, uint32_t width,
int j;
for(j = 0; (j < c) && (x < width) && ((size_t)pix < (size_t)beyond); j++, x++, pix++)
{
if(pixels_ptr >= pixels_end)
goto cleanup;
uint8_t c1 = *pixels_ptr++;
*pix = c1;
written++;
Expand Down Expand Up @@ -760,20 +777,27 @@ bool BMPFormat<T>::readRle4Data(uint8_t* pData, uint32_t stride, uint32_t width,
uint32_t x = 0, y = 0, written = 0;
uint8_t* pix = nullptr;
const uint8_t* pixels_ptr = nullptr;
const uint8_t* pixels_end = nullptr;
const uint8_t* beyond = nullptr;
bool rc = false;
auto pixels = new uint8_t[infoHeader_.biSizeImage];
if(!read(pixels, infoHeader_.biSizeImage))
goto cleanup;
pixels_ptr = pixels;
pixels_end = pixels + infoHeader_.biSizeImage;
beyond = pData + (size_t)stride * height;
pix = pData;
while(y < height)
{
// every read below must stay within the biSizeImage-sized input buffer
if(pixels_ptr >= pixels_end)
goto cleanup;
int c = *pixels_ptr++;
if(c)
{ /* encoded mode */
int j;
if(pixels_ptr >= pixels_end)
goto cleanup;
uint8_t c1 = *pixels_ptr++;

for(j = 0; (j < c) && (x < width) && ((size_t)pix < (size_t)beyond); j++, x++, pix++)
Expand All @@ -784,6 +808,8 @@ bool BMPFormat<T>::readRle4Data(uint8_t* pData, uint32_t stride, uint32_t width,
}
else
{ /* absolute mode */
if(pixels_ptr >= pixels_end)
goto cleanup;
c = *pixels_ptr++;
if(c == 0x00)
{ /* EOL */
Expand All @@ -797,6 +823,8 @@ bool BMPFormat<T>::readRle4Data(uint8_t* pData, uint32_t stride, uint32_t width,
}
else if(c == 0x02)
{ /* MOVE by dxdy */
if(pixels_ptr + 2 > pixels_end)
goto cleanup;
c = *pixels_ptr++;
x += (uint32_t)c;

Expand All @@ -812,7 +840,11 @@ bool BMPFormat<T>::readRle4Data(uint8_t* pData, uint32_t stride, uint32_t width,
for(j = 0; (j < c) && (x < width) && ((size_t)pix < (size_t)beyond); j++, x++, pix++)
{
if((j & 1) == 0)
{
if(pixels_ptr >= pixels_end)
goto cleanup;
c1 = *pixels_ptr++;
}
*pix = (uint8_t)((j & 1) ? (c1 & 0x0fU) : ((uint8_t)(c1 >> 4) & 0x0fU));
written++;
}
Expand Down
8 changes: 8 additions & 0 deletions src/lib/codec/formats/JPEGFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,14 @@ grk_image* JPEGFormat<T>::jpegtoimage(const char* filename, grk_cparameters* par
success = false;
goto cleanup;
}
// cmptparm[]/planes[] are sized for at most 3 components; a 4-component
// (Adobe CMYK/YCCK) JPEG would otherwise overflow those stack arrays.
if(cinfo.output_components > 3)
{
spdlog::error("jpegtoimage: Unsupported number of components {}", cinfo.output_components);
success = false;
goto cleanup;
}

decompress_num_comps = (uint16_t)cinfo.output_components;
w = cinfo.image_width;
Expand Down
11 changes: 11 additions & 0 deletions src/lib/codec/formats/TIFFFormat.h
Original file line number Diff line number Diff line change
Expand Up @@ -2273,6 +2273,17 @@ grk_image* TIFFFormat<T>::readImage(const std::string& filename, grk_cparameters
image->meta->exif_len = exif_size;
}
}
// For chunky (interleaved) data the per-row scratch buffer is sized from
// tiSpp, but the de-interleavers read width*numcomps samples. If the
// photometric/extrasamples-derived numcomps exceeds the SamplesPerPixel tag,
// the readers over-read the row buffer, so require them to agree.
if(tiPC == PLANARCONFIG_CONTIG && numcomps != tiSpp)
{
spdlog::error("TIFFFormat<T>::readImage: component count {} does not match "
"SamplesPerPixel {} for contiguous (chunky) data",
numcomps, tiSpp);
goto cleanup;
}
// 9. read pixel data
if(needSignedPixelReader)
{
Expand Down
26 changes: 25 additions & 1 deletion src/lib/core/codestream/decompress/CodeStreamDecompress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2451,9 +2451,33 @@ bool CodeStreamDecompress::activateScratch(bool singleTile, GrkImage* scratch)
auto comp = scratch->comps + i;
comp->y0 = ceildivpow2<uint32_t>(ceildiv<uint32_t>(unreducedTileY0, comp->dy), reduce);
uint32_t compY1 = ceildivpow2<uint32_t>(ceildiv<uint32_t>(unreducedTileY1, comp->dy), reduce);
// The drain loop advances comp->h to each later tile-row height without
// reallocating; a non-tile-aligned YTOsiz can make an interior row taller
// than the first, so size the buffer for the TALLEST reduced row.
uint32_t maxH = compY1 - comp->y0;
for(uint16_t ty = (uint16_t)(slatedRect.y0 + 1); ty < slatedRect.y1; ty++)
{
uint32_t uy0 = cp_.ty0_ + (uint32_t)ty * cp_.t_height_;
uint32_t uy1 = std::min(uy0 + cp_.t_height_, (uint32_t)scratch->y1);
if(uy0 >= uy1)
continue;
uint32_t ry0 = ceildivpow2<uint32_t>(ceildiv<uint32_t>(uy0, comp->dy), reduce);
uint32_t ry1 = ceildivpow2<uint32_t>(ceildiv<uint32_t>(uy1, comp->dy), reduce);
maxH = std::max(maxH, ry1 - ry0);
}
comp->h = maxH;
}
if(!scratch->allocCompositeData())
return false;
// report the first tile-row height for the initial decode; the buffer stays
// sized for maxH so later (taller) rows still fit.
for(uint16_t i = 0; i < scratch->numcomps; i++)
{
auto comp = scratch->comps + i;
uint32_t compY1 = ceildivpow2<uint32_t>(ceildiv<uint32_t>(unreducedTileY1, comp->dy), reduce);
comp->h = compY1 - comp->y0;
}
return scratch->allocCompositeData();
return true;
}

return cp_.codingParams_.dec_.skipAllocateComposite_ || scratch->allocCompositeData();
Expand Down
14 changes: 12 additions & 2 deletions src/lib/core/fileformat/FileFormatJP2Family.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1279,10 +1279,20 @@ bool FileFormatJP2Family::read_asoc(uint8_t* header_data, uint32_t header_data_s

return true;
}
// maximum JP2/JPX asoc superbox nesting depth (stack-exhaustion guard)
static constexpr uint32_t maxAsocDepth = 64;
uint32_t FileFormatJP2Family::read_asoc(AsocBox* parent, uint8_t** header_data,
uint32_t* header_data_size, uint32_t asocSize)
uint32_t* header_data_size, uint32_t asocSize,
uint32_t depth)
{
assert(*header_data);
// cap nesting: each level consumes only the 8-byte child header, so a few-MB
// payload of nested asoc boxes would otherwise exhaust the stack.
if(depth > maxAsocDepth)
{
grklog.error("ASOC box nesting exceeds maximum depth %u", maxAsocDepth);
throw BadAsocException();
}
if(asocSize < 8)
{
grklog.error("ASOC box must be at least 8 bytes in size");
Expand Down Expand Up @@ -1329,7 +1339,7 @@ uint32_t FileFormatJP2Family::read_asoc(AsocBox* parent, uint8_t** header_data,
asocBytesUsed += childSize;
break;
case JP2_ASOC:
asocBytesUsed += read_asoc(childAsoc, header_data, header_data_size, childSize);
asocBytesUsed += read_asoc(childAsoc, header_data, header_data_size, childSize, depth + 1);
break;
case JP2_XML:
childAsoc->alloc(childSize);
Expand Down
2 changes: 1 addition & 1 deletion src/lib/core/fileformat/FileFormatJP2Family.h
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ class FileFormatJP2Family
const FindHandlerInfo img_find_handler(uint32_t id);

uint32_t read_asoc(AsocBox* parent, uint8_t** header_data, uint32_t* header_data_size,
uint32_t asocSize);
uint32_t asocSize, uint32_t depth = 0);
bool read_asoc(uint8_t* header_data, uint32_t header_data_size);
void serializeAsoc(AsocBox* asoc, grk_asoc* serial_asocs, uint32_t* num_asocs, uint32_t level);
/***
Expand Down
67 changes: 63 additions & 4 deletions src/lib/core/fileformat/decompress/FileFormatMJ2Decompress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,16 @@ bool FileFormatMJ2Decompress::read_vmhd(uint8_t* headerData, uint32_t headerSize

bool FileFormatMJ2Decompress::read_url(uint8_t* headerData, uint32_t headerSize)
{
// a dref/url box can appear with no preceding tkhd, leaving current_track_ null
if(!current_track_)
{
grklog.error("MJ2: url box outside of a track");
return false;
}
// version+flags is a 4-byte fullbox header; guard the subtraction so a short
// box does not underflow headerSize into a huge value and over-read below.
if(headerSize < 4)
return false;
uint8_t version;
uint32_t flag;
read_version_and_flag(&headerData, version, flag);
Expand All @@ -356,6 +366,16 @@ bool FileFormatMJ2Decompress::read_url(uint8_t* headerData, uint32_t headerSize)

bool FileFormatMJ2Decompress::read_urn(uint8_t* headerData, uint32_t headerSize)
{
// a dref/urn box can appear with no preceding tkhd, leaving current_track_ null
if(!current_track_)
{
grklog.error("MJ2: urn box outside of a track");
return false;
}
// version+flags is a 4-byte fullbox header; guard the subtraction so a short
// box does not underflow headerSize into a huge value and over-read below.
if(headerSize < 4)
return false;
uint8_t version;
uint32_t flag;
read_version_and_flag(&headerData, version, flag);
Expand Down Expand Up @@ -505,18 +525,32 @@ bool FileFormatMJ2Decompress::read_stsd(uint8_t* headerData, uint32_t headerSize
* Time To Sample box Decompact
*
*/
void FileFormatMJ2Decompress::tts_decompact(mj2_tk* tk)
{
bool FileFormatMJ2Decompress::tts_decompact(mj2_tk* tk)
{
// Each materialised sample must map to at least one byte of media data, so
// the total sample count cannot exceed the file size. This caps a crafted
// STTS samples_count_ (raw uint32) that would otherwise drive billions of
// push_backs, and prevents num_samples_ (uint32) from wrapping.
uint64_t streamLen = stream_ ? stream_->tell() + stream_->numBytesLeft() : 0;
uint64_t total = tk->num_samples_;
for(const auto& tts : tk->tts_)
{
tk->num_samples_ += tts.samples_count_;
total += tts.samples_count_;
if(total > streamLen)
{
grklog.error("MJ2 STTS: sample count %llu exceeds %llu-byte stream",
(unsigned long long)total, (unsigned long long)streamLen);
return false;
}
for(uint32_t j = 0; j < tts.samples_count_; j++)
{
mj2_sample sample;
sample.samples_delta_ = tts.samples_delta_;
tk->samples_.push_back(sample);
}
}
tk->num_samples_ = (uint32_t)total;
return true;
}

bool FileFormatMJ2Decompress::read_stts(uint8_t* headerData, uint32_t headerSize)
Expand All @@ -535,7 +569,8 @@ bool FileFormatMJ2Decompress::read_stts(uint8_t* headerData, uint32_t headerSize
tk->tts_.push_back(tts);
}

tts_decompact(tk);
if(!tts_decompact(tk))
return false;

return true;
}
Expand Down Expand Up @@ -718,11 +753,24 @@ bool FileFormatMJ2Decompress::readHeader(grk_header_info* header_info)
return false;
if(current_track_)
{
// capture the total input size before seeking: numBytesLeft() is not
// reliable after seek(0) on the file buffer.
streamLength_ = stream_->tell() + stream_->numBytesLeft();
// seek to beginning of file to get base pointer for absolute STCO offsets
stream_->seek(0);
auto basePtr = stream_->currPtr();
for(const auto& sample : current_track_->samples_)
{
// offset_/samples_size_ come straight from the STCO/STSZ tables and are
// used as raw offsets into the file buffer; reject samples that fall
// outside it (also covers the 4-byte box-length probe below).
if((uint64_t)sample.offset_ + sample.samples_size_ > streamLength_ ||
(uint64_t)sample.offset_ + sizeof(uint32_t) > streamLength_)
{
grklog.error("MJ2: sample at offset %u (size %u) lies outside the %llu-byte stream",
sample.offset_, sample.samples_size_, (unsigned long long)streamLength_);
return false;
}
auto ptr = basePtr + sample.offset_;
// cross-check sample size with box length
// as long as box is not XL
Expand Down Expand Up @@ -762,6 +810,17 @@ bool FileFormatMJ2Decompress::decompressSampleInternal(uint32_t sampleIndex)
stream_->seek(0);
auto basePtr = stream_->currPtr();

// bound the sample against the file buffer before using offset_ as a pointer
// (streamLength_ was captured in readHeader before any seek)
if((uint64_t)sample.offset_ + sample.samples_size_ > streamLength_ ||
(uint64_t)sample.offset_ + sizeof(uint32_t) > streamLength_)
{
grklog.error("MJ2: sample %u at offset %u (size %u) lies outside the %llu-byte stream",
sampleIndex, sample.offset_, sample.samples_size_,
(unsigned long long)streamLength_);
return false;
}

auto samplePtr = basePtr + sample.offset_;

// skip 8-byte JP2C box header (4 bytes length + 4 bytes type)
Expand Down
6 changes: 5 additions & 1 deletion src/lib/core/fileformat/decompress/FileFormatMJ2Decompress.h
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,17 @@ class FileFormatMJ2Decompress : public IDecompressor, public FileFormatMJ2
bool read_url(uint8_t* headerData, uint32_t headerSize);
bool read_urn(uint8_t* headerData, uint32_t headerSize);

void tts_decompact(mj2_tk* tk);
bool tts_decompact(mj2_tk* tk);
void stsc_decompact(mj2_tk* tk);
void stco_decompact(mj2_tk* tk);

grk_decompress_parameters decompressParams_;
bool decompressParamsSet_;
std::vector<GrkImage*> decompressedImages_;
// total input size, captured during readHeader before any seek (numBytesLeft
// is not reliable after seek(0) on the file buffer); used to bound raw
// STCO/STSZ sample offsets.
uint64_t streamLength_ = 0;

struct SampleCodeStream
{
Expand Down
Loading
Loading