Skip to content

Commit c3d9ccd

Browse files
edwardnealrzikm
andauthored
Reliable clean-up of SafeHandles in Deflater/Inflater (#114826)
Fixes #89445 This reimplements #71991 in `Deflater` and `Inflater`. The PR was previously reverted in #85001 as a result of issue #84994. As noted in the reverting PR, `Deflater` simply needed to dispose of the returned SafeHandle and supress its own finalizer if `CreateZLibStreamForDeflate` threw an exception. We now do this. We couldn't do this for `Inflater` at the time - it had to support concatenated GZip data, and as part of that support it would recreate its `ZLibStreamHandle`. As a result of #113587, this recreation no longer happens and we can treat `Inflater` as we would treat `Deflater`. I've refactored `Inflater.InflateInit` out of existence and made `_zlibStream` readonly to reflect this. This also incorporates a change to both class' `DeallocateBufferHandle` methods (when called by `Dispose`) to prevent them dereferencing a `ZLibStreamHandle` after it's been disposed of. This is roughly how #84994 was originally discovered. --------- Co-authored-by: Radek Zikmund <[email protected]>
1 parent eaafd7c commit c3d9ccd

File tree

10 files changed

+169
-165
lines changed

10 files changed

+169
-165
lines changed

src/libraries/Common/src/System/IO/Compression/ZLibNative.ZStream.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ internal static partial class ZLibNative
1010
/// <summary>
1111
/// ZLib stream descriptor data structure
1212
/// Do not construct instances of <see cref="ZStream" /> explicitly.
13-
/// Always use <see cref="ZLibNative.CreateZLibStreamForDeflate" /> or <see cref="ZLibNative.CreateZLibStreamForInflate" /> instead.
13+
/// Always use <see cref="ZLibNative.ZLibStreamHandle.CreateForDeflate" /> or <see cref="ZLibNative.ZLibStreamHandle.CreateForInflate" /> instead.
1414
/// Those methods will wrap this structure into a <see cref="ZLibStreamHandle" /> and thus make sure that it is always disposed correctly.
1515
/// </summary>
1616
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]

src/libraries/Common/src/System/IO/Compression/ZLibNative.cs

Lines changed: 93 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
45
using System.Runtime.InteropServices;
56
using System.Runtime.InteropServices.Marshalling;
67
using System.Security;
@@ -173,6 +174,41 @@ public ZLibStreamHandle()
173174
SetHandle(IntPtr.Zero);
174175
}
175176

177+
public static ZLibStreamHandle CreateForDeflate(CompressionLevel level, int windowBits,
178+
int memLevel, CompressionStrategy strategy)
179+
{
180+
ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();
181+
182+
try
183+
{
184+
zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy);
185+
}
186+
catch (Exception)
187+
{
188+
zLibStreamHandle.Dispose();
189+
throw;
190+
}
191+
192+
return zLibStreamHandle;
193+
}
194+
195+
public static ZLibStreamHandle CreateForInflate(int windowBits)
196+
{
197+
ZLibStreamHandle zLibStreamHandle = new ZLibStreamHandle();
198+
199+
try
200+
{
201+
zLibStreamHandle.InflateInit2_(windowBits);
202+
}
203+
catch (Exception)
204+
{
205+
zLibStreamHandle.Dispose();
206+
throw;
207+
}
208+
209+
return zLibStreamHandle;
210+
}
211+
176212
public override bool IsInvalid
177213
{
178214
get { return handle == new IntPtr(-1); }
@@ -228,20 +264,53 @@ private void EnsureState(State requiredState)
228264
throw new InvalidOperationException("InitializationState != " + requiredState.ToString());
229265
}
230266

231-
public unsafe ErrorCode DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
267+
private void EnsureNativeHandleInitialized(ErrorCode zlibErrorCode, string zlibErrorContext)
232268
{
233-
EnsureNotDisposed();
234-
EnsureState(State.NotInitialized);
269+
Debug.Assert(zlibErrorContext is "deflateInit2_" or "inflateInit2_");
235270

236-
fixed (ZStream* stream = &_zStream)
271+
if (zlibErrorCode is not ErrorCode.Ok)
237272
{
238-
ErrorCode errC = Interop.ZLib.DeflateInit2_(stream, level, CompressionMethod.Deflated, windowBits, memLevel, strategy);
239-
_initializationState = State.InitializedForDeflate;
273+
string zlibErrorMessage = GetErrorMessage();
274+
string exceptionMessage = zlibErrorCode switch
275+
{
276+
// Not enough memory
277+
ErrorCode.MemError => SR.ZLibErrorNotEnoughMemory,
240278

241-
return errC;
279+
// zlib library is incompatible with the version assumed
280+
ErrorCode.VersionError => SR.ZLibErrorVersionMismatch,
281+
282+
// Parameters are invalid
283+
ErrorCode.StreamError => SR.ZLibErrorIncorrectInitParameters,
284+
285+
_ => SR.Format(SR.ZLibErrorUnexpected, (int)zlibErrorCode)
286+
};
287+
288+
throw new ZLibException(exceptionMessage, zlibErrorContext, (int)zlibErrorCode, zlibErrorMessage);
242289
}
243290
}
244291

