Skip to content

Commit

Permalink
VHDX LogEntry.TryRead() Bug Fix and Improvements
Browse files Browse the repository at this point in the history
Change LogEntryHeader member variables to properties.
Read header into 64-byte, stackalloc'd buffer, then read remaining bytes directly into logEntryBuffer.
Remove buggy byte availability calculation.
Remove redundant signature check (which is done in LogEntryHeader.IsValid).
Use ReadMaximum instread of ReadExactly to guarantee no exception will be thrown, and use its return value to verify correct byte count.
  • Loading branch information
glenebob committed Mar 1, 2024
1 parent 8699b63 commit b0ecacc
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 77 deletions.
116 changes: 55 additions & 61 deletions Library/DiscUtils.Vhdx/LogEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ namespace DiscUtils.Vhdx;

internal sealed class LogEntry
{
public const int LogSectorSize = (int)(4 * Sizes.OneKiB);
private readonly List<Descriptor> _descriptors = new List<Descriptor>();
public const int LogSectorSize = 4 * Sizes.OneKiB;

private readonly List<Descriptor> _descriptors = new List<Descriptor>();
private readonly LogEntryHeader _header;

private LogEntry(long position, LogEntryHeader header, List<Descriptor> descriptors)
Expand Down Expand Up @@ -101,90 +101,84 @@ public static bool TryRead(Stream logStream, out LogEntry entry)
{
var position = logStream.Position;

var sectorBuffer = ArrayPool<byte>.Shared.Rent(LogSectorSize);
var bytesToRead = LogEntryHeader.ByteCount;
Span<byte> headerBuffer = stackalloc byte[bytesToRead];
if (logStream.ReadMaximum(headerBuffer) != bytesToRead)
{
entry = null;
return false;
}

var header = new LogEntryHeader();
header.ReadFrom(headerBuffer);

if (!header.IsValid)
{
entry = null;
return false;
}

var entryLength = checked((int)header.EntryLength);
var logEntryBuffer = ArrayPool<byte>.Shared.Rent(entryLength);

try
{
if (logStream.ReadMaximum(sectorBuffer, 0, LogSectorSize) != LogSectorSize)
{
entry = null;
return false;
}
headerBuffer.CopyTo(logEntryBuffer);

var sig = EndianUtilities.ToUInt32LittleEndian(sectorBuffer, 0);
if (sig != LogEntryHeader.LogEntrySignature)
bytesToRead = entryLength - LogEntryHeader.ByteCount;
if (logStream.ReadMaximum(logEntryBuffer, LogEntryHeader.ByteCount, bytesToRead) != bytesToRead)
{
entry = null;
return false;
}

var header = new LogEntryHeader();
header.ReadFrom(sectorBuffer.AsSpan(0, LogSectorSize));

if (!header.IsValid || header.EntryLength > logStream.Length)
Array.Clear(logEntryBuffer, 4, sizeof(uint));
if (header.Checksum !=
Crc32LittleEndian.Compute(Crc32Algorithm.Castagnoli, logEntryBuffer, 0, entryLength))
{
entry = null;
return false;
}

var logEntryBuffer = ArrayPool<byte>.Shared.Rent(checked((int)header.EntryLength));
try
{
System.Buffer.BlockCopy(sectorBuffer, 0, logEntryBuffer, 0, LogSectorSize);

logStream.ReadExactly(logEntryBuffer, LogSectorSize, checked((int)(header.EntryLength - LogSectorSize)));

EndianUtilities.WriteBytesLittleEndian(0, logEntryBuffer, 4);
if (header.Checksum !=
Crc32LittleEndian.Compute(Crc32Algorithm.Castagnoli, logEntryBuffer, 0, (int)header.EntryLength))
{
entry = null;
return false;
}
var dataPos = MathUtilities.RoundUp((int)header.DescriptorCount * 32 + 64, LogSectorSize);

var dataPos = MathUtilities.RoundUp((int)header.DescriptorCount * 32 + 64, LogSectorSize);
var descriptors = new List<Descriptor>();
for (var i = 0; i < header.DescriptorCount; ++i)
{
var offset = i * 32 + 64;
Descriptor descriptor;

var descriptors = new List<Descriptor>();
for (var i = 0; i < header.DescriptorCount; ++i)
var descriptorSig = EndianUtilities.ToUInt32LittleEndian(logEntryBuffer, offset);
switch (descriptorSig)
{
var offset = i * 32 + 64;
Descriptor descriptor;

var descriptorSig = EndianUtilities.ToUInt32LittleEndian(logEntryBuffer, offset);
switch (descriptorSig)
{
case Descriptor.ZeroDescriptorSignature:
descriptor = new ZeroDescriptor();
break;
case Descriptor.DataDescriptorSignature:
descriptor = new DataDescriptor(logEntryBuffer, dataPos);
dataPos += LogSectorSize;
break;
default:
entry = null;
return false;
}

descriptor.ReadFrom(logEntryBuffer, offset);
if (!descriptor.IsValid(header.SequenceNumber))
{
case Descriptor.ZeroDescriptorSignature:
descriptor = new ZeroDescriptor();
break;
case Descriptor.DataDescriptorSignature:
descriptor = new DataDescriptor(logEntryBuffer, dataPos);
dataPos += LogSectorSize;
break;
default:
entry = null;
return false;
}
}

descriptors.Add(descriptor);
descriptor.ReadFrom(logEntryBuffer, offset);
if (!descriptor.IsValid(header.SequenceNumber))
{
entry = null;
return false;
}

entry = new LogEntry(position, header, descriptors);
return true;
}
finally
{
ArrayPool<byte>.Shared.Return(logEntryBuffer);
descriptors.Add(descriptor);
}

entry = new LogEntry(position, header, descriptors);
return true;
}
finally
{
ArrayPool<byte>.Shared.Return(sectorBuffer);
ArrayPool<byte>.Shared.Return(logEntryBuffer);
}
}

Expand Down
29 changes: 13 additions & 16 deletions Library/DiscUtils.Vhdx/LogEntryHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,18 @@ namespace DiscUtils.Vhdx;
internal sealed class LogEntryHeader : IByteArraySerializable
{
public const uint LogEntrySignature = 0x65676F6C;
public const int ByteCount = 64;

//private byte[] _data;
public uint Checksum;
public uint DescriptorCount;
public uint EntryLength;
public ulong FlushedFileOffset;
public ulong LastFileOffset;
public Guid LogGuid;
public uint Reserved;
public ulong SequenceNumber;

public uint Signature;
public uint Tail;
public uint Checksum { get; private set; }
public uint DescriptorCount { get; private set; }
public uint EntryLength { get; private set; }
public ulong FlushedFileOffset { get; private set; }
public ulong LastFileOffset { get; private set; }
public Guid LogGuid { get; private set; }
public uint Reserved { get; private set; }
public ulong SequenceNumber { get; private set; }
public uint Signature { get; private set; }
public uint Tail { get; private set; }

public bool IsValid
{
Expand All @@ -49,13 +48,11 @@ public bool IsValid

public int Size
{
get { return 64; }
get { return ByteCount; }
}

public int ReadFrom(ReadOnlySpan<byte> buffer)
{
//_data = buffer.Slice(0, Size).ToArray();

Signature = EndianUtilities.ToUInt32LittleEndian(buffer);
Checksum = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(4));
EntryLength = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(8));
Expand All @@ -67,7 +64,7 @@ public int ReadFrom(ReadOnlySpan<byte> buffer)
FlushedFileOffset = EndianUtilities.ToUInt64LittleEndian(buffer.Slice(48));
LastFileOffset = EndianUtilities.ToUInt64LittleEndian(buffer.Slice(56));

return Size;
return ByteCount;
}

void IByteArraySerializable.WriteTo(Span<byte> buffer)
Expand Down

0 comments on commit b0ecacc

Please sign in to comment.