Skip to content

[Debug] Fix monitor-bridge use-after-free in GdbServer#4

Open
r-log wants to merge 1 commit into
Krilliac:masterfrom
r-log:fix/gdb-monitor-uaf
Open

[Debug] Fix monitor-bridge use-after-free in GdbServer#4
r-log wants to merge 1 commit into
Krilliac:masterfrom
r-log:fix/gdb-monitor-uaf

Conversation

@r-log

@r-log r-log commented Jul 4, 2026

Copy link
Copy Markdown

Second follow-up to mangoszero#406 (independent of the thread-safety PR). Merge into your master and it lands in mangoszero#406.

Fixes a use-after-free in the monitor bridge. SubmitMonitorLine queues a raw GdbMonSocket* as MonitorReq.ctx with no ownership taken. Monitor requests are drained only on the world thread (DrainMonitorRequests in OnWorldUpdate), and while the world thread is parked in EnterStop they aren't drained at all — so if the client disconnects before the request is drained, the reactor thread's handle_closeremove_reference() frees the socket and the later req.writer(req.ctx, …) writes through a dangling pointer → ACCESS_VIOLATION (RtlEnterCriticalSection on the freed outBufferLock). A long-blocking worldtick stop just widens a window that exists for any mid-flight monitor disconnect.

Fix: pin the socket's ACE reference count for the queued request's lifetime. SubmitMonitorLine takes add_reference() before enqueue; MonitorReq is now move-only RAII that calls remove_reference() exactly once — on service, drop, or queue teardown at shutdown. ACE stays out of the game layer: the ref ops are passed as void* thunks, same pattern as the existing writer. 3 files, +90/-4.

Verified on the same live 1.12.1 server: the deterministic crash-repro (send+close a monitor probe during a worldtick stop) that previously produced a signature-identical dump now survives with no new dump, and the thread-safety tests (off-thread no-stall, on-thread stop+resume) still pass. Compiles clean, MSVC 2022 Release, 0 warnings / 0 errors.

SubmitMonitorLine queued a MonitorReq holding a raw GdbMonSocket* with
no ownership taken. If a monitor client sent a line and disconnected
before the world thread's DrainMonitorRequests popped the request
(e.g. any time the world thread is parked in EnterStop, which pumps
only RSP traffic), the ACE reactor thread's handle_close/remove_reference
could free the socket first. DrainMonitorRequests would then call
req.writer() through the dangling pointer -> ACCESS_VIOLATION.

Fix: pin the socket's ACE reference count for the lifetime of a queued
request. SubmitMonitorLine now takes two extra thunks (addRef/release,
mirroring the existing writer thunk) so the game-layer GdbServer stays
free of any ACE dependency; GdbMonSocket wires them to add_reference()/
remove_reference(). MonitorReq became move-only and RAII-releases its
pin in its destructor, so the reference is dropped exactly once on
every path: normal servicing, an unrecognized command, or the queue
itself being torn down with requests still pending.

The RSP side (m_rspWriter/m_rspCtx, guarded by m_writerLock +
m_peerClosed, cleared in DetachRsp) was already safe and is untouched.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant