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

Reply via email to