From 34e7f9700d8e68bf00c27cffa678b0dfcf539365 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 14:44:03 +1000 Subject: [PATCH 1/4] Fix #3067 --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 19 ++++++++++----- .../Formats/Bmp/BmpDecoderTests.cs | 23 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index 17c1f545b7..db53c69464 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -130,6 +130,13 @@ internal sealed class BmpDecoderCore : ImageDecoderCore Image? image = null; try { + ushort bitsPerPixel = this.infoHeader.BitsPerPixel; + + if (bitsPerPixel is not (1 or 2 or 4 or 8 or 16 or 24 or 32)) + { + BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); + } + int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); @@ -138,23 +145,23 @@ internal sealed class BmpDecoderCore : ImageDecoderCore switch (this.infoHeader.Compression) { - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: + case BmpCompression.RGB when bitsPerPixel is 32 && this.bmpMetadata.InfoHeaderType is BmpInfoHeaderType.WinVersion3: this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 32: + case BmpCompression.RGB when bitsPerPixel is 32: this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 24: + case BmpCompression.RGB when bitsPerPixel is 24: this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is 16: + case BmpCompression.RGB when bitsPerPixel is 16: this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8 && this.processedAlphaMask: + case BmpCompression.RGB when bitsPerPixel is <= 8 && this.processedAlphaMask: this.ReadRgbPaletteWithAlphaMask( stream, pixels, @@ -166,7 +173,7 @@ internal sealed class BmpDecoderCore : ImageDecoderCore inverted); break; - case BmpCompression.RGB when this.infoHeader.BitsPerPixel is <= 8: + case BmpCompression.RGB when bitsPerPixel is <= 8: this.ReadRgbPalette( stream, pixels, diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 94cfe85ee5..9487c4ce9a 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -571,4 +571,27 @@ public class BmpDecoderTests }); Assert.IsType(ex.InnerException); } + + [Fact] + public void BmpDecoder_ThrowsException_Issue3067() + { + // Construct minimal BMP with bitsPerPixel = 0 + byte[] bmp = new byte[54]; + bmp[0] = (byte)'B'; + bmp[1] = (byte)'M'; + BitConverter.GetBytes(54).CopyTo(bmp, 2); + BitConverter.GetBytes(54).CopyTo(bmp, 10); + BitConverter.GetBytes(40).CopyTo(bmp, 14); + BitConverter.GetBytes(1).CopyTo(bmp, 18); + BitConverter.GetBytes(1).CopyTo(bmp, 22); + BitConverter.GetBytes((short)1).CopyTo(bmp, 26); + BitConverter.GetBytes((short)0).CopyTo(bmp, 28); // bitsPerPixel = 0 + + using MemoryStream stream = new(bmp); + + InvalidImageContentException ex = Assert.Throws(() => + { + using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); + }); + } } From 07d2c04bf19b4698b6aca9b9bb4d4a33ba0f7622 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 14:51:01 +1000 Subject: [PATCH 2/4] Potential fix for pull request finding 'Useless assignment to local variable' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com> --- tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs index 9487c4ce9a..6ebe1bf4e0 100644 --- a/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Bmp/BmpDecoderTests.cs @@ -589,7 +589,7 @@ public class BmpDecoderTests using MemoryStream stream = new(bmp); - InvalidImageContentException ex = Assert.Throws(() => + Assert.Throws(() => { using Image image = BmpDecoder.Instance.Decode(DecoderOptions.Default, stream); }); From 1d87e61a96640140d45eb8a1c3deb9af654d4363 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 15:05:47 +1000 Subject: [PATCH 3/4] Update BmpDecoderCore.cs --- src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs | 21 +++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs index db53c69464..a1de790c3d 100644 --- a/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs +++ b/src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs @@ -130,14 +130,8 @@ internal sealed class BmpDecoderCore : ImageDecoderCore Image? image = null; try { - ushort bitsPerPixel = this.infoHeader.BitsPerPixel; - - if (bitsPerPixel is not (1 or 2 or 4 or 8 or 16 or 24 or 32)) - { - BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); - } - int bytesPerColorMapEntry = this.ReadImageHeaders(stream, out bool inverted, out byte[] palette); + ushort bitsPerPixel = this.infoHeader.BitsPerPixel; image = new Image(this.configuration, this.infoHeader.Width, this.infoHeader.Height, this.metadata); @@ -149,19 +143,23 @@ internal sealed class BmpDecoderCore : ImageDecoderCore this.ReadRgb32Slow(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 32: this.ReadRgb32Fast(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 24: this.ReadRgb24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; + case BmpCompression.RGB when bitsPerPixel is 16: this.ReadRgb16(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); break; - case BmpCompression.RGB when bitsPerPixel is <= 8 && this.processedAlphaMask: + + case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8 && this.processedAlphaMask: this.ReadRgbPaletteWithAlphaMask( stream, pixels, @@ -173,7 +171,8 @@ internal sealed class BmpDecoderCore : ImageDecoderCore inverted); break; - case BmpCompression.RGB when bitsPerPixel is <= 8: + + case BmpCompression.RGB when bitsPerPixel is > 0 and <= 8: this.ReadRgbPalette( stream, pixels, @@ -186,6 +185,10 @@ internal sealed class BmpDecoderCore : ImageDecoderCore break; + case BmpCompression.RGB when bitsPerPixel is <= 0 or > 32: + BmpThrowHelper.ThrowInvalidImageContentException($"Invalid bits per pixel: {bitsPerPixel}"); + break; + case BmpCompression.RLE24: this.ReadRle24(stream, pixels, this.infoHeader.Width, this.infoHeader.Height, inverted); From 128d6f943a8e6b190352db53674e3ef87fb15d36 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Fri, 6 Mar 2026 15:31:20 +1000 Subject: [PATCH 4/4] Update IcoDecoderTests.cs --- .../Formats/Icon/Ico/IcoDecoderTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 85ff51b185..539826799e 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -204,12 +204,19 @@ public class IcoDecoderTests } [Theory] - [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidBpp, PixelTypes.Rgba32)] + public void InvalidThrows_InvalidImageContentException(TestImageProvider provider) + => Assert.Throws(() => + { + using Image image = provider.GetImage(IcoDecoder.Instance); + }); + + [Theory] + [WithFile(InvalidAll, PixelTypes.Rgba32)] [WithFile(InvalidCompression, PixelTypes.Rgba32)] [WithFile(InvalidRLE4, PixelTypes.Rgba32)] [WithFile(InvalidRLE8, PixelTypes.Rgba32)] - public void InvalidTest(TestImageProvider provider) + public void InvalidThows_NotSupportedException(TestImageProvider provider) => Assert.Throws(() => { using Image image = provider.GetImage(IcoDecoder.Instance);