Browse Source

Fix 925 (#929)

* Prevent overflow

* Cleanup huffman table

* Search for RST markers.

* Fix Benchmarks project
pull/938/head
James Jackson-South 7 years ago
committed by GitHub
parent
commit
7a484f7a57
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 92
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs
  2. 14
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs
  3. 48
      src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs
  4. 6
      src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
  5. 11
      tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj
  6. 8
      tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
  7. 2
      tests/Images/External

92
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanBuffer.cs

@ -17,7 +17,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
private ulong data; private ulong data;
// The number of valid bits left to read in the buffer. // The number of valid bits left to read in the buffer.
private int remain; private int remainingBits;
// Whether there is no more good data to pull from the stream for the current mcu. // Whether there is no more good data to pull from the stream for the current mcu.
private bool badData; private bool badData;
@ -26,10 +26,9 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
this.stream = stream; this.stream = stream;
this.data = 0ul; this.data = 0ul;
this.remain = 0; this.remainingBits = 0;
this.Marker = JpegConstants.Markers.XFF; this.Marker = JpegConstants.Markers.XFF;
this.MarkerPosition = 0; this.MarkerPosition = 0;
this.BadMarker = false;
this.badData = false; this.badData = false;
this.NoData = false; this.NoData = false;
} }
@ -44,11 +43,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// </summary> /// </summary>
public long MarkerPosition { get; private set; } public long MarkerPosition { get; private set; }
/// <summary>
/// Gets a value indicating whether a bad marker has been detected, I.E. One that is not between RST0 and RST7
/// </summary>
public bool BadMarker { get; private set; }
/// <summary> /// <summary>
/// Gets a value indicating whether to continue reading the input stream. /// Gets a value indicating whether to continue reading the input stream.
/// </summary> /// </summary>
@ -57,7 +51,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void CheckBits() public void CheckBits()
{ {
if (this.remain < 16) if (this.remainingBits < 16)
{ {
this.FillBuffer(); this.FillBuffer();
} }
@ -67,27 +61,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
public void Reset() public void Reset()
{ {
this.data = 0ul; this.data = 0ul;
this.remain = 0; this.remainingBits = 0;
this.Marker = JpegConstants.Markers.XFF; this.Marker = JpegConstants.Markers.XFF;
this.MarkerPosition = 0; this.MarkerPosition = 0;
this.BadMarker = false;
this.badData = false; this.badData = false;
this.NoData = false; this.NoData = false;
} }
/// <summary>
/// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7
/// </summary>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public bool HasRestart() public bool HasRestartMarker() => HasRestart(this.Marker);
{
byte m = this.Marker; /// <summary>
return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7; /// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7
} /// </summary>
[MethodImpl(InliningOptions.ShortMethod)]
public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public void FillBuffer() public void FillBuffer()
{ {
// Attempt to load at least the minimum number of required bits into the buffer. // Attempt to load at least the minimum number of required bits into the buffer.
// We fail to do so only if we hit a marker or reach the end of the input stream. // We fail to do so only if we hit a marker or reach the end of the input stream.
this.remain += 48; this.remainingBits += 48;
this.data = (this.data << 48) | this.GetBytes(); this.data = (this.data << 48) | this.GetBytes();
} }
@ -101,17 +99,17 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
if (size == JpegConstants.Huffman.SlowBits) if (size == JpegConstants.Huffman.SlowBits)
{ {
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain); ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits);
while (x > h.MaxCode[size]) while (x > h.MaxCode[size])
{ {
size++; size++;
} }
v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size)); v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
symbol = h.Values[h.ValOffset[size] + v]; symbol = h.Values[(h.ValOffset[size] + v) & 0xFF];
} }
this.remain -= size; this.remainingBits -= size;
return symbol; return symbol;
} }
@ -124,10 +122,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
} }
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits); private static bool HasRestart(byte marker)
=> marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7;
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits); public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits);
[MethodImpl(InliningOptions.ShortMethod)]
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1); private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
@ -149,22 +151,18 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
int c = this.ReadStream(); int c = this.ReadStream();
while (c == JpegConstants.Markers.XFF) while (c == JpegConstants.Markers.XFF)
{ {
// Loop here to discard any padding FF's on terminating marker, // Loop here to discard any padding FF bytes on terminating marker,
// so that we can save a valid marker value. // so that we can save a valid marker value.
c = this.ReadStream(); c = this.ReadStream();
} }
// We accept multiple FF's followed by a 0 as meaning a single FF data byte. // We accept multiple FF bytes followed by a 0 as meaning a single FF data byte.
// This data pattern is not valid according to the standard. // This data pattern is not valid according to the standard.
if (c != 0) if (c != 0)
{ {
this.Marker = (byte)c; this.Marker = (byte)c;
this.badData = true; this.badData = true;
if (!this.HasRestart()) this.MarkerPosition = this.stream.Position - 2;
{
this.MarkerPosition = this.stream.Position - 2;
this.BadMarker = true;
}
} }
} }
@ -174,6 +172,42 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return temp; return temp;
} }
[MethodImpl(InliningOptions.ShortMethod)]
public bool FindNextMarker()
{
while (true)
{
int b = this.stream.ReadByte();
if (b == -1)
{
return false;
}
// Found a marker.
if (b == JpegConstants.Markers.XFF)
{
while (b == JpegConstants.Markers.XFF)
{
// Loop here to discard any padding FF bytes on terminating marker.
b = this.stream.ReadByte();
if (b == -1)
{
return false;
}
}
// Found a valid marker. Exit loop
if (b != 0)
{
this.Marker = (byte)b;
this.badData = true;
this.MarkerPosition = this.stream.Position - 2;
return true;
}
}
}
}
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private int ReadStream() private int ReadStream()
{ {

14
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanScanDecoder.cs

@ -106,7 +106,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.ParseProgressiveData(); this.ParseProgressiveData();
} }
if (this.scanBuffer.BadMarker) if (this.scanBuffer.HasBadMarker())
{ {
this.stream.Position = this.scanBuffer.MarkerPosition; this.stream.Position = this.scanBuffer.MarkerPosition;
} }
@ -684,15 +684,23 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
if (this.restartInterval > 0 && (--this.todo) == 0) if (this.restartInterval > 0 && (--this.todo) == 0)
{ {
if (this.scanBuffer.Marker == JpegConstants.Markers.XFF)
{
if (!this.scanBuffer.FindNextMarker())
{
return false;
}
}
this.todo = this.restartInterval; this.todo = this.restartInterval;
if (this.scanBuffer.HasRestart()) if (this.scanBuffer.HasRestartMarker())
{ {
this.Reset(); this.Reset();
return true; return true;
} }
if (this.scanBuffer.Marker != JpegConstants.Markers.XFF) if (this.scanBuffer.HasBadMarker())
{ {
this.stream.Position = this.scanBuffer.MarkerPosition; this.stream.Position = this.scanBuffer.MarkerPosition;
this.Reset(); this.Reset();

48
src/ImageSharp/Formats/Jpeg/Components/Decoder/HuffmanTable.cs

@ -1,20 +1,18 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
/// <summary> /// <summary>
/// Represents a Huffman coding table containing basic coding data plus tables for accellerated computation. /// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation.
/// </summary> /// </summary>
[StructLayout(LayoutKind.Sequential)] [StructLayout(LayoutKind.Sequential)]
internal unsafe struct HuffmanTable internal unsafe struct HuffmanTable
{ {
private readonly MemoryAllocator memoryAllocator;
private bool isConfigured; private bool isConfigured;
/// <summary> /// <summary>
@ -60,13 +58,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct. /// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
/// </summary> /// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
/// <param name="codeLengths">The code lengths</param> /// <param name="codeLengths">The code lengths</param>
/// <param name="values">The huffman values</param> /// <param name="values">The huffman values</param>
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values) public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
{ {
this.isConfigured = false; this.isConfigured = false;
this.memoryAllocator = memoryAllocator;
Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length); Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length); Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
} }
@ -81,33 +77,31 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
return; return;
} }
int p, si; Span<char> huffSize = stackalloc char[257];
Span<char> huffsize = stackalloc char[257]; Span<uint> huffCode = stackalloc uint[257];
Span<uint> huffcode = stackalloc uint[257];
uint code;
// Figure C.1: make table of Huffman code length for each symbol // Figure C.1: make table of Huffman code length for each symbol
p = 0; int p = 0;
for (int l = 1; l <= 16; l++) for (int l = 1; l <= 16; l++)
{ {
int i = this.Sizes[l]; int i = this.Sizes[l];
while (i-- != 0) while (i-- != 0)
{ {
huffsize[p++] = (char)l; huffSize[p++] = (char)l;
} }
} }
huffsize[p] = (char)0; huffSize[p] = (char)0;
// Figure C.2: generate the codes themselves // Figure C.2: generate the codes themselves
code = 0; uint code = 0;
si = huffsize[0]; int si = huffSize[0];
p = 0; p = 0;
while (huffsize[p] != 0) while (huffSize[p] != 0)
{ {
while (huffsize[p] == si) while (huffSize[p] == si)
{ {
huffcode[p++] = code; huffCode[p++] = code;
code++; code++;
} }
@ -121,10 +115,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
if (this.Sizes[l] != 0) if (this.Sizes[l] != 0)
{ {
int offset = p - (int)huffcode[p]; int offset = p - (int)huffCode[p];
this.ValOffset[l] = offset; this.ValOffset[l] = offset;
p += this.Sizes[l]; p += this.Sizes[l];
this.MaxCode[l] = huffcode[p - 1]; // Maximum code of length l this.MaxCode[l] = huffCode[p - 1]; // Maximum code of length l
this.MaxCode[l] <<= 64 - l; // Left justify this.MaxCode[l] <<= 64 - l; // Left justify
this.MaxCode[l] |= (1ul << (64 - l)) - 1; this.MaxCode[l] |= (1ul << (64 - l)) - 1;
} }
@ -150,14 +144,14 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{ {
for (int i = 1; i <= this.Sizes[length]; i++, p++) for (int i = 1; i <= this.Sizes[length]; i++, p++)
{ {
// length = current code's length, p = its index in huffcode[] & huffval[]. // length = current code's length, p = its index in huffCode[] & Values[].
// Generate left-justified code followed by all possible bit sequences // Generate left-justified code followed by all possible bit sequences
int lookbits = (int)(huffcode[p] << (JpegConstants.Huffman.LookupBits - length)); int lookBits = (int)(huffCode[p] << (JpegConstants.Huffman.LookupBits - length));
for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--) for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
{ {
this.LookaheadSize[lookbits] = (byte)length; this.LookaheadSize[lookBits] = (byte)length;
this.LookaheadValue[lookbits] = this.Values[p]; this.LookaheadValue[lookBits] = this.Values[p];
lookbits++; lookBits++;
} }
} }
} }
@ -165,4 +159,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
this.isConfigured = true; this.isConfigured = true;
} }
} }
} }

