diff --git a/.gitignore b/.gitignore index d6a5a1d1..f101b3b1 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,8 @@ TestResults/ packages/*/ project.lock.json tests/TestArchives/Scratch +tests/TestArchives/*/Scratch +tests/TestArchives/*/Scratch2 .vs tools .vscode @@ -18,4 +20,3 @@ tools .DS_Store *.snupkg -/tests/TestArchives/6d23a38c-f064-4ef1-ad89-b942396f53b9/Scratch diff --git a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs index 1a04740f..06a1f10a 100644 --- a/src/SharpCompress/Common/Tar/Headers/TarHeader.cs +++ b/src/SharpCompress/Common/Tar/Headers/TarHeader.cs @@ -25,6 +25,10 @@ internal sealed class TarHeader internal const int BLOCK_SIZE = 512; + // Maximum size for long name/link headers to prevent memory exhaustion attacks + // This is generous enough for most real-world scenarios (32KB) + private const int MAX_LONG_NAME_SIZE = 32768; + internal void Write(Stream output) { var buffer = new byte[BLOCK_SIZE]; @@ -186,6 +190,15 @@ internal sealed class TarHeader private string ReadLongName(BinaryReader reader, byte[] buffer) { var size = ReadSize(buffer); + + // Validate size to prevent memory exhaustion from malformed headers + if (size < 0 || size > MAX_LONG_NAME_SIZE) + { + throw new InvalidFormatException( + $"Long name size {size} is invalid or exceeds maximum allowed size of {MAX_LONG_NAME_SIZE} bytes" + ); + } + var nameLength = (int)size; var nameBytes = reader.ReadBytes(nameLength); var remainingBytesToRead = BLOCK_SIZE - (nameLength % BLOCK_SIZE); diff --git a/tests/SharpCompress.Test/Tar/TarReaderTests.cs b/tests/SharpCompress.Test/Tar/TarReaderTests.cs index c9977664..c4763b3c 100644 --- a/tests/SharpCompress.Test/Tar/TarReaderTests.cs +++ b/tests/SharpCompress.Test/Tar/TarReaderTests.cs @@ -254,4 +254,58 @@ public class TarReaderTests : ReaderTests } } #endif + + [Fact] + public void Tar_Malformed_LongName_Excessive_Size() + { + // Create a malformed TAR header with an excessively large LongName size + // This simulates what happens during auto-detection of compressed files + var buffer = new byte[512]; + + // Set up a basic TAR header structure + // Name field (offset 0, 100 bytes) - set to "././@LongLink" which is typical for LongName + var nameBytes = System.Text.Encoding.ASCII.GetBytes("././@LongLink"); + Array.Copy(nameBytes, 0, buffer, 0, nameBytes.Length); + + // Set entry type to LongName (offset 156) + buffer[156] = (byte)'L'; // EntryType.LongName + + // Set an excessively large size (offset 124, 12 bytes, octal format) + // This simulates a corrupted/misinterpreted size field + // Using "77777777777" (octal) = 8589934591 bytes (~8GB) + var sizeBytes = System.Text.Encoding.ASCII.GetBytes("77777777777 "); + Array.Copy(sizeBytes, 0, buffer, 124, sizeBytes.Length); + + // Calculate and set checksum (offset 148, 8 bytes) + // Set checksum field to spaces first + for (var i = 148; i < 156; i++) + { + buffer[i] = (byte)' '; + } + + // Calculate checksum + var checksum = 0; + foreach (var b in buffer) + { + checksum += b; + } + + var checksumStr = Convert.ToString(checksum, 8).PadLeft(6, '0') + "\0 "; + var checksumBytes = System.Text.Encoding.ASCII.GetBytes(checksumStr); + Array.Copy(checksumBytes, 0, buffer, 148, checksumBytes.Length); + + // Create a stream with this malformed header + using var stream = new MemoryStream(); + stream.Write(buffer, 0, buffer.Length); + stream.Position = 0; + + // Attempt to read this malformed archive + // The InvalidFormatException from the validation gets caught and converted to IncompleteArchiveException + // The important thing is it doesn't cause OutOfMemoryException + Assert.Throws(() => + { + using var reader = TarReader.Open(stream); + reader.MoveToNextEntry(); + }); + } }