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 aecaa2feca9e07..77d4de1bec6c4c 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 @@ -26,6 +26,15 @@ internal static class SslStreamPal // API is supported since Windows 10 1809 (17763) but there is no reason to use at the moment. Environment.OSVersion.Version.Major >= 10 && Environment.OSVersion.Version.Build >= 18836; + // On Windows Server 2022 (build 20348) and older, Schannel has a race condition where + // ApplyControlToken(SSL_SESSION_DISABLE_RECONNECTS) doesn't reliably prevent the session + // cache from being repopulated. The workaround is to delete the context and retry + // InitializeSecurityContext after ApplyControlToken. This follows the same pattern used by + // Schannel's own webcli.c test and http.sys. The issue was fixed in newer Schannel builds + // shipping with Windows 11+ (build 22000+). + private static readonly bool NeedsDisableTlsResumeWorkaround = + Environment.OSVersion.Version.Build < 22000; + private const string SecurityPackage = "Microsoft Unified Security Protocol Provider"; private const Interop.SspiCli.ContextFlags RequiredFlags = @@ -188,13 +197,6 @@ public static ProtocolToken InitializeSecurityContext( token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode); - consumed = inputBuffer.Length; - if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA) - { - // not all data were consumed - consumed -= inputBuffers._item1.Token.Length; - } - bool allowTlsResume = sslAuthenticationOptions.AllowTlsResume && !LocalAppContextSwitches.DisableTlsResume; if (!allowTlsResume && newContext && context != null) @@ -206,11 +208,44 @@ public static ProtocolToken InitializeSecurityContext( ref context, in securityBuffer)); - if (result.ErrorCode != SecurityStatusPalErrorCode.OK) { token.Status = result; } + else if (NeedsDisableTlsResumeWorkaround) + { + // On affected builds, Schannel's internal LookupCacheByName finds a fresh + // resumable entry and embeds the session ID in the ClientHello before + // ApplyControlToken can expire it. Deleting the context and retrying ISC + // ensures the new ClientHello is generated without a stale session ID. + // We can reuse inputBuffers since this only runs on the very first ISC call + // (newContext == true) where the input is empty. + context?.Dispose(); + context = null; + token.ReleasePayload(); + token = default; + token.RentBuffer = true; + + errorCode = SSPIWrapper.InitializeSecurityContext( + GlobalSSPI.SSPISecureChannel, + ref credentialsHandle, + ref context, + targetName, + RequiredFlags | Interop.SspiCli.ContextFlags.InitManualCredValidation, + Interop.SspiCli.Endianness.SECURITY_NATIVE_DREP, + ref inputBuffers, + ref token, + ref unusedAttributes); + + token.Status = SecurityStatusAdapterPal.GetSecurityStatusPalFromNativeInt(errorCode); + } + } + + consumed = inputBuffer.Length; + if (inputBuffers._item1.Type == SecurityBufferType.SECBUFFER_EXTRA) + { + // not all data were consumed + consumed -= inputBuffers._item1.Token.Length; } return token; diff --git a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAllowTlsResumeTests.cs b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAllowTlsResumeTests.cs index 54031f0224d717..34cb3b50901d12 100644 --- a/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAllowTlsResumeTests.cs +++ b/src/libraries/System.Net.Security/tests/FunctionalTests/SslStreamAllowTlsResumeTests.cs @@ -34,7 +34,6 @@ private bool CheckResumeFlag(SslStream ssl) [ConditionalTheory] [InlineData(true)] [InlineData(false)] - [ActiveIssue("https://github.com/dotnet/runtime/issues/103449", TestPlatforms.Windows)] public async Task ClientDisableTlsResume_Succeeds(bool testClient) { SslServerAuthenticationOptions serverOptions = new SslServerAuthenticationOptions