Skip to content

fix(psd): bounds-check thumbnail, color data, and layer channel lengths - #5457

Open
lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-psdfuzz
Open

lgritz wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
lgritz:lg-psdfuzz

Conversation

@lgritz

@lgritz lgritz commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Fuzzing the psd reader turned up several corrupt-input failures:

  • load_resource_thumbnail() computed jpeg_length = length - 28 with no
    lower bound check, so a short resource underflowed into a multi-GB
    allocation.
  • load_color_data() allocated from the declared length without checking it
    against the file size.
  • load_layer_channel() sized compressed_data from the raw channel
    data_length. Bound it by the bytes remaining from the start of the
    channel, compared unsigned: data_length is 64 bits for PSB, where a
    corrupt value cast to int64_t wraps negative and slips past the guard
    into an uncaught bad_alloc.
  • The RLE path subtracted the RLE length table from data_length without
    checking that it fit, underflowing on corrupt input.
  • load_layers_16_32() asserted layer_info.length == 0, so a file carrying
    both a normal layer info section and an Lr16/Lr32 block aborted a debug
    build. Return an error instead.

Adds fixtures for each new rejection path.

Assisted-by: Claude Code / Claude Opus 5

@lgritz

lgritz commented Sep 13, 2026

Copy link
Copy Markdown
Collaborator Author

Comments or concerns?

Copilot AI left a comment

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.

🟡 Changes recommended

Unresolved thumbnail and channel-length validation issues remain, along with missing regression coverage.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Hardens the PSD reader against corrupt length fields and conflicting layer-info sections discovered through fuzzing.

Changes:

  • Validates color, thumbnail, and layer-channel lengths.
  • Replaces the Lr16/Lr32 layer-info assertion with an error.
  • Adds a thumbnail corruption regression test and expected output.
File summaries
File Description
testsuite/psd/run.py Runs the thumbnail corruption test.
testsuite/psd/ref/out.txt Records expected failure output.
src/psd.imageio/psdinput.cpp Adds PSD length validation and duplicate-section handling.
Review details

Suppressed comments (2)

src/psd.imageio/psdinput.cpp:1471

  • length includes the 28-byte thumbnail header, but this check runs before that header is consumed and only compares jpeg_length. A block declared up to 28 bytes past EOF can therefore pass this guard and allocate nearly the entire remaining file before the later read fails. Compare the full resource length with the bytes remaining at the resource start (or subtract the header from the available bytes) before allocating.
    if (int64_t(jpeg_length) > file_size - iotell()) {

src/psd.imageio/psdinput.cpp:1099

  • Please add a PSD regression fixture for this new color-data length check. The added test only exercises the thumbnail path, so a truncated color-mode-data section could regress to allocating the reported length without being caught by testsuite/psd.
        if (size_t(iotell()) + m_color_data.length > ioproxy()->size())
  • Files reviewed: 3/4 changed files
  • Comments generated: 3
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/psd.imageio/psdinput.cpp Outdated
Comment on lines +1783 to +1784
int64_t file_size = ioproxy() ? ioproxy()->size() : 0;
if (int64_t(channel_info.data_length) > file_size - channel_info.data_pos) {
Comment thread src/psd.imageio/psdinput.cpp Outdated

// A channel's stored data cannot extend past the end of the file.
int64_t file_size = ioproxy() ? ioproxy()->size() : 0;
if (int64_t(channel_info.data_length) > file_size - channel_info.data_pos) {
Comment on lines +2001 to +2004
if (layer_info.length != 0) {
errorfmt("[Global Additional Layer Info] unexpected second layer info "
"section");
return false;
Fuzzing the psd reader turned up several corrupt-input failures:

- load_resource_thumbnail() computed jpeg_length = length - 28 with no
  lower bound check, so a short resource underflowed into a multi-GB
  allocation.
- load_color_data() allocated from the declared length without checking it
  against the file size.
- load_layer_channel() sized compressed_data from the raw channel
  data_length. Bound it by the bytes remaining from the start of the
  channel, compared unsigned: data_length is 64 bits for PSB, where a
  corrupt value cast to int64_t wraps negative and slips past the guard
  into an uncaught bad_alloc.
- The RLE path subtracted the RLE length table from data_length without
  checking that it fit, underflowing on corrupt input.
- load_layers_16_32() asserted layer_info.length == 0, so a file carrying
  both a normal layer info section and an Lr16/Lr32 block aborted a debug
  build. Return an error instead.

Adds fixtures for each new rejection path.

Assisted-by: Claude Code / Claude Opus 5

Signed-off-by: Larry Gritz <lg@larrygritz.com>
@lgritz

lgritz commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator Author

Updated based on review comments

@lgritz lgritz changed the title fix(psd): reject corrupt thumbnail/channel lengths, drop layer-info assert fix(psd): bounds-check thumbnail, color data, and layer channel lengths Sep 15, 2026
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