Skip to content

Commit 245581d

Browse files
committed
Merge #118: shutdown bugfix: Prevent segfault in server if connection is broken during long function call
196e6fc bugfix: prevent null pointer dereference in server if client disconnects during method call (Ryan Ofsky) 9d11042 bugfix: prevent double delete segfault in server if client disconnects during method call (Ryan Ofsky) Pull request description: Fix issue where if a client disconnects in the middle of a long running IPC method call, the server will segfault when the method eventually returns. This issue was reported tdb3 in bitcoin/bitcoin#31003 (review) with a stack trace showing where the crash happens in the `PassField` `mp.Context` overload while calling `request_threads.erase`. Top commit has no ACKs. Tree-SHA512: a49d3d6b46a319944ad54883d390a248bb9a3ece34bbb424222f8d5bfa015b8392e4fb7b830b4ae69c90d257d16c2122c4b8405a1078d7b3a979dbdd2c3d07be
2 parents 181837b + 196e6fc commit 245581d

File tree

2 files changed

+32
-3
lines changed

2 files changed

+32
-3
lines changed

include/mp/proxy-types.h

+27-3
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
122122
const auto& params = call_context.getParams();
123123
Context::Reader context_arg = Accessor::get(params);
124124
ServerContext server_context{server, call_context, req};
125+
bool disconnected{false};
125126
{
126127
// Before invoking the function, store a reference to the
127128
// callbackThread provided by the client in the
@@ -152,13 +153,36 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
152153
// inserted, one already existed, meaning this must be a
153154
// recursive call (IPC call calling back to the caller which
154155
// makes another IPC call), so avoid modifying the map.
155-
auto erase_thread{inserted ? request_thread : request_threads.end()};
156-
KJ_DEFER(if (erase_thread != request_threads.end()) {
156+
const bool erase_thread{inserted};
157+
KJ_DEFER({
157158
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
158-
request_threads.erase(erase_thread);
159+
// Call erase here with a Connection* argument instead
160+
// of an iterator argument, because the `request_thread`
161+
// iterator may be invalid if the connection is closed
162+
// during this function call. More specifically, the
163+
// iterator may be invalid because SetThread adds a
164+
// cleanup callback to the Connection destructor that
165+
// erases the thread from the map, and also because the
166+
// ProxyServer<Thread> destructor calls
167+
// request_threads.clear().
168+
if (erase_thread) {
169+
disconnected = !request_threads.erase(server.m_context.connection);
170+
} else {
171+
disconnected = !request_threads.count(server.m_context.connection);
172+
}
159173
});
160174
fn.invoke(server_context, args...);
161175
}
176+
if (disconnected) {
177+
// If disconnected is true, the Connection object was
178+
// destroyed during the method call. Deal with this by
179+
// returning without ever fulfilling the promise, which will
180+
// cause the ProxyServer object to leak. This is not ideal,
181+
// but fixing the leak will require nontrivial code changes
182+
// because there is a lot of code assuming ProxyServer
183+
// objects are destroyed before Connection objects.
184+
return;
185+
}
162186
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
163187
server.m_context.connection->m_loop.sync([&] {
164188
auto fulfiller_dispose = kj::mv(fulfiller);

src/mp/proxy.cpp

+5
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,11 @@ std::tuple<ConnThread, bool> SetThread(ConnThreads& threads, std::mutex& mutex,
277277
std::piecewise_construct, std::forward_as_tuple(connection),
278278
std::forward_as_tuple(make_thread(), connection, /* destroy_connection= */ false)).first;
279279
thread->second.setCleanup([&threads, &mutex, thread] {
280+
// Note: it is safe to use the `thread` iterator in this cleanup
281+
// function, because the iterator would only be invalid if the map entry
282+
// was removed, and if the map entry is removed the ProxyClient<Thread>
283+
// destructor unregisters the cleanup.
284+
280285
// Connection is being destroyed before thread client is, so reset
281286
// thread client m_cleanup member so thread client destructor does not
282287
// try unregister this callback after connection is destroyed.

0 commit comments

Comments
 (0)