Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Security and validation #233

Merged
merged 2 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 5 additions & 10 deletions OpenMcdf.Tests/BinaryReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,31 +39,26 @@ public void ReadHeader(string fileName)

stream.CopyAllTo(memoryStream);
memoryStream.WriteByte(1); // Corrupt signature
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());
Assert.ThrowsException<FileFormatException>(() => reader.ReadHeader());

stream.CopyAllTo(memoryStream);
memoryStream.Position = 24;
memoryStream.WriteByte(1); // Corrupt CLSID
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());
Assert.ThrowsException<FileFormatException>(() => reader.ReadHeader());

stream.CopyAllTo(memoryStream);
memoryStream.Position = 26;
memoryStream.WriteByte(1); // Corrupt Major version
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());
Assert.ThrowsException<FileFormatException>(() => reader.ReadHeader());

stream.CopyAllTo(memoryStream);
memoryStream.Position = 28;
memoryStream.WriteByte(1); // Corrupt byte order
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());
Assert.ThrowsException<FileFormatException>(() => reader.ReadHeader());

stream.CopyAllTo(memoryStream);
memoryStream.Position = 32;
memoryStream.WriteByte(1); // Corrupt mini sector shift
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());

stream.CopyAllTo(memoryStream);
memoryStream.Position = 32;
memoryStream.WriteByte(1); // Corrupt mini sector shift
Assert.ThrowsException<FormatException>(() => reader.ReadHeader());
Assert.ThrowsException<FileFormatException>(() => reader.ReadHeader());
}
}
16 changes: 8 additions & 8 deletions OpenMcdf/CfbBinaryReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,19 @@ public Header ReadHeader()
ReadExactly(buffer, 0, Header.Signature.Length);
Span<byte> signature = buffer.AsSpan(0, Header.Signature.Length);
if (!signature.SequenceEqual(Header.Signature))
throw new FormatException("Invalid header signature.");
throw new FileFormatException("Invalid header signature.");
header.CLSID = ReadGuid();
if (header.CLSID != Guid.Empty)
throw new FormatException($"Invalid header CLSID: {header.CLSID}.");
throw new FileFormatException($"Invalid header CLSID: {header.CLSID}.");
header.MinorVersion = ReadUInt16();
header.MajorVersion = ReadUInt16();
if (header.MajorVersion is not (ushort)Version.V3 and not (ushort)Version.V4)
throw new FormatException($"Unsupported major version: {header.MajorVersion}.");
throw new FileFormatException($"Unsupported major version: {header.MajorVersion}.");
else if (header.MinorVersion is not Header.ExpectedMinorVersion)
Trace.WriteLine($"Unexpected minor version: {header.MinorVersion}.");
ushort byteOrder = ReadUInt16();
if (byteOrder != Header.LittleEndian)
throw new FormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4}).");
throw new FileFormatException($"Unsupported byte order: {byteOrder:X4}. Only little-endian is supported ({Header.LittleEndian:X4}).");
header.SectorShift = ReadUInt16();
header.MiniSectorShift = ReadUInt16();
FillBuffer(6);
Expand All @@ -95,7 +95,7 @@ public Header ReadHeader()
FillBuffer(4);
uint miniStreamCutoffSize = ReadUInt32();
if (miniStreamCutoffSize != Header.MiniStreamCutoffSize)
throw new FormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes.");
throw new FileFormatException($"Mini stream cutoff size must be {Header.MiniStreamCutoffSize} bytes.");
header.FirstMiniFatSectorId = ReadUInt32();
header.MiniFatSectorCount = ReadUInt32();
header.FirstDifatSectorId = ReadUInt32();
Expand All @@ -113,15 +113,15 @@ public StorageType ReadStorageType()
{
var type = (StorageType)ReadByte();
if (type is not StorageType.Storage and not StorageType.Stream and not StorageType.Root and not StorageType.Unallocated)
throw new FormatException($"Invalid storage type: {type}.");
throw new FileFormatException($"Invalid storage type: {type}.");
return type;
}

public NodeColor ReadColor()
{
var color = (NodeColor)ReadByte();
if (color is not NodeColor.Black and not NodeColor.Red)
throw new FormatException($"Invalid node color: {color}.");
throw new FileFormatException($"Invalid node color: {color}.");
return color;
}

