Skip to content

Commit

Permalink
Corrected registry log replay logic
Browse files Browse the repository at this point in the history
* Header size was not updated. Large log files that increased the number of hbins in the hive therefore corrupted the hive. This has now been resolved.
  • Loading branch information
LTRData committed Jun 12, 2024
1 parent 752815b commit ffac312
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Library/DiscUtils.Registry/BinHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public int ReadFrom(ReadOnlySpan<byte> buffer)
var sig = EndianUtilities.ToUInt32LittleEndian(buffer);
if (sig != Signature)
{
throw new IOException("Invalid signature for registry bin");
throw new RegistryCorruptException("Invalid signature for registry bin");
}

FileOffset = EndianUtilities.ToInt32LittleEndian(buffer.Slice(0x04));
Expand Down
41 changes: 30 additions & 11 deletions Library/DiscUtils.Registry/Cell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
//

using System;
using System.Linq;
using System.Text;
using DiscUtils.Streams;

namespace DiscUtils.Registry;
Expand All @@ -45,20 +47,37 @@ public Cell(int index)

internal static Cell Parse(RegistryHive hive, int index, ReadOnlySpan<byte> buffer)
{
var type = EncodingUtilities
.GetLatin1Encoding()
.GetString(buffer.Slice(0, 2));
var type = buffer.Slice(0, 2);

Cell result = type switch
Cell result;

if (type.SequenceEqual("nk"u8))
{
result = new KeyNodeCell(index);
}
else if (type.SequenceEqual("sk"u8))
{
result = new SecurityCell(index);
}
else if (type.SequenceEqual("vk"u8))
{
result = new ValueCell(index);
}
else if (type.SequenceEqual("lh"u8) || type.SequenceEqual("lf"u8))
{
"nk" => new KeyNodeCell(index),
"sk" => new SecurityCell(index),
"vk" => new ValueCell(index),
"lh" or "lf" => new SubKeyHashedListCell(hive, index),
"li" or "ri" => new SubKeyIndirectListCell(hive, index),
_ => throw new RegistryCorruptException($"Unknown cell type '{type}'"),
};
result = new SubKeyHashedListCell(hive, index);
}
else if (type.SequenceEqual("li"u8) || type.SequenceEqual("ri"u8))
{
result = new SubKeyIndirectListCell(hive, index);
}
else
{
throw new RegistryCorruptException($"Unknown cell type '{Encoding.ASCII.GetString(type)}'");
}

result.ReadFrom(buffer);

