Skip to content

Commit 39f37d0

Browse files
Merge pull request #1899 from br3aker/dp/jpeg-sampling-factor-validation
Jpeg sampling factor validation #1894
2 parents a356848 + 791a1a1 commit 39f37d0

File tree

6 files changed

+58
-34
lines changed

6 files changed

+58
-34
lines changed

src/ImageSharp/Common/Helpers/Numerics.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,5 +963,14 @@ public static uint RotateRight(uint value, int offset)
963963
public static uint RotateRightSoftwareFallback(uint value, int offset)
964964
=> (value >> offset) | (value << (32 - offset));
965965
#endif
966+
967+
/// <summary>
968+
/// Tells whether input value is outside of the given range.
969+
/// </summary>
970+
/// <param name="value">Value.</param>
971+
/// <param name="min">Mininum value, inclusive.</param>
972+
/// <param name="max">Maximum value, inclusive.</param>
973+
public static bool IsOutOfRange(int value, int min, int max)
974+
=> (uint)(value - min) > (uint)(max - min);
966975
}
967976
}

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

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,10 @@ public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id,
1919
this.Frame = frame;
2020
this.Id = id;
2121

22-
// Validate sampling factors.
23-
if (horizontalFactor == 0 || verticalFactor == 0)
24-
{
25-
JpegThrowHelper.ThrowBadSampling();
26-
}
27-
2822
this.HorizontalSamplingFactor = horizontalFactor;
2923
this.VerticalSamplingFactor = verticalFactor;
3024
this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor);
3125

32-
if (quantizationTableIndex > 3)
33-
{
34-
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
35-
}
36-
3726
this.QuantizationTableIndex = quantizationTableIndex;
3827
this.Index = index;
3928
}

src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,10 +1022,26 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
10221022
int index = 0;
10231023
for (int i = 0; i < componentCount; i++)
10241024
{
1025+
// 1 byte: component identifier
1026+
byte componentId = this.temp[index];
1027+
1028+
// 1 byte: component sampling factors
10251029
byte hv = this.temp[index + 1];
10261030
int h = (hv >> 4) & 15;
10271031
int v = hv & 15;
10281032

1033+
// Validate: 1-4 range
1034+
if (Numerics.IsOutOfRange(h, 1, 4))
1035+
{
1036+
JpegThrowHelper.ThrowBadSampling(h);
1037+
}
1038+
1039+
// Validate: 1-4 range
1040+
if (Numerics.IsOutOfRange(v, 1, 4))
1041+
{
1042+
JpegThrowHelper.ThrowBadSampling(v);
1043+
}
1044+
10291045
if (maxH < h)
10301046
{
10311047
maxH = h;
@@ -1036,10 +1052,19 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
10361052
maxV = v;
10371053
}
10381054

1039-
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
1055+
// 1 byte: quantization table destination selector
1056+
byte quantTableIndex = this.temp[index + 2];
1057+
1058+
// Validate: 0-3 range
1059+
if (quantTableIndex > 3)
1060+
{
1061+
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex);
1062+
}
1063+
1064+
var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);
10401065

10411066
this.Frame.Components[i] = component;
1042-
this.Frame.ComponentIds[i] = component.Id;
1067+
this.Frame.ComponentIds[i] = componentId;
10431068

10441069
index += componentBytes;
10451070
}

src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,23 +22,6 @@ internal static class JpegThrowHelper
2222
[MethodImpl(InliningOptions.ColdPath)]
2323
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);
2424

25-
/// <summary>
26-
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
27-
/// </summary>
28-
/// <param name="errorMessage">The error message for the exception.</param>
29-
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference
30-
/// if no inner exception is specified.</param>
31-
[MethodImpl(InliningOptions.ColdPath)]
32-
public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException);
33-
34-
/// <summary>
35-
/// Cold path optimization for throwing <see cref="NotImplementedException"/>'s
36-
/// </summary>
37-
/// <param name="errorMessage">The error message for the exception.</param>
38-
[MethodImpl(InliningOptions.ColdPath)]
39-
public static void ThrowNotImplementedException(string errorMessage)
40-
=> throw new NotImplementedException(errorMessage);
41-
4225
[MethodImpl(InliningOptions.ColdPath)]
4326
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");
4427

@@ -51,6 +34,9 @@ public static void ThrowNotImplementedException(string errorMessage)
5134
[MethodImpl(InliningOptions.ColdPath)]
5235
public static void ThrowBadSampling() => throw new InvalidImageContentException("Bad sampling factor.");
5336

37+
[MethodImpl(InliningOptions.ColdPath)]
38+
public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}");
39+
5440
[MethodImpl(InliningOptions.ColdPath)]
5541
public static void ThrowBadProgressiveScan(int ss, int se, int ah, int al) => throw new InvalidImageContentException($"Invalid progressive parameters Ss={ss} Se={se} Ah={ah} Al={al}.");
5642

tests/ImageSharp.Tests/Common/NumericsTests.cs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ public NumericsTests(ITestOutputHelper output)
1616
this.Output = output;
1717
}
1818

19+
public static TheoryData<int> IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue };
20+
1921
private static int Log2_ReferenceImplementation(uint value)
2022
{
2123
int n = 0;
@@ -97,5 +99,20 @@ public void DivideCeil_RandomValues(int seed, int count)
9799
Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}");
98100
}
99101
}
102+
103+
private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max;
104+
105+
[Theory]
106+
[MemberData(nameof(IsOutOfRangeTestData))]
107+
public void IsOutOfRange(int value)
108+
{
109+
const int min = 7;
110+
const int max = 92;
111+
112+
bool expected = IsOutOfRange_ReferenceImplementation(value, min, max);
113+
bool actual = Numerics.IsOutOfRange(value, min, max);
114+
115+
Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})");
116+
}
100117
}
101118
}

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

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ public partial class JpegDecoderTests
4040
// LibJpeg can open this despite incorrect colorspace metadata.
4141
TestImages.Jpeg.Issues.IncorrectColorspace855,
4242

43-
// LibJpeg can open this despite the invalid subsampling units.
44-
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
45-
4643
// High depth images
4744
TestImages.Jpeg.Baseline.Testorig12bit,
4845
};
@@ -90,7 +87,8 @@ public partial class JpegDecoderTests
9087
TestImages.Jpeg.Issues.Fuzz.AccessViolationException827,
9188
TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839,
9289
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
93-
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B
90+
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B,
91+
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
9492
};
9593

9694
private static readonly Dictionary<string, float> CustomToleranceValues =

0 commit comments

Comments
 (0)