[PATCH] Cygwin: exceptions: Fix AArch64 non-incyg signal handling
Máte Dimand <[email protected]> Fri, 12 Jun 2026 10:02:26 +0200
| Newsgroups | gmane.os.cygwin.patches |
|---|---|
| Message-ID | <[email protected]> |
This patch fixes crashes that occur when a signal interrupts sigfe or any non-cygwin function that does not preserve the LR register in its prologue/epilogue. This crash was discovered through the "run-heredoc" testcase in bash's testsuite, which caused bash to call "read" frequently, leading to a high chance of a signal interrupting sigfe. The "sigdelayed" function in gendef clobbers the LR register to return to the instruction where the thread was interrupted. Picking any other register for branching back would also clobber said register. Leaf functions are not guaranteed to be compiled with LR being preserved on the stack. The solution is to use RtlRestoreContext to restore all registers without needing to sacrifice any. The patch includes a C++ version of sigdelayed, which calls RtlRestoreContext at the end. The non-incyg signal handling codepath will change the thread's IP register to this new function instead of the original sigdelayed function written in assembly. Cygwin functions interrupted by signals still use the original function. Signed-off-by: Máté Dimand <[email protected]> --- winsup/cygwin/exceptions.cc | 87 ++++++++++++++++++++++++++- winsup/cygwin/local_includes/cygtls.h | 13 ++++ 2 files changed, 98 insertions(+), 2 deletions(-) diff --git a/winsup/cygwin/exceptions.cc b/winsup/cygwin/exceptions.cc index 1e129b319..d3fdb2a44 100644 --- a/winsup/cygwin/exceptions.cc +++ b/winsup/cygwin/exceptions.cc @@ -947,6 +947,56 @@ singlestep_handler (EXCEPTION_POINTERS *ep) } #endif +#ifdef __aarch64__ +/* This function is a C version of sigdelayed, with the CPU state + restoration code being replaced with RtlRestoreContext to ensure that + LR does not get clobbered. Note that this function should not return, + and the stack contents created by this function are left un-popped. + This should not be a problem, since the context restoration also + restores SP. */ +void +_cygtls::sigdelayed_non_incyg() +{ + int backup_errno = saved_errno; + + call_signal_handler(); + + lock(); + + if(backup_errno) + { + *errno_addr = backup_errno; + } + + /* In the asm version of sigdelayed the stack is popped to restore LR, + however we already have it in the stored context, so we don't need the + popped value itself. */ + pop(); + + /* In order to stay accurate to the asm version of sigdelayed, we also + atomically clear the return address. */ + InterlockedExchange64 ((LONG64*)stackptr, 0); + + /* We copy the context to ensure nothing overwrites it + after unlocking and before restoring. */ + CONTEXT cx = sigdelayed_context; + + incyg = 0; + unlock(); + + RtlRestoreContext(&cx, NULL); + + /* If we got here, something was wrong. */ + api_fatal ("Failed to restore context in sigdelayed_non_incyg"); +} + +static void +call_non_cygwin_sigdelayed(_cygtls* tls) +{ + tls->sigdelayed_non_incyg(); +} +#endif + bool _cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler, struct sigaction& siga) @@ -998,10 +1048,31 @@ _cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler, return false; /* Not interrupted */ } #endif + +#ifdef __aarch64__ + /* Copy unmodified context to be restored by thread + after signals are handled. */ + sigdelayed_context = *cx; +#endif + DWORD64 &ip = cx->_CX_instPtr; push (ip); + +#ifdef __aarch64__ + _interrupt_setup (si, handler, siga); + + /* Instead of setting IP to the asm sigdelayed pushed by interrupt_setup, + we redirect to an alternative version of it that restores the CPU state + using RtlRestoreContext. */ + ip = reinterpret_cast<DWORD64>(&call_non_cygwin_sigdelayed); + + /* X0 overwritten to pass this _cygtls in first argument to call_non_cygwin_sigdelayed. */ + cx->X[0] = (DWORD64)this; +#else interrupt_setup (si, handler, siga); ip = pop (); +#endif + SetThreadContext (*this, cx); /* Restart the thread in a new location */ interrupted = true; } @@ -1009,9 +1080,8 @@ _cygtls::interrupt_now (CONTEXT *cx, siginfo_t& si, void *handler, } void -_cygtls::interrupt_setup (siginfo_t& si, void *handler, struct sigaction& siga) +_cygtls::_interrupt_setup (siginfo_t& si, void *handler, struct sigaction& siga) { - push ((__tlsstack_t) sigdelayed); deltamask = siga.sa_mask & ~SIG_NONMASKABLE; sa_flags = siga.sa_flags; func = (void (*) (int, siginfo_t *, void *)) handler; @@ -1037,6 +1107,13 @@ _cygtls::interrupt_setup (siginfo_t& si, void *handler, struct sigaction& siga) signal_arrived, si.si_signo); } +void +_cygtls::interrupt_setup (siginfo_t& si, void *handler, struct sigaction& siga) +{ + push ((__tlsstack_t) sigdelayed); + _interrupt_setup(si, handler, siga); +} + extern "C" void set_sig_errno (int e) { @@ -1097,7 +1174,13 @@ sigpacket::setup_handler (void *handler, struct sigaction& siga, _cygtls *tls) ResumeThread (hth); goto out; } +#ifdef __aarch64__ + /* Since the non-incyg codepath uses a C++ version of sigdelayed, we have to preserve + the FPU registers within the thread context now. */ + cx.ContextFlags = CONTEXT_FULL; +#else cx.ContextFlags = CONTEXT_CONTROL | CONTEXT_INTEGER; +#endif if (!GetThreadContext (hth, &cx)) sigproc_printf ("couldn't get context of thread, %E"); else diff --git a/winsup/cygwin/local_includes/cygtls.h b/winsup/cygwin/local_includes/cygtls.h index 0b5255495..1ad98c844 100644 --- a/winsup/cygwin/local_includes/cygtls.h +++ b/winsup/cygwin/local_includes/cygtls.h @@ -39,7 +39,13 @@ details. */ #include "thread.h" #endif +#ifdef __aarch64__ +/* This allows us to use the CONTEXT struct in _cygtls without + violating alignment rules. */ +#pragma pack(push,16) +#else #pragma pack(push,8) +#endif /* Defined here to support auto rebuild of tlsoffsets.h. */ class tls_pathbuf @@ -191,6 +197,9 @@ public: /* Do NOT remove this public: line, it's a marker for gentls_offsets. */ aligned. The gentls_offsets script checks for that now and fails if the alignment is wrong. */ ucontext_t context; +#ifdef __aarch64__ + CONTEXT sigdelayed_context; +#endif DWORD thread_id; siginfo_t infodata; struct pthread *tid; @@ -229,7 +238,11 @@ public: /* Do NOT remove this public: line, it's a marker for gentls_offsets. */ return initialized == CYGTLS_INITIALIZED; } bool interrupt_now (CONTEXT *, siginfo_t&, void *, struct sigaction&); + void _interrupt_setup (siginfo_t&, void *, struct sigaction&); void interrupt_setup (siginfo_t&, void *, struct sigaction&); +#ifdef __aarch64__ + void sigdelayed_non_incyg(); +#endif bool inside_kernel (CONTEXT *, bool inside_cygwin = false); void signal_debugger (siginfo_t&); -- 2.51.0