return result;
}
}
4 changes: 2 additions & 2 deletions Library/DiscUtils.Registry/HiveHeader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public int ReadFrom(ReadOnlySpan<byte> buffer, bool throwOnInvalidData)
{
if (throwOnInvalidData)
{
throw new IOException("Invalid signature for registry hive");
throw new RegistryCorruptException("Invalid signature for registry hive");
}
else
{
Expand Down Expand Up @@ -102,7 +102,7 @@ public int ReadFrom(ReadOnlySpan<byte> buffer, bool throwOnInvalidData)
{
if (throwOnInvalidData)
{
throw new IOException("Invalid checksum on registry file");
throw new RegistryCorruptException("Invalid checksum on registry file");
}
else
{
Expand Down
10 changes: 8 additions & 2 deletions Library/DiscUtils.Registry/LogFile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,24 @@ public IEnumerator<LogEntry> GetEnumerator()
private static long CalculateLogEntryHash(ReadOnlySpan<byte> buffer) =>
Marvin.ComputeHash(buffer, 0x82EF4D887A4E55C5);

public int UpdateHive(Stream hive)
public (int SequenceNumber, int MaxPosition) UpdateHive(Stream hive)
{
var sequenceNumber = 0;
var maxPosition = 0;

foreach (var entry in this)
{
hive.Position = 0x1000 + entry.PageOffset;
hive.Write(buffer, entry.BufferOffset, entry.PageSize);
sequenceNumber = entry.SequenceNumber;

if (hive.Position - RegistryHive.BinStart > maxPosition)
{
maxPosition = (int)(hive.Position - RegistryHive.BinStart);
}
}

return sequenceNumber;
return (sequenceNumber, maxPosition);
}
}

39 changes: 29 additions & 10 deletions Library/DiscUtils.Registry/RegistryHive.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ namespace DiscUtils.Registry;
/// </summary>
public class RegistryHive : IDisposable
{
private const long BinStart = 4 * Sizes.OneKiB;
internal const int BinStart = 4 * Sizes.OneKiB;
private readonly List<BinHeader> _bins;

private Stream _fileStream;
Expand Down Expand Up @@ -150,7 +150,9 @@ public RegistryHive(Stream hive, Ownership ownership, params Stream[] logstreams
// If header validation failed or dirty state, look for transaction logs
if (headerSize == 0 || _header.Sequence1 != _header.Sequence2)
{
var logs = logstreams?.Where(log => log.Length > 0x1000).ToArray();
var logs = logstreams?
.Where(log => log.Length > 0x1000)
.ToArray();

if (logs is not null && logs.Length > 0)
{
Expand Down Expand Up @@ -184,26 +186,32 @@ public RegistryHive(Stream hive, Ownership ownership, params Stream[] logstreams
if (logfiles.Length > 1 &&
logfiles[0].HiveHeader.Sequence1 >= logfiles[1].HiveHeader.Sequence1)
{
logfiles = new[] { logfiles[1], logfiles[0] };
logs = new[] { logs[1], logs[0] };
(logfiles[0], logfiles[1]) = (logfiles[1], logfiles[0]);
(logs[0], logs[1]) = (logs[1], logs[0]);
}

// If hive header failed validation, recover from latest log
if (headerSize == 0)
{
var lastvalid = logfiles.LastOrDefault(logfile => logfile.HeaderValid)
?? throw new IOException("Registry transaction logs are corrupt");
?? throw new RegistryCorruptException("Registry transaction logs are corrupt");

_header = lastvalid.HiveHeader;
}

int lastSequenceNumber;
int maxPosition;

// First log
if (logfiles.Length > 0 &&
logfiles[0].HiveHeader.Sequence1 >= _header.Sequence2)
{
lastSequenceNumber = logfiles[0].UpdateHive(_fileStream);
(lastSequenceNumber, maxPosition) = logfiles[0].UpdateHive(_fileStream);

if (maxPosition > _header.Length)
{
_header.Length = maxPosition;
}

// Also a secondary log
if (logfiles.Length > 1 &&
Expand All @@ -212,7 +220,12 @@ public RegistryHive(Stream hive, Ownership ownership, params Stream[] logstreams
// If secondary log continues right after last record in first log
if (logfiles[1].HiveHeader.Sequence1 == lastSequenceNumber + 1)
{
lastSequenceNumber = logfiles[1].UpdateHive(_fileStream);
(lastSequenceNumber, maxPosition) = logfiles[1].UpdateHive(_fileStream);

if (maxPosition > _header.Length)
{
_header.Length = maxPosition;
}
}
else
{
Expand All @@ -231,24 +244,30 @@ public RegistryHive(Stream hive, Ownership ownership, params Stream[] logstreams
else if (logfiles.Length > 1 &&
logfiles[1].HiveHeader.Sequence1 >= _header.Sequence2)
{
lastSequenceNumber = logfiles[1].UpdateHive(_fileStream);
(lastSequenceNumber, maxPosition) = logfiles[1].UpdateHive(_fileStream);

if (maxPosition > _header.Length)
{
_header.Length = maxPosition;
}
}
else
{
throw new IOException("Registry transaction logs are corrupt");
throw new RegistryCorruptException("Registry transaction logs are corrupt");
}

// Store latest recovered sequence number in the hive header
// and write this modified header to the hive file
_header.Sequence1 = _header.Sequence2 = lastSequenceNumber + 1;
_header.Timestamp = DateTime.UtcNow;
_header.WriteTo(buffer);
_fileStream.Position = 0;
_fileStream.Write(buffer);
_fileStream.Position = 0;
}
else if (_fileStream.CanWrite)
{
throw new IOException("Registry hive needs transaction logs to recover pending changes");
throw new RegistryCorruptException("Registry hive needs transaction logs to recover pending changes");
}
}

Expand Down

0 comments on commit ffac312

Please sign in to comment.