Expand Down Expand Up @@ -154,7 +154,7 @@ public DirectoryEntry ReadDirectoryEntry(Version version, uint sid)
{
entry.StreamLength = ReadUInt32();
if (entry.StreamLength > DirectoryEntry.MaxV3StreamLength)
throw new FormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}.");
throw new FileFormatException($"Stream length {entry.StreamLength} exceeds maximum value {DirectoryEntry.MaxV3StreamLength}.");
ReadUInt32(); // Skip unused 4 bytes
}
else if (version == Version.V4)
Expand Down
2 changes: 1 addition & 1 deletion OpenMcdf/DifatSectorEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public void Add()
{
bool ok = MoveTo(header.DifatSectorCount - 1);
if (!ok)
throw new InvalidOperationException("Failed to move to last DIFAT sector.");
throw new FileFormatException("The DIFAT sector count is invalid.");

writer.Position = current.EndPosition - sizeof(uint);
writer.Write(newDifatSector.Id);
Expand Down
2 changes: 1 addition & 1 deletion OpenMcdf/DirectoryEntries.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ public bool TryGetDictionaryEntry(uint streamId, out DirectoryEntry? entry)
}

if (streamId > StreamId.Maximum)
throw new ArgumentException($"Invalid directory entry stream ID: ${streamId:X8}.", nameof(streamId));
throw new FileFormatException($"Invalid directory entry stream ID: ${streamId:X8}.");

uint chainIndex = GetChainIndexAndEntryIndex(streamId, out long entryIndex);
if (!fatChainEnumerator.MoveTo(chainIndex))
Expand Down
2 changes: 1 addition & 1 deletion OpenMcdf/DirectoryEntry.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ public void Recycle(StorageType storageType, string name)
StorageType.Stream => EntryType.Stream,
StorageType.Storage => EntryType.Storage,
StorageType.Root => EntryType.Storage,
_ => throw new InvalidOperationException("Invalid storage type.")
_ => throw new FileFormatException($"Invalid storage type: {Type}.")
};

public EntryInfo ToEntryInfo(string path) => new(EntryType, path, NameString, StreamLength, CLSID, CreationTime, ModifiedTime);
Expand Down
20 changes: 18 additions & 2 deletions OpenMcdf/DirectoryTree.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,27 @@ public bool TryGetDirectoryEntry(string name, out DirectoryEntry? entry)
int compare = DirectoryEntryComparer.Compare(nameSpan, child.NameCharSpan);
if (compare < 0)
{
directories.TryGetDictionaryEntry(child.LeftSiblingId, out child);
directories.TryGetDictionaryEntry(child.LeftSiblingId, out DirectoryEntry? leftChild);
if (leftChild is not null)
{
compare = DirectoryEntryComparer.Compare(leftChild.NameCharSpan, child.NameCharSpan);
if (compare >= 0)
throw new FileFormatException("Directory tree is not sorted.");
}

child = leftChild;
}
else if (compare > 0)
{
directories.TryGetDictionaryEntry(child.RightSiblingId, out child);
directories.TryGetDictionaryEntry(child.RightSiblingId, out DirectoryEntry? rightChild);
if (rightChild is not null)
{
compare = DirectoryEntryComparer.Compare(rightChild.NameCharSpan, child.NameCharSpan);
if (compare <= 0)
throw new FileFormatException("Directory tree is not sorted.");
}

child = rightChild;
}
else
{
Expand Down
6 changes: 3 additions & 3 deletions OpenMcdf/Fat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,17 +190,17 @@ internal void Validate()
{
Sector sector = new(entry.Index, Context.SectorSize);
if (entry.Value <= SectorType.Maximum && sector.EndPosition > Context.Length)
throw new FormatException($"FAT entry {entry} is beyond the end of the stream.");
throw new FileFormatException($"FAT entry {entry} is beyond the end of the stream.");
if (entry.Value == SectorType.Fat)
fatSectorCount++;
if (entry.Value == SectorType.Difat)
difatSectorCount++;
}

if (Context.Header.FatSectorCount != fatSectorCount)
throw new FormatException($"FAT sector count mismatch. Expected: {Context.Header.FatSectorCount} Actual: {fatSectorCount}.");
throw new FileFormatException($"FAT sector count mismatch. Expected: {Context.Header.FatSectorCount} Actual: {fatSectorCount}.");
if (Context.Header.DifatSectorCount != difatSectorCount)
throw new FormatException($"DIFAT sector count mismatch: Expected: {Context.Header.DifatSectorCount} Actual: {difatSectorCount}.");
throw new FileFormatException($"DIFAT sector count mismatch: Expected: {Context.Header.DifatSectorCount} Actual: {difatSectorCount}.");
}