6
src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

@ -1,4 +1,4 @@
// Copyright (c) Six Labors and contributors. // Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0. // Licensed under the Apache License, Version 2.0.
using System; using System;
@ -952,7 +952,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
/// <param name="values">The values</param> /// <param name="values">The values</param>
[MethodImpl(InliningOptions.ShortMethod)] [MethodImpl(InliningOptions.ShortMethod)]
private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values) private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
=> tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values); => tables[index] = new HuffmanTable(codeLengths, values);
/// <summary> /// <summary>
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes /// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
@ -992,4 +992,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg
return image; return image;
} }
} }
} }

11
tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

@ -6,11 +6,8 @@
<OutputType>Exe</OutputType> <OutputType>Exe</OutputType>
<RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace> <RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace>
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks> <TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
</PropertyGroup> <IsPackable>false</IsPackable>
<GenerateProgramFile>false</GenerateProgramFile>
<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@ -18,10 +15,6 @@
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" /> <Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
<Compile Remove="Program.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" /> <PackageReference Include="BenchmarkDotNet" />
<PackageReference Include="Colourful" /> <PackageReference Include="Colourful" />

8
tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs

@ -15,14 +15,10 @@ namespace SixLabors.ImageSharp.Tests.Formats.Jpg
TestImages.Jpeg.Baseline.Jpeg400, TestImages.Jpeg.Baseline.Jpeg400,
TestImages.Jpeg.Baseline.Testorig420, TestImages.Jpeg.Baseline.Testorig420,
// BUG: The following image has a high difference compared to the expected output: // BUG: The following image has a high difference compared to the expected output: 1.0096%
// TestImages.Jpeg.Baseline.Jpeg420Small, // TestImages.Jpeg.Baseline.Jpeg420Small,
// BUG: While we can decode this image we do not return the same output as libjpeg TestImages.Jpeg.Issues.Fuzz.AccessViolationException922,
// based decoders and are inserting a number of lines equal to the corrupted lines
// below said lines.
// TestImages.Jpeg.Issues.Fuzz.AccessViolationException922,
TestImages.Jpeg.Baseline.Jpeg444, TestImages.Jpeg.Baseline.Jpeg444,
TestImages.Jpeg.Baseline.Bad.BadEOF, TestImages.Jpeg.Baseline.Bad.BadEOF,
TestImages.Jpeg.Baseline.MultiScanBaselineCMYK, TestImages.Jpeg.Baseline.MultiScanBaselineCMYK,

2
tests/Images/External

@ -1 +1 @@
Subproject commit 42b3b980ed07afd7b6603a5bfa6ffb91d6c8a124 Subproject commit acc32594c125656840f8a17e69b0ebb49a370fa6
Loading…
Cancel
Save