5050else :
5151 from io import StringIO
5252
53+ import binascii
5354import struct
5455import zlib
5556
@@ -169,25 +170,29 @@ def decode(data, decodeParms=None):
169170 :return: a string conversion in base-7 ASCII, where each of its values
170171 v is such that 0 <= ord(v) <= 127.
171172 """
172- retval = ""
173- hex_pair = ""
174- index = 0
175- while True :
176- if index >= len (data ):
177- raise PdfStreamError ("Unexpected EOD in ASCIIHexDecode" )
178- char = data [index ]
179- if char == ">" :
180- break
181- elif char .isspace ():
182- index += 1
183- continue
184- hex_pair += char
185- if len (hex_pair ) == 2 :
186- retval += chr (int (hex_pair , base = 16 ))
187- hex_pair = ""
188- index += 1
189- assert hex_pair == ""
190- return retval
173+ # CVE-2026-28804: the previous character-by-character accumulation
174+ # (retval += ..., hex_pair += ...) is quadratic, so a large
175+ # /ASCIIHexDecode stream caused excessive CPU time. Locate the EOD
176+ # marker once, strip whitespace, and bulk-decode with binascii.
177+ eod = data .find (">" )
178+ if eod == - 1 :
179+ raise PdfStreamError ("Unexpected EOD in ASCIIHexDecode" )
180+ hex_str = b"" .join (data [:eod ].split ()) if isinstance (
181+ data , bytes
182+ ) else "" .join (data [:eod ].split ())
183+ # Per ISO 32000 §7.4.2, a final odd hex digit is assumed to be
184+ # followed by a "0".
185+ if len (hex_str ) % 2 == 1 :
186+ hex_str += b"0" if isinstance (hex_str , bytes ) else "0"
187+ try :
188+ return binascii .unhexlify (hex_str )
189+ except (binascii .Error , TypeError ):
190+ raise PdfStreamError ("Invalid hexadecimal data in ASCIIHexDecode" )
191+
192+
193+ # CVE-2025-62708 / CVE-2025-66019: bound LZWDecode output so a small stream
194+ # cannot amplify into gigabytes of memory. Set to 0 to disable (trusted input).
195+ LZW_MAX_OUTPUT_LENGTH = 75000000 # 75 MB
191196
192197
193198class LZWDecode (object ):
@@ -196,10 +201,11 @@ class LZWDecode(object):
196201 """
197202
198203 class Decoder (object ):
199- def __init__ (self , data ):
204+ def __init__ (self , data , max_output_length = LZW_MAX_OUTPUT_LENGTH ):
200205 self .STOP = 257
201206 self .CLEARDICT = 256
202207 self .data = data
208+ self .max_output_length = max_output_length
203209 self .bytepos = 0
204210 self .bitpos = 0
205211 self .dict = ["" ] * 4096
@@ -246,6 +252,11 @@ def decode(self):
246252 cW = self .CLEARDICT
247253 baos = ""
248254 while True :
255+ if self .max_output_length and len (baos ) > self .max_output_length :
256+ raise PdfReadError (
257+ "Output exceeds maximum allowed length (%d bytes) "
258+ "while decoding LZW stream." % self .max_output_length
259+ )
249260 pW = cW
250261 cW = self .next_code ()
251262 if cW == - 1 :
0 commit comments