Skip to content

Read from tall PSD tiles safely - #10151

Merged
radarhere merged 4 commits into
python-pillow:mainfrom
Kesavamoorthig06:fix/psd-rle-bytecounts-dos
Oct 9, 2026
Merged

radarhere merged 4 commits into
python-pillow:mainfrom
Kesavamoorthig06:fix/psd-rle-bytecounts-dos

Conversation

@Kesavamoorthig06

Copy link
Copy Markdown
Contributor

Issue

When opening a PackBits-compressed PSD, PsdImagePlugin._maketile reads the per-row bytecounts table using a size taken straight from the file header:

bytecount = read(channels * ysize * 2)

ysize is the declared image height, an unsigned 32-bit field that is never validated. A 40-byte PSD declaring a height of 0xFFFFFFFF makes Pillow ask read() for ~26 GB while parsing the header. Where the allocation is refused, Image.open() dies with a raw MemoryError; where it succeeds, the process still pins tens of gigabytes of address space for a file containing no image data. Any service that opens untrusted PSDs can be taken down by a single tiny upload.

Both the merged image (read during Image.open()) and individual layers (read through .layers) go through _maketile, so both paths are affected.

Reproduction

import struct
from PIL import Image

hdr = b"8BPS" + struct.pack(">H", 1) + b"\x00" * 6
hdr += struct.pack(">HIIHH", 3, 0xFFFFFFFF, 1, 8, 3)  # 4-billion-row RGB image
hdr += struct.pack(">IIIH", 0, 0, 0, 1)  # empty sections, PackBits compression

with open("crash.psd", "wb") as f:
    f.write(hdr)  # 40 bytes total

Image.open("crash.psd")  # MemoryError

Fix

Read the table with ImageFile._safe_read(), which exists for exactly this case ("Unlike fp.read(n), this function doesn't trust the user"). It reads block by block instead of pre-allocating the declared size, and raises OSError("Truncated File Read") when the file cannot supply the table:

OSError: Truncated File Read

One subtlety: _safe_read returns b"" for a non-positive size, but read() raises ValueError there, and test_bounds_crash_overflow relies on a crafted layer with a negative height being rejected with ValueError. The fix rejects a negative height explicitly first, preserving that behavior.

The regression test opens a 40-byte PSD declaring a 0xFFFFFFFF-tall image and asserts it is rejected with OSError. Without the fix, the test dies with MemoryError.

@radarhere

Copy link
Copy Markdown
Member

I suspect this problem was only introduced in #10089

@Kesavamoorthig06

Kesavamoorthig06 commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor Author

Ah interesting - I went back and tried the file on d401c61^, just before #10089, and it still hits the same MemoryError. This comes through _open() -> _maketile(self.fp, ...) for the merged image, rather than the layers code changed in that PR. I also found that read in 6.2.0, 7.2.0, 8.4.0 and 9.5.0, so it looks like it's been around for a while.

@radarhere radarhere added the 🤖-assisted AI-assisted label Oct 7, 2026
@radarhere
radarhere force-pushed the fix/psd-rle-bytecounts-dos branch 2 times, most recently from ab45e52 to 5511602 Compare October 9, 2026 11:40
@radarhere
radarhere force-pushed the fix/psd-rle-bytecounts-dos branch from 5511602 to e4340eb Compare October 9, 2026 12:14
@radarhere
radarhere enabled auto-merge (squash) October 9, 2026 12:17
@radarhere radarhere changed the title Fix unbounded memory allocation reading PSD packbits bytecounts Read from tall PSD tiles safetly Oct 9, 2026
@radarhere
radarhere disabled auto-merge October 9, 2026 12:17
@radarhere radarhere changed the title Read from tall PSD tiles safetly Read from tall PSD tiles safely Oct 9, 2026
@radarhere
radarhere enabled auto-merge (squash) October 9, 2026 12:18
@radarhere
radarhere merged commit 371accd into python-pillow:main Oct 9, 2026
58 of 60 checks passed
@aclark4life aclark4life added the PSD Adobe Photoshop label Oct 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🤖-assisted AI-assisted PSD Adobe Photoshop

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants