DOKIMASecurity review report

Overview

Image preview accepts 100-megapixel images (boundary off-by-one) and fully decodes them into memory

medium Possibly Valid medium confidence

DOK-100025-FILEBROWSER-PREVIEW-THUMBNAILS-THUMBNAIL-DECODE-G · filebrowser · preview-thumbnails

Status

Possibly Valid

The boundary-inclusive 100MP acceptance and full-decode sink are proven; the memory-amplification DoS magnitude is gated on host memory/concurrency, so possibly_valid. Canonical of the pair (100061). Distinct from the disk-cache DoS DOK-100005.

Repository / Component

Repository
filebrowser
Component
preview-thumbnails

Plain-English Description

The preview feature caps image dimensions but the check is off by one, so a picture that is exactly 10000x10000 (100 million pixels) is accepted and fully loaded into memory. A user can request several such previews at once and consume large amounts of server memory.

Description of the Underlying Issue

The dimension guard at img/service.go:205-208 rejects only width>MaxImageWidth || height>MaxImageHeight, with both maxima = 10000 (service.go:26-29). Because the comparison is strictly greater-than, an image of exactly 10000x10000 = 100 megapixels passes the guard and is fully materialized by imaging.Decode before Fill/Fit (service.go:178-192), roughly ~400MB transient per image (100MP x 4 bytes/pixel RGBA). The only concurrency bound is the worker semaphore sized by imageProcessors, default 4 (cmd/root.go:93,166-170). There is no total-pixel budget and no cap on total in-flight decode memory.

Potential Attack

An authenticated user with Perm.Download uploads or references a 10000x10000 image and repeatedly requests its thumb/preview, driving up to imageProcessors concurrent full decodes, each allocating hundreds of MB.

Outcomes of Potential Attack

Memory-amplification denial of service: transient allocations of ~4x~400MB can exhaust host memory and OOM the process, degrading or crashing the service for all users. Magnitude depends on host memory and concurrency.

Affected Scope

GET /api/preview for any Perm.Download user: a 10000x10000 (100MP) image is accepted and fully decoded, ~400MB transient per image, up to imageProcessors concurrent.

Suggested Fix (plain english)

Limit images by total pixel count (width times height), not just each side, and lower the ceiling; also cap how much decode work runs at once so previews cannot exhaust memory.

Suggested Fix (detailed)

Replace the per-dimension guard (service.go:205-208) with a total-pixel budget check (width*height <= budget) using an inclusive comparison, and/or lower MaxImageWidth/MaxImageHeight (service.go:26-29). Additionally bound total in-flight decode memory (not just worker count) so imageProcessors concurrent large decodes cannot sum to an OOM. Verify by requesting a 10000x10000 preview and confirming it is now rejected, and by load-testing concurrent large previews under a memory limit. Use a total-pixel budget (e.g. width*height) rather than per-dimension caps, and/or lower the maximum dimensions; bound total in-flight decode memory.

Validation

The boundary-inclusive 100MP acceptance and full-decode sink are proven; the memory-amplification DoS magnitude is gated on host memory/concurrency, so possibly_valid. Canonical of the pair (100061). Distinct from the disk-cache DoS DOK-100005.

first_opinion

Full Evidence

img/service.go:26-29 MaxImageWidth/MaxImageHeight=10000
img/service.go:205-208 guard rejects only width>10000||height>10000, so exactly 10000x10000=100MP is allowed
img/service.go:178-192 imaging.Decode fully materializes then Fill/Fit
img/service.go:147-151 the worker semaphore is the only concurrency bound
cmd/root.go:93,166-170 imageProcessors default 4 (min 1)
Proven fact: The dimension guard is off-by-one at the boundary: exactly 10000x10000 (100 megapixels) passes and is fully decoded into memory
Proven fact: Concurrency is bounded only by the imageProcessors semaphore (default 4)
Unvalidated fact: Whether ~4x400MB transient allocations actually OOM the host depends on available memory and the NoOp default cache (repeatability) — a runtime magnitude fact
img/service.go:205-208 guard rejects only width>10000||height>10000, so exactly 10000x10000=100MP is allowed (verified: strict > comparison)
img/service.go:178-192 imaging.Decode fully materializes the image then Fill/Fit
Unvalidated fact: Whether ~4x~400MB transient allocations actually OOM the host depends on available memory and the NoOp default cache (repeatability) — a runtime magnitude fact
img/service.go:26-29 (MaxImageWidth/MaxImageHeight = 10000)
img/service.go:205-208 (guard rejects only width>10000 || height>10000, so exactly 10000x10000 = 100MP is allowed)
img/service.go:178-192 (imaging.Decode full materialization + imaging.Fill/Fit)
img/service.go:147-151 (semaphore is the only concurrency bound)
cmd/root.go:93 and cmd/root.go:166-170 (imageProcessors default 4, min 1)