Headline
CVE-2022-35166: Infinite loop in JPEG::ReadInternal · Issue #76 · thorfdbg/libjpeg
libjpeg commit 842c7ba was discovered to contain an infinite loop via the component JPEG::ReadInternal.
version: latest commit 842c7ba
poc: poc
command: ./jpeg poc /dev/null
Here is the backtrace in GDB:
pwndbg> backtrace
#0 0x000055555558fa8a in Image::StartParseFrame (this=0x5555557433a0, io=0x555555741ad0) at image.cpp:658
#1 0x0000555555584739 in JPEG::ReadInternal (this=0x5555557414b8, tags=0x7fffffffdcf0) at jpeg.cpp:286
#2 0x00005555555843de in JPEG::Read (this=0x5555557414b8, tags=0x7fffffffdcf0) at jpeg.cpp:210
#3 0x000055555557a23e in Reconstruct (infile=0x7fffffffe6cf "../../script/test-libjpeg/modify8", outfile=0x7fffffffe6f1 "/dev/null", colortrafo=1, alpha=0x0, upsample=true) at reconstruct.cpp:121
#4 0x00005555555715be in main (argc=3, argv=0x7fffffffe448) at main.cpp:747
#5 0x00007ffff7abf0b3 in __libc_start_main (main=0x55555556fac1 <main(int, char**)>, argc=3, argv=0x7fffffffe448, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe438) at ../csu/libc-start.c:308
#6 0x000055555556f5ee in _start ()
When marker==0xffd9, ParseFrameHeader will return NULL (line 627, image.cpp), which initializes m_pCurrent with NULL (line 667, image.cpp).
marker = io->GetWord();
switch(marker) {
case ByteStream::EOF:
JPG_THROW(MALFORMED_STREAM,"Image::ParseFrameHeader","unexpected EOF while parsing the image");
break;
case 0xffd9: // EOI
return NULL;
class Frame *Image::StartParseFrame(class ByteStream *io)
{
//
// This should only be called from the main image.
assert(m_pParent == NULL && m_pMaster == NULL);
//
// Check whether we have the frame header. Residual and alpha
// already parse that off as part of ParseTrailer().
if (m_bReceivedFrameHeader == false) {
assert(m_pTables);
m_pCurrent = ParseFrameHeader(io);
// Create the checksum if it is needed.
CreateChecksumWhenNeeded(m_pChecksum);
//
// Is now there.
m_bReceivedFrameHeader = true;
}
//
// Otherwise, the frame header has already been parsed off and need not to be
// looked at here again.
return m_pCurrent;
}
Since m_bReceivedFrameHeadere is set to true after that (lne 672, image.cpp), further calls to StartParseFrame will keep returning m_pCurreent=NULL.
while(m_bDecoding) {
if (m_pFrame == NULL) {
m_pFrame = m_pImage->StartParseFrame(m_pIOStream);
if (m_pFrame) {
m_pDecoder->ParseTags(tags);
if (stopflags & JPGFLAG_DECODER_STOP_FRAME)
return;
}
}
if (m_pFrame) {
while (m_pScan == NULL) {
m_pScan = m_pFrame->StartParseScan(m_pImage->InputStreamOf(m_pIOStream),m_pImage->ChecksumOf());
//
if (m_pScan == NULL) {
// This is not yet the start of the scan, but might
// either be a frame trailer, or part of the frame header.
if (m_pFrame->isEndOfFrame()) {
if (!m_pFrame->ParseTrailer(m_pImage->InputStreamOf(m_pIOStream))) {
// Frame done, advance to the next frame.
m_pFrame = NULL;
if (!m_pImage->ParseTrailer(m_pIOStream)) {
// Image done, stop decoding, image is now loaded.
StopDecoding();
return;
}
}
} else {
if (stopflags & JPGFLAG_DECODER_STOP_FRAME)
return;
}
// else continue looking for the start of scan.
} else {
if (stopflags & JPGFLAG_DECODER_STOP_SCAN)
return;
}
}
if (m_pScan) {
if (m_bRow == false) {
m_bRow = m_pScan->StartMCURow();
if (m_bRow) {
if (stopflags & JPGFLAG_DECODER_STOP_ROW)
return;
} else {
// Scan done, advance to the next scan.
m_pFrame->EndParseScan();
m_pScan = NULL;
if (!m_pFrame->ParseTrailer(m_pImage->InputStreamOf(m_pIOStream))) {
// Frame done, advance to the next frame.
m_pFrame = NULL;
if (!m_pImage->ParseTrailer(m_pIOStream)) {
// Image done, stop decoding, image is now loaded.
StopDecoding();
return;
}
}
}
}
if (m_bRow) {
while (m_pScan->ParseMCU()) {
if (stopflags & JPGFLAG_DECODER_STOP_MCU)
return;
}
m_bRow = false;
}
}
}
}
Such behavior leads to an infinite loop in JPEG::ReadInternal. When m_pFrame==NULL (line 285, jpeg.cpp), it will invoke m_pImage->StartParseFrame to initialize it (line 286, jpeg.cpp). Since StartParseFrame keeps returning NULL in this case, the while loop from line 284-353 cannot terminate.