From 3607f79bb44c35d0be4fa8b6e24c0502b51415a9 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Sat, 13 Jun 2026 10:44:11 +0800 Subject: [PATCH] fix(thumb): max pixel dimensions constraint in builtin thumb generator --- pkg/thumb/builtin.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/thumb/builtin.go b/pkg/thumb/builtin.go index 10174099..0f6be179 100644 --- a/pkg/thumb/builtin.go +++ b/pkg/thumb/builtin.go @@ -1,6 +1,7 @@ package thumb import ( + "bytes" "context" "fmt" "github.com/cloudreve/Cloudreve/v4/pkg/filemanager/manager/entitysource" @@ -19,6 +20,12 @@ import ( const thumbTempFolder = "thumb" +// maxDecodePixels caps the decoded pixel count to prevent pixel-bomb DoS: +// stdlib decoders allocate bytesPerPixel*W*H from the header before reading +// pixel data, so a tiny file with huge dimensions can otherwise OOM-kill the +// process. 64 MP covers any realistic camera output. +const maxDecodePixels = 64 * 1000 * 1000 + // BuiltinSupportedExts lists file extensions supported by the built-in // thumbnail generator. Extensions are lowercased and do not include the dot. var BuiltinSupportedExts = []string{"jpg", "jpeg", "png", "gif"} @@ -37,7 +44,18 @@ func NewThumbFromFile(file io.Reader, ext string) (*Thumb, error) { return nil, fmt.Errorf("unknown image format: %w", ErrPassThrough) } - var err error + var hdr bytes.Buffer + cfg, _, err := image.DecodeConfig(io.TeeReader(file, &hdr)) + if err != nil { + return nil, fmt.Errorf("failed to read image config: %w (%w)", err, ErrPassThrough) + } + if cfg.Width <= 0 || cfg.Height <= 0 || + int64(cfg.Width)*int64(cfg.Height) > maxDecodePixels { + return nil, fmt.Errorf("image dimensions too large (%dx%d): %w", + cfg.Width, cfg.Height, ErrPassThrough) + } + file = io.MultiReader(&hdr, file) + var img image.Image switch ext { case "jpg", "jpeg":