Skip to content

Commit 7a484f7

Browse files
Fix 925 (#929)
* Prevent overflow * Cleanup huffman table * Search for RST markers. * Fix Benchmarks project
1 parent 6fcfe09 commit 7a484f7

File tree

7 files changed

+103
-78
lines changed

7 files changed

+103
-78
lines changed

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

Lines changed: 63 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ internal struct HuffmanScanBuffer
1717
private ulong data;
1818

1919
// The number of valid bits left to read in the buffer.
20-
private int remain;
20+
private int remainingBits;
2121

2222
// Whether there is no more good data to pull from the stream for the current mcu.
2323
private bool badData;
@@ -26,10 +26,9 @@ public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
2626
{
2727
this.stream = stream;
2828
this.data = 0ul;
29-
this.remain = 0;
29+
this.remainingBits = 0;
3030
this.Marker = JpegConstants.Markers.XFF;
3131
this.MarkerPosition = 0;
32-
this.BadMarker = false;
3332
this.badData = false;
3433
this.NoData = false;
3534
}
@@ -44,11 +43,6 @@ public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
4443
/// </summary>
4544
public long MarkerPosition { get; private set; }
4645

47-
/// <summary>
48-
/// Gets a value indicating whether a bad marker has been detected, I.E. One that is not between RST0 and RST7
49-
/// </summary>
50-
public bool BadMarker { get; private set; }
51-
5246
/// <summary>
5347
/// Gets a value indicating whether to continue reading the input stream.
5448
/// </summary>
@@ -57,7 +51,7 @@ public HuffmanScanBuffer(DoubleBufferedStreamReader stream)
5751
[MethodImpl(InliningOptions.ShortMethod)]
5852
public void CheckBits()
5953
{
60-
if (this.remain < 16)
54+
if (this.remainingBits < 16)
6155
{
6256
this.FillBuffer();
6357
}
@@ -67,27 +61,31 @@ public void CheckBits()
6761
public void Reset()
6862
{
6963
this.data = 0ul;
70-
this.remain = 0;
64+
this.remainingBits = 0;
7165
this.Marker = JpegConstants.Markers.XFF;
7266
this.MarkerPosition = 0;
73-
this.BadMarker = false;
7467
this.badData = false;
7568
this.NoData = false;
7669
}
7770

71+
/// <summary>
72+
/// Whether a RST marker has been detected, I.E. One that is between RST0 and RST7
73+
/// </summary>
7874
[MethodImpl(InliningOptions.ShortMethod)]
79-
public bool HasRestart()
80-
{
81-
byte m = this.Marker;
82-
return m >= JpegConstants.Markers.RST0 && m <= JpegConstants.Markers.RST7;
83-
}
75+
public bool HasRestartMarker() => HasRestart(this.Marker);
76+
77+
/// <summary>
78+
/// Whether a bad marker has been detected, I.E. One that is not between RST0 and RST7
79+
/// </summary>
80+
[MethodImpl(InliningOptions.ShortMethod)]
81+
public bool HasBadMarker() => this.Marker != JpegConstants.Markers.XFF && !this.HasRestartMarker();
8482

8583
[MethodImpl(InliningOptions.ShortMethod)]
8684
public void FillBuffer()
8785
{
8886
// Attempt to load at least the minimum number of required bits into the buffer.
8987
// We fail to do so only if we hit a marker or reach the end of the input stream.
90-
this.remain += 48;
88+
this.remainingBits += 48;
9189
this.data = (this.data << 48) | this.GetBytes();
9290
}
9391

@@ -101,17 +99,17 @@ public unsafe int DecodeHuffman(ref HuffmanTable h)
10199

102100
if (size == JpegConstants.Huffman.SlowBits)
103101
{
104-
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remain);
102+
ulong x = this.data << (JpegConstants.Huffman.RegisterSize - this.remainingBits);
105103
while (x > h.MaxCode[size])
106104
{
107105
size++;
108106
}
109107

110108
v = (int)(x >> (JpegConstants.Huffman.RegisterSize - size));
111-
symbol = h.Values[h.ValOffset[size] + v];
109+
symbol = h.Values[(h.ValOffset[size] + v) & 0xFF];
112110
}
113111

114-
this.remain -= size;
112+
this.remainingBits -= size;
115113

116114
return symbol;
117115
}
@@ -124,10 +122,14 @@ public int Receive(int nbits)
124122
}
125123

