Skip to content

Commit 196e6fc

Browse files
committed
bugfix: prevent null pointer dereference in server if client disconnects during method call
This an imperfect workaround for a crash that can be triggered if a client disconnects in the middle of a long running IPC call. The workaround results in memory leak but avoids a crash. This can be improved later, and more details are in a code comment.
1 parent 9d11042 commit 196e6fc

File tree

1 file changed

+17
-2
lines changed

1 file changed

+17
-2
lines changed

include/mp/proxy-types.h

Lines changed: 17 additions & 2 deletions
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
@@ -153,7 +154,7 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
153154
// recursive call (IPC call calling back to the caller which
154155
// makes another IPC call), so avoid modifying the map.
155156
const bool erase_thread{inserted};
156-
KJ_DEFER(if (erase_thread) {
157+
KJ_DEFER({
157158
std::unique_lock<std::mutex> lock(thread_context.waiter->m_mutex);
158159
// Call erase here with a Connection* argument instead
159160
// of an iterator argument, because the `request_thread`
@@ -164,10 +165,24 @@ auto PassField(Priority<1>, TypeList<>, ServerContext& server_context, const Fn&
164165
// erases the thread from the map, and also because the
165166
// ProxyServer<Thread> destructor calls
166167
// request_threads.clear().
167-
request_threads.erase(server.m_context.connection);
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+
}
168173
});
169174
fn.invoke(server_context, args...);
170175
}
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+
}
171186
KJ_IF_MAYBE(exception, kj::runCatchingExceptions([&]() {
172187
server.m_context.connection->m_loop.sync([&] {
173188
auto fulfiller_dispose = kj::mv(fulfiller);

0 commit comments

Comments
 (0)