diff --git a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
index a3701f2c1..250e98c02 100644
--- a/src/ImageSharp/Formats/Jpeg/Components/ZigZag.cs
+++ b/src/ImageSharp/Formats/Jpeg/Components/ZigZag.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.
using System;
@@ -19,30 +19,37 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
///
/// Copy of in a value type
///
- public fixed byte Data[64];
+ public fixed byte Data[64 + 16];
///
+ ///
/// Unzig maps from the zigzag ordering to the natural ordering. For example,
/// unzig[3] is the column and row of the fourth element in zigzag order. The
/// value is 16, which means first column (16%8 == 0) and third row (16/8 == 2).
+ ///
+ ///
+ /// When reading corrupted data, the Huffman decoders could attempt
+ /// to reference an entry beyond the end of this array (if the decoded
+ /// zero run length reaches past the end of the block). To prevent
+ /// wild stores without adding an inner-loop test, we put some extra
+ /// "63"s after the real entries. This will cause the extra coefficient
+ /// to be stored in location 63 of the block, not somewhere random.
+ /// The worst case would be a run-length of 15, which means we need 16
+ /// fake entries.
+ ///
///
private static readonly byte[] Unzig =
{
- 0,
- 1, 8,
- 16, 9, 2,
- 3, 10, 17, 24,
- 32, 25, 18, 11, 4,
- 5, 12, 19, 26, 33, 40,
- 48, 41, 34, 27, 20, 13, 6,
- 7, 14, 21, 28, 35, 42, 49, 56,
- 57, 50, 43, 36, 29, 22, 15,
- 23, 30, 37, 44, 51, 58,
- 59, 52, 45, 38, 31,
- 39, 46, 53, 60,
- 61, 54, 47,
- 55, 62,
- 63
+ 0, 1, 8, 16, 9, 2, 3, 10,
+ 17, 24, 32, 25, 18, 11, 4, 5,
+ 12, 19, 26, 33, 40, 48, 41, 34,
+ 27, 20, 13, 6, 7, 14, 21, 28,
+ 35, 42, 49, 56, 57, 50, 43, 36,
+ 29, 22, 15, 23, 30, 37, 44, 51,
+ 58, 59, 52, 45, 38, 31, 39, 46,
+ 53, 60, 61, 54, 47, 55, 62, 63,
+ 63, 63, 63, 63, 63, 63, 63, 63, // Extra entries for safety in decoder
+ 63, 63, 63, 63, 63, 63, 63, 63
};
///
@@ -68,7 +75,7 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
{
ZigZag result = default;
byte* unzigPtr = result.Data;
- Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64);
+ Marshal.Copy(Unzig, 0, (IntPtr)unzigPtr, 64 + 16);
return result;
}
@@ -87,4 +94,4 @@ namespace SixLabors.ImageSharp.Formats.Jpeg.Components
return result;
}
}
-}
\ No newline at end of file
+}
diff --git a/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs
new file mode 100644
index 000000000..e59584991
--- /dev/null
+++ b/tests/ImageSharp.Tests/Formats/Jpg/ZigZagTests.cs
@@ -0,0 +1,44 @@
+// Copyright (c) Six Labors and contributors.
+// Licensed under the Apache License, Version 2.0.
+
+using SixLabors.ImageSharp.Formats.Jpeg.Components;
+using Xunit;
+
+namespace SixLabors.ImageSharp.Tests.Formats.Jpg
+{
+ public class ZigZagTests
+ {
+ [Fact]
+ public void ZigZagCanHandleAllPossibleCoefficients()
+ {
+ // Mimic the behaviour of the huffman scan decoder using all possible byte values
+ short[] block = new short[64];
+ var zigzag = ZigZag.CreateUnzigTable();
+
+ for (int h = 0; h < 255; h++)
+ {
+ for (int i = 1; i < 64; i++)
+ {
+ int s = h;
+ int r = s >> 4;
+ s &= 15;
+
+ if (s != 0)
+ {
+ i += r;
+ block[zigzag[i++]] = (short)s;
+ }
+ else
+ {
+ if (r == 0)
+ {
+ break;
+ }
+
+ i += 16;
+ }
+ }
+ }
+ }
+ }
+}