From b0ecacc58e773a3be0453277066871301b728248 Mon Sep 17 00:00:00 2001 From: Glen Parker Date: Thu, 29 Feb 2024 11:29:49 -0800 Subject: [PATCH] VHDX LogEntry.TryRead() Bug Fix and Improvements 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. --- Library/DiscUtils.Vhdx/LogEntry.cs | 116 +++++++++++------------ Library/DiscUtils.Vhdx/LogEntryHeader.cs | 29 +++--- 2 files changed, 68 insertions(+), 77 deletions(-) diff --git a/Library/DiscUtils.Vhdx/LogEntry.cs b/Library/DiscUtils.Vhdx/LogEntry.cs index 2153ec8d4..0b97ed54d 100644 --- a/Library/DiscUtils.Vhdx/LogEntry.cs +++ b/Library/DiscUtils.Vhdx/LogEntry.cs @@ -33,9 +33,9 @@ namespace DiscUtils.Vhdx; internal sealed class LogEntry { - public const int LogSectorSize = (int)(4 * Sizes.OneKiB); - private readonly List _descriptors = new List(); + public const int LogSectorSize = 4 * Sizes.OneKiB; + private readonly List _descriptors = new List(); private readonly LogEntryHeader _header; private LogEntry(long position, LogEntryHeader header, List descriptors) @@ -101,90 +101,84 @@ public static bool TryRead(Stream logStream, out LogEntry entry) { var position = logStream.Position; - var sectorBuffer = ArrayPool.Shared.Rent(LogSectorSize); + var bytesToRead = LogEntryHeader.ByteCount; + Span 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.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.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(); + for (var i = 0; i < header.DescriptorCount; ++i) + { + var offset = i * 32 + 64; + Descriptor descriptor; - var descriptors = new List(); - 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.Shared.Return(logEntryBuffer); + descriptors.Add(descriptor); } + + entry = new LogEntry(position, header, descriptors); + return true; } finally { - ArrayPool.Shared.Return(sectorBuffer); + ArrayPool.Shared.Return(logEntryBuffer); } } diff --git a/Library/DiscUtils.Vhdx/LogEntryHeader.cs b/Library/DiscUtils.Vhdx/LogEntryHeader.cs index 8a59f1431..5e4052d47 100644 --- a/Library/DiscUtils.Vhdx/LogEntryHeader.cs +++ b/Library/DiscUtils.Vhdx/LogEntryHeader.cs @@ -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 { @@ -49,13 +48,11 @@ public bool IsValid public int Size { - get { return 64; } + get { return ByteCount; } } public int ReadFrom(ReadOnlySpan buffer) { - //_data = buffer.Slice(0, Size).ToArray(); - Signature = EndianUtilities.ToUInt32LittleEndian(buffer); Checksum = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(4)); EntryLength = EndianUtilities.ToUInt32LittleEndian(buffer.Slice(8)); @@ -67,7 +64,7 @@ public int ReadFrom(ReadOnlySpan buffer) FlushedFileOffset = EndianUtilities.ToUInt64LittleEndian(buffer.Slice(48)); LastFileOffset = EndianUtilities.ToUInt64LittleEndian(buffer.Slice(56)); - return Size; + return ByteCount; } void IByteArraySerializable.WriteTo(Span buffer)