Skip to content

Commit 404453c

Browse files
benhillisBen Hillis
andauthored
Fix a teardown race in HttpProxyStateTracker (#41097)
* Fix a teardown race in HttpProxyStateTracker HttpProxyStateTracker::QueryProxySettingsAsync() published the WinHTTP resolver/session handles and reset m_requestFinished only after calling WinHttpGetProxySettingsEx(). Since that call can complete (or fail synchronously) before returning, the destructor could run concurrently with request setup: it could observe m_requestFinished still signaled and tear down the queue and unregister the proxy-change notification while a request was still starting up, or close handles before they were fully published. Fix this by: - Adding a lock (m_requestLock) that serializes handle creation/teardown between QueryProxySettingsAsync, RequestCompleted, and the destructor, plus an m_stopping flag so no new request can start once teardown has begun. - Marking the request as in-flight (resetting m_requestFinished and setting m_queryState) before calling WinHttpGetProxySettingsEx, so the destructor cannot proceed past m_requestFinished.wait() while a request is actually outstanding. - Setting WINHTTP_OPTION_CONTEXT_VALUE on the resolver handle so that WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING always carries a valid context, including when WinHttpGetProxySettingsEx fails synchronously (which produces no completion callback), so RequestClosed() reliably runs and re-signals m_requestFinished instead of leaving it stuck. - Reordering the constructor to register the proxy-change notification before submitting the initial query, so a throw during registration can't leave a queued task running against a partially-constructed object. Tested locally with repeated concurrent wsl -e / wsl --shutdown races (including tight construct-then-shutdown loops) with no crashes or hangs observed. * fix formatting --------- Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
1 parent 96b220c commit 404453c

2 files changed

Lines changed: 39 additions & 13 deletions

File tree

src/windows/service/exe/LxssHttpProxy.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -277,8 +277,11 @@ try
277277

278278
// It is guaranteed that after closing the handles, a callback with status WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING
279279
// will be issued to indicate that the callback has been cleaned up.
280-
m_resolver.reset();
281-
m_session.reset();
280+
{
281+
const auto requestLock = m_requestLock.lock();
282+
m_resolver.reset();
283+
m_session.reset();
284+
}
282285
}
283286
CATCH_LOG()
284287

