Skip to content

Commit

Permalink
Merge pull request #1899 from br3aker/dp/jpeg-sampling-factor-validation
Browse files Browse the repository at this point in the history
Jpeg sampling factor validation #1894
  • Loading branch information
JimBobSquarePants authored Dec 20, 2021
2 parents a356848 + 791a1a1 commit 39f37d0
Show file tree
Hide file tree
Showing 6 changed files with 58 additions and 34 deletions.
9 changes: 9 additions & 0 deletions src/ImageSharp/Common/Helpers/Numerics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -963,5 +963,14 @@ public static uint RotateRight(uint value, int offset)
public static uint RotateRightSoftwareFallback(uint value, int offset)
=> (value >> offset) | (value << (32 - offset));
#endif

/// <summary>
/// Tells whether input value is outside of the given range.
/// </summary>
/// <param name="value">Value.</param>
/// <param name="min">Mininum value, inclusive.</param>
/// <param name="max">Maximum value, inclusive.</param>
public static bool IsOutOfRange(int value, int min, int max)
=> (uint)(value - min) > (uint)(max - min);
}
}
11 changes: 0 additions & 11 deletions src/ImageSharp/Formats/Jpeg/Components/Decoder/JpegComponent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,10 @@ public JpegComponent(MemoryAllocator memoryAllocator, JpegFrame frame, byte id,
this.Frame = frame;
this.Id = id;

// Validate sampling factors.
if (horizontalFactor == 0 || verticalFactor == 0)
{
JpegThrowHelper.ThrowBadSampling();
}

this.HorizontalSamplingFactor = horizontalFactor;
this.VerticalSamplingFactor = verticalFactor;
this.SamplingFactors = new Size(this.HorizontalSamplingFactor, this.VerticalSamplingFactor);

if (quantizationTableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantizationTableIndex);
}

this.QuantizationTableIndex = quantizationTableIndex;
this.Index = index;
}
Expand Down
29 changes: 27 additions & 2 deletions src/ImageSharp/Formats/Jpeg/JpegDecoderCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1022,10 +1022,26 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
int index = 0;
for (int i = 0; i < componentCount; i++)
{
// 1 byte: component identifier
byte componentId = this.temp[index];

// 1 byte: component sampling factors
byte hv = this.temp[index + 1];
int h = (hv >> 4) & 15;
int v = hv & 15;

// Validate: 1-4 range
if (Numerics.IsOutOfRange(h, 1, 4))
{
JpegThrowHelper.ThrowBadSampling(h);
}

// Validate: 1-4 range
if (Numerics.IsOutOfRange(v, 1, 4))
{
JpegThrowHelper.ThrowBadSampling(v);
}

if (maxH < h)
{
maxH = h;
Expand All @@ -1036,10 +1052,19 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
maxV = v;
}

var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, this.temp[index], h, v, this.temp[index + 2], i);
// 1 byte: quantization table destination selector
byte quantTableIndex = this.temp[index + 2];

// Validate: 0-3 range
if (quantTableIndex > 3)
{
JpegThrowHelper.ThrowBadQuantizationTableIndex(quantTableIndex);
}

var component = new JpegComponent(this.Configuration.MemoryAllocator, this.Frame, componentId, h, v, quantTableIndex, i);

this.Frame.Components[i] = component;
this.Frame.ComponentIds[i] = component.Id;
this.Frame.ComponentIds[i] = componentId;

index += componentBytes;
}
Expand Down
20 changes: 3 additions & 17 deletions src/ImageSharp/Formats/Jpeg/JpegThrowHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,6 @@ internal static class JpegThrowHelper
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage) => throw new InvalidImageContentException(errorMessage);

/// <summary>
/// Cold path optimization for throwing <see cref="InvalidImageContentException"/>'s.
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
/// <param name="innerException">The exception that is the cause of the current exception, or a null reference
/// if no inner exception is specified.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowInvalidImageContentException(string errorMessage, Exception innerException) => throw new InvalidImageContentException(errorMessage, innerException);

/// <summary>
/// Cold path optimization for throwing <see cref="NotImplementedException"/>'s
/// </summary>
/// <param name="errorMessage">The error message for the exception.</param>
[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowNotImplementedException(string errorMessage)
=> throw new NotImplementedException(errorMessage);

[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadMarker(string marker, int length) => throw new InvalidImageContentException($"Marker {marker} has bad length {length}.");

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

[MethodImpl(InliningOptions.ColdPath)]
public static void ThrowBadSampling(int factor) => throw new InvalidImageContentException($"Bad sampling factor: {factor}");

[MethodImpl(InliningOptions.ColdPath)]
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}.");

Expand Down
17 changes: 17 additions & 0 deletions tests/ImageSharp.Tests/Common/NumericsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public NumericsTests(ITestOutputHelper output)
this.Output = output;
}

public static TheoryData<int> IsOutOfRangeTestData = new() { int.MinValue, -1, 0, 1, 6, 7, 8, 91, 92, 93, int.MaxValue };

private static int Log2_ReferenceImplementation(uint value)
{
int n = 0;
Expand Down Expand Up @@ -97,5 +99,20 @@ public void DivideCeil_RandomValues(int seed, int count)
Assert.True(expected == actual, $"Expected: {expected}\nActual: {actual}\n{value} / {divisor} = {expected}");
}
}

private static bool IsOutOfRange_ReferenceImplementation(int value, int min, int max) => value < min || value > max;

[Theory]
[MemberData(nameof(IsOutOfRangeTestData))]
public void IsOutOfRange(int value)
{
const int min = 7;
const int max = 92;

bool expected = IsOutOfRange_ReferenceImplementation(value, min, max);
bool actual = Numerics.IsOutOfRange(value, min, max);

Assert.True(expected == actual, $"IsOutOfRange({value}, {min}, {max})");
}
}
}
6 changes: 2 additions & 4 deletions tests/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.Images.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,6 @@ public partial class JpegDecoderTests
// LibJpeg can open this despite incorrect colorspace metadata.
TestImages.Jpeg.Issues.IncorrectColorspace855,

// LibJpeg can open this despite the invalid subsampling units.
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,

// High depth images
TestImages.Jpeg.Baseline.Testorig12bit,
};
Expand Down Expand Up @@ -90,7 +87,8 @@ public partial class JpegDecoderTests
TestImages.Jpeg.Issues.Fuzz.AccessViolationException827,
TestImages.Jpeg.Issues.Fuzz.ExecutionEngineException839,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693A,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException1693B,
TestImages.Jpeg.Issues.Fuzz.IndexOutOfRangeException824C,
};

private static readonly Dictionary<string, float> CustomToleranceValues =
Expand Down

0 comments on commit 39f37d0

Please sign in to comment.