Browse Source

Better sanitation for scan decoder.

pull/549/head
James Jackson-South 8 years ago
parent
commit
4a895b430a
  1. 276
      src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs
  2. 2
      src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

276
src/ImageSharp/Formats/Jpeg/PdfJsPort/Components/PdfJsScanDecoder.cs

@ -5,7 +5,6 @@ using System;
#if DEBUG #if DEBUG
using System.Diagnostics; using System.Diagnostics;
#endif #endif
using System.IO;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using SixLabors.ImageSharp.Formats.Jpeg.Common; using SixLabors.ImageSharp.Formats.Jpeg.Common;
@ -95,7 +94,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
mcuExpected = mcusPerLine * frame.McusPerColumn; mcuExpected = mcusPerLine * frame.McusPerColumn;
} }
PdfJsFileMarker fileMarker;
while (mcu < mcuExpected) while (mcu < mcuExpected)
{ {
// Reset interval stuff // Reset interval stuff
@ -120,21 +118,15 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream); this.DecodeScanProgressive(huffmanTables, isAc, isFirst, components, componentsLength, mcusPerLine, mcuToRead, ref mcu, stream);
} }
// Find marker // Reset
this.bitsCount = 0; this.bitsCount = 0;
this.bitsData = 0; this.bitsData = 0;
fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream); this.unexpectedMarkerReached = false;
// Some bad images seem to pad Scan blocks with e.g. zero bytes, skip past // Some images include more scan blocks than expected, skip past those and
// those to attempt to find a valid marker (fixes issue4090.pdf) in original code. // attempt to find the next valid marker
if (fileMarker.Invalid) PdfJsFileMarker fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
{ byte marker = fileMarker.Marker;
#if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif
}
ushort marker = fileMarker.Marker;
// RSTn - We've already read the bytes and altered the position so no need to skip // RSTn - We've already read the bytes and altered the position so no need to skip
if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7) if (marker >= PdfJsJpegConstants.Markers.RST0 && marker <= PdfJsJpegConstants.Markers.RST7)
@ -149,24 +141,11 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
stream.Position = fileMarker.Position; stream.Position = fileMarker.Position;
break; break;
} }
}
fileMarker = PdfJsJpegDecoderCore.FindNextFileMarker(this.markerBuffer, stream);
// Some images include more Scan blocks than expected, skip past those and
// attempt to find the next valid marker (fixes issue8182.pdf) ref original code.
if (fileMarker.Invalid)
{
#if DEBUG #if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}"); Debug.WriteLine($"DecodeScan - Unexpected MCU data at {stream.Position}, next marker is: {fileMarker.Marker:X}");
#endif #endif
} }
else
{
// We've found a valid marker.
// Rewind the stream to the position of the marker
stream.Position = fileMarker.Position;
}
} }
private void DecodeScanBaseline( private void DecodeScanBaseline(
@ -295,9 +274,10 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
for (int k = 0; k < h; k++) for (int k = 0; k < h; k++)
{ {
// No need to continue here.
if (this.endOfStreamReached || this.unexpectedMarkerReached) if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
continue; break;
} }
if (isAC) if (isAC)
@ -432,19 +412,24 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReadBit(DoubleBufferedStreamReader stream) private bool TryReadBit(DoubleBufferedStreamReader stream, out int bits)
{ {
if (this.bitsCount == 0) if (this.bitsCount == 0)
{ {
this.FillBits(stream); if (!this.TryFillBits(stream))
{
bits = 0;
return false;
}
} }
this.bitsCount--; this.bitsCount--;
return (this.bitsData >> this.bitsCount) & 1; bits = (this.bitsData >> this.bitsCount) & 1;
return true;
} }
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private void FillBits(DoubleBufferedStreamReader stream) private bool TryFillBits(DoubleBufferedStreamReader stream)
{ {
// TODO: Read more then 1 byte at a time. // TODO: Read more then 1 byte at a time.
// In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work // In LibJpegTurbo this is be 25 bits (32-7) but I cannot get this to work
@ -452,46 +437,61 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// to detect it. // to detect it.
const int MinGetBits = 7; const int MinGetBits = 7;
// Attempt to load to the minimum bit count. if (!this.unexpectedMarkerReached)
while (this.bitsCount < MinGetBits)
{ {
int c = stream.ReadByte(); // Attempt to load to the minimum bit count.
while (this.bitsCount < MinGetBits)
if (c == -0x1)
{ {
// We've encountered the end of the file stream which means there's no EOI marker in the image. int c = stream.ReadByte();
this.endOfStreamReached = true;
// Fill buffer with zero bits. switch (c)
this.bitsData <<= MinGetBits - this.bitsCount;
this.bitsCount = MinGetBits;
break;
}
if (c == PdfJsJpegConstants.Markers.Prefix)
{
int nextByte = stream.ReadByte();
if (nextByte != 0)
{ {
case -0x1:
// We've encountered the end of the file stream which means there's no EOI marker in the image.
this.endOfStreamReached = true;
return false;
case PdfJsJpegConstants.Markers.Prefix:
int nextByte = stream.ReadByte();
if (nextByte == -0x1)
{
this.endOfStreamReached = true;
return false;
}
if (nextByte != 0)
{
#if DEBUG #if DEBUG
Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}"); Debug.WriteLine($"DecodeScan - Unexpected marker {(c << 8) | nextByte:X} at {stream.Position}");
#endif #endif
// We've encountered an unexpected marker. Reverse the stream and exit. // We've encountered an unexpected marker. Reverse the stream and exit.
this.unexpectedMarkerReached = true; this.unexpectedMarkerReached = true;
stream.Position -= 2; stream.Position -= 2;
// Fill buffer with zero bits. // TODO: double check we need this.
this.bitsData <<= MinGetBits - this.bitsCount; // Fill buffer with zero bits.
this.bitsCount = MinGetBits; if (this.bitsCount == 0)
break; {
this.bitsData <<= MinGetBits;
this.bitsCount = MinGetBits;
}
return true;
}
break;
} }
}
// OK, load c into get_buffer // OK, load the next byte into bitsData
this.bitsData = (this.bitsData << 8) | c; this.bitsData = (this.bitsData << 8) | c;
this.bitsCount += 8; this.bitsCount += 8;
}
} }
return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -507,14 +507,16 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private short DecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream) private bool TryDecodeHuffman(ref PdfJsHuffmanTable tree, DoubleBufferedStreamReader stream, out short value)
{ {
value = -1;
// TODO: Implement fast Huffman decoding. // TODO: Implement fast Huffman decoding.
// In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream // In LibJpegTurbo a minimum of 25 bits (32-7) is collected from the stream
// Then a LUT is used to avoid the loop when decoding the Huffman value. // Then a LUT is used to avoid the loop when decoding the Huffman value.
// using 3 methods: FillBits, PeekBits, and DropBits. // using 3 methods: FillBits, PeekBits, and DropBits.
// The LUT has been ported from LibJpegTurbo as has this code but it doesn't work. // The LUT has been ported from LibJpegTurbo as has this code but it doesn't work.
// this.FillBits(stream); // this.TryFillBits(stream);
// //
// const int LookAhead = 8; // const int LookAhead = 8;
// int look = this.PeekBits(LookAhead); // int look = this.PeekBits(LookAhead);
@ -524,86 +526,104 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
// if (bits <= LookAhead) // if (bits <= LookAhead)
// { // {
// this.DropBits(bits); // this.DropBits(bits);
// return (short)(look & ((1 << LookAhead) - 1)); // value = (short)(look & ((1 << LookAhead) - 1));
// return true;
// } // }
short code = (short)this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return -1; return false;
} }
short code = (short)bit;
// "DECODE", section F.2.2.3, figure F.16, page 109 of T.81 // "DECODE", section F.2.2.3, figure F.16, page 109 of T.81
int i = 1; int i = 1;
while (code > tree.MaxCode[i]) while (code > tree.MaxCode[i])
{ {
code <<= 1; if (!this.TryReadBit(stream, out bit))
code |= (short)this.ReadBit(stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return -1; return false;
} }
code <<= 1;
code |= (short)bit;
i++; i++;
} }
int j = tree.ValOffset[i]; int j = tree.ValOffset[i];
return tree.HuffVal[(j + code) & 0xFF]; value = tree.HuffVal[(j + code) & 0xFF];
return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int Receive(int length, DoubleBufferedStreamReader stream) private bool TryReceive(int length, DoubleBufferedStreamReader stream, out int value)
{ {
int n = 0; value = 0;
while (length > 0) while (length > 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return -1; return false;
} }
n = (n << 1) | bit; value = (value << 1) | bit;
length--; length--;
} }
return n; return true;
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private int ReceiveAndExtend(int length, DoubleBufferedStreamReader stream) private bool TryReceiveAndExtend(int length, DoubleBufferedStreamReader stream, out int value)
{ {
if (length == 1) if (length == 1)
{ {
return this.ReadBit(stream) == 1 ? 1 : -1; if (!this.TryReadBit(stream, out value))
} {
return false;
}
int n = this.Receive(length, stream); value = value == 1 ? 1 : -1;
if (n >= 1 << (length - 1)) }
else
{ {
return n; if (!this.TryReceive(length, stream, out value))
{
return false;
}
if (value < 1 << (length - 1))
{
value += (-1 << length) + 1;
}
} }
return n + (-1 << length) + 1; return true;
} }
private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream) private void DecodeBaseline(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, ref PdfJsHuffmanTable acHuffmanTable, DoubleBufferedStreamReader stream)
{ {
short t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream); int diff = 0;
if (t != 0)
{
if (!this.TryReceiveAndExtend(t, stream, out diff))
{
return;
}
}
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff); Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff);
int k = 1; int k = 1;
while (k < 64) while (k < 64)
{ {
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -625,8 +645,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r; k += r;
byte z = this.dctZigZag[k]; byte z = this.dctZigZag[k];
short re = (short)this.ReceiveAndExtend(s, stream);
Unsafe.Add(ref blockDataRef, offset + z) = re; if (!this.TryReceiveAndExtend(s, stream, out int re))
{
return;
}
Unsafe.Add(ref blockDataRef, offset + z) = (short)re;
k++; k++;
} }
} }
@ -634,21 +659,27 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream) private void DecodeDCFirst(PdfJsFrameComponent component, ref short blockDataRef, int offset, ref PdfJsHuffmanTable dcHuffmanTable, DoubleBufferedStreamReader stream)
{ {
short t = this.DecodeHuffman(ref dcHuffmanTable, stream); if (!this.TryDecodeHuffman(ref dcHuffmanTable, stream, out short t))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
int diff = t == 0 ? 0 : this.ReceiveAndExtend(t, stream) << this.successiveState; int diff = 0;
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff); if (t != 0)
{
if (!this.TryReceiveAndExtend(t, stream, out diff))
{
return;
}
}
Unsafe.Add(ref blockDataRef, offset) = (short)(component.Pred += diff << this.successiveState);
} }
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream) private void DecodeDCSuccessive(PdfJsFrameComponent component, ref short blockDataRef, int offset, DoubleBufferedStreamReader stream)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -668,8 +699,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
int e = this.specEnd; int e = this.specEnd;
while (k <= e) while (k <= e)
{ {
short rs = this.DecodeHuffman(ref acHuffmanTable, stream); if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -681,7 +711,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
if (r < 15) if (r < 15)
{ {
this.eobrun = this.Receive(r, stream) + (1 << r) - 1; if (!this.TryReceive(r, stream, out int eob))
{
return;
}
this.eobrun = eob + (1 << r) - 1;
break; break;
} }
@ -692,7 +727,13 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
k += r; k += r;
byte z = this.dctZigZag[k]; byte z = this.dctZigZag[k];
Unsafe.Add(ref blockDataRef, offset + z) = (short)(this.ReceiveAndExtend(s, stream) * (1 << this.successiveState));
if (!this.TryReceiveAndExtend(s, stream, out int v))
{
return;
}
Unsafe.Add(ref blockDataRef, offset + z) = (short)(v * (1 << this.successiveState));
k++; k++;
} }
} }
@ -712,8 +753,8 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
switch (this.successiveACState) switch (this.successiveACState)
{ {
case 0: // Initial state case 0: // Initial state
short rs = this.DecodeHuffman(ref acHuffmanTable, stream);
if (this.endOfStreamReached || this.unexpectedMarkerReached) if (!this.TryDecodeHuffman(ref acHuffmanTable, stream, out short rs))
{ {
return; return;
} }
@ -724,7 +765,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
{ {
if (r < 15) if (r < 15)
{ {
this.eobrun = this.Receive(r, stream) + (1 << r); if (!this.TryReceive(r, stream, out int eob))
{
return;
}
this.eobrun = eob + (1 << r);
this.successiveACState = 4; this.successiveACState = 4;
} }
else else
@ -740,7 +786,12 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
throw new ImageFormatException("Invalid ACn encoding"); throw new ImageFormatException("Invalid ACn encoding");
} }
this.successiveACNextValue = this.ReceiveAndExtend(s, stream); if (!this.TryReceiveAndExtend(s, stream, out int v))
{
return;
}
this.successiveACNextValue = v;
this.successiveACState = r > 0 ? 2 : 3; this.successiveACState = r > 0 ? 2 : 3;
} }
@ -749,8 +800,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 2: case 2:
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -770,8 +820,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 3: // Set value for a zero item case 3: // Set value for a zero item
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }
@ -788,8 +837,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort.Components
case 4: // Eob case 4: // Eob
if (blockOffsetZRef != 0) if (blockOffsetZRef != 0)
{ {
int bit = this.ReadBit(stream); if (!this.TryReadBit(stream, out int bit))
if (this.endOfStreamReached || this.unexpectedMarkerReached)
{ {
return; return;
} }

2
src/ImageSharp/Formats/Jpeg/PdfJsPort/PdfJsJpegDecoderCore.cs

@ -169,8 +169,6 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.PdfJsPort
m = suffix; m = suffix;
} }
marker[1] = (byte)m;
return new PdfJsFileMarker((byte)m, stream.Position - 2); return new PdfJsFileMarker((byte)m, stream.Position - 2);
} }

Loading…
Cancel
Save