CVE-2026-55497
Cloudreve: Denial of Service - Image decompression / pixel bomb in thumbnail & avatar decoding crashes the server
Summary
## Summary Cloudreve's built-in image processor decodes user-supplied images with Go's standard-library decoders (`image/png`, `image/jpeg`, `image/gif`) and guards **only the compressed file size** — never the *decoded* pixel dimensions. Go's decoders allocate a pixel buffer sized `bytesPerPixel × width × height` taken straight from the image header (e.g. a PNG's IHDR), with no upper bound on `width`/`height`. A tiny (tens-of-bytes) image that *declares* enormous dimensions therefore forces a multi-gigabyte-to-terabyte allocation (`make([]uint8, …)`), exhausting memory. The resulting out-of-memory condition is a **fatal Go runtime error / kernel OOM-kill that `recover()` cannot catch**, terminating the whole Cloudreve process for all users. Two reachable sinks share the same root cause: 1. **Avatar upload** (`PUT /api/v4/user/setting/avatar`) — decodes synchronously in the request handler. Any authenticated user. **Cleanest single-request PoC.** 2. **Thumbnail generation** (built-in generator, **enabled by default**) — decodes in the thumbnail queue worker. Reachable for the user's own files *and* for files inside a share, so a planted bomb can be (re)triggered through a public share link. Post-auth, low privilege. A single 65-byte upload deterministically takes the instance offline. ### Details ### Root cause — decode guarded by file size, not pixel count The built-in generator is the **default** image thumbnailer: ```go // inventory/setting.go @ 26b6b10 "thumb_builtin_enabled": "1", // ON by default "thumb_builtin_max_size": "78643200", // 75 MB — a *file size* cap "thumb_vips_enabled": "0", // libvips (which has its own limits) OFF by default ... "avatar_size": "4194304", // 4 MB — a *file size* cap ``` `Builtin.Generate` checks the on-disk/entity size, then hands the raw bytes to the stdlib decoders: ```go // pkg/thumb/builtin.go:144-152 @ 26b6b10 func (b Builtin) Generate(ctx context.Context, es entitysource.EntitySource, ext string, previous *Result) (*Result, error) { if es.Entity().Size() > b.settings.BuiltinThumbMaxSize(ctx) { // 75 MB compressed-size check ONLY return nil, fmt.Errorf("file is too big: %w", ErrPassThrough) } img, err := NewThumbFromFile(es, ext) // <-- decode; allocation happens here ... } // pkg/thumb/builtin.go:34-60 @ 26b6b10 func NewThumbFromFile(file io.Reader, ext string) (*Thumb, error) { switch ext { case "jpg", "jpeg": img, err = jpeg.Decode(file) case "gif": img, err = gif.Decode(file) case "png": img, err = png.Decode(file) // <-- unbounded allocation ... } } ``` There is **no `image.DecodeConfig` pre-check** and **no dimension/pixel cap** anywhere on the path. The only `Bounds()`/`MaxWidth` references in the package (`builtin.go:70,92,125`, `avatar_size_l=200`) act on the *already-decoded* image and are the *output* resize target — they execute long after the oversized input buffer has been allocated. ### Why the allocation is unbounded (Go stdlib `image/png`) Go's PNG reader takes the dimensions verbatim from IHDR and rejects only non-positive values — there is no maximum: ```go // Go src/image/png/reader.go — parseIHDR w := int32(binary.BigEndian.Uint32(d.tmp[0:4])) h := int32(binary.BigEndian.Uint32(d.tmp[4:8])) if w <= 0 || h <= 0 { return FormatError("non-positive dimension") } // only guard d.width, d.height = int(w), int(h) ``` On the first IDAT, `readImagePass` allocates the destination image **before** consuming the compressed pixel data, e.g. for colour-type 6 (RGBA, 8-bit): ```go // Go src/image/png/reader.go — readImagePass nrgba = image.NewNRGBA(image.Rect(0, 0, width, height)) // make([]uint8, 4*width*height) ``` `image.NewNRGBA` → `pixelBufferLength` → `mul3NonNeg(4, w, h)` only guards against *integer overflow* (returns −1, which panics), not against huge-but-valid sizes. So any `4·w·h` that fits in an `int` and is below the runtime's `maxAllo
Affected packages
| Ecosystem | Package | Affected versions | Fixed versions |
|---|---|---|---|
| Go | github.com/cloudreve/Cloudreve/v4 | — | 4.0.0-20260613024411-3607f79bb44c |
| Go | github.com/cloudreve/Cloudreve/v3 | — | — |
Remediation: Upgrade to 4.0.0-20260613024411-3607f79bb44c or later.
References
Includes data from the GitHub Advisory Database, licensed under CC-BY 4.0.
CVE® is a registered trademark of The MITRE Corporation. CVE content reproduced under the CVE Terms of Use; copyright designation © MITRE.