mirror of
https://github.com/navidrome/navidrome.git
synced 2026-08-01 07:21:17 +00:00
fix(artwork): compare the pixel cap without multiplying
Defence in depth rather than a live hole: the reported crafted PNG (0xffffffff square) never reaches the multiplication, because image/png rejects it at DecodeConfig, and the largest dimensions any supported format can declare — 2^30-1 for PNG, 16-bit for JPEG and GIF, 14-bit for WebP — cannot overflow the int64 product. decodeCapped is format-agnostic though, so the guard should not depend on a decoder's own limits staying where they are. Comparing by division holds for any dimensions a decoder might report, and non-positive ones are now rejected outright.
This commit is contained in:
parent
ca8f4be369
commit
19d89143f7
@ -177,7 +177,9 @@ func decodeCapped(data []byte) (image.Image, string, error) {
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("decode image config: %w", err)
|
||||
}
|
||||
if int64(cfg.Width)*int64(cfg.Height) > maxImagePixels {
|
||||
// Compared by division so the cap holds for any dimensions a decoder might report, without
|
||||
// depending on a multiplication staying inside int64.
|
||||
if cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width > maxImagePixels/cfg.Height {
|
||||
return nil, "", fmt.Errorf("image dimensions %dx%d exceed pixel cap %d", cfg.Width, cfg.Height, maxImagePixels)
|
||||
}
|
||||
img, _, err := image.Decode(bytes.NewReader(data))
|
||||
|
||||
@ -374,6 +374,20 @@ var _ = Describe("processItem", func() {
|
||||
Expect(err).To(MatchError(model.ErrNotFound))
|
||||
})
|
||||
|
||||
// The cap is compared by division, so it cannot be slipped by dimensions whose product
|
||||
// would overflow. No supported format can declare such dimensions today — image/png caps
|
||||
// them at 2^30-1 and the rest are 16-bit — so this pins the arithmetic, not a live hole.
|
||||
DescribeTable("rejects out-of-range declared dimensions",
|
||||
func(w, h uint32) {
|
||||
_, err := decodeArtwork(ctx, "bomb", pngHeaderWithDims(w, h))
|
||||
Expect(err).To(HaveOccurred())
|
||||
},
|
||||
Entry("both dimensions at the 32-bit maximum", uint32(0xffffffff), uint32(0xffffffff)),
|
||||
Entry("both dimensions at the signed 32-bit maximum", uint32(0x7fffffff), uint32(0x7fffffff)),
|
||||
Entry("zero width", uint32(0), uint32(100)),
|
||||
Entry("zero height", uint32(100), uint32(0)),
|
||||
)
|
||||
|
||||
It("decompression bomb: rejects huge declared dimensions before the full decode", func() {
|
||||
data := pngHeaderWithDims(50000, 50000) // 2.5 gigapixels, far above the cap
|
||||
_, err := decodeArtwork(ctx, "bomb", data)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user