From 649f971b1bcce59ce0aba2d30a199d19dd91a4d2 Mon Sep 17 00:00:00 2001 From: Yuqiong Liu <38703685+ukingliu@users.noreply.github.com> Date: Thu, 28 Sep 2023 10:40:43 -0700 Subject: [PATCH] Expose Interface for App to Set Stateless Reset Key (#3879) * Allow stateless reset token key to be set #1719 * Update src/core/library.c How about we use the same way as the random initial reset key, we call MsQuicLibraryFreePartitions() to set all PerProc->ResetTokenHash to NULL Co-authored-by: Nick Banks * Address the code review feedback * Revert clog.sidecar changes * Add event for stateless reset key * Revert the file clog.sidecar * Remove DatapathTcpAuxBinding changes from clog.sidecar * Remove parameter from function QuicTestStatelessResetKey and address other comments * Update src/test/MsQuicTests.h Co-authored-by: Nick Banks * Update src/core/library.c Co-authored-by: Nick Banks * Update src/test/lib/DataTest.cpp Co-authored-by: Nick Banks * Address code review comments * Move stateless reset key after the other global param * Address CheckDotnet test and update document * Address the failure at CIFuzz * Address the failure in CIFuzz * Add one test case when MsQuic lib has not been initialized yet --------- Co-authored-by: Nick Banks --- docs/Settings.md | 3 +- src/core/library.c | 31 +++++++++ src/cs/lib/msquic_generated.cs | 6 ++ src/inc/msquic.h | 7 +- src/test/MsQuicTests.h | 10 ++- src/test/bin/quic_gtest.cpp | 9 +++ src/test/bin/winkernel/control.cpp | 6 ++ src/test/lib/ApiTest.cpp | 33 ++++++++++ src/test/lib/DataTest.cpp | 102 +++++++++++++++++++++++++++++ src/tools/spin/spinquic.cpp | 12 ++++ 10 files changed, 216 insertions(+), 3 deletions(-) diff --git a/docs/Settings.md b/docs/Settings.md index 440b6ad1eb..357440a954 100644 --- a/docs/Settings.md +++ b/docs/Settings.md @@ -107,8 +107,9 @@ These parameters are accessed by calling [GetParam](./api/GetParam.md) or [SetPa | `QUIC_PARAM_GLOBAL_GLOBAL_SETTINGS`
6 | QUIC_GLOBAL_SETTINGS | Both | Globally change global only settings. | | `QUIC_PARAM_GLOBAL_VERSION_SETTINGS`
7 | QUIC_VERSIONS_SETTINGS | Both | Globally change version settings for all subsequent connections. | | `QUIC_PARAM_GLOBAL_LIBRARY_GIT_HASH`
8 | char[64] | Get-only | Git hash used to build MsQuic (null terminated string) | -| `QUIC_PARAM_GLOBAL_EXECUTION_CONFIG`
9 | QUIC_EXECUTION_CONFIG | Both | Globally configure the execution model used for QUIC. Must be set before opening registration. | +| `QUIC_PARAM_GLOBAL_EXECUTION_CONFIG`
9 | QUIC_EXECUTION_CONFIG | Both | Globally configure the execution model used for QUIC. Must be set before opening registration. | | `QUIC_PARAM_GLOBAL_TLS_PROVIDER`
10 | QUIC_TLS_PROVIDER | Get-Only | The TLS provider being used by MsQuic for the TLS handshake. | +| `QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY`
11 | uint8_t[] | Set-Only | Globally change the stateless reset key for all subsequent connections. | ## Registration Parameters diff --git a/src/core/library.c b/src/core/library.c index 8ca5d93d5d..f68ccad650 100644 --- a/src/core/library.c +++ b/src/core/library.c @@ -1112,6 +1112,37 @@ QuicLibrarySetGlobalParam( Status = QUIC_STATUS_SUCCESS; break; + case QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY: + if (!MsQuicLib.LazyInitComplete) { + Status = QUIC_STATUS_INVALID_STATE; + break; + } + if (BufferLength != QUIC_STATELESS_RESET_KEY_LENGTH * sizeof(uint8_t)) { + Status = QUIC_STATUS_INVALID_PARAMETER; + break; + } + + Status = QUIC_STATUS_SUCCESS; + for (uint16_t i = 0; i < MsQuicLib.ProcessorCount; ++i) { + CXPLAT_HASH* TokenHash = NULL; + Status = + CxPlatHashCreate( + CXPLAT_HASH_SHA256, + (uint8_t*)Buffer, + QUIC_STATELESS_RESET_KEY_LENGTH * sizeof(uint8_t), + &TokenHash); + if (QUIC_FAILED(Status)) { + break; + } + + QUIC_LIBRARY_PP* PerProc = &MsQuicLib.PerProc[i]; + CxPlatLockAcquire(&PerProc->ResetTokenLock); + CxPlatHashFree(PerProc->ResetTokenHash); + PerProc->ResetTokenHash = TokenHash; + CxPlatLockRelease(&PerProc->ResetTokenLock); + } + break; + default: Status = QUIC_STATUS_INVALID_PARAMETER; break; diff --git a/src/cs/lib/msquic_generated.cs b/src/cs/lib/msquic_generated.cs index 36709bc43f..7d82104cb6 100644 --- a/src/cs/lib/msquic_generated.cs +++ b/src/cs/lib/msquic_generated.cs @@ -3077,6 +3077,9 @@ internal static unsafe partial class MsQuic [NativeTypeName("#define QUIC_MAX_RESUMPTION_APP_DATA_LENGTH 1000")] internal const uint QUIC_MAX_RESUMPTION_APP_DATA_LENGTH = 1000; + [NativeTypeName("#define QUIC_STATELESS_RESET_KEY_LENGTH 32")] + internal const uint QUIC_STATELESS_RESET_KEY_LENGTH = 32; + [NativeTypeName("#define QUIC_EXECUTION_CONFIG_MIN_SIZE (uint32_t)FIELD_OFFSET(QUIC_EXECUTION_CONFIG, ProcessorList)")] internal static readonly uint QUIC_EXECUTION_CONFIG_MIN_SIZE = unchecked((uint)((int)(Marshal.OffsetOf("ProcessorList")))); @@ -3143,6 +3146,9 @@ internal static unsafe partial class MsQuic [NativeTypeName("#define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A")] internal const uint QUIC_PARAM_GLOBAL_TLS_PROVIDER = 0x0100000A; + [NativeTypeName("#define QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY 0x0100000B")] + internal const uint QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY = 0x0100000B; + [NativeTypeName("#define QUIC_PARAM_CONFIGURATION_SETTINGS 0x03000000")] internal const uint QUIC_PARAM_CONFIGURATION_SETTINGS = 0x03000000; diff --git a/src/inc/msquic.h b/src/inc/msquic.h index 75e4401158..235acfef80 100644 --- a/src/inc/msquic.h +++ b/src/inc/msquic.h @@ -70,6 +70,11 @@ typedef _In_range_(0, QUIC_UINT62_MAX) uint64_t QUIC_UINT62; // #define QUIC_MAX_RESUMPTION_APP_DATA_LENGTH 1000 +// +// The number of bytes of stateless reset key. +// +#define QUIC_STATELESS_RESET_KEY_LENGTH 32 + typedef enum QUIC_TLS_PROVIDER { QUIC_TLS_PROVIDER_SCHANNEL = 0x0000, QUIC_TLS_PROVIDER_OPENSSL = 0x0001, @@ -832,7 +837,7 @@ void #define QUIC_PARAM_GLOBAL_EXECUTION_CONFIG 0x01000009 // QUIC_EXECUTION_CONFIG #endif #define QUIC_PARAM_GLOBAL_TLS_PROVIDER 0x0100000A // QUIC_TLS_PROVIDER - +#define QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY 0x0100000B // uint8_t[] - Array size is QUIC_STATELESS_RESET_KEY_LENGTH // // Parameters for Registration. // diff --git a/src/test/MsQuicTests.h b/src/test/MsQuicTests.h index 05333b181b..685fde512e 100644 --- a/src/test/MsQuicTests.h +++ b/src/test/MsQuicTests.h @@ -418,6 +418,11 @@ QuicTestClientDisconnect( bool StopListenerFirst ); +void +QuicTestStatelessResetKey( + void + ); + void QuicTestKeyUpdate( _In_ int Family, @@ -1211,4 +1216,7 @@ typedef struct { QUIC_CTL_CODE(112, METHOD_BUFFERED, FILE_WRITE_DATA) // QUIC_RUN_FEATURE_NEGOTIATION -#define QUIC_MAX_IOCTL_FUNC_CODE 112 +#define IOCTL_QUIC_RUN_STATELESS_RESET_KEY \ + QUIC_CTL_CODE(113, METHOD_BUFFERED, FILE_WRITE_DATA) + +#define QUIC_MAX_IOCTL_FUNC_CODE 113 diff --git a/src/test/bin/quic_gtest.cpp b/src/test/bin/quic_gtest.cpp index 21db0eda5c..f2db4d62dc 100644 --- a/src/test/bin/quic_gtest.cpp +++ b/src/test/bin/quic_gtest.cpp @@ -1896,6 +1896,15 @@ TEST(Misc, ClientDisconnect) { } } +TEST(Misc, StatelessResetKey) { + TestLogger Logger("QuicTestStatelessResetKey"); + if (TestingKernelMode) { + ASSERT_TRUE(DriverClient.Run(IOCTL_QUIC_RUN_STATELESS_RESET_KEY)); + } else { + QuicTestStatelessResetKey(); + } +} + TEST_P(WithKeyUpdateArgs1, KeyUpdate) { TestLoggerT Logger("QuicTestKeyUpdate", GetParam()); if (TestingKernelMode) { diff --git a/src/test/bin/winkernel/control.cpp b/src/test/bin/winkernel/control.cpp index 2427eb78b0..df0fb17f1b 100644 --- a/src/test/bin/winkernel/control.cpp +++ b/src/test/bin/winkernel/control.cpp @@ -484,6 +484,7 @@ size_t QUIC_IOCTL_BUFFER_SIZES[] = sizeof(QUIC_RUN_CUSTOM_CERT_VALIDATION), sizeof(QUIC_RUN_FEATURE_NEGOTIATION), sizeof(QUIC_RUN_FEATURE_NEGOTIATION), + 0, }; CXPLAT_STATIC_ASSERT( @@ -1363,6 +1364,11 @@ QuicTestCtlEvtIoDeviceControl( Params->FeatureNegotiationParams.ClientSupport)); break; #endif + + case IOCTL_QUIC_RUN_STATELESS_RESET_KEY: + QuicTestCtlRun(QuicTestStatelessResetKey()); + break; + default: Status = STATUS_NOT_IMPLEMENTED; break; diff --git a/src/test/lib/ApiTest.cpp b/src/test/lib/ApiTest.cpp index 5fddd7f06e..fc6f51076a 100644 --- a/src/test/lib/ApiTest.cpp +++ b/src/test/lib/ApiTest.cpp @@ -2654,6 +2654,39 @@ void QuicTestGlobalParam() } #endif + // + // QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY + // + { + TestScopeLogger LogScope0("QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY"); + { + TestScopeLogger LogScope1("SetParam"); + uint8_t StatelessResetkey[QUIC_STATELESS_RESET_KEY_LENGTH - 1]; + CxPlatRandom(sizeof(StatelessResetkey), StatelessResetkey); + { + TestScopeLogger LogScope2("StatelessResetkey fail with invalid state"); + TEST_QUIC_STATUS( + QUIC_STATUS_INVALID_STATE, + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY, + sizeof(StatelessResetkey), + StatelessResetkey)); + } + { + TestScopeLogger LogScope2("StatelessResetkey fail with invalid parameter"); + MsQuicRegistration Registration; + TEST_QUIC_STATUS( + QUIC_STATUS_INVALID_PARAMETER, + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY, + sizeof(StatelessResetkey), + StatelessResetkey)); + } + } + } + // // Invalid parameter // diff --git a/src/test/lib/DataTest.cpp b/src/test/lib/DataTest.cpp index 8de2558550..1de9e36121 100644 --- a/src/test/lib/DataTest.cpp +++ b/src/test/lib/DataTest.cpp @@ -788,6 +788,108 @@ QuicTestClientDisconnect( } } +void +QuicTestStatelessResetKey( + ) +{ + // + // By changing the stateless reset key, the stateless reset packets the client + // receives after the server side is shut down no longer match, eventually resulting + // in a timeout on the client instead of an abort. + // + + PingStats ClientStats(UINT64_MAX - 1, 1, 1, TRUE, TRUE, FALSE, FALSE, TRUE, QUIC_STATUS_CONNECTION_TIMEOUT); + + CxPlatEvent EventClientDeleted(true); + + MsQuicRegistration Registration; + TEST_TRUE(Registration.IsValid()); + + MsQuicAlpn Alpn("MsQuicTest"); + + MsQuicSettings Settings; + Settings.SetIdleTimeoutMs(10000); + Settings.SetPeerUnidiStreamCount(1); + + MsQuicConfiguration ServerConfiguration(Registration, Alpn, Settings, ServerSelfSignedCredConfig); + TEST_TRUE(ServerConfiguration.IsValid()); + + MsQuicCredentialConfig ClientCredConfig; + MsQuicConfiguration ClientConfiguration(Registration, Alpn, Settings, ClientCredConfig); + TEST_TRUE(ClientConfiguration.IsValid()); + + { + TestListener Listener(Registration, ListenerAcceptConnectionAndStreams, ServerConfiguration); + TEST_TRUE(Listener.IsValid()); + TEST_QUIC_SUCCEEDED(Listener.Start(Alpn)); + + QuicAddr ServerLocalAddr; + TEST_QUIC_SUCCEEDED(Listener.GetLocalAddr(ServerLocalAddr)); + + { + UniquePtr Server; + ServerAcceptContext ServerAcceptCtx((TestConnection**)&Server); + Listener.Context = &ServerAcceptCtx; + + TestConnection* Client = + NewPingConnection( + Registration, + &ClientStats, + false); + if (Client == nullptr) { + return; + } + + Client->SetDeletedEvent(&EventClientDeleted.Handle); + + Client->SetExpectedTransportCloseStatus(ClientStats.ExpectedCloseStatus); + TEST_QUIC_SUCCEEDED(Client->SetDisconnectTimeout(1000)); // ms + + if (!SendPingBurst( + Client, + ClientStats.StreamCount, + ClientStats.PayloadLength)) { + return; + } + + TEST_QUIC_SUCCEEDED( + Client->Start( + ClientConfiguration, + QUIC_ADDRESS_FAMILY_INET, + QUIC_TEST_LOOPBACK_FOR_AF(QUIC_ADDRESS_FAMILY_INET), + ServerLocalAddr.GetPort())); + + if (!Client->WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Client->GetIsConnected()); + + TEST_NOT_EQUAL(nullptr, Server); + if (!Server->WaitForConnectionComplete()) { + return; + } + TEST_TRUE(Server->GetIsConnected()); + + CxPlatSleep(15); // Sleep for just a bit. + + uint8_t StatelessResetKey[QUIC_STATELESS_RESET_KEY_LENGTH]; + CxPlatRandom(sizeof(StatelessResetKey), StatelessResetKey); + TEST_QUIC_SUCCEEDED( + MsQuic->SetParam( + nullptr, + QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY, + sizeof(StatelessResetKey), + StatelessResetKey)); + + Server->Shutdown(QUIC_CONNECTION_SHUTDOWN_FLAG_SILENT, 0); + } + + if (!CxPlatEventWaitWithTimeout(EventClientDeleted.Handle, TestWaitTimeout)) { + TEST_FAILURE("Wait for EventClientDeleted timed out after %u ms.", TestWaitTimeout); + } + } +} + struct AbortiveTestContext { AbortiveTestContext( _In_ HQUIC ServerConfiguration, diff --git a/src/tools/spin/spinquic.cpp b/src/tools/spin/spinquic.cpp index 8bfec3cc43..dddcfa07fc 100644 --- a/src/tools/spin/spinquic.cpp +++ b/src/tools/spin/spinquic.cpp @@ -1324,6 +1324,18 @@ CXPLAT_THREAD_CALLBACK(RunThread, Context) } } + if (0 == GetRandom(4)) { + uint8_t StatelessResetKey[QUIC_STATELESS_RESET_KEY_LENGTH]; + CxPlatRandom(sizeof(StatelessResetKey), StatelessResetKey); + if (!QUIC_SUCCEEDED(MsQuic.SetParam( + nullptr, + QUIC_PARAM_GLOBAL_STATELESS_RESET_KEY, + sizeof(StatelessResetKey), + StatelessResetKey))) { + break; + } + } + QUIC_REGISTRATION_CONFIG RegConfig; RegConfig.AppName = "spinquic"; RegConfig.ExecutionProfile = FuzzData ? QUIC_EXECUTION_PROFILE_TYPE_SCAVENGER : (QUIC_EXECUTION_PROFILE)GetRandom(4);