CVE-2026-42500
Description
Decoding a paletted BMP file with an out-of-range palette index results in a panic when accessing pixels in the invalid image.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A panic in the BMP decoder of golang.org/x/image when decoding a paletted BMP file with an out-of-range palette index allows denial of service via crafted image.
Vulnerability
The vulnerability exists in the BMP decoder of the golang.org/x/image library. When decoding a paletted BMP file, if the palette index in the pixel data is out of the valid range (i.e., greater than the number of palette entries), the decoder panics instead of returning an error. This affects versions prior to v0.41.0. [1][3]
Exploitation
An attacker can craft a malicious BMP file with an out-of-range palette index. No special privileges or authentication are required; the victim only needs to decode the crafted image using the vulnerable library. The decoding process triggers a panic, causing the application to crash. [1]
Impact
Successful exploitation results in a denial of service (DoS) due to the panic. The application terminates unexpectedly. No data confidentiality or integrity is compromised, but availability is affected. [1]
Mitigation
The fix was released in version v0.41.0 of golang.org/x/image on May 21, 2026. Users should update to this version or later. The decoder now correctly returns an error instead of panicking. No workarounds are mentioned. [1][3]
AI Insight generated on May 29, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected products
1Patches
10d61147654dcbmp: reject input with invalid palette index
2 files changed · +117 −2
bmp/reader.go+9 −2 modified@@ -18,6 +18,8 @@ import ( // feature. var ErrUnsupported = errors.New("bmp: unsupported BMP image") +var errInvalidPaletteIndex = errors.New("bmp: invalid palette index") + func readUint16(b []byte) uint16 { return uint16(b[0]) | uint16(b[1])<<8 } @@ -29,7 +31,8 @@ func readUint32(b []byte) uint32 { // decodePaletted reads a 1, 2, 4 or 8 bit-per-pixel BMP image from r. // If topDown is false, the image rows will be read bottom-up. func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.Image, error) { - paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette)) + palette := c.ColorModel.(color.Palette) + paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), palette) if c.Width == 0 || c.Height == 0 { return paletted, nil } @@ -51,7 +54,11 @@ func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.I byteIndex, bitIndex, mask := 0, 8, byte((1<<bpp)-1) for pixIndex := 0; pixIndex < c.Width; pixIndex++ { bitIndex -= bpp - p[pixIndex] = (b[byteIndex]) >> bitIndex & mask + paletteIndex := (b[byteIndex]) >> bitIndex & mask + if int(paletteIndex) >= len(palette) { + return nil, errInvalidPaletteIndex + } + p[pixIndex] = paletteIndex if bitIndex == 0 { byteIndex++ bitIndex = 8
bmp/reader_test.go+108 −0 modified@@ -6,6 +6,7 @@ package bmp import ( "bytes" + "encoding/binary" "fmt" "image" "io" @@ -83,6 +84,67 @@ func TestDecode(t *testing.T) { } } +func TestDecodeConstructed(t *testing.T) { + for _, tc := range []struct { + name string + b []byte + wantErr error + }{{ + name: "1x1 paletted", + b: bmpBuilder{ + width: 1, + height: 1, + planes: 1, + bitsPerPixel: 1, + colorsUsed: 2, + colorTable: []colorTableEntry{ + {0, 0, 0}, + {0xff, 0xff, 0xff}, + }, + data: []byte{0, 0, 0, 0}, + }.Bytes(), + wantErr: nil, // successful base case + }, { + name: "1x1 rgb", + b: bmpBuilder{ + width: 1, + height: 1, + planes: 1, + bitsPerPixel: 24, + data: []byte{ + 0, 0, 0, 0, + }, + }.Bytes(), + wantErr: nil, // successful base case + }, { + name: "invalid palette index", + b: bmpBuilder{ + width: 1, + height: 1, + planes: 1, + bitsPerPixel: 8, + colorsUsed: 2, + colorTable: []colorTableEntry{ + {0, 0, 0}, + {0xff, 0xff, 0xff}, + }, + data: []byte{ + 2, 0, 0, 0, // index 2 + }, + }.Bytes(), + wantErr: errInvalidPaletteIndex, + }} { + img, _, err := image.Decode(bytes.NewReader(tc.b)) + if err != tc.wantErr { + t.Errorf("%v: Decode error %v; want %v", tc.name, err, tc.wantErr) + } + if err != nil { + continue + } + _ = img.At(0, 0) // try accessing a pixel + } +} + // TestEOF tests that decoding a BMP image returns io.ErrUnexpectedEOF // when there are no headers or data is empty func TestEOF(t *testing.T) { @@ -91,3 +153,49 @@ func TestEOF(t *testing.T) { t.Errorf("Error should be io.ErrUnexpectedEOF on nil but got %v", err) } } + +type bmpBuilder struct { + width int32 + height int32 + planes uint16 + bitsPerPixel uint16 + compression uint32 + imageSize uint32 + xppm uint32 + yppm uint32 + colorsUsed uint32 + colorsImportant uint32 + colorTable []colorTableEntry + data []byte +} + +type colorTableEntry struct { + r, g, b byte +} + +func (b bmpBuilder) Bytes() []byte { + buf := []byte{ + 0x42, 0x4d, // 'BM' + 0x00, 0x00, 0x00, 0x00, // file size + 0x00, 0x00, 0x00, 0x00, // reserved + 0x00, 0x00, 0x00, 0x00, // data offset + 0x28, 0x00, 0x00, 0x00, // header size (40) + } + buf = binary.LittleEndian.AppendUint32(buf, uint32(b.width)) + buf = binary.LittleEndian.AppendUint32(buf, uint32(b.height)) + buf = binary.LittleEndian.AppendUint16(buf, b.planes) + buf = binary.LittleEndian.AppendUint16(buf, b.bitsPerPixel) + buf = binary.LittleEndian.AppendUint32(buf, b.compression) + buf = binary.LittleEndian.AppendUint32(buf, b.imageSize) + buf = binary.LittleEndian.AppendUint32(buf, b.xppm) + buf = binary.LittleEndian.AppendUint32(buf, b.yppm) + buf = binary.LittleEndian.AppendUint32(buf, b.colorsUsed) + buf = binary.LittleEndian.AppendUint32(buf, b.colorsImportant) + for _, e := range b.colorTable { + buf = append(buf, e.r, e.g, e.b, 0) + } + binary.LittleEndian.PutUint32(buf[10:], uint32(len(buf))) // data offset + buf = append(buf, b.data...) + binary.LittleEndian.PutUint32(buf[2:], uint32(len(buf))) // file size + return buf +}
Vulnerability mechanics
No source-code context for this CVE — mechanics is only generated when we can read the actual fix diff. Without that, the four sections (root cause, attack vector, affected code, fix) would be speculation rather than analysis.
References
4News mentions
0No linked articles in our index yet.