From ff4d80fd38a67092e955d8f5b4e8d755ad483e6b Mon Sep 17 00:00:00 2001 From: Abhikr Date: Fri, 16 Oct 2026 13:28:13 +0530 Subject: [PATCH] Validate ZstandardStream constructor arguments and prevent ArrayPool buffer leak on failure - Validate compressionOptions with ArgumentNullException.ThrowIfNull before calling Init in ZstandardStream(Stream, ZstandardCompressionOptions, bool). - Validate compressionLevel before calling Init in ZstandardStream(Stream, CompressionLevel, bool) to avoid renting a buffer when GetQualityFromCompressionLevel throws ArgumentOutOfRangeException. - Dispose _buffer in try-catch blocks if encoder or decoder instantiation fails after Init. - Add unit tests for constructor argument validation. --- .../Zstandard/ZstandardStream.Compress.cs | 26 +++++++++++++-- .../Zstandard/ZstandardStream.Decompress.cs | 10 +++++- .../Compression/Zstandard/ZstandardStream.cs | 32 ++++++++++++++----- .../CompressionStreamUnitTests.Zstandard.cs | 15 +++++++++ 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Compress.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Compress.cs index f2caa372ccd2bb..bad4293f37b74a 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Compress.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Compress.cs @@ -29,26 +29,46 @@ public ZstandardStream(Stream stream, CompressionLevel compressionLevel) : this( /// is not a valid value. public ZstandardStream(Stream stream, CompressionLevel compressionLevel, bool leaveOpen) { + int quality = ZstandardUtils.GetQualityFromCompressionLevel(compressionLevel); + Init(stream, CompressionMode.Compress); _mode = CompressionMode.Compress; _leaveOpen = leaveOpen; - _encoder = new ZstandardEncoder(ZstandardUtils.GetQualityFromCompressionLevel(compressionLevel)); + try + { + _encoder = new ZstandardEncoder(quality); + } + catch + { + _buffer.Dispose(); + throw; + } } /// Initializes a new instance of the class by using the specified stream, options, and optionally leaves the stream open. /// The stream to which compressed data is written or from which data to decompress is read. /// The options to use for Zstandard compression or decompression. /// to leave the stream open after the object is disposed; otherwise, . - /// is . + /// or is . /// does not support writing. public ZstandardStream(Stream stream, ZstandardCompressionOptions compressionOptions, bool leaveOpen = false) { + ArgumentNullException.ThrowIfNull(compressionOptions); + Init(stream, CompressionMode.Compress); _mode = CompressionMode.Compress; _leaveOpen = leaveOpen; - _encoder = new ZstandardEncoder(compressionOptions); + try + { + _encoder = new ZstandardEncoder(compressionOptions); + } + catch + { + _buffer.Dispose(); + throw; + } } /// Initializes a new instance of the class by using the specified stream and encoder instance. diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs index a963a083e0a010..6d9adaac02d380 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.Decompress.cs @@ -50,7 +50,15 @@ public ZstandardStream(Stream stream, ZstandardDecompressionOptions decompressio _mode = CompressionMode.Decompress; _leaveOpen = leaveOpen; - _decoder = new ZstandardDecoder(decompressionOptions); + try + { + _decoder = new ZstandardDecoder(decompressionOptions); + } + catch + { + _buffer.Dispose(); + throw; + } } // Decompresses buffered input for the current frame only. Returns true when there is a result for diff --git a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.cs b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.cs index 051ed0de891e50..dea54d729d3224 100644 --- a/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.cs +++ b/src/libraries/System.IO.Compression/src/System/IO/Compression/Zstandard/ZstandardStream.cs @@ -66,13 +66,21 @@ public ZstandardStream(Stream stream, CompressionMode mode, bool leaveOpen) _leaveOpen = leaveOpen; _mode = mode; - if (mode == CompressionMode.Compress) + try { - _encoder = new ZstandardEncoder(); + if (mode == CompressionMode.Compress) + { + _encoder = new ZstandardEncoder(); + } + else + { + _decoder = new ZstandardDecoder(); + } } - else + catch { - _decoder = new ZstandardDecoder(); + _buffer.Dispose(); + throw; } } @@ -98,13 +106,21 @@ public ZstandardStream(Stream stream, CompressionMode mode, ZstandardDictionary _mode = mode; _leaveOpen = leaveOpen; - if (mode == CompressionMode.Compress) + try { - _encoder = new ZstandardEncoder(dictionary); + if (mode == CompressionMode.Compress) + { + _encoder = new ZstandardEncoder(dictionary); + } + else + { + _decoder = new ZstandardDecoder(dictionary); + } } - else + catch { - _decoder = new ZstandardDecoder(dictionary); + _buffer.Dispose(); + throw; } } diff --git a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs index 109574db7ef22c..53ab6b582e592c 100644 --- a/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs +++ b/src/libraries/System.IO.Compression/tests/Zstandard/CompressionStreamUnitTests.Zstandard.cs @@ -310,6 +310,21 @@ public void ZstandardStream_WithDecompressionOptions_NullOptions_ThrowsArgumentN Assert.Throws("decompressionOptions", () => new ZstandardStream(input, (ZstandardDecompressionOptions)null!)); } + [Fact] + public void ZstandardStream_WithCompressionOptions_NullOptions_ThrowsArgumentNullException() + { + using MemoryStream output = new(); + Assert.Throws("compressionOptions", () => new ZstandardStream(output, (ZstandardCompressionOptions)null!)); + } + + [Fact] + public void ZstandardStream_InvalidCompressionLevel_ThrowsArgumentOutOfRangeException() + { + using MemoryStream output = new(); + Assert.Throws("compressionLevel", () => new ZstandardStream(output, (CompressionLevel)999)); + Assert.Throws("compressionLevel", () => new ZstandardStream(output, (CompressionLevel)999, leaveOpen: true)); + } + [Theory] [InlineData(false)] [InlineData(true)]