From c1408ccbd655af095f745b139a9a3ad9eeb34c66 Mon Sep 17 00:00:00 2001 From: LTRData Date: Sat, 23 Nov 2024 22:39:06 +0100 Subject: [PATCH] File name length validation etc * Added some overrun checks for numeric calculations in CDBuilder. Ref: https://github.com/LTRData/DiscUtils/issues/20 --- .../DiscUtils.Iso9660/BuildDirectoryInfo.cs | 2 +- Library/DiscUtils.Iso9660/CDBuilder.cs | 355 +++++++++--------- Library/DiscUtils.Iso9660/IsoUtilities.cs | 2 +- Library/DiscUtils.Iso9660/PathTableRecord.cs | 28 +- Library/DiscUtils.Iso9660/VfsCDReader.cs | 2 +- Tests/LibraryTests/Iso9660/BuilderTest.cs | 13 + 6 files changed, 214 insertions(+), 188 deletions(-) diff --git a/Library/DiscUtils.Iso9660/BuildDirectoryInfo.cs b/Library/DiscUtils.Iso9660/BuildDirectoryInfo.cs index 44902ed88..9308b8b71 100644 --- a/Library/DiscUtils.Iso9660/BuildDirectoryInfo.cs +++ b/Library/DiscUtils.Iso9660/BuildDirectoryInfo.cs @@ -100,7 +100,7 @@ internal uint GetPathTableEntrySize(Encoding enc) { var nameBytes = enc.GetByteCount(PickName(null, enc)); - return (uint)(8 + nameBytes + ((nameBytes & 0x1) == 1 ? 1 : 0)); + return checked((uint)(8 + nameBytes + ((nameBytes & 0x1) == 1 ? 1 : 0))); } internal int Write(Span buffer, Dictionary locationTable, Encoding enc) diff --git a/Library/DiscUtils.Iso9660/CDBuilder.cs b/Library/DiscUtils.Iso9660/CDBuilder.cs index be940bf0a..55f770913 100644 --- a/Library/DiscUtils.Iso9660/CDBuilder.cs +++ b/Library/DiscUtils.Iso9660/CDBuilder.cs @@ -287,205 +287,208 @@ protected override List FixExtents(out long totalLength) var suppEncoding = _buildParams.UseJoliet ? Encoding.BigEndianUnicode : Encoding.ASCII; var primaryLocationTable = new Dictionary(); - var supplementaryLocationTable = - new Dictionary(); + + var supplementaryLocationTable = new Dictionary(); - var focus = DiskStart + 3 * IsoUtilities.SectorSize; // Primary, Supplementary, End (fixed at end...) - if (_bootEntry != null) - { - focus += IsoUtilities.SectorSize; - } - - // #################################################################### - // # 0. Fix boot image location - // #################################################################### - long bootCatalogPos = 0; - if (_bootEntry != null) + checked { - // Boot catalog MUST be at beginning - bootCatalogPos = focus; - focus += IsoUtilities.SectorSize; - var bootImagePos = focus; - var realBootImage = PatchBootImage(_bootImage, (uint)(DiskStart / IsoUtilities.SectorSize), - (uint)(bootImagePos / IsoUtilities.SectorSize)); - var bootImageExtent = new BuilderStreamExtent(focus, realBootImage); - fixedRegions.Add(bootImageExtent); - focus += MathUtilities.RoundUp(bootImageExtent.Length, IsoUtilities.SectorSize); - - var bootCatalog = new byte[IsoUtilities.SectorSize]; - var bve = new BootValidationEntry + var focus = DiskStart + 3 * IsoUtilities.SectorSize; // Primary, Supplementary, End (fixed at end...) + if (_bootEntry != null) { - ManfId = ManufacturerId - }; - bve.WriteTo(bootCatalog, 0x00); - _bootEntry.ImageStart = (uint)MathUtilities.Ceil(bootImagePos, IsoUtilities.SectorSize); - if (_bootEntry.BootMediaType == BootDeviceEmulation.NoEmulation) - { - _bootEntry.SectorCount = (ushort)MathUtilities.Ceil(_bootImage.Length, Sizes.Sector); + focus += IsoUtilities.SectorSize; } - else + + // #################################################################### + // # 0. Fix boot image location + // #################################################################### + long bootCatalogPos = 0; + if (_bootEntry != null) { - _bootEntry.SectorCount = 1; - } + // Boot catalog MUST be at beginning + bootCatalogPos = focus; + focus += IsoUtilities.SectorSize; + var bootImagePos = focus; + var realBootImage = PatchBootImage(_bootImage, (uint)(DiskStart / IsoUtilities.SectorSize), + (uint)(bootImagePos / IsoUtilities.SectorSize)); + var bootImageExtent = new BuilderStreamExtent(focus, realBootImage); + fixedRegions.Add(bootImageExtent); + focus += MathUtilities.RoundUp(bootImageExtent.Length, IsoUtilities.SectorSize); + + var bootCatalog = new byte[IsoUtilities.SectorSize]; + var bve = new BootValidationEntry + { + ManfId = ManufacturerId + }; + bve.WriteTo(bootCatalog, 0x00); + _bootEntry.ImageStart = (uint)MathUtilities.Ceil(bootImagePos, IsoUtilities.SectorSize); + if (_bootEntry.BootMediaType == BootDeviceEmulation.NoEmulation) + { + _bootEntry.SectorCount = (ushort)MathUtilities.Ceil(_bootImage.Length, Sizes.Sector); + } + else + { + _bootEntry.SectorCount = 1; + } - _bootEntry.WriteTo(bootCatalog, 0x20); - fixedRegions.Add(new BuilderBufferExtent(bootCatalogPos, bootCatalog)); + _bootEntry.WriteTo(bootCatalog, 0x20); + fixedRegions.Add(new BuilderBufferExtent(bootCatalogPos, bootCatalog)); - // Don't add to focus, we already skipped the length of the bootCatalog - } + // Don't add to focus, we already skipped the length of the bootCatalog + } - // #################################################################### - // # 1. Fix file locations - // #################################################################### + // #################################################################### + // # 1. Fix file locations + // #################################################################### - // Find end of the file data, fixing the files in place as we go - foreach (var fi in _files) - { - if (TrackEqualSourceFiles && primaryLocationTable.TryGetValue(fi, out var existing_sector)) + // Find end of the file data, fixing the files in place as we go + foreach (var fi in _files) { - primaryLocationTable.Add(fi, existing_sector); - supplementaryLocationTable.Add(fi, existing_sector); + if (TrackEqualSourceFiles && primaryLocationTable.TryGetValue(fi, out var existing_sector)) + { + primaryLocationTable.Add(fi, existing_sector); + supplementaryLocationTable.Add(fi, existing_sector); - var existing_focus = existing_sector * IsoUtilities.SectorSize; + var existing_focus = existing_sector * IsoUtilities.SectorSize; - var extent = new FileExtent(fi, existing_focus); + var extent = new FileExtent(fi, existing_focus); - // Only remember files of non-zero length (otherwise we'll stomp on a valid file) - if (extent.Length != 0) + // Only remember files of non-zero length (otherwise we'll stomp on a valid file) + if (extent.Length != 0) + { + fixedRegions.Add(extent); + } + } + else { - fixedRegions.Add(extent); + var sector = (uint)(focus / IsoUtilities.SectorSize); + + primaryLocationTable.Add(fi, sector); + supplementaryLocationTable.Add(fi, sector); + var extent = new FileExtent(fi, focus); + + // Only remember files of non-zero length (otherwise we'll stomp on a valid file) + if (extent.Length != 0) + { + fixedRegions.Add(extent); + } + + focus += MathUtilities.RoundUp(extent.Length, IsoUtilities.SectorSize); } } - else - { - var sector = (uint)(focus / IsoUtilities.SectorSize); - primaryLocationTable.Add(fi, sector); - supplementaryLocationTable.Add(fi, sector); - var extent = new FileExtent(fi, focus); + // #################################################################### + // # 2. Fix directory locations + // #################################################################### - // Only remember files of non-zero length (otherwise we'll stomp on a valid file) - if (extent.Length != 0) - { - fixedRegions.Add(extent); - } + // There are two directory tables + // 1. Primary (std ISO9660) + // 2. Supplementary (Joliet) + // Find start of the second set of directory data, fixing ASCII directories in place. + var startOfFirstDirData = focus; + foreach (var di in _dirs) + { + primaryLocationTable.Add(di, (uint)(focus / IsoUtilities.SectorSize)); + var extent = new DirectoryExtent(di, primaryLocationTable, Encoding.ASCII, focus); + fixedRegions.Add(extent); focus += MathUtilities.RoundUp(extent.Length, IsoUtilities.SectorSize); } - } - - // #################################################################### - // # 2. Fix directory locations - // #################################################################### - // There are two directory tables - // 1. Primary (std ISO9660) - // 2. Supplementary (Joliet) - - // Find start of the second set of directory data, fixing ASCII directories in place. - var startOfFirstDirData = focus; - foreach (var di in _dirs) - { - primaryLocationTable.Add(di, (uint)(focus / IsoUtilities.SectorSize)); - var extent = new DirectoryExtent(di, primaryLocationTable, Encoding.ASCII, focus); - fixedRegions.Add(extent); - focus += MathUtilities.RoundUp(extent.Length, IsoUtilities.SectorSize); - } + // Find end of the second directory table, fixing supplementary directories in place. + var startOfSecondDirData = focus; + foreach (var di in _dirs) + { + supplementaryLocationTable.Add(di, (uint)(focus / IsoUtilities.SectorSize)); + var extent = new DirectoryExtent(di, supplementaryLocationTable, suppEncoding, focus); + fixedRegions.Add(extent); + focus += MathUtilities.RoundUp(extent.Length, IsoUtilities.SectorSize); + } - // Find end of the second directory table, fixing supplementary directories in place. - var startOfSecondDirData = focus; - foreach (var di in _dirs) - { - supplementaryLocationTable.Add(di, (uint)(focus / IsoUtilities.SectorSize)); - var extent = new DirectoryExtent(di, supplementaryLocationTable, suppEncoding, focus); - fixedRegions.Add(extent); - focus += MathUtilities.RoundUp(extent.Length, IsoUtilities.SectorSize); - } + // #################################################################### + // # 3. Fix path tables + // #################################################################### + + // There are four path tables: + // 1. LE, ASCII + // 2. BE, ASCII + // 3. LE, Supp Encoding (Joliet) + // 4. BE, Supp Encoding (Joliet) + + // Find end of the path table + var startOfFirstPathTable = focus; + var pathTable = new PathTable(byteSwap: false, Encoding.ASCII, _dirs, primaryLocationTable, focus); + fixedRegions.Add(pathTable); + focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); + var primaryPathTableLength = pathTable.Length; + + var startOfSecondPathTable = focus; + pathTable = new PathTable(byteSwap: true, Encoding.ASCII, _dirs, primaryLocationTable, focus); + fixedRegions.Add(pathTable); + focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); + + var startOfThirdPathTable = focus; + pathTable = new PathTable(byteSwap: false, suppEncoding, _dirs, supplementaryLocationTable, focus); + fixedRegions.Add(pathTable); + focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); + var supplementaryPathTableLength = pathTable.Length; + + var startOfFourthPathTable = focus; + pathTable = new PathTable(byteSwap: true, suppEncoding, _dirs, supplementaryLocationTable, focus); + fixedRegions.Add(pathTable); + focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); + + // Find the end of the disk + totalLength = focus; + + // #################################################################### + // # 4. Prepare volume descriptors now other structures are fixed + // #################################################################### + var regionIdx = 0; + focus = DiskStart; + var pvDesc = new PrimaryVolumeDescriptor( + (uint)(totalLength / IsoUtilities.SectorSize), // VolumeSpaceSize + (uint)primaryPathTableLength, // PathTableSize + (uint)(startOfFirstPathTable / IsoUtilities.SectorSize), // TypeLPathTableLocation + (uint)(startOfSecondPathTable / IsoUtilities.SectorSize), // TypeMPathTableLocation + (uint)(startOfFirstDirData / IsoUtilities.SectorSize), // RootDirectory.LocationOfExtent + (uint)_rootDirectory.GetDataSize(Encoding.ASCII), // RootDirectory.DataLength + buildTime) + { + VolumeIdentifier = _buildParams.VolumeIdentifier + }; + var pvdr = new PrimaryVolumeDescriptorRegion(pvDesc, focus); + fixedRegions.Insert(regionIdx++, pvdr); + focus += IsoUtilities.SectorSize; - // #################################################################### - // # 3. Fix path tables - // #################################################################### - - // There are four path tables: - // 1. LE, ASCII - // 2. BE, ASCII - // 3. LE, Supp Encoding (Joliet) - // 4. BE, Supp Encoding (Joliet) - - // Find end of the path table - var startOfFirstPathTable = focus; - var pathTable = new PathTable(false, Encoding.ASCII, _dirs, primaryLocationTable, focus); - fixedRegions.Add(pathTable); - focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); - var primaryPathTableLength = pathTable.Length; - - var startOfSecondPathTable = focus; - pathTable = new PathTable(true, Encoding.ASCII, _dirs, primaryLocationTable, focus); - fixedRegions.Add(pathTable); - focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); - - var startOfThirdPathTable = focus; - pathTable = new PathTable(false, suppEncoding, _dirs, supplementaryLocationTable, focus); - fixedRegions.Add(pathTable); - focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); - var supplementaryPathTableLength = pathTable.Length; - - var startOfFourthPathTable = focus; - pathTable = new PathTable(true, suppEncoding, _dirs, supplementaryLocationTable, focus); - fixedRegions.Add(pathTable); - focus += MathUtilities.RoundUp(pathTable.Length, IsoUtilities.SectorSize); - - // Find the end of the disk - totalLength = focus; - - // #################################################################### - // # 4. Prepare volume descriptors now other structures are fixed - // #################################################################### - var regionIdx = 0; - focus = DiskStart; - var pvDesc = new PrimaryVolumeDescriptor( - (uint)(totalLength / IsoUtilities.SectorSize), // VolumeSpaceSize - (uint)primaryPathTableLength, // PathTableSize - (uint)(startOfFirstPathTable / IsoUtilities.SectorSize), // TypeLPathTableLocation - (uint)(startOfSecondPathTable / IsoUtilities.SectorSize), // TypeMPathTableLocation - (uint)(startOfFirstDirData / IsoUtilities.SectorSize), // RootDirectory.LocationOfExtent - (uint)_rootDirectory.GetDataSize(Encoding.ASCII), // RootDirectory.DataLength - buildTime) - { - VolumeIdentifier = _buildParams.VolumeIdentifier - }; - var pvdr = new PrimaryVolumeDescriptorRegion(pvDesc, focus); - fixedRegions.Insert(regionIdx++, pvdr); - focus += IsoUtilities.SectorSize; + if (_bootEntry != null) + { + var bvDesc = new BootVolumeDescriptor( + (uint)(bootCatalogPos / IsoUtilities.SectorSize)); + var bvdr = new BootVolumeDescriptorRegion(bvDesc, focus); + fixedRegions.Insert(regionIdx++, bvdr); + focus += IsoUtilities.SectorSize; + } - if (_bootEntry != null) - { - var bvDesc = new BootVolumeDescriptor( - (uint)(bootCatalogPos / IsoUtilities.SectorSize)); - var bvdr = new BootVolumeDescriptorRegion(bvDesc, focus); - fixedRegions.Insert(regionIdx++, bvdr); + var svDesc = new SupplementaryVolumeDescriptor( + (uint)(totalLength / IsoUtilities.SectorSize), // VolumeSpaceSize + (uint)supplementaryPathTableLength, // PathTableSize + (uint)(startOfThirdPathTable / IsoUtilities.SectorSize), // TypeLPathTableLocation + (uint)(startOfFourthPathTable / IsoUtilities.SectorSize), // TypeMPathTableLocation + (uint)(startOfSecondDirData / IsoUtilities.SectorSize), // RootDirectory.LocationOfExtent + (uint)_rootDirectory.GetDataSize(suppEncoding), // RootDirectory.DataLength + buildTime, + suppEncoding) + { + VolumeIdentifier = _buildParams.VolumeIdentifier + }; + var svdr = new SupplementaryVolumeDescriptorRegion(svDesc, focus); + fixedRegions.Insert(regionIdx++, svdr); focus += IsoUtilities.SectorSize; - } - var svDesc = new SupplementaryVolumeDescriptor( - (uint)(totalLength / IsoUtilities.SectorSize), // VolumeSpaceSize - (uint)supplementaryPathTableLength, // PathTableSize - (uint)(startOfThirdPathTable / IsoUtilities.SectorSize), // TypeLPathTableLocation - (uint)(startOfFourthPathTable / IsoUtilities.SectorSize), // TypeMPathTableLocation - (uint)(startOfSecondDirData / IsoUtilities.SectorSize), // RootDirectory.LocationOfExtent - (uint)_rootDirectory.GetDataSize(suppEncoding), // RootDirectory.DataLength - buildTime, - suppEncoding) - { - VolumeIdentifier = _buildParams.VolumeIdentifier - }; - var svdr = new SupplementaryVolumeDescriptorRegion(svDesc, focus); - fixedRegions.Insert(regionIdx++, svdr); - focus += IsoUtilities.SectorSize; - - var evDesc = new VolumeDescriptorSetTerminator(); - var evdr = new VolumeDescriptorSetTerminatorRegion(evDesc, focus); - fixedRegions.Insert(regionIdx++, evdr); + var evDesc = new VolumeDescriptorSetTerminator(); + var evdr = new VolumeDescriptorSetTerminatorRegion(evDesc, focus); + fixedRegions.Insert(regionIdx++, evdr); + } return fixedRegions; } @@ -564,7 +567,7 @@ public BuildDirectoryMember GetFile(string path) private void CheckDirectoryForFilePath(string name, out ReadOnlyMemory[] nameElements, out BuildDirectoryInfo dir) { nameElements = name.AsMemory().TokenEnum('\\', '/', StringSplitOptions.RemoveEmptyEntries).ToArray(); - dir = GetDirectory(nameElements, nameElements.Length - 1, true); + dir = GetDirectory(nameElements, nameElements.Length - 1, createMissing: true); if (dir.TryGetMember(nameElements[nameElements.Length - 1].ToString(), out _)) { @@ -586,12 +589,14 @@ private BuildDirectoryInfo TryGetDirectory(ReadOnlyMemory[] path, int path for (var i = 0; i < pathLength; ++i) { - if (!focus.TryGetMember(path[i].ToString(), out var next)) + var name = path[i].ToString(); + + if (!focus.TryGetMember(name, out var next)) { if (createMissing) { // This directory doesn't exist, create it... - var di = new BuildDirectoryInfo(path[i].ToString(), focus); + var di = new BuildDirectoryInfo(name, focus); focus.Add(di); _dirs.Add(di); focus = di; diff --git a/Library/DiscUtils.Iso9660/IsoUtilities.cs b/Library/DiscUtils.Iso9660/IsoUtilities.cs index 119a7ddb3..4c966c536 100644 --- a/Library/DiscUtils.Iso9660/IsoUtilities.cs +++ b/Library/DiscUtils.Iso9660/IsoUtilities.cs @@ -253,7 +253,7 @@ internal static bool IsValidDirectoryName(string str) internal static string NormalizeFileName(string name) { var parts = SplitFileName(name); - return parts[0] + '.' + parts[1] + ';' + parts[2]; + return $"{parts[0]}.{parts[1]};{parts[2]}"; } internal static string[] SplitFileName(string name) diff --git a/Library/DiscUtils.Iso9660/PathTableRecord.cs b/Library/DiscUtils.Iso9660/PathTableRecord.cs index 048c253b9..b8e4bd1b1 100644 --- a/Library/DiscUtils.Iso9660/PathTableRecord.cs +++ b/Library/DiscUtils.Iso9660/PathTableRecord.cs @@ -54,18 +54,26 @@ internal int Write(bool byteSwap, Encoding enc, Span buffer) { var nameBytes = enc.GetByteCount(DirectoryIdentifier); - buffer[0] = (byte)nameBytes; - buffer[1] = 0; // ExtendedAttributeRecordLength; - IsoUtilities.ToBytesFromUInt32(buffer.Slice(2), - byteSwap ? Utilities.BitSwap(LocationOfExtent) : LocationOfExtent); - IsoUtilities.ToBytesFromUInt16(buffer.Slice(6), - byteSwap ? Utilities.BitSwap(ParentDirectoryNumber) : ParentDirectoryNumber); - IsoUtilities.WriteString(buffer.Slice(8, nameBytes), false, DirectoryIdentifier, enc); - if ((nameBytes & 1) == 1) + if (nameBytes > byte.MaxValue) { - buffer[8 + nameBytes] = 0; + throw new InvalidOperationException($"File name '{DirectoryIdentifier}' is too long"); } - return 8 + nameBytes + ((nameBytes & 0x1) == 1 ? 1 : 0); + checked + { + buffer[0] = (byte)nameBytes; + buffer[1] = 0; // ExtendedAttributeRecordLength; + IsoUtilities.ToBytesFromUInt32(buffer.Slice(2), + byteSwap ? Utilities.BitSwap(LocationOfExtent) : LocationOfExtent); + IsoUtilities.ToBytesFromUInt16(buffer.Slice(6), + byteSwap ? Utilities.BitSwap(ParentDirectoryNumber) : ParentDirectoryNumber); + IsoUtilities.WriteString(buffer.Slice(8, nameBytes), false, DirectoryIdentifier, enc); + if ((nameBytes & 1) == 1) + { + buffer[8 + nameBytes] = 0; + } + + return 8 + nameBytes + ((nameBytes & 0x1) == 1 ? 1 : 0); + } } } \ No newline at end of file diff --git a/Library/DiscUtils.Iso9660/VfsCDReader.cs b/Library/DiscUtils.Iso9660/VfsCDReader.cs index 55441d8f4..c38ceec9c 100644 --- a/Library/DiscUtils.Iso9660/VfsCDReader.cs +++ b/Library/DiscUtils.Iso9660/VfsCDReader.cs @@ -466,7 +466,7 @@ public static ushort DetectSectorCountFromBootSector(Stream stream) var totalSectors = bpbTotSec16 + bpbTotSec32; // Can't be greater than unsigned short - if (totalSectors > 65535) + if (totalSectors > ushort.MaxValue) { return 0; } diff --git a/Tests/LibraryTests/Iso9660/BuilderTest.cs b/Tests/LibraryTests/Iso9660/BuilderTest.cs index 22e49acd4..874e5be29 100644 --- a/Tests/LibraryTests/Iso9660/BuilderTest.cs +++ b/Tests/LibraryTests/Iso9660/BuilderTest.cs @@ -20,6 +20,7 @@ // DEALINGS IN THE SOFTWARE. // +using System; using System.IO; using DiscUtils; using DiscUtils.Iso9660; @@ -91,4 +92,16 @@ public void BootImage() Assert.Equal(BootDeviceEmulation.HardDisk, fs.BootEmulation); Assert.Equal(0x543, fs.BootLoadSegment); } + + [Fact] + public void LongPathTest() + { + const string testPath = @"Layout\Microsoft.VisualStudio.Debugger.Concord.Remote.Resources,version=17.12.35504.99,chip=x64,language=en-US,productarch=neutral,machinearch=ARM64\payload.vsix"; + + var builder = new CDBuilder(); + + builder.AddFile(testPath, System.Array.Empty()); + + Assert.Throws(() => builder.Build(new MemoryStream())); + } }