Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit 232d704

Browse files
committed
Remove unnecessary SafeHandle allocations in X509Certificates library
Several related issues: - The custom SafeHandle types expose an InvalidHandle property. As a property, one typically expects that each call to the property returns the same instance, but each call to these properties is manufacturing a new SafeHandle instance. - Lots of the accesses to InvalidHandle are to get an initially "null" handle to pass to a native function that expects a null handle on the first invocation. This means that we're allocating a finalizable object only to then throw it away. - In some cases, we were completely ignoring the allocated handle, e.g. initializing a handle to InvalidHandle and then immediately using that variable as an out argument. - etc. This commit addresses these issues, such that the number of allocated handles noticeably decreases. As an example, during the execution of the System.Security.Cryptography.X509Certificates.Tests as they currently exist, prior to this change 1661 SafePointerHandles were being allocated; after this change, that number drops to 1410, for a 15% reduction.
1 parent db3cf03 commit 232d704

File tree

12 files changed

+107
-50
lines changed

12 files changed

+107
-50
lines changed
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright (c) Microsoft. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Runtime.InteropServices;
7+
using System.Threading;
8+
9+
namespace Microsoft.Win32.SafeHandles
10+
{
11+
/// <summary>Provides a cache for special instances of SafeHandles.</summary>
12+
/// <typeparam name="T">Specifies the type of SafeHandle.</typeparam>
13+
internal static class SafeHandleCache<T> where T : SafeHandle
14+
{
15+
private static T s_invalidHandle;
16+
17+
/// <summary>
18+
/// Gets a cached, invalid handle. As the instance is cached, it should either never be Disposed
19+
/// or it should override <see cref="SafeHandle.Dispose(bool)"/> to prevent disposal when the
20+
/// instance is the <see cref="InvalidHandle"/>.
21+
/// </summary>
22+
internal static T GetInvalidHandle(Func<T> invalidHandleFactory)
23+
{
24+
T currentHandle = Volatile.Read(ref s_invalidHandle);
25+
if (currentHandle == null)
26+
{
27+
T newHandle = invalidHandleFactory();
28+
currentHandle = Interlocked.CompareExchange(ref s_invalidHandle, newHandle, null);
29+
if (currentHandle == null)
30+
{
31+
GC.SuppressFinalize(newHandle);
32+
currentHandle = newHandle;
33+
}
34+
else
35+
{
36+
newHandle.Dispose();
37+
}
38+
}
39+
Debug.Assert(currentHandle.IsInvalid);
40+
return currentHandle;
41+
}
42+
43+
/// <summary>Gets whether the specified handle is <see cref="InvalidHandle"/>.</summary>
44+
/// <param name="handle">The handle to compare.</param>
45+
/// <returns>true if <paramref name="handle"/> is <see cref="InvalidHandle"/>; otherwise, false.</returns>
46+
internal static bool IsCachedInvalidHandle(SafeHandle handle)
47+
{
48+
Debug.Assert(handle != null);
49+
bool isCachedInvalidHandle = ReferenceEquals(handle, Volatile.Read(ref s_invalidHandle));
50+
Debug.Assert(!isCachedInvalidHandle || handle.IsInvalid, "The cached invalid handle must still be invalid.");
51+
return isCachedInvalidHandle;
52+
}
53+
}
54+
}

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/CertificatePal.Import.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ private static SafeCertContextHandle GetSignerInPKCS7Store(SafeCertStoreHandle h
137137
certInfo.SerialNumber.pbData = pCmsgSignerInfo->SerialNumber.pbData;
138138
}
139139

140-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
140+
SafeCertContextHandle pCertContext = null;
141141
if (!Interop.crypt32.CertFindCertificateInStore(hCertStore, CertFindType.CERT_FIND_SUBJECT_CERT, &certInfo, ref pCertContext))
142142
throw new CryptographicException(Marshal.GetHRForLastWin32Error());
143143

