mirror of
https://github.com/aaru-dps/Aaru.git
synced 2026-07-08 18:16:24 +00:00
[Partclone] Enhance CRC32 handling with support for legacy x64 bug and verification logic. Fixes #89
This commit is contained in:
@@ -34,7 +34,20 @@ namespace Aaru.Images;
|
||||
|
||||
public sealed partial class PartClone
|
||||
{
|
||||
const int CRC_SIZE = 4;
|
||||
/// <summary>Regular size of the per-block CRC32 trailer in partclone v0001.</summary>
|
||||
const int CRC_SIZE_NORMAL = 4;
|
||||
/// <summary>
|
||||
/// Size of the per-block CRC32 trailer in partclone v0001 images affected by the legacy 64-bit platform bug
|
||||
/// (the CRC was written using <c>sizeof(unsigned long)</c>, producing 8 bytes on x86_64 instead of 4).
|
||||
/// </summary>
|
||||
const int CRC_SIZE_X64_BUG = 8;
|
||||
/// <summary>Polynomial used by partclone's reflected CRC-32 (Ethernet / ISO).</summary>
|
||||
const uint CRC32_POLYNOMIAL = 0xEDB88320;
|
||||
/// <summary>Initial seed used by partclone's <c>init_crc32()</c>.</summary>
|
||||
const uint CRC32_SEED = 0xFFFFFFFF;
|
||||
/// <summary>Only partclone image format 0001 is supported.</summary>
|
||||
const string SUPPORTED_VERSION = "0001";
|
||||
|
||||
const uint MAX_CACHE_SIZE = 16777216;
|
||||
const uint MAX_CACHED_SECTORS = MAX_CACHE_SIZE / 512;
|
||||
readonly byte[] _biTmAgIc = "BiTmAgIc"u8.ToArray();
|
||||
|
||||
@@ -34,6 +34,60 @@ namespace Aaru.Images;
|
||||
|
||||
public sealed partial class PartClone
|
||||
{
|
||||
/// <summary>
|
||||
/// Reflected CRC-32 lookup table (polynomial <see cref="CRC32_POLYNOMIAL" />) used by partclone's
|
||||
/// <c>crc32_0001()</c> implementation. Kept private to the plugin because that routine carries a source-level
|
||||
/// bug (see <see cref="UpdateCrc32_0001" />) that would be incorrect for any other CRC-32 consumer.
|
||||
/// </summary>
|
||||
static readonly uint[] _crc0001Table = BuildCrc0001Table();
|
||||
|
||||
static uint[] BuildCrc0001Table()
|
||||
{
|
||||
var table = new uint[256];
|
||||
|
||||
for(uint i = 0; i < 256; i++)
|
||||
{
|
||||
uint entry = i;
|
||||
|
||||
for(var j = 0; j < 8; j++)
|
||||
{
|
||||
if((entry & 1) != 0)
|
||||
entry = entry >> 1 ^ CRC32_POLYNOMIAL;
|
||||
else
|
||||
entry >>= 1;
|
||||
}
|
||||
|
||||
table[i] = entry;
|
||||
}
|
||||
|
||||
return table;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advances a running CRC-32 seed the way partclone's <c>crc32_0001()</c> does for image format 0001. The
|
||||
/// upstream routine has a long-standing bug: the input pointer is <em>never advanced</em>, so every iteration
|
||||
/// folds the very first byte of <paramref name="buffer" /> into the CRC. This quirk is intentionally
|
||||
/// reproduced here so that Aaru's verification matches the checksums actually stored on disk.
|
||||
/// </summary>
|
||||
/// <param name="crc">Previous cumulative CRC (<see cref="CRC32_SEED" /> at the start of the stream).</param>
|
||||
/// <param name="buffer">Block buffer; only its first byte is used.</param>
|
||||
/// <param name="size">Number of iterations to run (i.e. the block size in bytes).</param>
|
||||
/// <returns>Updated cumulative CRC.</returns>
|
||||
static uint UpdateCrc32_0001(uint crc, byte[] buffer, int size)
|
||||
{
|
||||
if(size <= 0 || buffer is null || buffer.Length == 0) return crc;
|
||||
|
||||
byte b = buffer[0];
|
||||
|
||||
for(var s = 0; s < size; s++)
|
||||
{
|
||||
uint tmp = crc ^ b;
|
||||
crc = crc >> 8 ^ _crc0001Table[tmp & 0xFF];
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
ulong BlockOffset(ulong sectorAddress)
|
||||
{
|
||||
_extents.GetStart(sectorAddress, out ulong extentStart);
|
||||
|
||||
@@ -46,7 +46,9 @@ public sealed partial class PartClone : IMediaImage, IVerifiableImage
|
||||
|
||||
// The used block "bitmap" uses one byte per block
|
||||
// TODO: Convert on-image bytemap to on-memory bitmap
|
||||
byte[] _byteMap;
|
||||
byte[] _byteMap;
|
||||
/// <summary>Detected stride of the per-block CRC trailer (4 for normal images, 8 for x64-bug ones).</summary>
|
||||
int _crcSize;
|
||||
long _dataOff;
|
||||
ExtentsULong _extents;
|
||||
Dictionary<ulong, ulong> _extentsOff;
|
||||
|
||||
@@ -70,6 +70,17 @@ public sealed partial class PartClone
|
||||
AaruLogging.Debug(MODULE_NAME, "pHdr.totalBlocks = {0}", _pHdr.totalBlocks);
|
||||
AaruLogging.Debug(MODULE_NAME, "pHdr.usedBlocks = {0}", _pHdr.usedBlocks);
|
||||
|
||||
string version = StringHandlers.CToString(_pHdr.version);
|
||||
|
||||
if(version != SUPPORTED_VERSION)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME,
|
||||
"Unsupported partclone image version '{0}', only '0001' is supported",
|
||||
version);
|
||||
|
||||
return ErrorNumber.NotSupported;
|
||||
}
|
||||
|
||||
_byteMap = new byte[_pHdr.totalBlocks];
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Reading_bytemap_0_bytes, _byteMap.Length);
|
||||
stream.EnsureRead(_byteMap, 0, _byteMap.Length);
|
||||
@@ -89,6 +100,35 @@ public sealed partial class PartClone
|
||||
_dataOff = stream.Position;
|
||||
AaruLogging.Debug(MODULE_NAME, "pHdr.dataOff = {0}", _dataOff);
|
||||
|
||||
// Autodetect the legacy x64 CRC bug: on 64-bit platforms old partclone versions serialized
|
||||
// the 4-byte CRC using sizeof(unsigned long), writing 8 bytes per block instead of 4.
|
||||
// Compare the trailing data region length against both possibilities to pick the right stride.
|
||||
long trailing = stream.Length - _dataOff;
|
||||
var expectedNormal = (long)(_pHdr.usedBlocks * (_pHdr.blockSize + CRC_SIZE_NORMAL));
|
||||
var expectedX64 = (long)(_pHdr.usedBlocks * (_pHdr.blockSize + CRC_SIZE_X64_BUG));
|
||||
|
||||
if(trailing == expectedX64 && trailing != expectedNormal)
|
||||
{
|
||||
_crcSize = CRC_SIZE_X64_BUG;
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Detected partclone v0001 x64 CRC bug: using 8-byte CRC stride (trailing={0})",
|
||||
trailing);
|
||||
}
|
||||
else
|
||||
{
|
||||
_crcSize = CRC_SIZE_NORMAL;
|
||||
|
||||
if(trailing != expectedNormal && _pHdr.usedBlocks > 0)
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME,
|
||||
"Partclone data region size {0} does not match either layout (normal={1}, x64={2}); assuming 4-byte CRC stride",
|
||||
trailing,
|
||||
expectedNormal,
|
||||
expectedX64);
|
||||
}
|
||||
}
|
||||
|
||||
AaruLogging.Debug(MODULE_NAME, Localization.Filling_extents);
|
||||
var extentFillStopwatch = new Stopwatch();
|
||||
extentFillStopwatch.Start();
|
||||
@@ -164,7 +204,7 @@ public sealed partial class PartClone
|
||||
|
||||
if(_sectorCache.TryGetValue(sectorAddress, out buffer)) return ErrorNumber.NoError;
|
||||
|
||||
long imageOff = _dataOff + (long)(BlockOffset(sectorAddress) * (_pHdr.blockSize + CRC_SIZE));
|
||||
long imageOff = _dataOff + (long)(BlockOffset(sectorAddress) * (_pHdr.blockSize + (uint)_crcSize));
|
||||
|
||||
buffer = new byte[_pHdr.blockSize];
|
||||
_imageStream.Seek(imageOff, SeekOrigin.Begin);
|
||||
|
||||
@@ -30,15 +30,102 @@
|
||||
// Copyright © 2011-2026 Natalia Portillo
|
||||
// ****************************************************************************/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using Aaru.Helpers;
|
||||
using Aaru.Logging;
|
||||
|
||||
namespace Aaru.Images;
|
||||
|
||||
public sealed partial class PartClone
|
||||
{
|
||||
#region IVerifiableImage Members
|
||||
|
||||
// TODO: All blocks contain a CRC32 that's incompatible with current implementation. Need to check for compatibility.
|
||||
/// <inheritdoc />
|
||||
public bool? VerifyMediaImage() => null;
|
||||
/// <summary>
|
||||
/// Verifies a partclone v0001 image by replaying the cumulative CRC-32 stored after each used block. The
|
||||
/// checksum is produced by partclone's buggy <c>crc32_0001()</c> routine (see <see cref="UpdateCrc32_0001" />)
|
||||
/// and is never reseeded, so the running seed is propagated from one block to the next.
|
||||
/// </summary>
|
||||
public bool? VerifyMediaImage()
|
||||
{
|
||||
if(_imageStream is null) return null;
|
||||
|
||||
if(_pHdr.usedBlocks == 0) return true;
|
||||
|
||||
var blockSize = (int)_pHdr.blockSize;
|
||||
|
||||
if(blockSize <= 0 || _crcSize != CRC_SIZE_NORMAL && _crcSize != CRC_SIZE_X64_BUG) return null;
|
||||
|
||||
var dataBuffer = new byte[blockSize];
|
||||
var crcBuffer = new byte[_crcSize];
|
||||
var allOk = true;
|
||||
|
||||
try
|
||||
{
|
||||
_imageStream.Seek(_dataOff, SeekOrigin.Begin);
|
||||
|
||||
uint running = CRC32_SEED;
|
||||
|
||||
for(ulong blockIdx = 0; blockIdx < _pHdr.usedBlocks; blockIdx++)
|
||||
{
|
||||
int dataRead = _imageStream.EnsureRead(dataBuffer, 0, blockSize);
|
||||
|
||||
if(dataRead != blockSize)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME,
|
||||
"Short read while verifying partclone data block {0}: expected {1}, got {2}",
|
||||
blockIdx,
|
||||
blockSize,
|
||||
dataRead);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
running = UpdateCrc32_0001(running, dataBuffer, blockSize);
|
||||
|
||||
int crcRead = _imageStream.EnsureRead(crcBuffer, 0, _crcSize);
|
||||
|
||||
if(crcRead != _crcSize)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME,
|
||||
"Short read while reading CRC trailer at data block {0}: expected {1}, got {2}",
|
||||
blockIdx,
|
||||
_crcSize,
|
||||
crcRead);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// The x64 bug stores the CRC as an 8-byte little-endian value whose lower 4 bytes are the actual
|
||||
// CRC (source cast the seed via `*(uint32_t*)checksum`). Upper 4 bytes are padding and ignored.
|
||||
var storedCrc = BitConverter.ToUInt32(crcBuffer, 0);
|
||||
|
||||
if(storedCrc != running)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME,
|
||||
"CRC mismatch at data block {0}: stored 0x{1:X8}, computed 0x{2:X8}",
|
||||
blockIdx,
|
||||
storedCrc,
|
||||
running);
|
||||
|
||||
allOk = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
AaruLogging.Debug(MODULE_NAME, "Data block {0} CRC 0x{1:X8} OK", blockIdx, storedCrc);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(IOException ex)
|
||||
{
|
||||
AaruLogging.Error(MODULE_NAME, "I/O error while verifying partclone data blocks: {0}", ex.Message);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return allOk;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
Reference in New Issue
Block a user