From ed64ea71976886c300cc246876b4a8859905ba47 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Thu, 30 Jul 2026 13:03:41 +1000 Subject: [PATCH] Reject ICO entries at stream-end offset Tightened icon frame bounds validation to treat `ImageOffset == available` as invalid, preventing non-empty resources from starting at the exclusive end of the icon stream. Added a regression test that mutates an ICO directory entry to this boundary value and verifies both `Decode` and `Identify` throw `InvalidImageContentException` with the expected message. --- .../Formats/Icon/IconDecoderCore.cs | 4 +-- .../Formats/Icon/Ico/IcoDecoderTests.cs | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs index 79d7e3786..83a571083 100644 --- a/src/ImageSharp/Formats/Icon/IconDecoderCore.cs +++ b/src/ImageSharp/Formats/Icon/IconDecoderCore.cs @@ -344,11 +344,11 @@ internal abstract class IconDecoderCore : ImageDecoderCore long available = stream.Length - basePosition; uint directorySize = (uint)(IconDir.Size + (this.fileHeader.Count * IconDirEntry.Size)); - // Offsets are relative to the icon resource and must not point into its directory or beyond its containing stream. + // Offsets are relative to the icon resource and must not point into its directory or at or beyond its containing stream. if (entry.Reserved is not 0 || entry.BytesInRes is 0 || entry.ImageOffset < directorySize - || entry.ImageOffset > available) + || entry.ImageOffset >= available) { throw new InvalidImageContentException("The icon directory contains an invalid image resource range."); } diff --git a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs index 0329f0e4d..bdbf67461 100644 --- a/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs +++ b/tests/ImageSharp.Tests/Formats/Icon/Ico/IcoDecoderTests.cs @@ -341,6 +341,32 @@ public class IcoDecoderTests Assert.Equal(entryCount - 1, info.FrameMetadataCollection.Count); } + /// + /// Verifies that a non-empty resource cannot begin at the exclusive end of its containing icon stream. + /// + [Fact] + public void EntryAtEndOfStream_ThrowsInvalidResourceRange() + { + byte[] data = TestFile.Create(Bpp32Size1x1).Bytes.ToArray(); + Assert.Equal(1, BinaryPrimitives.ReadUInt16LittleEndian(data.AsSpan(4))); + + // ImageOffset is the final DWORD in the sole directory entry. The stream length is an exclusive boundary, + // so accepting this value would create an empty child stream despite the entry declaring non-empty data. + BinaryPrimitives.WriteUInt32LittleEndian(data.AsSpan(IconDir.Size + 12), (uint)data.Length); + + using MemoryStream decodeStream = new(data, false); + InvalidImageContentException decodeException = Assert.Throws( + () => IcoDecoder.Instance.Decode(DecoderOptions.Default, decodeStream)); + + Assert.Equal("The icon directory contains an invalid image resource range.", decodeException.Message); + + using MemoryStream identifyStream = new(data, false); + InvalidImageContentException identifyException = Assert.Throws( + () => IcoDecoder.Instance.Identify(DecoderOptions.Default, identifyStream)); + + Assert.Equal("The icon directory contains an invalid image resource range.", identifyException.Message); + } + [Fact] public void IcoFrameMetadata_ScalesZeroEncodingDimensionsFrom256() {