126124
[MethodImpl(InliningOptions.ShortMethod)]
127-
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remain -= nbits, nbits);
125+
private static bool HasRestart(byte marker)
126+
=> marker >= JpegConstants.Markers.RST0 && marker <= JpegConstants.Markers.RST7;
128127

129128
[MethodImpl(InliningOptions.ShortMethod)]
130-
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remain - nbits, nbits);
129+
public int GetBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits -= nbits, nbits);
130+
131+
[MethodImpl(InliningOptions.ShortMethod)]
132+
public int PeekBits(int nbits) => (int)ExtractBits(this.data, this.remainingBits - nbits, nbits);
131133

132134
[MethodImpl(InliningOptions.ShortMethod)]
133135
private static ulong ExtractBits(ulong value, int offset, int size) => (value >> offset) & (ulong)((1 << size) - 1);
@@ -149,22 +151,18 @@ private ulong GetBytes()
149151
int c = this.ReadStream();
150152
while (c == JpegConstants.Markers.XFF)
151153
{
152-
// Loop here to discard any padding FF's on terminating marker,
154+
// Loop here to discard any padding FF bytes on terminating marker,
153155
// so that we can save a valid marker value.
154156
c = this.ReadStream();
155157
}
156158

157-
// We accept multiple FF's followed by a 0 as meaning a single FF data byte.
159+
// We accept multiple FF bytes followed by a 0 as meaning a single FF data byte.
158160
// This data pattern is not valid according to the standard.
159161
if (c != 0)
160162
{
161163
this.Marker = (byte)c;
162164
this.badData = true;
163-
if (!this.HasRestart())
164-
{
165-
this.MarkerPosition = this.stream.Position - 2;
166-
this.BadMarker = true;
167-
}
165+
this.MarkerPosition = this.stream.Position - 2;
168166
}
169167
}
170168

@@ -174,6 +172,42 @@ private ulong GetBytes()
174172
return temp;
175173
}
176174

175+
[MethodImpl(InliningOptions.ShortMethod)]
176+
public bool FindNextMarker()
177+
{
178+
while (true)
179+
{
180+
int b = this.stream.ReadByte();
181+
if (b == -1)
182+
{
183+
return false;
184+
}
185+
186+
// Found a marker.
187+
if (b == JpegConstants.Markers.XFF)
188+
{
189+
while (b == JpegConstants.Markers.XFF)
190+
{
191+
// Loop here to discard any padding FF bytes on terminating marker.
192+
b = this.stream.ReadByte();
193+
if (b == -1)
194+
{
195+
return false;
196+
}
197+
}
198+
199+
// Found a valid marker. Exit loop
200+
if (b != 0)
201+
{
202+
this.Marker = (byte)b;
203+
this.badData = true;
204+
this.MarkerPosition = this.stream.Position - 2;
205+
return true;
206+
}
207+
}
208+
}
209+
}
210+
177211
[MethodImpl(InliningOptions.ShortMethod)]
178212
private int ReadStream()
179213
{

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public void ParseEntropyCodedData()
106106
this.ParseProgressiveData();
107107
}
108108

