https://gcc.gnu.org/g:b536ed6ad7a504fb8c0da8f9197a8b67be290b73

commit r17-5537-gb536ed6ad7a504fb8c0da8f9197a8b67be290b73
Author: Matt Turner <[email protected]>
Date:   Sat Oct 24 08:09:33 2026 -0600

    [PATCH 2/2] ia64: Make ia64_copy_rbs take its length in bytes
    
    When the unwinder steps out of a signal frame whose handler ran on an
    alternate stack, it copies the handler's dirty register backing store
    back to the interrupted thread's own.  The kernel's signal trampoline
    stores the size of that region in bytes, shifted left by 16, in
    sc_loadrs, and both Linux callers use it as a byte count when computing
    alt_bspstore.  They then pass the same value to ia64_copy_rbs, which
    takes its length in 8-byte words, so eight times the dirty region is
    copied.
    
    The excess is read from beyond the end of the alternate backing store,
    and written beyond the end of the thread's.  In a thread, the memory
    stack grows down towards that point, so unwinding through such a frame
    (for example, with _Unwind_Backtrace from the handler) overwrites live
    stack data or faults.
    
    All callers have a byte count, so make ia64_copy_rbs take one.  This
    also fixes the same mistake in ia64_vms_fallback_frame_state, which
    passes q_bsp - q_bspstore.  That copy only happens when bit 16 of the
    EH_DEBUG logical name is set, and I can't build or test for VMS.
    
    gcc.dg/cleanup-10.c and cleanup-11.c pass with either this patch or the
    previous one; the new test needs both.
    
    libgcc/ChangeLog:
    
            * config/ia64/unwind-ia64.c (ia64_copy_rbs): Take LEN in bytes,
            not words.
    
    gcc/testsuite/ChangeLog:
    
            * gcc.target/ia64/sigaltstack-unwind-1.c: New test.

Diff:
---
 .../gcc.target/ia64/sigaltstack-unwind-1.c         | 123 +++++++++++++++++++++
 libgcc/config/ia64/unwind-ia64.c                   |   3 +-
 2 files changed, 124 insertions(+), 2 deletions(-)

diff --git a/gcc/testsuite/gcc.target/ia64/sigaltstack-unwind-1.c 
b/gcc/testsuite/gcc.target/ia64/sigaltstack-unwind-1.c
new file mode 100644
index 000000000000..8c2a4ca0615b
--- /dev/null
+++ b/gcc/testsuite/gcc.target/ia64/sigaltstack-unwind-1.c
@@ -0,0 +1,123 @@
+/* Unwinding out of a signal handler that runs on an alternate stack must
+   copy the handler's register backing store back to the thread's own,
+   and nothing more.  In a thread, the memory stack grows down towards the
+   top of the register backing store, so writing past the end of the copy
+   corrupts the memory stack.  */
+/* { dg-do run { target *-*-linux* } } */
+/* { dg-require-effective-target pthread } */
+/* { dg-options "-O1 -pthread" } */
+
+#include <limits.h>
+#include <pthread.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <unwind.h>
+
+static _Unwind_Reason_Code
+trace (struct _Unwind_Context *ctx, void *arg)
+{
+  return _URC_NO_REASON;
+}
+
+static void
+handler (int sig)
+{
+  _Unwind_Backtrace (trace, 0);
+}
+
+/* Fill the memory stack down to 1 KB above the register stack, raise the
+   signal, and check that the filled memory survived.  raise needs some
+   of the gap, but a larger one can hide copying too much.  */
+static int __attribute__ ((noinline))
+leaf (void)
+{
+  char here;
+  size_t size = &here - (char *) __builtin_ia64_bsp () - 1024;
+  volatile unsigned char *buf = __builtin_alloca (size);
+  size_t i;
+
+  memset ((void *) buf, 0x5a, size);
+  raise (SIGUSR1);
+  for (i = 0; i < size; i++)
+    if (buf[i] != 0x5a)
+      return 0;
+  return 1;
+}
+
+/* Recurse N times before raising the signal, and check that the frames
+   are intact afterwards.  */
+static long __attribute__ ((noinline))
+deep (int n)
+{
+  volatile long a = n, b = n * 2, c = n * 3, d = n * 4;
+  long r;
+
+  if (n == 0)
+    return leaf () ? 0 : -1;
+  r = deep (n - 1);
+  if (r < 0)
+    return r;
+  return r + a + b + c + d;
+}
+
+static stack_t alt;
+
+static void *
+thread_fn (void *arg)
+{
+  int depth = *(int *) arg;
+  long want = 0;
+  int n;
+
+  for (n = 1; n <= depth; n++)
+    want += 10L * n;
+  if (sigaltstack (&alt, 0) != 0)
+    abort ();
+  if (deep (depth) != want)
+    abort ();
+  return 0;
+}
+
+int
+main (void)
+{
+  /* The depths move the thread's backing store relative to its NaT
+     collections, so that the copy ends in either of the two runs that
+     ia64_copy_rbs copies between them.  */
+  static int depths[] = { 0, 5, 10, 20, 40, 80 };
+  size_t page = sysconf (_SC_PAGESIZE);
+  size_t stack_size = (PTHREAD_STACK_MIN + page - 1) & ~(page - 1);
+  size_t alt_size = 1 << 20;
+  char *map;
+  struct sigaction sa;
+  pthread_attr_t attr;
+  pthread_t t;
+  unsigned i;
+
+  map = mmap (0, stack_size + alt_size, PROT_READ | PROT_WRITE,
+             MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+  if (map == MAP_FAILED)
+    abort ();
+  alt.ss_sp = map + stack_size;
+  alt.ss_size = alt_size;
+
+  memset (&sa, 0, sizeof sa);
+  sa.sa_handler = handler;
+  sa.sa_flags = SA_ONSTACK;
+  if (sigaction (SIGUSR1, &sa, 0) != 0)
+    abort ();
+
+  if (pthread_attr_init (&attr) != 0
+      || pthread_attr_setstack (&attr, map, stack_size) != 0)
+    abort ();
+  for (i = 0; i < sizeof depths / sizeof depths[0]; i++)
+    {
+      if (pthread_create (&t, &attr, thread_fn, &depths[i]) != 0)
+       abort ();
+      pthread_join (t, 0);
+    }
+  return 0;
+}
diff --git a/libgcc/config/ia64/unwind-ia64.c b/libgcc/config/ia64/unwind-ia64.c
index 45359a36a168..3207cd2034fe 100644
--- a/libgcc/config/ia64/unwind-ia64.c
+++ b/libgcc/config/ia64/unwind-ia64.c
@@ -1491,7 +1491,7 @@ ia64_rse_skip_regs (unw_word *addr, int num_regs)
 }
 
 
-/* Copy register backing store from SRC to DST, LEN words
+/* Copy register backing store from SRC to DST, LEN bytes
    (which include both saved registers and nat collections).
    DST_RNAT is a partial nat collection for DST.  SRC and DST
    don't have to be equal modulo 64 slots, so it cannot be
@@ -1505,7 +1505,6 @@ ia64_copy_rbs (struct _Unwind_Context *info, unw_word dst,
   unw_word src_rnat;
   unw_word shift1, shift2;
 
-  len <<= 3;
   dst_rnat &= (1ULL << ((dst >> 3) & 0x3f)) - 1;
   src_rnat = src >= info->regstk_top
             ? info->rnat : *(unw_word *) (src | 0x1f8);

Reply via email to