diff --git a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs index d62ca32807..204e3f3ffc 100644 --- a/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs +++ b/src/ImageSharp/Formats/Pbm/BufferedReadStreamExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using System.Runtime.CompilerServices; using SixLabors.ImageSharp.IO; namespace SixLabors.ImageSharp.Formats.Pbm; @@ -28,16 +29,78 @@ public static void SkipWhitespaceAndComments(this BufferedReadStream stream) { innerValue = stream.ReadByte(); } - while (innerValue != 0x0a); + while (!IsNewlineOrEndOfStream(innerValue)); // Continue searching for whitespace. val = innerValue; } - isWhitespace = val is 0x09 or 0x0a or 0x0d or 0x20; + isWhitespace = IsWhitespace(val); } while (isWhitespace); stream.Seek(-1, SeekOrigin.Current); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsNewlineOrEndOfStream(int val) + { + // See comment below for how this works + if (Environment.Is64BitProcess) + { + const ulong magicConstant = 0x8010000000000000UL; + + ulong i = (uint)val + 0x1; // - -0x1 -> + 0x1 + ulong shift = magicConstant << (int)i; + ulong mask = i - 64; + + return (long)(shift & mask) < 0; + } + + return val is 0x0a or -0x1; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool IsWhitespace(int val) + { + // The magic constant 0xC800010000000000 is a 64 bit value containing 1s at the indices + // of all whitespace chars minus 0x09 in a backwards order (from the MSB downwards). + // The subtraction of 0x09 is necessary so that the entire range fits in 64 bits. + // + // From the input 0x09 is subtracted, then it's zero-extended to ulong, meaning the upper + // 32 bits are 0. Then the constant is left shifted by that offset. + // A bitmask, that has the sign bit (the highest bit) set iff 'val' is in the [0x09, 0x09 + 64) range, + // is applied. Thus we only need to check if the final result is < 0, which will only be the case if + // 'i' was the index of a set bit in the magic constant, and if val was in the allowed range. + /* Constant created with + using System; + using System.Linq; + + int[] chars = { 0x09, 0x0a, 0x0d, 0x20 }; + int min = chars.Min(); + ulong magic = 0; + + foreach (int c in chars) + { + int idx = c - min; + magic |= 1UL << (64 - 1 - idx); + } + + Console.WriteLine(magic); + Console.WriteLine($"0x{magic:X16}"); + */ + + if (Environment.Is64BitProcess) + { + const ulong magicConstant = 0xC800010000000000UL; + + ulong i = (uint)val - 0x09; + ulong shift = magicConstant << (int)i; + ulong mask = i - 64; + + return (long)(shift & mask) < 0; + } + + return val is 0x09 or 0x0a or 0x0d or 0x20; + } } /// diff --git a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs index 1f7c7586eb..4115e28480 100644 --- a/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs +++ b/src/ImageSharp/Formats/Webp/Lossless/Vp8LEncoder.cs @@ -915,7 +915,7 @@ private void StoreFullHuffmanCode(Span huffTree, HuffmanTreeToken[] while (i-- > 0) { int ix = tokens[i].Code; - if (ix is 0 or 17 or 18) + if (Is0Or17Or18(ix)) { trimmedLength--; // Discount trailing zeros. trailingZeroBits += codeLengthBitDepth[ix]; @@ -934,6 +934,19 @@ private void StoreFullHuffmanCode(Span huffTree, HuffmanTreeToken[] } } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static bool Is0Or17Or18(int val) + { + // See comment in BufferedReadStreamExtensions on how this works. + const ulong magicConstant = 0x8000600000000000UL; + + ulong i = (uint)val; + ulong shift = magicConstant << (int)i; + ulong mask = i - 64; + + return (long)(shift & mask) < 0; + } + bool writeTrimmedLength = trimmedLength > 1 && trailingZeroBits > 12; int length = writeTrimmedLength ? trimmedLength : numTokens; this.bitWriter.PutBits((uint)(writeTrimmedLength ? 1 : 0), 1); diff --git a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs index 499607772f..c40ec7318a 100644 --- a/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs +++ b/tests/ImageSharp.Tests/Formats/Pbm/PbmMetadataTests.cs @@ -1,6 +1,7 @@ // Copyright (c) Six Labors. // Licensed under the Six Labors Split License. +using SixLabors.ImageSharp.Formats; using SixLabors.ImageSharp.Formats.Pbm; using static SixLabors.ImageSharp.Tests.TestImages.Pbm; @@ -80,4 +81,14 @@ public void Identify_DetectsCorrectComponentType(string imagePath, PbmComponentT Assert.NotNull(bitmapMetadata); Assert.Equal(expectedComponentType, bitmapMetadata.ComponentType); } + + [Fact] + public void Identify_HandlesCraftedDenialOfServiceString() + { + byte[] bytes = Convert.FromBase64String("UDEjWAAACQAAAAA="); + ImageInfo info = Image.Identify(bytes); + Assert.Equal(default, info.Size); + Configuration.Default.ImageFormatsManager.TryFindFormatByFileExtension("pbm", out IImageFormat format); + Assert.Equal(format!, info.Metadata.DecodedImageFormat); + } }