mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 19:06:15 +08:00
Fix PNG chunk CRC validation (#241)
This commit is contained in:
@@ -8,6 +8,9 @@ namespace SharpEmu.Libs.VideoOut;
|
|||||||
|
|
||||||
internal static class PngSplashLoader
|
internal static class PngSplashLoader
|
||||||
{
|
{
|
||||||
|
private const uint CrcPolynomial = 0xEDB88320;
|
||||||
|
private static readonly uint[] CrcTable = BuildCrcTable();
|
||||||
|
|
||||||
private static ReadOnlySpan<byte> PngSignature =>
|
private static ReadOnlySpan<byte> PngSignature =>
|
||||||
[
|
[
|
||||||
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A,
|
||||||
@@ -90,6 +93,13 @@ internal static class PngSplashLoader
|
|||||||
|
|
||||||
var chunkType = png.Slice(offset + 4, 4);
|
var chunkType = png.Slice(offset + 4, 4);
|
||||||
var chunkData = png.Slice(offset + 8, (int)chunkLength);
|
var chunkData = png.Slice(offset + 8, (int)chunkLength);
|
||||||
|
var expectedCrc = BinaryPrimitives.ReadUInt32BigEndian(
|
||||||
|
png.Slice(offset + 8 + (int)chunkLength, 4));
|
||||||
|
if (CalculateCrc(chunkType, chunkData) != expectedCrc)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (chunkType.SequenceEqual("IHDR"u8))
|
if (chunkType.SequenceEqual("IHDR"u8))
|
||||||
{
|
{
|
||||||
if (chunkData.Length != 13)
|
if (chunkData.Length != 13)
|
||||||
@@ -186,6 +196,41 @@ internal static class PngSplashLoader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static uint CalculateCrc(ReadOnlySpan<byte> chunkType, ReadOnlySpan<byte> chunkData)
|
||||||
|
{
|
||||||
|
var crc = UpdateCrc(uint.MaxValue, chunkType);
|
||||||
|
return ~UpdateCrc(crc, chunkData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint UpdateCrc(uint crc, ReadOnlySpan<byte> bytes)
|
||||||
|
{
|
||||||
|
foreach (var value in bytes)
|
||||||
|
{
|
||||||
|
crc = CrcTable[(byte)(crc ^ value)] ^ (crc >> 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
return crc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static uint[] BuildCrcTable()
|
||||||
|
{
|
||||||
|
var table = new uint[256];
|
||||||
|
for (var index = 0; index < table.Length; index++)
|
||||||
|
{
|
||||||
|
var value = (uint)index;
|
||||||
|
for (var bit = 0; bit < 8; bit++)
|
||||||
|
{
|
||||||
|
value = (value & 1) != 0
|
||||||
|
? CrcPolynomial ^ (value >> 1)
|
||||||
|
: value >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
table[index] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return table;
|
||||||
|
}
|
||||||
|
|
||||||
private static bool TryUnfilter(
|
private static bool TryUnfilter(
|
||||||
byte filter,
|
byte filter,
|
||||||
ReadOnlySpan<byte> source,
|
ReadOnlySpan<byte> source,
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
using SharpEmu.Libs.VideoOut;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace SharpEmu.Libs.Tests.VideoOut;
|
||||||
|
|
||||||
|
public sealed class PngSplashLoaderTests
|
||||||
|
{
|
||||||
|
private const int IhdrCrcOffset = 29;
|
||||||
|
private const int IdatCrcOffset = 53;
|
||||||
|
private const int IendCrcOffset = 65;
|
||||||
|
private const string ValidRgbPng =
|
||||||
|
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGMQMgkDAAD4AJ3MaiF4AAAAAElFTkSuQmCC";
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData(IhdrCrcOffset)]
|
||||||
|
[InlineData(IdatCrcOffset)]
|
||||||
|
[InlineData(IendCrcOffset)]
|
||||||
|
public void TryLoadRejectsInvalidChunkCrc(int crcOffset)
|
||||||
|
{
|
||||||
|
var app0Root = Path.Combine(Path.GetTempPath(), $"sharpemu-png-{Guid.NewGuid():N}");
|
||||||
|
var sceSysPath = Path.Combine(app0Root, "sce_sys");
|
||||||
|
var pngPath = Path.Combine(sceSysPath, "pic0.png");
|
||||||
|
var previousApp0Root = Environment.GetEnvironmentVariable("SHARPEMU_APP0_DIR");
|
||||||
|
|
||||||
|
Directory.CreateDirectory(sceSysPath);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("SHARPEMU_APP0_DIR", app0Root);
|
||||||
|
|
||||||
|
var png = Convert.FromBase64String(ValidRgbPng);
|
||||||
|
File.WriteAllBytes(pngPath, png);
|
||||||
|
|
||||||
|
Assert.True(PngSplashLoader.TryLoad(out var pixels, out var width, out var height));
|
||||||
|
Assert.Equal(1U, width);
|
||||||
|
Assert.Equal(1U, height);
|
||||||
|
Assert.Equal([0x56, 0x34, 0x12, 0xFF], pixels);
|
||||||
|
|
||||||
|
png[crcOffset] ^= 0x01;
|
||||||
|
File.WriteAllBytes(pngPath, png);
|
||||||
|
|
||||||
|
Assert.False(PngSplashLoader.TryLoad(out _, out _, out _));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
Environment.SetEnvironmentVariable("SHARPEMU_APP0_DIR", previousApp0Root);
|
||||||
|
Directory.Delete(app0Root, recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user