@@ -147,7 +147,7 @@ private static SafeCertContextHandle GetSignerInPKCS7Store(SafeCertStoreHandle h
147147

148148
private static SafeCertContextHandle FilterPFXStore(byte[] rawData, String password, PfxCertStoreFlags pfxCertStoreFlags)
149149
{
150-
SafeCertStoreHandle hStore = SafeCertStoreHandle.InvalidHandle;
150+
SafeCertStoreHandle hStore;
151151
unsafe
152152
{
153153
fixed (byte* pbRawData = rawData)
@@ -164,7 +164,7 @@ private static SafeCertContextHandle FilterPFXStore(byte[] rawData, String passw
164164
// Find the first cert with private key. If none, then simply take the very first cert. Along the way, delete the keycontainers
165165
// of any cert we don't accept.
166166
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
167-
SafeCertContextHandle pEnumContext = SafeCertContextHandle.InvalidHandle;
167+
SafeCertContextHandle pEnumContext = null;
168168
while (Interop.crypt32.CertEnumCertificatesInStore(hStore, ref pEnumContext))
169169
{
170170
if (pEnumContext.ContainsPrivateKey)

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Helpers.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,10 @@ internal static class Helpers
2323
/// </summary>
2424
public static SafeHandle ToLpstrArray(this OidCollection oids, out int numOids)
2525
{
26-
SafeLocalAllocHandle safeLocalAllocHandle = SafeLocalAllocHandle.InvalidHandle;
2726
if (oids == null || oids.Count == 0)
2827
{
2928
numOids = 0;
30-
return safeLocalAllocHandle;
29+
return SafeLocalAllocHandle.InvalidHandle;
3130
}
3231

3332
// Copy the oid strings to a local list to prevent a security race condition where
@@ -52,7 +51,7 @@ public static SafeHandle ToLpstrArray(this OidCollection oids, out int numOids)
5251
}
5352
}
5453

55-
safeLocalAllocHandle = SafeLocalAllocHandle.Create(allocationSize);
54+
SafeLocalAllocHandle safeLocalAllocHandle = SafeLocalAllocHandle.Create(allocationSize);
5655
byte** pOidPointers = (byte**)(safeLocalAllocHandle.DangerousGetHandle());
5756
byte* pOidContents = (byte*)(pOidPointers + numOids);
5857

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/Interop.crypt32.cs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ public static SafeCertStoreHandle CertOpenStore(CertStoreProvider lpszStoreProvi
104104
/// <summary>
105105
/// A less error-prone wrapper for CertEnumCertificatesInStore().
106106
///
107-
/// To begin the enumeration, set pCertContext to SafeCertStoreHandle.InvalidHandle. Each iteration replaces pCertContext with
108-
/// the next certificate in the iteration. The final call sets pCertContext back to SafeCertStoreHandle.InvalidHandle and returns "false"
109-
/// to indicate the the end of the store has been reached.
107+
/// To begin the enumeration, set pCertContext to null. Each iteration replaces pCertContext with
108+
/// the next certificate in the iteration. The final call sets pCertContext to an invalid SafeCertStoreHandle
109+
/// and returns "false" to indicate the the end of the store has been reached.
110110
/// </summary>
111111
public static bool CertEnumCertificatesInStore(SafeCertStoreHandle hCertStore, ref SafeCertContextHandle pCertContext)
112112
{
113113
unsafe
114114
{
115-
CERT_CONTEXT* pPrevCertContext = pCertContext.Disconnect();
115+
CERT_CONTEXT* pPrevCertContext = pCertContext == null ? null : pCertContext.Disconnect();
116116
pCertContext = CertEnumCertificatesInStore(hCertStore, pPrevCertContext);
117117
return !pCertContext.IsInvalid;
118118
}
@@ -226,13 +226,13 @@ public static unsafe bool CertGetCertificateChain(ChainEngine hChainEngine, Safe
226226
/// <summary>
227227
/// A less error-prone wrapper for CertEnumCertificatesInStore().
228228
///
229-
/// To begin the enumeration, set pCertContext to SafeCertStoreHandle.InvalidHandle. Each iteration replaces pCertContext with
230-
/// the next certificate in the iteration. The final call sets pCertContext back to SafeCertStoreHandle.InvalidHandle and returns "false"
231-
/// to indicate the the end of the store has been reached.
229+
/// To begin the enumeration, set pCertContext to null. Each iteration replaces pCertContext with
230+
/// the next certificate in the iteration. The final call sets pCertContext to an invalid SafeCertStoreHandle
231+
/// and returns "false" to indicate the the end of the store has been reached.
232232
/// </summary>
233233
public static unsafe bool CertFindCertificateInStore(SafeCertStoreHandle hCertStore, CertFindType dwFindType, void* pvFindPara, ref SafeCertContextHandle pCertContext)
234234
{
235-
CERT_CONTEXT* pPrevCertContext = pCertContext.Disconnect();
235+
CERT_CONTEXT* pPrevCertContext = pCertContext == null ? null : pCertContext.Disconnect();
236236
pCertContext = CertFindCertificateInStore(hCertStore, CertEncodingType.All, CertFindFlags.None, dwFindType, pvFindPara, pPrevCertContext);
237237
return !pCertContext.IsInvalid;
238238
}

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/Native/SafeHandles.cs

Lines changed: 15 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using Microsoft.Win32.SafeHandles;
45
using System;
56
using System.Diagnostics;
67
using System.Runtime.InteropServices;
@@ -12,22 +13,27 @@ namespace Internal.Cryptography.Pal.Native
1213
/// </summary>
1314
internal abstract class SafePointerHandle<T> : SafeHandle where T : SafeHandle, new()
1415
{
15-
public SafePointerHandle()
16+
protected SafePointerHandle()
1617
: base(IntPtr.Zero, true)
1718
{
1819
}
1920

2021
public sealed override bool IsInvalid
2122
{
22-
get
23-
{
24-
return handle == IntPtr.Zero;
25-
}
23+
get { return handle == IntPtr.Zero; }
2624
}
2725

2826
public static T InvalidHandle
2927
{
30-
get { return new T(); }
28+
get { return SafeHandleCache<T>.GetInvalidHandle(() => new T()); }
29+
}
30+
31+
protected override void Dispose(bool disposing)
32+
{
33+
if (!SafeHandleCache<T>.IsCachedInvalidHandle(this))
34+
{
35+
base.Dispose(disposing);
36+
}
3137
}
3238
}
3339

@@ -146,28 +152,17 @@ protected sealed override bool ReleaseHandle()
146152
/// </summary>
147153
internal sealed class SafeLocalAllocHandle : SafePointerHandle<SafeLocalAllocHandle>
148154
{
149-
public SafeLocalAllocHandle()
150-
: base()
151-
{
152-
}
153-
154155
public static SafeLocalAllocHandle Create(int cb)
155156
{
156-
return new SafeLocalAllocHandle(Marshal.AllocHGlobal(cb));
157+
var h = new SafeLocalAllocHandle();
158+
h.SetHandle(Marshal.AllocHGlobal(cb));
159+
return h;
157160
}
158161

159162
protected sealed override bool ReleaseHandle()
160163
{
161164
Marshal.FreeHGlobal(handle);
162165
return true;
163166
}
164-
165-
private SafeLocalAllocHandle(IntPtr p)
166-
: base()
167-
{
168-
handle = p;
169-
}
170167
}
171168
}
172-
173-

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/StorePal.Export.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public byte[] Export(X509ContentType contentType, String password)
2727
{
2828
case X509ContentType.Cert:
2929
{
30-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
30+
SafeCertContextHandle pCertContext = null;
3131
if (!Interop.crypt32.CertEnumCertificatesInStore(_certStore, ref pCertContext))
3232
return null;
3333
try
@@ -48,7 +48,7 @@ public byte[] Export(X509ContentType contentType, String password)
4848

4949
case X509ContentType.SerializedCert:
5050
{
51-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
51+
SafeCertContextHandle pCertContext = null;
5252
if (!Interop.crypt32.CertEnumCertificatesInStore(_certStore, ref pCertContext))
5353
return null;
5454

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/StorePal.Find.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ private unsafe StorePal FindCore(CertFindType dwFindType, void* pvFindPara, bool
478478
if (findResults.IsInvalid)
479479
throw new CryptographicException(Marshal.GetHRForLastWin32Error());
480480

481-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
481+
SafeCertContextHandle pCertContext = null;
482482
while (Interop.crypt32.CertFindCertificateInStore(_certStore, dwFindType, pvFindPara, ref pCertContext))
483483
{
484484
if (filter != null && !filter(pCertContext))

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/StorePal.Import.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ private static StorePal FromBlobOrFile(byte[] rawData, String fileName, String p
4848
void* pvObject = fromFile ? (void*)pFileName : (void*)&blob;
4949

5050
ContentType contentType;
51-
SafeCertStoreHandle certStore = SafeCertStoreHandle.InvalidHandle;
51+
SafeCertStoreHandle certStore;
5252
if (!Interop.crypt32.CryptQueryObject(
5353
fromFile ? CertQueryObjectType.CERT_QUERY_OBJECT_FILE : CertQueryObjectType.CERT_QUERY_OBJECT_BLOB,
5454
pvObject,
@@ -86,7 +86,7 @@ private static StorePal FromBlobOrFile(byte[] rawData, String fileName, String p
8686

8787
if (!persistKeySet)
8888
{
89-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
89+
SafeCertContextHandle pCertContext = null;
9090
while (Interop.crypt32.CertEnumCertificatesInStore(certStore, ref pCertContext))
9191
{
9292
CRYPTOAPI_BLOB nullBlob = new CRYPTOAPI_BLOB(0, null);

src/System.Security.Cryptography.X509Certificates/src/Internal/Cryptography/Pal.Windows/StorePal.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public IEnumerable<X509Certificate2> Certificates
2727
{
2828
LowLevelListWithIList<X509Certificate2> certificates = new LowLevelListWithIList<X509Certificate2>();
2929

30-
SafeCertContextHandle pCertContext = SafeCertContextHandle.InvalidHandle;
30+
SafeCertContextHandle pCertContext = null;
3131
while (Interop.crypt32.CertEnumCertificatesInStore(_certStore, ref pCertContext))
3232
{
3333
X509Certificate2 cert = new X509Certificate2(pCertContext.DangerousGetHandle());
@@ -49,7 +49,7 @@ public void Remove(ICertificatePal certificate)
4949
unsafe
5050
{
5151
SafeCertContextHandle existingCertContext = ((CertificatePal)certificate).CertContext;
52-
SafeCertContextHandle enumCertContext = SafeCertContextHandle.InvalidHandle;
52+
SafeCertContextHandle enumCertContext = null;
5353
CERT_CONTEXT* pCertContext = existingCertContext.CertContext;
5454
if (!Interop.crypt32.CertFindCertificateInStore(_certStore, CertFindType.CERT_FIND_EXISTING, pCertContext, ref enumCertContext))
5555
return; // The certificate is not present in the store, simply return.

src/System.Security.Cryptography.X509Certificates/src/Microsoft/Win32/SafeHandles/SafeX509ChainHandle.cs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,11 @@
11
// Copyright (c) Microsoft. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4+
using Internal.Cryptography.Pal;
45
using System;
5-
using System.IO;
6-
using System.Text;
76
using System.Diagnostics;
8-
using System.Globalization;
97
using System.Runtime.InteropServices;
108

11-
using Internal.Cryptography;
12-
using Internal.Cryptography.Pal;
13-
149
namespace Microsoft.Win32.SafeHandles
1510
{
1611
public sealed class SafeX509ChainHandle : SafeHandle
@@ -25,15 +20,22 @@ public override bool IsInvalid
2520
get { return handle == IntPtr.Zero; }
2621
}
2722

23+
public static SafeX509ChainHandle InvalidHandle
24+
{
25+
get { return SafeHandleCache<SafeX509ChainHandle>.GetInvalidHandle(() => new SafeX509ChainHandle()); }
26+
}
27+
2828
protected override bool ReleaseHandle()
2929
{
3030
return ChainPal.ReleaseSafeX509ChainHandle(handle);
3131
}
3232

33-
internal static SafeX509ChainHandle InvalidHandle
33+
protected override void Dispose(bool disposing)
3434
{
35-
get { return new SafeX509ChainHandle(); }
35+
if (!SafeHandleCache<SafeX509ChainHandle>.IsCachedInvalidHandle(this))
36+
{
37+
base.Dispose(disposing);
38+
}
3639
}
3740
}
3841
}
39-

0 commit comments

Comments
 (0)