When the thread is suspended and Rip (instruction pointer) points to an instruction that causes an exception, modifying Rip and calling ResumeThread() may sometimes result in a crash. To prevent this, advance execution by a single instruction by setting the trap flag (TF) before calling ResumeThread() as a workaround. This will trigger either STATUS_SINGLE_STEP or the exception caused by the instruction that Rip originally pointed to. By suspending the targeted thread within exception::handle(), Rip no longer points to the problematic instruction, allowing safe handling of the interrupt. As a result, Rip can be adjusted appropriately, and the thread can resume execution without unexpected crashes.
Addresses: https://cygwin.com/pipermail/cygwin/2025-May/258153.html Fixes: 1fd5e000ace5 ("import winsup-2000-02-17 snapshot") Reported-by: Christian Franke <[email protected]> Reviewed-by: Signed-off-by: Takashi Yano <[email protected]> --- winsup/cygwin/exceptions.cc | 37 +++++++++++++++++++++++++++ winsup/cygwin/local_includes/cygtls.h | 1 + winsup/cygwin/local_includes/ntdll.h | 3 ++- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc index 876b79e36..804adc92b 100644 --- a/winsup/cygwin/exceptions.cc +++ b/winsup/cygwin/exceptions.cc @@ -653,6 +653,13 @@ exception::handle (EXCEPTION_RECORD *e, exception_list *frame, CONTEXT *in, static int NO_COPY debugging = 0; _cygtls& me = _my_tls; + if (me.suspend_on_exception) + { + SuspendThread (GetCurrentThread ()); + if (e->ExceptionCode == (DWORD) STATUS_SINGLE_STEP) + return ExceptionContinueExecution; + } + if (debugging && ++debugging < 500000) { SetThreadPriority (hMainThread, THREAD_PRIORITY_NORMAL); @@ -929,6 +936,36 @@ _cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler, interrupted = false; else { +#ifdef __x86_64__ + /* When the Rip points to an instruction that causes an exception, + modifying Rip and calling ResumeThread() may sometimes result in + a crash. To prevent this, advance execution by a single instruction + by setting the trap flag (TF) before calling ResumeThread(). This + will trigger either STATUS_SINGLE_STEP or the exception caused by + the instruction that Rip originally pointed to. By suspending the + targeted thread within exception::handle(), Rip no longer points + to the problematic instruction, allowing safe handling of the + interrupt. As a result, Rip can be adjusted appropriately, and the + thread can resume execution without unexpected crashes. */ + if (!inside_kernel (cx, true)) + { + cx->EFlags |= 0x100; /* Set TF (setup single step execution) */ + SetThreadContext (*this, cx); + suspend_on_exception = true; + ResumeThread (*this); + ULONG cnt = 0; + NTSTATUS status; + do + { + yield (); + status = NtQueryInformationThread (*this, ThreadSuspendCount, + &cnt, sizeof (cnt), NULL); + } + while (NT_SUCCESS (status) && cnt == 0); + GetThreadContext (*this, cx); + suspend_on_exception = false; + } +#endif DWORD64 &ip = cx->_CX_instPtr; push (ip); interrupt_setup (si, handler, siga); diff --git a/winsup/cygwin/local_includes/cygtls.h b/winsup/cygwin/local_includes/cygtls.h index 4698352ae..1b3bf65f1 100644 --- a/winsup/cygwin/local_includes/cygtls.h +++ b/winsup/cygwin/local_includes/cygtls.h @@ -203,6 +203,7 @@ public: /* Do NOT remove this public: line, it's a marker for gentls_offsets. */ __tlsstack_t *stackptr; __tlsstack_t stack[TLS_STACK_SIZE]; unsigned initialized; + volatile bool suspend_on_exception; public: /* Do NOT remove this public: line, it's a marker for gentls_offsets. */ void init_thread (void *, DWORD (*) (void *, void *)); diff --git a/winsup/cygwin/local_includes/ntdll.h b/winsup/cygwin/local_includes/ntdll.h index 97a83d1e3..f32e850f4 100644 --- a/winsup/cygwin/local_includes/ntdll.h +++ b/winsup/cygwin/local_includes/ntdll.h @@ -1362,7 +1362,8 @@ typedef enum _THREADINFOCLASS ThreadBasicInformation = 0, ThreadTimes = 1, ThreadImpersonationToken = 5, - ThreadQuerySetWin32StartAddress = 9 + ThreadQuerySetWin32StartAddress = 9, + ThreadSuspendCount = 35 } THREADINFOCLASS, *PTHREADINFOCLASS; typedef struct _THREAD_BASIC_INFORMATION -- 2.45.1