109-
if (this.scanBuffer.BadMarker)
109+
if (this.scanBuffer.HasBadMarker())
110110
{
111111
this.stream.Position = this.scanBuffer.MarkerPosition;
112112
}
@@ -684,15 +684,23 @@ private bool HandleRestart()
684684
{
685685
if (this.restartInterval > 0 && (--this.todo) == 0)
686686
{
687+
if (this.scanBuffer.Marker == JpegConstants.Markers.XFF)
688+
{
689+
if (!this.scanBuffer.FindNextMarker())
690+
{
691+
return false;
692+
}
693+
}
694+
687695
this.todo = this.restartInterval;
688696

689-
if (this.scanBuffer.HasRestart())
697+
if (this.scanBuffer.HasRestartMarker())
690698
{
691699
this.Reset();
692700
return true;
693701
}
694702

695-
if (this.scanBuffer.Marker != JpegConstants.Markers.XFF)
703+
if (this.scanBuffer.HasBadMarker())
696704
{
697705
this.stream.Position = this.scanBuffer.MarkerPosition;
698706
this.Reset();

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

Lines changed: 21 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
55
using System.Runtime.CompilerServices;
66
using System.Runtime.InteropServices;
7-
using SixLabors.Memory;
87

98
namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
109
{
1110
/// <summary>
12-
/// Represents a Huffman coding table containing basic coding data plus tables for accellerated computation.
11+
/// Represents a Huffman coding table containing basic coding data plus tables for accelerated computation.
1312
/// </summary>
1413
[StructLayout(LayoutKind.Sequential)]
1514
internal unsafe struct HuffmanTable
1615
{
17-
private readonly MemoryAllocator memoryAllocator;
1816
private bool isConfigured;
1917

2018
/// <summary>
@@ -60,13 +58,11 @@ internal unsafe struct HuffmanTable
6058
/// <summary>
6159
/// Initializes a new instance of the <see cref="HuffmanTable"/> struct.
6260
/// </summary>
63-
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/> to use for buffer allocations.</param>
6461
/// <param name="codeLengths">The code lengths</param>
6562
/// <param name="values">The huffman values</param>
66-
public HuffmanTable(MemoryAllocator memoryAllocator, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
63+
public HuffmanTable(ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
6764
{
6865
this.isConfigured = false;
69-
this.memoryAllocator = memoryAllocator;
7066
Unsafe.CopyBlockUnaligned(ref this.Sizes[0], ref MemoryMarshal.GetReference(codeLengths), (uint)codeLengths.Length);
7167
Unsafe.CopyBlockUnaligned(ref this.Values[0], ref MemoryMarshal.GetReference(values), (uint)values.Length);
7268
}
@@ -81,33 +77,31 @@ public void Configure()
8177
return;
8278
}
8379

84-
int p, si;
85-
Span<char> huffsize = stackalloc char[257];
86-
Span<uint> huffcode = stackalloc uint[257];
87-
uint code;
80+
Span<char> huffSize = stackalloc char[257];
81+
Span<uint> huffCode = stackalloc uint[257];
8882

8983
// Figure C.1: make table of Huffman code length for each symbol
90-
p = 0;
84+
int p = 0;
9185
for (int l = 1; l <= 16; l++)
9286
{
9387
int i = this.Sizes[l];
9488
while (i-- != 0)
9589
{
96-
huffsize[p++] = (char)l;
90+
huffSize[p++] = (char)l;
9791
}
9892
}
9993

100-
huffsize[p] = (char)0;
94+
huffSize[p] = (char)0;
10195

10296
// Figure C.2: generate the codes themselves
103-
code = 0;
104-
si = huffsize[0];
97+
uint code = 0;
98+
int si = huffSize[0];
10599
p = 0;
106-
while (huffsize[p] != 0)
100+
while (huffSize[p] != 0)
107101
{
108-
while (huffsize[p] == si)
102+
while (huffSize[p] == si)
109103
{
110-
huffcode[p++] = code;
104+
huffCode[p++] = code;
111105
code++;
112106
}
113107

@@ -121,10 +115,10 @@ public void Configure()
121115
{
122116
if (this.Sizes[l] != 0)
123117
{
124-
int offset = p - (int)huffcode[p];
118+
int offset = p - (int)huffCode[p];
125119
this.ValOffset[l] = offset;
126120
p += this.Sizes[l];
127-
this.MaxCode[l] = huffcode[p - 1]; // Maximum code of length l
121+
this.MaxCode[l] = huffCode[p - 1]; // Maximum code of length l
128122
this.MaxCode[l] <<= 64 - l; // Left justify
129123
this.MaxCode[l] |= (1ul << (64 - l)) - 1;
130124
}
@@ -150,19 +144,19 @@ public void Configure()
150144
{
151145
for (int i = 1; i <= this.Sizes[length]; i++, p++)
152146
{
153-
// length = current code's length, p = its index in huffcode[] & huffval[].
147+
// length = current code's length, p = its index in huffCode[] & Values[].
154148
// Generate left-justified code followed by all possible bit sequences
155-
int lookbits = (int)(huffcode[p] << (JpegConstants.Huffman.LookupBits - length));
149+
int lookBits = (int)(huffCode[p] << (JpegConstants.Huffman.LookupBits - length));
156150
for (int ctr = 1 << (JpegConstants.Huffman.LookupBits - length); ctr > 0; ctr--)
157151
{
158-
this.LookaheadSize[lookbits] = (byte)length;
159-
this.LookaheadValue[lookbits] = this.Values[p];
160-
lookbits++;
152+
this.LookaheadSize[lookBits] = (byte)length;
153+
this.LookaheadValue[lookBits] = this.Values[p];
154+
lookBits++;
161155
}
162156
}
163157
}
164158

165159
this.isConfigured = true;
166160
}
167161
}
168-
}
162+
}

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
@@ -952,7 +952,7 @@ private void ProcessStartOfScanMarker()
952952
/// <param name="values">The values</param>
953953
[MethodImpl(InliningOptions.ShortMethod)]
954954
private void BuildHuffmanTable(HuffmanTable[] tables, int index, ReadOnlySpan<byte> codeLengths, ReadOnlySpan<byte> values)
955-
=> tables[index] = new HuffmanTable(this.configuration.MemoryAllocator, codeLengths, values);
955+
=> tables[index] = new HuffmanTable(codeLengths, values);
956956

957957
/// <summary>
958958
/// Reads a <see cref="ushort"/> from the stream advancing it by two bytes
@@ -992,4 +992,4 @@ private Image<TPixel> PostProcessIntoImage<TPixel>()
992992
return image;
993993
}
994994
}
995-
}
995+
}

