From 80bcd775401168de87a42dad300c3f5b932e386d Mon Sep 17 00:00:00 2001 From: wfurt Date: Mon, 22 Jan 2024 13:14:14 -0800 Subject: [PATCH 1/3] avoid some allocations during TLS handshake --- .../Interop/Windows/SspiCli/ISSPIInterface.cs | 1 + .../Interop/Windows/SspiCli/SSPIAuthType.cs | 5 ++ .../Windows/SspiCli/SSPISecureChannelType.cs | 5 ++ .../Interop/Windows/SspiCli/SSPIWrapper.cs | 49 ++++++++++++------- .../Windows/SspiCli/SecuritySafeHandles.cs | 32 ++++++++---- .../Net/CertificateValidationPal.Windows.cs | 5 +- .../Net/Security/SslStreamPal.Windows.cs | 2 + 7 files changed, 69 insertions(+), 30 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs index ccaca072c6bf8b..f504e175cfdc78 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/ISSPIInterface.cs @@ -22,6 +22,7 @@ internal interface ISSPIInterface int QueryContextChannelBinding(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeContextBufferChannelBinding refHandle); int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, Span buffer, Type? handleType, out SafeHandle? refHandle); + unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, IntPtr* refHandle); int QuerySecurityContextToken(SafeDeleteContext phContext, out SecurityContextTokenHandle phToken); int CompleteAuthToken(ref SafeDeleteSslContext? refContext, in InputSecurityBuffer inputBuffer); int ApplyControlToken(ref SafeDeleteSslContext? refContext, in SecurityBuffer inputBuffer); diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs index 283392fde56100..88c01ca16242af 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs @@ -101,6 +101,11 @@ public int QueryContextChannelBinding(SafeDeleteContext context, Interop.SspiCli throw new NotSupportedException(); } + public unsafe int QueryContextAttributes(SafeDeleteContext context, Interop.SspiCli.ContextAttribute attribute, IntPtr* refHandle) + { + return SafeFreeContextBuffer.QueryContextAttributes(context, attribute, refHandle); + } + public unsafe int QueryContextAttributes(SafeDeleteContext context, Interop.SspiCli.ContextAttribute attribute, Span buffer, Type? handleType, out SafeHandle? refHandle) { refHandle = null; diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs index e8103da46e3d19..8b5bbb6bf52847 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPISecureChannelType.cs @@ -102,6 +102,11 @@ public unsafe int QueryContextChannelBinding(SafeDeleteContext phContext, Intero return SafeFreeContextBufferChannelBinding.QueryContextChannelBinding(phContext, attribute, &bindings, refHandle); } + public unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, IntPtr* refHandle) + { + return SafeFreeContextBuffer.QueryContextAttributes(phContext, attribute, refHandle); + } + public unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute attribute, Span buffer, Type? handleType, out SafeHandle? refHandle) { refHandle = null; diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs index 014e357bc23eaf..b41a8b3ce20eac 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIWrapper.cs @@ -270,29 +270,42 @@ public static bool QueryBlittableContextAttributes(ISSPIInterface secModule, } } - private static bool QueryCertContextAttribute(ISSPIInterface secModule, SafeDeleteContext securityContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeCertContext? certContext) + private static unsafe bool QueryCertContextAttribute(ISSPIInterface secModule, SafeDeleteContext securityContext, Interop.SspiCli.ContextAttribute attribute, out SafeFreeCertContext? certContext) { - Span buffer = stackalloc IntPtr[1]; - int errorCode = secModule.QueryContextAttributes( - securityContext, - attribute, - MemoryMarshal.AsBytes(buffer), - typeof(SafeFreeCertContext), - out SafeHandle? sspiHandle); + IntPtr handle = IntPtr.Zero; + certContext = null; - // certificate is not always present (e.g. on server when querying client certificate) - // but we still want to consider such case as a success. - bool success = errorCode == 0 || errorCode == (int)Interop.SECURITY_STATUS.NoCredentials; + try + { + int errorCode = secModule.QueryContextAttributes( + securityContext, + attribute, + &handle); + + // certificate is not always present (e.g. on server when querying client certificate) + // but we still want to consider such case as a success. + bool success = errorCode == 0 || errorCode == (int)Interop.SECURITY_STATUS.NoCredentials; - if (!success) + if (errorCode == 0 && handle != IntPtr.Zero) + { + certContext = new SafeFreeCertContext(); + certContext.Set(handle); + // Handle was successfully transferred to SafeHandle + handle = IntPtr.Zero; + } + if (!success) + { + if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, $"ERROR = {ErrorDescription(errorCode)}"); + } + return success; + } + finally { - sspiHandle?.Dispose(); - sspiHandle = null; - if (NetEventSource.Log.IsEnabled()) NetEventSource.Error(null, $"ERROR = {ErrorDescription(errorCode)}"); + if (handle != IntPtr.Zero) + { + Interop.Crypt32.CertFreeCertificateContext(handle); + } } - - certContext = sspiHandle as SafeFreeCertContext; - return success; } public static bool QueryContextAttributes_SECPKG_ATTR_REMOTE_CERT_CONTEXT(ISSPIInterface secModule, SafeDeleteContext securityContext, out SafeFreeCertContext? certContext) diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index d7e425bda630c6..7254d780b832f5 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -65,14 +65,28 @@ internal static SafeFreeContextBuffer CreateEmptyHandle() return new SafeFreeContextBuffer_SECURITY(); } - // - // After PInvoke call the method will fix the refHandle.handle with the returned value. - // The caller is responsible for creating a correct SafeHandle template or null can be passed if no handle is returned. - // - // This method switches between three non-interruptible helper methods. (This method can't be both non-interruptible and - // reference imports from all three DLLs - doing so would cause all three DLLs to try to be bound to.) - // - public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, byte* buffer, SafeHandle? refHandle) + public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, IntPtr* handle) + { + try + { + bool ignore = false; + phContext.DangerousAddRef(ref ignore); + return Interop.SspiCli.QueryContextAttributesW(ref phContext._handle, contextAttribute, handle); + } + finally + { + phContext.DangerousRelease(); + } + } + + // + // After PInvoke call the method will fix the refHandle.handle with the returned value. + // The caller is responsible for creating a correct SafeHandle template or null can be passed if no handle is returned. + // + // This method switches between three non-interruptible helper methods. (This method can't be both non-interruptible and + // reference imports from all three DLLs - doing so would cause all three DLLs to try to be bound to.) + // + public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, byte* buffer, SafeHandle? refHandle) { int status = (int)Interop.SECURITY_STATUS.InvalidHandle; @@ -95,7 +109,7 @@ public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Int } else { - ((SafeFreeCertContext)refHandle).Set(*(IntPtr*)buffer); + Debug.Assert(false); } } diff --git a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs index a3254540047f37..27224b4e4be046 100644 --- a/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/CertificateValidationPal.Windows.cs @@ -111,10 +111,9 @@ internal static bool IsLocalCertificateUsed(SafeFreeCredentials? _credentialsHan SafeFreeCertContext? localContext = null; try { - if (SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out localContext) && - localContext != null) + if (SSPIWrapper.QueryContextAttributes_SECPKG_ATTR_LOCAL_CERT_CONTEXT(GlobalSSPI.SSPISecureChannel, securityContext, out localContext)) { - return !localContext.IsInvalid; + return localContext != null ? !localContext.IsInvalid : false; } } finally diff --git a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs index a9fe7f0a4e741d..b04a0571f0a53d 100644 --- a/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs +++ b/src/libraries/System.Net.Security/src/System/Net/Security/SslStreamPal.Windows.cs @@ -119,6 +119,7 @@ public static ProtocolToken AcceptSecurityContext( } ProtocolToken token = default; + token.RentBuffer = true; int errorCode = SSPIWrapper.AcceptSecurityContext( GlobalSSPI.SSPISecureChannel, @@ -163,6 +164,7 @@ public static ProtocolToken InitializeSecurityContext( } ProtocolToken token = default; + token.RentBuffer = true; int errorCode = SSPIWrapper.InitializeSecurityContext( GlobalSSPI.SSPISecureChannel, ref credentialsHandle, From 4eee7a63e5e67c1e33ebcb3eecbb2369c86828db Mon Sep 17 00:00:00 2001 From: wfurt Date: Mon, 22 Jan 2024 13:38:39 -0800 Subject: [PATCH 2/3] SafeFreeCertContext --- .../Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs index 88c01ca16242af..0235437d5d7962 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SSPIAuthType.cs @@ -115,10 +115,6 @@ public unsafe int QueryContextAttributes(SafeDeleteContext context, Interop.Sspi { refHandle = SafeFreeContextBuffer.CreateEmptyHandle(); } - else if (handleType == typeof(SafeFreeCertContext)) - { - refHandle = new SafeFreeCertContext(); - } else { throw new ArgumentException(SR.Format(SR.SSPIInvalidHandleType, handleType.FullName), nameof(handleType)); From bdab12fca5820722be3dce4480acc3e1d363f12c Mon Sep 17 00:00:00 2001 From: wfurt Date: Thu, 8 Feb 2024 14:10:54 -0800 Subject: [PATCH 3/3] feedback --- .../Windows/SspiCli/SecuritySafeHandles.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs index f630faf811e44c..589bc56352bb7f 100644 --- a/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs +++ b/src/libraries/Common/src/Interop/Windows/SspiCli/SecuritySafeHandles.cs @@ -67,26 +67,29 @@ internal static SafeFreeContextBuffer CreateEmptyHandle() public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, IntPtr* handle) { + bool mustRelease = false; try { - bool ignore = false; - phContext.DangerousAddRef(ref ignore); + phContext.DangerousAddRef(ref mustRelease); return Interop.SspiCli.QueryContextAttributesW(ref phContext._handle, contextAttribute, handle); } finally { - phContext.DangerousRelease(); + if (mustRelease) + { + phContext.DangerousRelease(); + } } } - // - // After PInvoke call the method will fix the refHandle.handle with the returned value. - // The caller is responsible for creating a correct SafeHandle template or null can be passed if no handle is returned. - // - // This method switches between three non-interruptible helper methods. (This method can't be both non-interruptible and - // reference imports from all three DLLs - doing so would cause all three DLLs to try to be bound to.) - // - public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, byte* buffer, SafeHandle? refHandle) + // + // After PInvoke call the method will fix the refHandle.handle with the returned value. + // The caller is responsible for creating a correct SafeHandle template or null can be passed if no handle is returned. + // + // This method switches between three non-interruptible helper methods. (This method can't be both non-interruptible and + // reference imports from all three DLLs - doing so would cause all three DLLs to try to be bound to.) + // + public static unsafe int QueryContextAttributes(SafeDeleteContext phContext, Interop.SspiCli.ContextAttribute contextAttribute, byte* buffer, SafeHandle? refHandle) { int status = (int)Interop.SECURITY_STATUS.InvalidHandle;