From 6b9142f7c6f4cb29993523326e982f322f70b4e2 Mon Sep 17 00:00:00 2001 From: Radek Zikmund Date: Mon, 26 Feb 2024 15:10:05 +0100 Subject: [PATCH] Version check to work around https://github.com/microsoft/msquic/issues/4132 --- .../src/System/Net/Quic/Internal/MsQuicApi.cs | 14 +++++++++---- .../QuicConnection.SslConnectionOptions.cs | 21 +++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs index e89119844c744..4b284284f5262 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicApi.cs @@ -54,11 +54,16 @@ private MsQuicApi(QUIC_API_TABLE* apiTable) private static readonly Lazy _api = new Lazy(AllocateMsQuicApi); internal static MsQuicApi Api => _api.Value; + internal static Version? Version { get; private set; } + internal static bool IsQuicSupported { get; } internal static string MsQuicLibraryVersion { get; } = "unknown"; internal static string? NotSupportedReason { get; } + // workaround for https://github.com/microsoft/msquic/issues/4132 + internal static bool SupportsAsyncCertValidation => Version >= new Version(2, 4, 0); + internal static bool UsesSChannelBackend { get; } internal static bool Tls13ServerMayBeDisabled { get; } @@ -69,6 +74,7 @@ static MsQuicApi() { bool loaded = false; IntPtr msQuicHandle; + Version = default; // MsQuic is using DualMode sockets and that will fail even for IPv4 if AF_INET6 is not available. if (!Socket.OSSupportsIPv6) @@ -135,7 +141,7 @@ static MsQuicApi() } return; } - Version version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]); + Version = new Version((int)libVersion[0], (int)libVersion[1], (int)libVersion[2], (int)libVersion[3]); paramSize = 64 * sizeof(sbyte); sbyte* libGitHash = stackalloc sbyte[64]; @@ -150,11 +156,11 @@ static MsQuicApi() } string? gitHash = Marshal.PtrToStringUTF8((IntPtr)libGitHash); - MsQuicLibraryVersion = $"{Interop.Libraries.MsQuic} {version} ({gitHash})"; + MsQuicLibraryVersion = $"{Interop.Libraries.MsQuic} {Version} ({gitHash})"; - if (version < s_minMsQuicVersion) + if (Version < s_minMsQuicVersion) { - NotSupportedReason = $"Incompatible MsQuic library version '{version}', expecting higher than '{s_minMsQuicVersion}'."; + NotSupportedReason = $"Incompatible MsQuic library version '{Version}', expecting higher than '{s_minMsQuicVersion}'."; if (NetEventSource.Log.IsEnabled()) { NetEventSource.Info(null, NotSupportedReason); diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs index 00baa96daa4a3..063b8bb9f2ebf 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.SslConnectionOptions.cs @@ -110,11 +110,24 @@ internal unsafe void StartAsyncCertificateValidation(void* certificatePtr, void* } } - // Hand-off rest of the work to the threadpool, certificatePtr and chainPtr are invalid inside the lambda. - QuicConnection thisConnection = _connection; // cannot use "this" inside lambda since SslConnectionOptions is struct - _ = Task.Run(() => + QuicConnection connection = _connection; + if (MsQuicApi.SupportsAsyncCertValidation) { + // hand-off rest of the work to the thread pool, certificatePtr and chainPtr are invalid beyond this point + _ = Task.Run(() => + { + StartAsyncCertificateValidationCore(connection, certificate, certData, chainData, certDataRented, chainDataRented); + }); + } + else + { + // due to a bug in MsQuic, we need to call the callback synchronously to close the connection properly when + // we reject the certificate + StartAsyncCertificateValidationCore(connection, certificate, certData, chainData, certDataRented, chainDataRented); + } + static void StartAsyncCertificateValidationCore(QuicConnection thisConnection, X509Certificate2? certificate, Memory certData, Memory chainData, byte[]? certDataRented, byte[]? chainDataRented) + { QUIC_TLS_ALERT_CODES result; try { @@ -158,7 +171,7 @@ internal unsafe void StartAsyncCertificateValidation(void* certificatePtr, void* NetEventSource.Error(thisConnection, $"{thisConnection} ConnectionCertificateValidationComplete failed with {ThrowHelper.GetErrorMessageForStatus(status)}"); } } - }); + } } private QUIC_TLS_ALERT_CODES ValidateCertificate(X509Certificate2? certificate, Span certData, Span chainData)