From 869f9ce8023f1d7a66cfdb712edf7fa4974fb1ef Mon Sep 17 00:00:00 2001 From: Sergei Morozov Date: Sun, 31 May 2026 21:57:11 -0700 Subject: [PATCH] Don't free breakpoints racing the debugger walk Prevent the use-after-free crash when a gdb client disconnects while the emulator is running. --- debugger/breakpoint.c | 7 +++++++ debugger/debugger_internals.h | 5 +++++ debugger/gdbserver.c | 11 +++++++++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/debugger/breakpoint.c b/debugger/breakpoint.c index 83910d2ab..818f8d9da 100644 --- a/debugger/breakpoint.c +++ b/debugger/breakpoint.c @@ -43,6 +43,8 @@ /* The current breakpoints */ GSList *debugger_breakpoints; +volatile int debugger_breakpoints_remove_pending = 0; + /* The next breakpoint ID to use */ static size_t next_breakpoint_id; @@ -237,6 +239,11 @@ debugger_check( debugger_breakpoint_type type, libspectrum_dword value ) int signal_breakpoints_updated = 0; + if( debugger_breakpoints_remove_pending ) { + debugger_breakpoints_remove_pending = 0; + debugger_breakpoint_remove_all(); + } + switch( debugger_mode ) { case DEBUGGER_MODE_INACTIVE: return 0; diff --git a/debugger/debugger_internals.h b/debugger/debugger_internals.h index 0231742af..9ded57704 100644 --- a/debugger/debugger_internals.h +++ b/debugger/debugger_internals.h @@ -32,6 +32,11 @@ extern int debugger_memory_pool; /* The event type used to trigger time breakpoints */ extern int debugger_breakpoint_event; +/* Set by the gdbserver network thread when a client disconnects while the + emulator is running; honoured by debugger_check() on the emulator thread so + the breakpoint list is never freed underneath an in-progress walk. */ +extern volatile int debugger_breakpoints_remove_pending; + void debugger_breakpoint_time_fn( libspectrum_dword tstates, int type, void *user_data ); int debugger_breakpoint_remove( size_t id ); diff --git a/debugger/gdbserver.c b/debugger/gdbserver.c index 669b809b2..9bdc67f32 100644 --- a/debugger/gdbserver.c +++ b/debugger/gdbserver.c @@ -736,8 +736,15 @@ static void* network_thread(void* arg) while ((ret = process_network(gdbserver_client_socket)) == 0) ; printf("Socket closed: %d\n", gdbserver_client_socket); - debugger_breakpoint_remove_all(); - printf("Deleted all breakpoints.\n"); + /* Remove the client's breakpoints without racing the emulator's + debugger_check() walk: free directly only while the emulator is + parked in the trap loop; otherwise defer the free to the emulator + thread, which performs it from debugger_check(). */ + if (gdbserver_trapped) { + debugger_breakpoint_remove_all(); + } else { + debugger_breakpoints_remove_pending = 1; + } compat_socket_close(gdbserver_client_socket); gdbserver_client_socket = -1;