readAndValidateImage caps the byte size of incoming images but the resize() helper that follows still called image.Decode unconditionally, allocating pixel memory proportional to the *declared* image dimensions. A ~100 KB compressed PNG or GIF that declares 65535x65535 px forces image.Decode to allocate ~17 GB of raster, OOMing the service on a single comment upload (or on the proxy's CacheExternal path when caching a malicious upstream). Hardening: - maxImagePixels = 16 MP constant. Covers any realistic image (~4096x4096) while bounding peak allocation. - resize() now runs image.DecodeConfig first (cheap, no pixel allocation) to read declared width/height before any full decode. - Multiplication of width × height uses int64 to defeat 32-bit overflow (GOARCH=386, 32-bit arm): on those targets, int(cfg.Width)*int(cfg.Height) could wrap below maxImagePixels and bypass the cap. GIF's 16-bit logical screen and JPEG's 16-bit SOF dimensions both reach this if int-multiplied. - Bytes exceeding the cap, or non-image input that fails DecodeConfig, return nil. prepareImage propagates the rejection as a clear error instead of storing the malformed/oversized data verbatim. - The no-resize-needed path returns the validated original bytes verbatim so animated GIFs round-trip without being flattened to a single frame. The DecodeConfig precheck applies even when MaxWidth/MaxHeight are 0 (resize disabled) — the dimension cap is unconditional defense-in-depth. Two adjacent fixes surfaced by the new resize contract: 1. readAndValidateImage previously did `data[:512]` without a bounds check, panicking on any body shorter than 512 bytes. Now bounded with min(). 2. image/webp was listed as an allowed format but no WebP decoder was registered, so DecodeConfig would refuse legitimate WebP uploads. Added `_ "golang.org/x/image/webp"` (already in go.mod via x/image/draw) so the registered decoders match the allowlist. Tests: - TestService_resizeRejectsDecompressionBomb builds a 14-byte GIF87a header declaring 65535x65535 and asserts resize() refuses it both at the unit level and through SaveWithID end-to-end (no store write). - TestService_SaveWithIDShortPayload regression-tests the short-body panic. - TestService_SaveWithIDWebP regression-tests WebP round-trip through prepareImage with the new DecodeConfig requirement. - TestService_resize subtests updated to assert non-image bytes are now refused (previously the helper fell back to returning the raw bytes verbatim, letting malformed content reach the store).
38 B
1x1px
38 B
1x1px