Skip to content

fix(psd): harden reader against oversized/bomb headers and ZIP-layer overflow - #5348

Open
lgritz wants to merge 3 commits into
AcademySoftwareFoundation:mainfrom
lgritz:lg-psdbomb
Open

fix(psd): harden reader against oversized/bomb headers and ZIP-layer overflow#5348
lgritz wants to merge 3 commits into
AcademySoftwareFoundation:mainfrom
lgritz:lg-psdbomb

Conversation

@lgritz

@lgritz lgritz commented Jul 30, 2026

Copy link
Copy Markdown
Collaborator

Add open-time hardening to the PSD reader:

  • check_open() on the composite spec enforces the global limits:* (channels/resolution/imagesize) policy in addition to the existing per-dimension header validation.
  • check_compression_ratio() rejects a tiny file that declares a huge uncompressed composite before any pixel read (decompression bomb).
  • Size the ZIP / ZIP-prediction layer-channel decompression buffer with 64-bit arithmetic. width*height*(depth/8) was computed in uint32 and could wrap for large PSB layer dimensions, undersizing the buffer while read_channel_row() computes the row offset in 64-bit -> out-of-bounds read. Both now agree.

Add a decompression-bomb regression fixture (testsuite/psd); it is picked up as a fuzz seed automatically via FORMAT_SOURCES. Valid files read byte-identically.

Assisted-by: Claude Code / Claude Opus 4.8

…overflow

Add open-time hardening to the PSD reader:

- check_open() on the composite spec enforces the global limits:*
  (channels/resolution/imagesize) policy in addition to the existing
  per-dimension header validation.
- check_compression_ratio() rejects a tiny file that declares a huge
  uncompressed composite before any pixel read (decompression bomb).
- Size the ZIP / ZIP-prediction layer-channel decompression buffer with
  64-bit arithmetic. width*height*(depth/8) was computed in uint32 and
  could wrap for large PSB layer dimensions, undersizing the buffer while
  read_channel_row() computes the row offset in 64-bit -> out-of-bounds
  read. Both now agree.

Add a decompression-bomb regression fixture (testsuite/psd); it is picked up
as a fuzz seed automatically via FORMAT_SOURCES. Valid files read
byte-identically.

Assisted-by: Claude Code / Claude Opus 4.8

Signed-off-by: Larry Gritz <lg@larrygritz.com>
PSDInput::decompress_zip_prediction(span<char> src, span<char> dest,
const uint32_t width, const uint32_t height)
{
OIIO_ASSERT(width * height * (m_header.depth / 8) == dest.size());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this assert fail for PSB since it still uses uint32_t to compute, but dest.size() is now in 64-bit? Should we cast these to size_t as well?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are correct. Will fix.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it was easier (and maybe caught another couple problems) to change the width and height parameters themselves to uint64_t. Pushed an update.

…packbits

Signed-off-by: Larry Gritz <lg@larrygritz.com>
Comment thread src/psd.imageio/psdinput.cpp Outdated
Comment on lines +642 to +646
if (ok && !m_specs.empty()) {
if (!check_open(m_specs[0], { 0, 300000, 0, 300000, 0, 1, 0, 56 })
|| !check_compression_ratio(m_specs[0], ioproxy()->size())) {
close();
return false;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, one question: do we want this guard here, or before load_layers()? My read is that we decompress the file in load_layer_channel(), which runs during load_layers(), and we want to avoid that. Correct me if I'm wrong!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. I'm not sure if we can do it before load_layers, but I'm really eyeing load_image_data as something we certainly want to do only after all the cheap early-outs. Let me take a look at all the various load_* functions and see how early I can push the check_open/check_compression_ratio, versus which of these read functions are needed for the information that's crucial to the check functionality.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After some investigation, I think this can be arranged to happen right after load_header() and set_type_desc(), and before any of the other load_* functions get a chance to act on absurd header values. Update pushed, thanks for noticing this and making the suggestion!

Code review FTW!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Glad it helped. I had to deal with a bunch of PSD/PSB decompression bombs in the past, so it caught my eye haha

… load_header and set_type_spec, but before the rest of the load_*() functions and their allocations.

Signed-off-by: Larry Gritz <lg@larrygritz.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants