diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs b/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs index f6227168..7ff1f854 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.Async.cs @@ -108,8 +108,11 @@ internal sealed partial class GZipFilePart } if ((header[3] & 0x02) == 0x02) { - var buf = new byte[1]; - _ = await _stream.ReadAsync(buf, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore + var headerCrc = new byte[2]; + if (!await _stream.ReadFullyAsync(headerCrc, cancellationToken).ConfigureAwait(false)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header CRC."); + } } } diff --git a/src/SharpCompress/Common/GZip/GZipFilePart.cs b/src/SharpCompress/Common/GZip/GZipFilePart.cs index 35b25438..85de0611 100644 --- a/src/SharpCompress/Common/GZip/GZipFilePart.cs +++ b/src/SharpCompress/Common/GZip/GZipFilePart.cs @@ -133,7 +133,11 @@ internal sealed partial class GZipFilePart : FilePart } if ((header[3] & 0x02) == 0x02) { - _stream.ReadByte(); // CRC16, ignore + Span headerCrc = stackalloc byte[2]; + if (!_stream.ReadFully(headerCrc)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header CRC."); + } } } diff --git a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs index 6401169c..5054e37b 100644 --- a/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs +++ b/src/SharpCompress/Compressors/Deflate/ZlibBaseStream.cs @@ -778,7 +778,12 @@ internal class ZlibBaseStream : Stream, IStreamStack } if ((header[3] & 0x02) == 0x02) { - Read(_buf1, 0, 1); // CRC16, ignore + Span headerCrc = stackalloc byte[2]; + if (!_stream.ReadFully(headerCrc)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header CRC."); + } + totalBytesRead += 2; } return totalBytesRead; @@ -848,7 +853,12 @@ internal class ZlibBaseStream : Stream, IStreamStack } if ((header[3] & 0x02) == 0x02) { - await _stream.ReadAsync(_buf1, 0, 1, cancellationToken).ConfigureAwait(false); // CRC16, ignore + var headerCrc = new byte[2]; + if (!await _stream.ReadFullyAsync(headerCrc, cancellationToken).ConfigureAwait(false)) + { + throw new ZlibException("Unexpected end-of-file reading GZIP header CRC."); + } + totalBytesRead += 2; } return totalBytesRead; diff --git a/tests/SharpCompress.Test/GZip/GZipExtraFieldTests.cs b/tests/SharpCompress.Test/GZip/GZipExtraFieldTests.cs index ae8d2712..5c189904 100644 --- a/tests/SharpCompress.Test/GZip/GZipExtraFieldTests.cs +++ b/tests/SharpCompress.Test/GZip/GZipExtraFieldTests.cs @@ -21,6 +21,82 @@ public class GZipExtraFieldTests { private static readonly byte[] EXPECTED_PAYLOAD = [.. "gzip payload"u8]; + [Theory] + [InlineData("Stream", false)] + [InlineData("Stream", true)] + [InlineData("Archive", false)] + [InlineData("Archive", true)] + [InlineData("Reader", false)] + [InlineData("Reader", true)] + public async Task GZip_Rejects_Truncated_Header_Crc(string api, bool async) + { + byte[] bytes = [0x1F, 0x8B, 8, 2, 0, 0, 0, 0, 0, 3, 0]; + using var input = new ShortReadStream(new MemoryStream(bytes)); + await Assert.ThrowsAsync(() => + ReadPayload(input, api, async, CancellationToken.None) + ); + Assert.False(input.IsDisposed); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task GZip_Header_Crc_Field_Is_Two_Bytes(bool async) + { + var original = CreateGZipWithExtraField(0, null); + original[3] |= 2; + var headerCrc = SharpCompress.Crypto.Crc32Stream.Compute(original.AsSpan(0, 12).ToArray()); + byte[] bytes = + [ + .. original.AsSpan(0, 12), + (byte)headerCrc, + (byte)(headerCrc >> 8), + .. original.AsSpan(12), + ]; + using var input = new ShortReadStream(new MemoryStream(bytes)); + using var output = new MemoryStream(); + if (async) + { + await using var gzip = new SharpCompress.Compressors.Deflate.GZipStream( + input, + SharpCompressionMode.Decompress, + CompressionLevel.Default, + Encoding.UTF8, + leaveOpen: true + ); + await gzip.CopyToAsync(output, 81920, CancellationToken.None); + } + else + { + using var gzip = new SharpCompress.Compressors.Deflate.GZipStream( + input, + SharpCompressionMode.Decompress, + CompressionLevel.Default, + Encoding.UTF8, + leaveOpen: true + ); + gzip.CopyTo(output); + } + Assert.Equal(EXPECTED_PAYLOAD, output.ToArray()); + using var archiveInput = new ShortReadStream(new MemoryStream(bytes)); + if (async) + { + await using var archive = await GZipArchive.OpenAsyncArchive(archiveInput); + var entry = await archive.EntriesAsync.FirstAsync(); + using var entryStream = await entry.OpenEntryStreamAsync(CancellationToken.None); + output.SetLength(0); + await entryStream.CopyToAsync(output, 81920, CancellationToken.None); + } + else + { + using var archive = GZipArchive.OpenArchive(archiveInput); + using var entryStream = archive.Entries.Single().OpenEntryStream(); + output.SetLength(0); + entryStream.CopyTo(output); + } + Assert.Equal(EXPECTED_PAYLOAD, output.ToArray()); + } + public static IEnumerable FragmentedExtraFields() { foreach (var api in new[] { "Stream", "Reader", "Archive" })