tests/ImageSharp.Benchmarks/ImageSharp.Benchmarks.csproj

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,15 @@
66
<OutputType>Exe</OutputType>
77
<RootNamespace>SixLabors.ImageSharp.Benchmarks</RootNamespace>
88
<TargetFrameworks>netcoreapp2.1;net472</TargetFrameworks>
9-
</PropertyGroup>
10-
11-
<PropertyGroup Condition="'$(TargetFramework)' == 'net461'">
12-
<RuntimeIdentifier>win7-x64</RuntimeIdentifier>
13-
<Prefer32Bit>false</Prefer32Bit>
9+
<IsPackable>false</IsPackable>
10+
<GenerateProgramFile>false</GenerateProgramFile>
1411
</PropertyGroup>
1512

1613
<ItemGroup>
1714
<Compile Include="..\ImageSharp.Tests\TestImages.cs" Link="Tests\TestImages.cs" />
1815
<Compile Include="..\ImageSharp.Tests\TestUtilities\TestEnvironment.cs" Link="Tests\TestEnvironment.cs" />
1916
</ItemGroup>
2017

21-
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.1'">
22-
<Compile Remove="Program.cs" />
23-
</ItemGroup>
24-
2518
<ItemGroup>
2619
<PackageReference Include="BenchmarkDotNet" />
2720
<PackageReference Include="Colourful" />

0 commit comments

Comments
 (0)