[ExcludeFromCodeCoverage]
Expand Down
8 changes: 4 additions & 4 deletions OpenMcdf/FatStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ public override int Read(byte[] buffer, int offset, int count)

uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!chain.MoveTo(chainIndex))
throw new FormatException($"The FAT chain was shorter than the stream length.");
throw new FileFormatException($"The FAT chain was shorter than the stream length.");

int realCount = Math.Min(count, maxCount);
int readCount = 0;
Expand All @@ -109,7 +109,7 @@ public override int Read(byte[] buffer, int offset, int count)
if (readCount >= realCount)
return readCount;
if (!chain.MoveNext())
throw new FormatException($"The FAT chain was shorter than the stream length.");
throw new FileFormatException($"The FAT chain was shorter than the stream length.");
};
}

Expand Down Expand Up @@ -215,7 +215,7 @@ public override int Read(Span<byte> buffer)

uint chainIndex = GetFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!chain.MoveTo(chainIndex))
throw new FormatException($"The FAT chain was shorter than the stream length.");
throw new FileFormatException($"The FAT chain was shorter than the stream length.");

int realCount = Math.Min(buffer.Length, maxCount);
int readCount = 0;
Expand All @@ -236,7 +236,7 @@ public override int Read(Span<byte> buffer)
if (readCount >= realCount)
return readCount;
if (!chain.MoveNext())
throw new FormatException($"The FAT chain was shorter than the stream length.");
throw new FileFormatException($"The FAT chain was shorter than the stream length.");
}
}

Expand Down
19 changes: 19 additions & 0 deletions OpenMcdf/FileFormatException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
namespace OpenMcdf;

/// <summary>
/// The exception that is thrown when an compound file data stream contains invalid data.
/// </summary>
public class FileFormatException : FormatException
{
public FileFormatException()
{
}

public FileFormatException(string message) : base(message)
{
}

public FileFormatException(string message, Exception innerException) : base(message, innerException)
{
}
}
10 changes: 5 additions & 5 deletions OpenMcdf/Header.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ushort MajorVersion
get => majorVersion; set
{
if (value is not 3 and not 4)
throw new FormatException($"Unsupported major version: {value}. Only 3 and 4 are supported");
throw new FileFormatException($"Unsupported major version: {value}. Only 3 and 4 are supported");
majorVersion = value;
}
}
Expand All @@ -57,9 +57,9 @@ public ushort SectorShift
get => sectorShift; set
{
if (MajorVersion == 3 && value != SectorShiftV3)
throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3.");
throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV3:X4} is supported for Major Version 3.");
if (MajorVersion == 4 && value != SectorShiftV4)
throw new FormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4.");
throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {SectorShiftV4:X4} is supported for Major Version 4.");

