Do not treat zero-length checksum reads as end of entry

This commit is contained in:
Adam Hathcock
2026-10-10 15:40:37 +01:00
parent 06fea02e64
commit fed1d884c7
3 changed files with 72 additions and 8 deletions

View File

@@ -43,7 +43,10 @@ internal sealed class ChecksumValidationStream : Stream
public override int Read(byte[] buffer, int offset, int count)
{
var read = _stream.Read(buffer, offset, count);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
if (count != 0)
{
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
}
return read;
}
@@ -51,7 +54,10 @@ internal sealed class ChecksumValidationStream : Stream
public override int Read(Span<byte> buffer)
{
var read = _stream.Read(buffer);
UpdateAndValidateAtEof(buffer[..read], read);
if (!buffer.IsEmpty)
{
UpdateAndValidateAtEof(buffer[..read], read);
}
return read;
}
#endif
@@ -68,7 +74,10 @@ internal sealed class ChecksumValidationStream : Stream
var read = await _stream
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
if (count != 0)
{
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
}
return read;
}
@@ -79,7 +88,10 @@ internal sealed class ChecksumValidationStream : Stream
)
{
var read = await _stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.Span[..read], read);
if (!buffer.IsEmpty)
{
UpdateAndValidateAtEof(buffer.Span[..read], read);
}
return read;
}
#endif

View File

@@ -54,7 +54,10 @@ internal sealed class GZipChecksumValidationStream : Stream
public override int Read(byte[] buffer, int offset, int count)
{
var read = _source.Read(buffer, offset, count);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
if (count != 0)
{
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
}
return read;
}
@@ -62,7 +65,10 @@ internal sealed class GZipChecksumValidationStream : Stream
public override int Read(Span<byte> buffer)
{
var read = _source.Read(buffer);
UpdateAndValidateAtEof(buffer[..read], read);
if (!buffer.IsEmpty)
{
UpdateAndValidateAtEof(buffer[..read], read);
}
return read;
}
#endif
@@ -93,7 +99,10 @@ internal sealed class GZipChecksumValidationStream : Stream
var read = await _source
.ReadAsync(buffer, offset, count, cancellationToken)
.ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
if (count != 0)
{
UpdateAndValidateAtEof(buffer.AsSpan(offset, read), read);
}
return read;
}
@@ -104,7 +113,10 @@ internal sealed class GZipChecksumValidationStream : Stream
)
{
var read = await _source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
UpdateAndValidateAtEof(buffer.Span[..read], read);
if (!buffer.IsEmpty)
{
UpdateAndValidateAtEof(buffer.Span[..read], read);
}
return read;
}
#endif

View File

@@ -0,0 +1,40 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.Common.GZip;
using SharpCompress.Crypto;
using Xunit;
namespace SharpCompress.Test.Streams;
public class ChecksumReadRegressionTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Zero_Length_Read_Does_Not_Validate(bool gzip)
{
byte[] payload = [1, 2, 3];
var crc = Crc32Stream.Compute(payload);
using var input = new MemoryStream(payload);
using var raw = new MemoryStream();
using Stream stream = gzip
? new GZipChecksumValidationStream(input, raw, "test", crc, 3)
: new ChecksumValidationStream(
input,
new ChecksumDescriptor(ChecksumKind.Crc32, crc, true),
"test"
);
Assert.Equal(0, stream.Read(Array.Empty<byte>(), 0, 0));
Assert.Equal(0, await stream.ReadAsync(Array.Empty<byte>(), 0, 0, CancellationToken.None));
#if !LEGACY_DOTNET
Assert.Equal(0, stream.Read(Span<byte>.Empty));
Assert.Equal(0, await stream.ReadAsync(Memory<byte>.Empty, CancellationToken.None));
#endif
using var output = new MemoryStream();
await stream.CopyToAsync(output, 81920, CancellationToken.None);
Assert.Equal(payload, output.ToArray());
}
}