What did you do?
Open a malformed JP2 file whose first box declares an extended 64-bit length (0xffffffffff000004) that exceeds the platform's signed seek range (sys.maxsize on 64-bit). The issue is identified via fuzzing and manually reproduced to verify.
What did you expect to happen?
Pillow should reject the impossible box geometry through its normal corrupt-image path. A box that is too small (lbox < hlen) already raises SyntaxError("Invalid header length"), which Image.open() normalizes into UnidentifiedImageError. A box that is impossibly large should also be rejected the same way.
What actually happened?
Jpeg2KImagePlugin._parse_jp2_header() builds a top-level BoxReader with no known stream length. next_box_type() reads the extended 64-bit length, and because the reader has no length bound, _can_read(lbox - hlen) returns True and the huge value is stored in remaining_in_box. The parser does not recognize the box type and iterates; on the next next_box_type() call, the stored length is passed straight to fp.seek(...).
Because it exceeds C ssize_t, BytesIO.seek() raises OverflowError and it escapes to the caller:
Traceback (most recent call last):
File "repro.py", line 6, in <module>
Image.open(io.BytesIO(data))
File ".../PIL/Image.py", line 3687, in open
im = _open_core(
File ".../PIL/Image.py", line 3667, in _open_core
im = factory(fp, filename)
File ".../PIL/ImageFile.py", line 152, in __init__
self._open()
File ".../PIL/Jpeg2KImagePlugin.py", line 281, in _open
header = _parse_jp2_header(self.fp)
File ".../PIL/Jpeg2KImagePlugin.py", line 166, in _parse_jp2_header
tbox = reader.next_box_type()
File ".../PIL/Jpeg2KImagePlugin.py", line 86, in next_box_type
self.fp.seek(self.remaining_in_box, os.SEEK_CUR)
OverflowError: Python int too large to convert to C ssize_t
The relevant code in BoxReader.next_box_type() is:
def next_box_type(self) -> bytes:
# Skip the rest of the box if it has not been read
if self.remaining_in_box > 0:
self.fp.seek(self.remaining_in_box, os.SEEK_CUR) # <-- OverflowError here
self.remaining_in_box = -1
lbox, tbox = cast(tuple[int, bytes], self.read_fields(">I4s"))
if lbox == 1:
lbox = cast(int, self.read_fields(">Q")[0])
hlen = 16
else:
hlen = 8
if lbox < hlen or not self._can_read(lbox - hlen):
msg = "Invalid header length"
raise SyntaxError(msg)
self.remaining_in_box = lbox - hlen
return tbox
The _can_read() guard does not catch this for the top-level reader, because that reader is created with no length (has_length is False and remaining_in_box is -1 at the check), so _can_read() takes its return True # No length known, just read branch and accepts the oversized box.
These JP2 bytes, therefore, raise an unexpected OverflowError which becomes an uncaught exception. The failure is in the pure-Python side, so I assume it isnot a memory-safety vulnerability.
This is the JPEG 2000 counterpart of #9811
What are your OS, Python and Pillow versions?
Reproduced on the current main branch (commit 9e282f5) and confirmed on the
latest release 12.3.0.
- OS: Ubuntu 22.04.5 LTS, x86_64
- Python: 3.14.4
- Pillow: 13.0.0.dev0 (git
main)
--------------------------------------------------------------------
Pillow 13.0.0.dev0
Python 3.14.4 (main, Jun 12 2026, 00:08:58) [GCC 11.4.0]
--------------------------------------------------------------------
--- PIL CORE support ok, compiled for 13.0.0.dev0
--- JPEG support ok, compiled for libjpeg-turbo 2.1.2
--- OPENJPEG (JPEG2000) support ok, loaded 2.4.0
--- ZLIB (PNG/ZIP) support ok, loaded 1.2.11
--- LIBTIFF support ok, loaded 4.3.0
--------------------------------------------------------------------
import base64, io
from PIL import Image
data = base64.b64decode("AAAADGpQICANCocKAAAAAQAAAAD//////wAABA==")
Image.open(io.BytesIO(data)) # OverflowError: Python int too large to convert to C ssize_t
What did you do?
Open a malformed JP2 file whose first box declares an extended 64-bit length (
0xffffffffff000004) that exceeds the platform's signed seek range (sys.maxsizeon 64-bit). The issue is identified via fuzzing and manually reproduced to verify.What did you expect to happen?
Pillow should reject the impossible box geometry through its normal corrupt-image path. A box that is too small (
lbox < hlen) already raisesSyntaxError("Invalid header length"), whichImage.open()normalizes intoUnidentifiedImageError. A box that is impossibly large should also be rejected the same way.What actually happened?
Jpeg2KImagePlugin._parse_jp2_header() builds a top-level
BoxReaderwith no known stream length.next_box_type()reads the extended 64-bit length, and because the reader has no length bound,_can_read(lbox - hlen)returnsTrueand the huge value is stored inremaining_in_box. The parser does not recognize the box type and iterates; on the nextnext_box_type()call, the stored length is passed straight tofp.seek(...).Because it exceeds C
ssize_t,BytesIO.seek()raisesOverflowErrorand it escapes to the caller:The relevant code in
BoxReader.next_box_type()is:The
_can_read()guard does not catch this for the top-level reader, because that reader is created with no length (has_lengthisFalseandremaining_in_boxis-1at the check), so_can_read()takes itsreturn True # No length known, just readbranch and accepts the oversized box.These JP2 bytes, therefore, raise an unexpected
OverflowErrorwhich becomes an uncaught exception. The failure is in the pure-Python side, so I assume it isnot a memory-safety vulnerability.This is the JPEG 2000 counterpart of #9811
What are your OS, Python and Pillow versions?
Reproduced on the current
mainbranch (commit9e282f5) and confirmed on thelatest release 12.3.0.
main)