webp: reject VP8X headers with too-large canvases RFC 9649 states that the canvas width * height must be at most 2^32-1. Enforce this. This avoids creating an invalid image (which will panic when manipulated) when decoding a too-large image on 32-bit platforms. https://www.rfc-editor.org/rfc/rfc9649.html#section-2.7-12 Fixes golang/go#78407 Fixes CVE-2026-33813 Change-Id: I7e2b68374681da4f72ee51ebfd8833006a6a6964 Reviewed-on: https://go-review.googlesource.com/c/image/+/759860 LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Damien Neil <dneil@google.com> Reviewed-by: Neal Patel <nealpatel@google.com>
diff --git a/webp/decode.go b/webp/decode.go index 2371808..15dc0ee 100644 --- a/webp/decode.go +++ b/webp/decode.go
@@ -134,6 +134,12 @@ wantAlpha = (buf[0] & alphaBit) != 0 widthMinusOne = uint32(buf[4]) | uint32(buf[5])<<8 | uint32(buf[6])<<16 heightMinusOne = uint32(buf[7]) | uint32(buf[8])<<8 | uint32(buf[9])<<16 + if uint64(widthMinusOne+1)*uint64(heightMinusOne+1) > 1<<32-1 { + // The product of _Canvas Width_ and _Canvas Height_ MUST be + // at most 2^32 - 1. + // https://www.rfc-editor.org/rfc/rfc9649.html#section-2.7-12 + return nil, image.Config{}, errInvalidFormat + } if configOnly { if wantAlpha { return nil, image.Config{
diff --git a/webp/decode_test.go b/webp/decode_test.go index bb50ddd..56948ef 100644 --- a/webp/decode_test.go +++ b/webp/decode_test.go
@@ -280,6 +280,53 @@ } } +func TestVP8XImageTooLarge(t *testing.T) { + data := []byte{ + // WebP file header + 'R', 'I', 'F', 'F', + 22, 0, 0, 0, // file size + 'W', 'E', 'B', 'P', + // ChunkHeader('VP8X') + 'V', 'P', '8', 'X', + 10, 0, 0, 0, // chunk size + // bits + Reserved + 1 << 4, 0, 0, 0, // alpha bit set + // Canvas Width Minus One + 0xff, 0xff, 0x00, + // Canvas Height Minus One + 0xff, 0xff, 0x00, + } + _, err := DecodeConfig(bytes.NewReader(data)) + if err != errInvalidFormat { + t.Fatalf("unexpected error: want %q, got %q", errInvalidFormat, err) + } +} + +func TestVP8XImageNotQuiteTooLarge(t *testing.T) { + data := []byte{ + // WebP file header + 'R', 'I', 'F', 'F', + 22, 0, 0, 0, // file size + 'W', 'E', 'B', 'P', + // ChunkHeader('VP8X') + 'V', 'P', '8', 'X', + 10, 0, 0, 0, // chunk size + // bits + Reserved + 1 << 4, 0, 0, 0, // alpha bit set + // Canvas Width Minus One + 0xfe, 0xff, 0x00, + // Canvas Height Minus One + 0xfe, 0xff, 0x00, + } + cfg, err := DecodeConfig(bytes.NewReader(data)) + if err != nil { + t.Fatalf("unexpected error: want nil, got %q", err) + } + if cfg.Width != 0xffff || cfg.Height != 0xffff { + t.Fatalf("width x height: got %v x %v, want %v x %v", cfg.Width, cfg.Height, 0xffff, 0xffff) + } +} + func benchmarkDecode(b *testing.B, filename string) { data, err := ioutil.ReadFile("../testdata/blue-purple-pink-large." + filename + ".webp") if err != nil {