292+
private unsafe void DeflateInit2_(CompressionLevel level, int windowBits, int memLevel, CompressionStrategy strategy)
293+
{
294+
Debug.Assert(InitializationState == State.NotInitialized);
295+
296+
ErrorCode errC;
297+
298+
try
299+
{
300+
fixed (ZStream* stream = &_zStream)
301+
{
302+
errC = Interop.ZLib.DeflateInit2_(stream, level, CompressionMethod.Deflated, windowBits, memLevel, strategy);
303+
}
304+
}
305+
catch (Exception e) // Could not load the ZLib dll
306+
{
307+
throw new ZLibException(SR.ZLibErrorDLLLoadError, e);
308+
}
309+
310+
EnsureNativeHandleInitialized(errC, "deflateInit2_");
311+
_initializationState = State.InitializedForDeflate;
312+
}
313+
245314
public unsafe ErrorCode Deflate(FlushCode flush)
246315
{
247316
EnsureNotDisposed();
@@ -267,18 +336,26 @@ public unsafe ErrorCode DeflateEnd()
267336
}
268337
}
269338

270-
public unsafe ErrorCode InflateInit2_(int windowBits)
339+
private unsafe void InflateInit2_(int windowBits)
271340
{
272-
EnsureNotDisposed();
273-
EnsureState(State.NotInitialized);
341+
Debug.Assert(InitializationState == State.NotInitialized);
274342

275-
fixed (ZStream* stream = &_zStream)
276-
{
277-
ErrorCode errC = Interop.ZLib.InflateInit2_(stream, windowBits);
278-
_initializationState = State.InitializedForInflate;
343+
ErrorCode errC;
279344

280-
return errC;
345+
try
346+
{
347+
fixed (ZStream* stream = &_zStream)
348+
{
349+
errC = Interop.ZLib.InflateInit2_(stream, windowBits);
350+
}
281351
}
352+
catch (Exception e) // Could not load the ZLib dll
353+
{
354+
throw new ZLibException(SR.ZLibErrorDLLLoadError, e);
355+
}
356+
357+
EnsureNativeHandleInitialized(errC, "inflateInit2_");
358+
_initializationState = State.InitializedForInflate;
282359
}
283360

284361
public unsafe ErrorCode InflateReset2_(int windowBits)
@@ -317,21 +394,8 @@ public unsafe ErrorCode InflateEnd()
317394
}
318395
}
319396

320-
// This can work even after XxflateEnd().
397+
// This can work even after XxflateEnd(). Gets the error message from the native library.
321398
public unsafe string GetErrorMessage() => Utf8StringMarshaller.ConvertToManaged(_zStream.msg) ?? string.Empty;
322399
}
323-
324-
public static ErrorCode CreateZLibStreamForDeflate(out ZLibStreamHandle zLibStreamHandle, CompressionLevel level,
325-
int windowBits, int memLevel, CompressionStrategy strategy)
326-
{
327-
zLibStreamHandle = new ZLibStreamHandle();
328-
return zLibStreamHandle.DeflateInit2_(level, windowBits, memLevel, strategy);
329-
}
330-
331-
public static ErrorCode CreateZLibStreamForInflate(out ZLibStreamHandle zLibStreamHandle, int windowBits)
332-
{
333-
zLibStreamHandle = new ZLibStreamHandle();
334-
return zLibStreamHandle.InflateInit2_(windowBits);
335-
}
336400
}
337401
}

src/libraries/System.IO.Compression/src/Resources/Strings.resx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@
168168
<value>The version of the underlying compression routine does not match expected version.</value>
169169
</data>
170170
<data name="ZLibErrorUnexpected" xml:space="preserve">
171-
<value>The underlying compression routine returned an unexpected error code.</value>
171+
<value>The underlying compression routine returned an unexpected error code: '{0}'.</value>
172172
</data>
173173
<data name="CDCorrupt" xml:space="preserve">
174174
<value>Central Directory corrupt.</value>

