Skip to content

fix(thumbnails): bound declared image dimensions before decoding - #3457

Open
dschmidt wants to merge 1 commit into
mainfrom
fix/thumbnails-dimension-guard
Open

fix(thumbnails): bound declared image dimensions before decoding#3457
dschmidt wants to merge 1 commit into
mainfrom
fix/thumbnails-dimension-guard

Conversation

@dschmidt

@dschmidt dschmidt commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Description

The non-vips (imaging) build decodes the full pixel buffer from the header declared dimensions before the existing MaxInputWidth/MaxInputHeight guard runs. That guard sits in SimpleManager.Generate, at which point preprocessor.Convert has already decoded the image. The Go stdlib backend allocates image.NewGray/image.NewYCbCr for the declared width*height at the SOS marker, before it fails on the mismatched scan data, so a tiny crafted file forces a large allocation and can exhaust the memory of the thumbnails worker.

This reads only the header with DecodeConfig, which allocates no pixels, and rejects oversized sources before the decode happens. The limit is threaded through the decoders that recurse into a second attacker controlled image: audio cover art (mp3/flac/ogg) and the geogebra decoders. The vips build gets the same check on the header lazy vips.NewImageFromReader, before ThumbnailWithSize materializes anything.

The service now passes the configured MaxInputWidth/MaxInputHeight into the preprocessor options, so a zero limit keeps the previous behaviour and an unconfigured decoder stays unrestricted.

Impact is availability only, and the official container images are not affected because they build with ENABLE_VIPS=true. Affected are the non-vips builds produced by make release-linux / release-darwin, so development builds and the published standalone binaries. Reachable by anyone who can have a preview generated, which means authenticated upload or a public share.

Related Issue

Reported privately to security@opencloud.eu on 2026-08-18 and cleared for public handling, no public issue exists.

Motivation and Context

A 375 byte grayscale JPEG whose SOF0 declares large dimensions while the pixel data stays 8x8 drives the allocation in ImageDecoder.Convert. Peak live HeapAlloc: 20000x20000 gives roughly 381 MB, 65535x65535 gives roughly 4 GB. With the guard in place the same files stay at roughly 17 KB.

How Has This Been Tested?

  • test environment: local build, go test ./services/thumbnails/..., plus a running container to check the vips path
  • test case 1: crafted JPEG with a 20000x20000 SOF0 header is rejected with ErrImageTooLarge before the decode, verified by the new specs and by peak heap measurement
  • test case 2: legitimate images (800x600 JPEG, gif, ggs, ggp, audio cover art) decode unchanged, existing preprocessor specs stay green
  • test case 3: an oversized cover image embedded in a ggp file is rejected, which covers the recursive decoders
  • test case 4: on a vips container a crafted preview was already rejected (HTTP 403/404) with flat memory, so that build keeps its behaviour

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Technical debt
  • Tests only (no source changes)

Checklist:

  • Code changes
  • Unit tests added
  • Acceptance tests added
  • Documentation added

@dschmidt
dschmidt force-pushed the fix/thumbnails-dimension-guard branch from da512aa to 1a104b5 Compare September 3, 2026 12:16
@codacy-production

codacy-production Bot commented Sep 3, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 12 complexity

Metric Results
Complexity 12

View in Codacy

🟢 Coverage 83.58% diff coverage · +0.06% coverage variation

Metric Results
Coverage variation +0.06% coverage variation (-1.00%)
Diff coverage 83.58% diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (61037cc) 88237 20687 23.44%
Head commit (50c38bf) 88294 (+57) 20751 (+64) 23.50% (+0.06%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#3457) 67 56 83.58%

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@dschmidt
dschmidt force-pushed the fix/thumbnails-dimension-guard branch from 1a104b5 to bff00be Compare September 3, 2026 12:19
The imaging build decodes the full pixel buffer from the header-declared
dimensions before the existing MaxInputWidth/MaxInputHeight guard runs, so a
tiny crafted file whose header declares huge dimensions forces a multi-GB
allocation and can OOM the worker. Read the header with DecodeConfig and reject
oversized sources before the decode allocates, in both the imaging and vips
builds, and thread the limit through the audio cover-art and geogebra decoders
that decode a second attacker-controlled image.
@dschmidt
dschmidt force-pushed the fix/thumbnails-dimension-guard branch from bff00be to 50c38bf Compare September 3, 2026 12:21
@dschmidt
dschmidt marked this pull request as ready for review September 3, 2026 12:23
@dschmidt
dschmidt requested a review from rhafer September 3, 2026 12:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant