From 0746268403815f4cbec6a74cdc227e24c9977781 Mon Sep 17 00:00:00 2001 From: Gaurang Bhatia Date: Sun, 28 Dec 2025 23:05:39 +0530 Subject: [PATCH] Fix infinite loop in JBIG2 decoder with symbol dictionary context retention and add regression test Implements JBIG2 spec section 7.4.2.2 steps 3 and 7 for bitmap coding context retention and reuse. When bitmapCodingContextUsed is set, the decoder now reuses arithmetic coding contexts from the last referred-to symbol dictionary instead of creating new ones. When bitmapCodingContextRetained is set, contexts are saved for future reuse. Fixes #20461 --- src/core/jbig2.js | 73 ++++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/bitmap-symbol-context-reuse.pdf | Bin 0 -> 1192 bytes test/test_manifest.json | 7 +++ 4 files changed, 81 insertions(+) create mode 100644 test/pdfs/bitmap-symbol-context-reuse.pdf diff --git a/src/core/jbig2.js b/src/core/jbig2.js index e31bd1f5ec15b..3f647738df6d4 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -1584,6 +1584,20 @@ class SimpleSegmentVisitor { this.buffer = buffer; } + getRetainedBitmapContexts(segmentNumber) { + if (!this.retainedBitmapContexts) { + return null; + } + return this.retainedBitmapContexts[segmentNumber] || null; + } + + setRetainedBitmapContexts(segmentNumber, contexts) { + if (!this.retainedBitmapContexts) { + this.retainedBitmapContexts = {}; + } + this.retainedBitmapContexts[segmentNumber] = contexts; + } + drawBitmap(regionInfo, bitmap) { const pageInfo = this.currentPageInfo; const width = regionInfo.width, @@ -1683,16 +1697,64 @@ class SimpleSegmentVisitor { } const inputSymbols = []; + let lastReferredToSymbolDictionary = null; for (const referredSegment of referredSegments) { const referredSymbols = symbols[referredSegment]; // referredSymbols is undefined when we have a reference to a Tables // segment instead of a SymbolDictionary. if (referredSymbols) { inputSymbols.push(...referredSymbols); + lastReferredToSymbolDictionary = referredSegment; } } const decodingContext = new DecodingContext(data, start, end); + + // Handle bitmap coding context reuse (7.4.2.2 step 3) + if (dictionary.bitmapCodingContextUsed) { + if (lastReferredToSymbolDictionary === null) { + throw new Jbig2Error( + "symbol dictionary uses bitmap coding context, but has no referred-to symbol dictionary" + ); + } + + const retainedContexts = this.getRetainedBitmapContexts( + lastReferredToSymbolDictionary + ); + if (!retainedContexts) { + throw new Jbig2Error( + "symbol dictionary uses bitmap coding context, but referred-to dictionary did not retain contexts" + ); + } + + // Validate that parameters match (7.4.2.2 step 3) + if (retainedContexts.huffman !== dictionary.huffman) { + throw new Jbig2Error( + "symbol dictionary bitmap coding context: SDHUFF values do not match" + ); + } + if (retainedContexts.refinement !== dictionary.refinement) { + throw new Jbig2Error( + "symbol dictionary bitmap coding context: SDREFAGG values do not match" + ); + } + if (retainedContexts.template !== dictionary.template) { + throw new Jbig2Error( + "symbol dictionary bitmap coding context: SDTEMPLATE values do not match" + ); + } + if ( + retainedContexts.refinementTemplate !== dictionary.refinementTemplate + ) { + throw new Jbig2Error( + "symbol dictionary bitmap coding context: SDRTEMPLATE values do not match" + ); + } + + // Reuse the arithmetic coding contexts + decodingContext.contextCache = retainedContexts.contextCache; + } + symbols[currentSegment] = decodeSymbolDictionary( dictionary.huffman, dictionary.refinement, @@ -1707,6 +1769,17 @@ class SimpleSegmentVisitor { decodingContext, huffmanInput ); + + // Handle bitmap coding context retention (7.4.2.2 step 7) + if (dictionary.bitmapCodingContextRetained) { + this.setRetainedBitmapContexts(currentSegment, { + huffman: dictionary.huffman, + refinement: dictionary.refinement, + template: dictionary.template, + refinementTemplate: dictionary.refinementTemplate, + contextCache: decodingContext.contextCache, + }); + } } onImmediateTextRegion(region, referredSegments, data, start, end) { diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index 6080768c93f9e..fd74b89e0a2a5 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -752,6 +752,7 @@ !issue20319_1.pdf !issue20319_2.pdf !issue20439.pdf +!bitmap-symbol-context-reuse.pdf !bug1992868.pdf !bug1937438_af_from_latex.pdf !bug1937438_from_word.pdf diff --git a/test/pdfs/bitmap-symbol-context-reuse.pdf b/test/pdfs/bitmap-symbol-context-reuse.pdf new file mode 100644 index 0000000000000000000000000000000000000000..633c3e9a168880f04f0dc7cd1069b957dca6c99a GIT binary patch literal 1192 zcmY!laBnwZN4vLECQ zpc}w+axNFhM3;O}*g)05LJ4FI!k19POiWEcE`@|N)O8?%;L@ZLaOivH0>d065}ug? zv>a$5h~tr(nVwMsvKGX1&d8J<|NYPO@83VLAW%ZRInH2dzyt%&catP-O&E?QnrwO5_b)mw=S$<6{q?#+ddoG# z|E~e6W@1nVo1w*s#f-pJ9Jw2WUMDtB2-vXu2|Jt07Oaa@#!jQIX4~olJKtAMadUdwJ_R*H#%m4ISH0yWja2(s#7Q3fB z{B>$^q+Hbc2Wl@)OD#^D{!Hu7Yvx}&W{SQq>3Ofa-2C6YZ%L~cEIQhz;-r1S4mM~W=?7mw0H^5tV#vC k2AE8P^7Dad5STx}#Y%BWVo?d$v4#c)#$2kZuKsRZ0B{v#)&Kwi literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 860497c72ebc9..61c4e7fb4cc28 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -2819,6 +2819,13 @@ "rounds": 1, "type": "eq" }, + { + "id": "issue20461", + "file": "pdfs/bitmap-symbol-context-reuse.pdf", + "md5": "3d79e2d087515c2fdbed6fec0ad86e91", + "rounds": 1, + "type": "eq" + }, { "id": "issue20439", "file": "pdfs/issue20439.pdf",