src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/DeflateStream.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ internal DeflateStream(Stream stream, CompressionMode mode, bool leaveOpen, int
7979
if (!stream.CanRead)
8080
throw new ArgumentException(SR.NotSupported_UnreadableStream, nameof(stream));
8181

82-
_inflater = new Inflater(windowBits, uncompressedSize);
82+
_inflater = Inflater.CreateInflater(windowBits, uncompressedSize);
8383
_stream = stream;
8484
_mode = CompressionMode.Decompress;
8585
_leaveOpen = leaveOpen;
@@ -114,7 +114,7 @@ internal void InitializeDeflater(Stream stream, ZLibNative.CompressionLevel comp
114114
if (!stream.CanWrite)
115115
throw new ArgumentException(SR.NotSupported_UnwritableStream, nameof(stream));
116116

117-
_deflater = new Deflater(compressionLevel, strategy, windowBits, GetMemLevel(compressionLevel));
117+
_deflater = Deflater.CreateDeflater(compressionLevel, strategy, windowBits, GetMemLevel(compressionLevel));
118118

119119
_stream = stream;
120120
_mode = CompressionMode.Compress;

src/libraries/System.IO.Compression/src/System/IO/Compression/DeflateZLib/Deflater.cs

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using System.Buffers;
55
using System.Diagnostics;
6-
using System.Security;
76

87
using ZErrorCode = System.IO.Compression.ZLibNative.ErrorCode;
98
using ZFlushCode = System.IO.Compression.ZLibNative.FlushCode;
@@ -28,37 +27,9 @@ internal sealed class Deflater : IDisposable
2827
// on the stream explicitly.
2928
private object SyncLock => this;
3029

31-
internal Deflater(ZLibNative.CompressionLevel compressionLevel, ZLibNative.CompressionStrategy strategy, int windowBits, int memLevel)
30+
private Deflater(ZLibNative.ZLibStreamHandle zlibStream)
3231
{
33-
Debug.Assert(windowBits >= minWindowBits && windowBits <= maxWindowBits);
34-
35-
ZErrorCode errC;
36-
try
37-
{
38-
errC = ZLibNative.CreateZLibStreamForDeflate(out _zlibStream, compressionLevel, windowBits, memLevel, strategy);
39-
}
40-
catch (Exception cause)
41-
{
42-
throw new ZLibException(SR.ZLibErrorDLLLoadError, cause);
43-
}
44-
45-
switch (errC)
46-
{
47-
case ZErrorCode.Ok:
48-
return;
49-
50-
case ZErrorCode.MemError:
51-
throw new ZLibException(SR.ZLibErrorNotEnoughMemory, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());
52-
53-
case ZErrorCode.VersionError:
54-
throw new ZLibException(SR.ZLibErrorVersionMismatch, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());
55-
56-
case ZErrorCode.StreamError:
57-
throw new ZLibException(SR.ZLibErrorIncorrectInitParameters, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());
58-
59-
default:
60-
throw new ZLibException(SR.ZLibErrorUnexpected, "deflateInit2_", (int)errC, _zlibStream.GetErrorMessage());
61-
}
32+
_zlibStream = zlibStream;
6233
}
6334

6435
~Deflater()
@@ -77,9 +48,12 @@ private void Dispose(bool disposing)
7748
if (!_isDisposed)
7849
{
7950
if (disposing)
51+
{
8052
_zlibStream.Dispose();
53+
}
8154

82-
DeallocateInputBufferHandle();
55+
// Unpin the input buffer, but avoid modifying the ZLibStreamHandle (which may have been disposed of).
56+
DeallocateInputBufferHandle(resetStreamHandle: false);
8357
_isDisposed = true;
8458
}
8559
}
@@ -129,7 +103,7 @@ internal int GetDeflateOutput(byte[] outputBuffer)
129103
// Before returning, make sure to release input buffer if necessary:
130104
if (0 == _zlibStream.AvailIn)
131105
{
132-
DeallocateInputBufferHandle();
106+
DeallocateInputBufferHandle(resetStreamHandle: true);
133107
}
134108
}
135109
}
@@ -179,12 +153,16 @@ internal bool Flush(byte[] outputBuffer, out int bytesRead)
179153
return ReadDeflateOutput(outputBuffer, ZFlushCode.SyncFlush, out bytesRead) == ZErrorCode.Ok;
180154
}
181155

182-
private void DeallocateInputBufferHandle()
156+
private void DeallocateInputBufferHandle(bool resetStreamHandle)
183157
{
184158
lock (SyncLock)
185159
{
186-
_zlibStream.AvailIn = 0;
187-
_zlibStream.NextIn = ZLibNative.ZNullPtr;
160+
if (resetStreamHandle)
161+
{
162+
_zlibStream.AvailIn = 0;
163+
_zlibStream.NextIn = ZLibNative.ZNullPtr;
164+
}
165+
188166
_inputBufferHandle.Dispose();
189167
}
190168
}
@@ -217,5 +195,14 @@ private ZErrorCode Deflate(ZFlushCode flushCode)
217195
throw new ZLibException(SR.ZLibErrorUnexpected, "deflate", (int)errC, _zlibStream.GetErrorMessage());
218196
}
219197
}
198+
199+
public static Deflater CreateDeflater(ZLibNative.CompressionLevel compressionLevel, ZLibNative.CompressionStrategy strategy, int windowBits, int memLevel)
200+
{
201+
Debug.Assert(windowBits >= minWindowBits && windowBits <= maxWindowBits);
202+
203+
ZLibNative.ZLibStreamHandle zlibStream = ZLibNative.ZLibStreamHandle.CreateForDeflate(compressionLevel, windowBits, memLevel, strategy);
204+
205+
return new Deflater(zlibStream);
206+
}
220207
}
221208
}

0 commit comments

Comments
 (0)