sectorShift = value;
}
Expand All @@ -71,7 +71,7 @@ public ushort MiniSectorShift
set
{
if (value != ExpectedMiniSectorShift)
throw new FormatException($"Unsupported sector shift {value:X4}. Only {ExpectedMiniSectorShift:X4} is supported.");
throw new FileFormatException($"Unsupported sector shift {value:X4}. Only {ExpectedMiniSectorShift:X4} is supported.");

miniSectorShift = value;
}
Expand Down Expand Up @@ -130,7 +130,7 @@ public Header(Version version = Version.V3)
{
Version.V3 => SectorShiftV3,
Version.V4 => SectorShiftV4,
_ => throw new FormatException($"Unsupported version: {version}.")
_ => throw new FileFormatException($"Unsupported version: {version}.")
};
FirstDirectorySectorId = SectorType.EndOfChain;
DirectorySectorCount = 0; // Not used in v3
Expand Down
2 changes: 1 addition & 1 deletion OpenMcdf/MiniFat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ internal void Validate()
FatEntry current = miniFatEnumerator.Current;
if (current.Value <= SectorType.Maximum && miniFatEnumerator.CurrentSector.EndPosition > Context.MiniStream.Length)
{
throw new FormatException($"Mini FAT entry {current} is beyond the end of the mini stream.");
throw new FileFormatException($"Mini FAT entry {current} is beyond the end of the mini stream.");
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion OpenMcdf/MiniFatChainEnumerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public bool MoveNext()

uint nextIndex = index + 1;
if (nextIndex > SectorType.Maximum)
throw new FormatException("Mini FAT chain is corrupt.");
throw new FileFormatException("Mini FAT chain is corrupt.");

index = nextIndex;
current = sectorId;
Expand Down
12 changes: 6 additions & 6 deletions OpenMcdf/MiniFatStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public override int Read(byte[] buffer, int offset, int count)

uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!miniChain.MoveTo(chainIndex))
throw new FormatException($"The mini FAT chain was shorter than the stream length.");
throw new FileFormatException($"The mini FAT chain was shorter than the stream length.");

FatStream miniStream = Context.MiniStream;
int realCount = Math.Min(count, maxCount);
Expand All @@ -100,7 +100,7 @@ public override int Read(byte[] buffer, int offset, int count)
if (readCount >= realCount)
return readCount;
if (!miniChain.MoveNext())
throw new FormatException($"The mini FAT chain was shorter than the stream length.");
throw new FileFormatException($"The mini FAT chain was shorter than the stream length.");
}
}

Expand Down Expand Up @@ -165,7 +165,7 @@ public override void Write(byte[] buffer, int offset, int count)

uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!miniChain.MoveTo(chainIndex))
throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}.");
throw new FileFormatException($"Failed to move to mini FAT chain index: {chainIndex}.");

FatStream miniStream = Context.MiniStream;
int writeCount = 0;
Expand Down Expand Up @@ -211,7 +211,7 @@ public override int Read(Span<byte> buffer)

uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!miniChain.MoveTo(chainIndex))
throw new FormatException($"The mini FAT chain was shorter than the stream length.");
throw new FileFormatException($"The mini FAT chain was shorter than the stream length.");

FatStream miniStream = Context.MiniStream;
int realCount = Math.Min(buffer.Length, maxCount);
Expand All @@ -233,7 +233,7 @@ public override int Read(Span<byte> buffer)
if (readCount >= realCount)
return readCount;
if (!miniChain.MoveNext())
throw new FormatException($"The mini FAT chain was shorter than the stream length.");
throw new FileFormatException($"The mini FAT chain was shorter than the stream length.");
}
}

Expand All @@ -251,7 +251,7 @@ public override void Write(ReadOnlySpan<byte> buffer)

uint chainIndex = GetMiniFatChainIndexAndSectorOffset(position, out long sectorOffset);
if (!miniChain.MoveTo(chainIndex))
throw new InvalidOperationException($"Failed to move to mini FAT chain index: {chainIndex}.");
throw new FileFormatException($"Failed to move to mini FAT chain index: {chainIndex}.");

FatStream miniStream = Context.MiniStream;
int writeCount = 0;
Expand Down
7 changes: 4 additions & 3 deletions StructuredStorageExplorer/MainForm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.ComponentModel;
using System.Data;
using System.Globalization;
using FileFormatException = OpenMcdf.FileFormatException;

// Author Federico Blaseotto

Expand Down Expand Up @@ -110,7 +111,7 @@ private void CreateNewFile()

RefreshTree();
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException)
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException)
{
CloseCurrentFile();

Expand Down Expand Up @@ -149,7 +150,7 @@ private void LoadFile(string fileName)

RefreshTree();
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException)
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException)
{
CloseCurrentFile();

Expand Down Expand Up @@ -372,7 +373,7 @@ private void TreeView1_MouseUp(object sender, MouseEventArgs e)

propertyGrid1.SelectedObject = nodeSelection.EntryInfo;
}
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FormatException)
catch (Exception ex) when (ex is IOException or UnauthorizedAccessException or FileFormatException)
{
CloseCurrentFile();

Expand Down