@@ -442,6 +445,12 @@ void HttpProxyStateTracker::QueryProxySettingsAsync()
442445
try
443446
{
444447
WI_ASSERT(m_callbackQueue.isRunningInQueue());
448+
const auto requestLock = m_requestLock.lock();
449+
if (m_stopping)
450+
{
451+
return;
452+
}
453+
445454
if (m_queryState == QueryState::PendingAndQueueAdditional)
446455
{
447456
return;
@@ -469,6 +478,10 @@ void HttpProxyStateTracker::QueryProxySettingsAsync()
469478
wil::unique_winhttp_hinternet resolver{};
470479
THROW_IF_WIN32_ERROR(WinHttpCreateProxyResolver(session.get(), &resolver));
471480

481+
executionStep = "WinHttpSetOption";
482+
DWORD_PTR context = reinterpret_cast<DWORD_PTR>(this);
483+
THROW_IF_WIN32_BOOL_FALSE(WinHttpSetOption(resolver.get(), WINHTTP_OPTION_CONTEXT_VALUE, &context, sizeof(context)));
484+
472485
executionStep = "WinHttpSetStatusCallback";
473486
// We need to set flag WINHTTP_CALLBACK_FLAG_HANDLES in order to get the WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING
474487
// status callback when a handle is closed.
@@ -483,10 +496,13 @@ void HttpProxyStateTracker::QueryProxySettingsAsync()
483496

484497
WINHTTP_PROXY_SETTINGS_PARAM ProxySettingsParam{0, nullptr, nullptr};
485498

499+
// Track the request before calling WinHttp because it can complete asynchronously before returning.
500+
m_requestFinished.ResetEvent();
501+
m_queryState = QueryState::Pending;
502+
486503
executionStep = "WinHttpGetProxySettingsEx";
487504
// Query the proxy settings
488-
const DWORD dwError = s_WinHttpGetProxySettingsEx.value()(
489-
resolver.get(), WinHttpProxySettingsTypeWsl, &ProxySettingsParam, reinterpret_cast<DWORD_PTR>(this));
505+
const DWORD dwError = s_WinHttpGetProxySettingsEx.value()(resolver.get(), WinHttpProxySettingsTypeWsl, &ProxySettingsParam, context);
490506

491507
if (dwError != ERROR_IO_PENDING && dwError != ERROR_SUCCESS)
492508
{
@@ -496,8 +512,6 @@ void HttpProxyStateTracker::QueryProxySettingsAsync()
496512
// Transfer ownership of HTTP handles and track request
497513
m_resolver = std::move(resolver);
498514
m_session = std::move(session);
499-
m_requestFinished.ResetEvent();
500-
m_queryState = QueryState::Pending;
501515
}
502516
catch (...)
503517
{
@@ -514,8 +528,8 @@ HttpProxyStateTracker::HttpProxyStateTracker(int ProxyTimeout, HANDLE UserToken,
514528
m_localizedProxyChangeString{wsl::shared::Localization::MessageHttpProxyChangeDetected()}
515529
{
516530
THROW_IF_WIN32_BOOL_FALSE(::DuplicateTokenEx(UserToken, MAXIMUM_ALLOWED, nullptr, SecurityImpersonation, TokenImpersonation, &m_userToken));
517-
m_callbackQueue.submit([this] { QueryProxySettingsAsync(); });
518531
THROW_IF_WIN32_ERROR(s_WinHttpRegisterProxyChangeNotification.value()(WINHTTP_PROXY_NOTIFY_CHANGE, s_OnProxyChange, this, &m_proxyRegistrationHandle));
532+
m_callbackQueue.submit([this] { QueryProxySettingsAsync(); });
519533
}
520534

521535
HttpProxyStateTracker::~HttpProxyStateTracker()
@@ -529,10 +543,16 @@ HttpProxyStateTracker::~HttpProxyStateTracker()
529543
}
530544
CATCH_LOG()
531545
}
532-
// It is guaranteed that after closing the handles, a callback with status WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING
533-
// will be issued and it will be the last callback for this request, making it safe to delete the ProxyStateTracker.
534-
m_resolver.reset();
535-
m_session.reset();
546+
547+
{
548+
const auto requestLock = m_requestLock.lock();
549+
m_stopping = true;
550+
551+
// It is guaranteed that after closing the handles, a callback with status WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING
552+
// will be issued and it will be the last callback for this request, making it safe to delete the ProxyStateTracker.
553+
m_resolver.reset();
554+
m_session.reset();
555+
}
536556

537557
// Wait for all requests to complete. At this point no new requests can be started since we unregistered for proxy change
538558
// notifications. Without this we risk racing proxy callbacks and them calling into a cleaned up ProxyStateTracker.

src/windows/service/exe/LxssHttpProxy.h

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,12 @@ class HttpProxyStateTracker
198198
/// </summary>
199199
QueryState m_queryState{QueryState::NoQuery};
200200

201+
/// <summary>
202+
/// Synchronizes request startup and teardown.
203+
/// </summary>
204+
wil::critical_section m_requestLock{};
205+
_Guarded_by_(m_requestLock) bool m_stopping = false;
206+
201207
/// <summary>
202208
/// Used to impersonate user, as it is required for the proxy queries to run as the user; otherwise, the results will be incorrect.
203209
/// </summary>
@@ -229,8 +235,8 @@ class HttpProxyStateTracker
229235
wil::slim_event_manual_reset m_requestFinished{true};
230236

231237
// Handles associated with the request
232-
wil::unique_winhttp_hinternet m_session{};
233-
wil::unique_winhttp_hinternet m_resolver{};
238+
_Guarded_by_(m_requestLock) wil::unique_winhttp_hinternet m_session {};
239+
_Guarded_by_(m_requestLock) wil::unique_winhttp_hinternet m_resolver {};
234240

235241
/// <summary>
236242
/// Single-threaded queue to trigger work from winhttp callbacks.

0 commit comments

Comments
 (0)