[PATCH v3 2/2] aarch64: Add memory tagging support for setjmp/longjmp routines

claudiu.zissulescu-ianculescu@oracle.com claudiu.zissulescu-ianculescu@oracle.com
Tue Mar 10 14:11:07 GMT 2026


From: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>

When we perform stack sanitization using AArch64 memory tagging
instructions, there may be instances where the tagged memory stack is
not properly cleaned. Consequently, any further use of this memory
could lead to false tag mismatch exceptions.

One situation where this can occur is when a sanitized function
terminates exceptionally, as in the case of a longjmp. Therefore, when
executing a longjmp, it's essential to clean the tagged stack memory.

This patch modifies the longjmp implementation by adding stack cleanup
operations, a corresponding test, and necessary configuration
adjustments to check if stack sanitization using memory tagging is
supported by the compiler.

This patch relies on the USE_MTAG macro, which must be defined through
the --enable-memory-tagging option.

Signed-off-by: Claudiu Zissulescu <claudiu.zissulescu-ianculescu@oracle.com>
---
 configure                       |  36 +++++++++++
 configure.ac                    |  17 +++++
 sysdeps/aarch64/Makefile        |   9 +++
 sysdeps/aarch64/__longjmp.S     |  33 ++++++++++
 sysdeps/aarch64/tst-mte-jmp.c   | 108 ++++++++++++++++++++++++++++++++
 sysdeps/aarch64/tst-mte-stack.c |  74 ++++++++++++++++++++++
 6 files changed, 277 insertions(+)
 create mode 100644 sysdeps/aarch64/tst-mte-jmp.c
 create mode 100644 sysdeps/aarch64/tst-mte-stack.c

diff --git a/configure b/configure
index 0841355583..ddb2afd381 100755
--- a/configure
+++ b/configure
@@ -9406,6 +9406,42 @@ have-libgcc_s = $libc_cv_have_libgcc_s"
 
 
 
+# Determine whether the compiler can do stack sanitization using
+# memtags.
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: checking for -fsanitize=memtag-stack" >&5
+printf %s "checking for -fsanitize=memtag-stack... " >&6; }
+if test ${libc_cv_memtag_stack+y}
+then :
+  printf %s "(cached) " >&6
+else case e in #(
+  e) libc_cv_memtag_stack=no
+if test "$memory_tagging" = yes; then
+  # Only available on architectures that support it.
+  case $host_cpu in
+    aarch64)
+      if { ac_try='${CC-cc} $CFLAGS $CPPFLAGS -Werror -fsanitize=memtag-stack -march=armv8.5-a+memtag -xc /dev/null -S -o /dev/null'
+  { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$ac_try\""; } >&5
+  (eval $ac_try) 2>&5
+  ac_status=$?
+  printf "%s\n" "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5
+  test $ac_status = 0; }; }
+then :
+  libc_cv_memtag_stack=yes
+else case e in #(
+  e) libc_cv_memtag_stack=no ;;
+esac
+fi
+      ;;
+  esac
+fi
+ ;;
+esac
+fi
+{ printf "%s\n" "$as_me:${as_lineno-$LINENO}: result: $libc_cv_memtag_stack" >&5
+printf "%s\n" "$libc_cv_memtag_stack" >&6; }
+config_vars="$config_vars
+have-memtag-sanitizer = $libc_cv_memtag_stack"
+
 enable_gsframe=no
 if test $use_sframe = yes; then
   # SFrame requires to be explicit enabled by the architecture
diff --git a/configure.ac b/configure.ac
index ea81b0ea62..cb74188bf9 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2148,6 +2148,23 @@ LIBC_CONFIG_VAR([have-libgcc_s], [$libc_cv_have_libgcc_s])
 AC_SUBST(libc_cv_test_cc_mprefer_vector_width)
 AC_SUBST(test_enable_cet)
 
+# Determine whether the compiler can do stack sanitization using
+# memtags.
+AC_CACHE_CHECK(for -fsanitize=memtag-stack, libc_cv_memtag_stack, [dnl
+libc_cv_memtag_stack=no
+if test "$memory_tagging" = yes; then
+  # Only available on architectures that support it.
+  case $host_cpu in
+    aarch64)
+      LIBC_TRY_CC_OPTION([$CFLAGS $CPPFLAGS -Werror -fsanitize=memtag-stack -march=armv8.5-a+memtag],
+		     [libc_cv_memtag_stack=yes],
+		     [libc_cv_memtag_stack=no])
+      ;;
+  esac
+fi
+])
+LIBC_CONFIG_VAR([have-memtag-sanitizer], [$libc_cv_memtag_stack])
+
 enable_gsframe=no
 if test $use_sframe = yes; then
   # SFrame requires to be explicit enabled by the architecture
diff --git a/sysdeps/aarch64/Makefile b/sysdeps/aarch64/Makefile
index dfbc33d2f4..fcb3c60022 100644
--- a/sysdeps/aarch64/Makefile
+++ b/sysdeps/aarch64/Makefile
@@ -86,6 +86,15 @@ tests-internal += \
   # tests-internal
 
 $(objpfx)tst-sme-clone3: $(objpfx)clone3.o $(objpfx)__arm_za_disable.o
+
+ifeq (yes,$(have-memtag-sanitizer))
+tests += tst-mte-jmp \
+	tst-mte-stack
+CFLAGS-tst-mte-jmp.c += -fsanitize=memtag-stack -march=armv8.5-a+memtag
+LDFLAGS-tst-mte-jmp += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync
+CFLAGS-tst-mte-stack.c += -fsanitize=memtag-stack -march=armv8.5-a+memtag -w
+LDFLAGS-tst-mte-stack += -Wl,-z,memtag-stack -Wl,-z,memtag-mode=sync
+endif
 endif
 
 ifeq ($(subdir),malloc)
diff --git a/sysdeps/aarch64/__longjmp.S b/sysdeps/aarch64/__longjmp.S
index c4c8d72e51..62849babb8 100644
--- a/sysdeps/aarch64/__longjmp.S
+++ b/sysdeps/aarch64/__longjmp.S
@@ -25,6 +25,11 @@
 
 ENTRY (__longjmp)
 
+#ifdef USE_MTAG
+	.arch armv8.5-a
+        .arch_extension memtag
+#endif
+
 #if IS_IN(libc)
 	/* Disable ZA state of SME in libc.a and libc.so, but not in ld.so.  */
 	CALL_LIBC_ARM_ZA_DISABLE
@@ -136,6 +141,34 @@ L(gcs_done):
 #ifdef PTR_DEMANGLE
 	PTR_DEMANGLE (x4, x4, x3)
 #endif
+
+#ifdef USE_MTAG
+#define count	x2
+#define tmp	x2
+
+	subps	count, x4, sp
+	beq	L(tag_done)
+	tbz	count, 4, L(tag_clean32)
+	stg	sp, [sp], 16
+L(tag_clean32):
+	tbz	count, 5, L(tag_clean64)
+	st2g	sp, [sp], 32
+L(tag_clean64):
+	lsr	count, count, 6
+	cbz	count, L(tag_done)
+
+	mov	tmp, sp
+L(tag_loop):
+	st2g	sp, [tmp], 32
+	st2g	sp, [tmp], 32
+	cmp	x4, tmp
+	bhi	L(tag_loop)
+L(tag_done):
+
+#undef count
+#undef tmp
+#endif
+
 	mov	sp, x4
 
 	/* longjmp_target probe takes 3 arguments, address of jump buffer
diff --git a/sysdeps/aarch64/tst-mte-jmp.c b/sysdeps/aarch64/tst-mte-jmp.c
new file mode 100644
index 0000000000..664d872d0d
--- /dev/null
+++ b/sysdeps/aarch64/tst-mte-jmp.c
@@ -0,0 +1,108 @@
+/* Test for longjmp when we use MTE stack sanitation.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <setjmp.h>
+#include <ucontext.h>
+#include <signal.h>
+
+#include <sys/auxv.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+static jmp_buf jumpBuffer;
+
+/* Exception handler for MTE memory fails.  */
+void
+handler (int nSig)
+{
+  /* We hit the exception.  Return error.  */
+  FAIL_EXIT1 ("MTE exception hit");
+}
+
+/* Register an exception handler.  */
+static void
+setHandler (void)
+{
+  signal (SIGSEGV, handler);
+}
+
+/* The longjmp call is an abnormal exit, thus it lets the stack memory
+   mte dirty.  No exception is thrown here.  */
+__attribute__((noinline))
+static void
+foo (void)
+{
+  volatile unsigned char x[15];
+  volatile unsigned char *ptr = &x[0];
+
+  for (int i = 0; i < sizeof (x); ++i)
+    ptr[i] = 0xcc;
+
+  longjmp (jumpBuffer, 1);  // simulate throw
+}
+
+
+/* Reads data, from a pointer of a given size.  No MTE sanitization
+   for this one, any dirty memory should trap here.  */
+__attribute__((noinline, no_sanitize("memtag-stack")))
+static void
+hwasan_read(char *p, int size) {
+  volatile char __attribute__ ((unused)) sink;
+  for (int i = 0; i < size; ++i)
+    sink = p[i];
+}
+
+/* Creates a local array, large enough to include any previously tag
+   dirty stack memory.  */
+__attribute__((noinline, no_sanitize("memtag-stack")))
+static void
+after_catch (void)
+{
+  char x[10000];
+  hwasan_read (&x[0], sizeof(x));
+}
+
+__attribute__((noinline))
+static void f (void) {
+  if (setjmp (jumpBuffer) == 0) {
+    foo ();
+  } else {
+    after_catch ();
+  }
+}
+
+static int
+do_test (void)
+{
+  unsigned long hwcap2;
+
+  hwcap2 = getauxval (AT_HWCAP2);
+  if ((hwcap2 & HWCAP2_MTE) == 0)
+    return EXIT_UNSUPPORTED;
+
+  setHandler ();
+  f ();
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/aarch64/tst-mte-stack.c b/sysdeps/aarch64/tst-mte-stack.c
new file mode 100644
index 0000000000..16777f0c80
--- /dev/null
+++ b/sysdeps/aarch64/tst-mte-stack.c
@@ -0,0 +1,74 @@
+/* Test for longjmp when we use MTE stack sanitation.
+   Copyright (C) 2026 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Lesser General Public
+   License as published by the Free Software Foundation; either
+   version 2.1 of the License, or (at your option) any later version.
+
+   The GNU C Library is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+   Lesser General Public License for more details.
+
+   You should have received a copy of the GNU Lesser General Public
+   License along with the GNU C Library; if not, see
+   <https://www.gnu.org/licenses/>.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ucontext.h>
+#include <signal.h>
+
+#include <sys/auxv.h>
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+/* Exception handler for MTE memory fails.  */
+void
+handler (int nSig)
+{
+  /* We hit the exception.  */
+  exit (0);
+}
+
+/* Register an exception handler.  */
+static void
+setHandler (void)
+{
+  signal (SIGSEGV, handler);
+}
+
+void  __attribute__((noinline))
+use (volatile unsigned char *ptr)
+{
+  ptr[0] = 0x41;
+  ptr[1] = 0x42;
+}
+
+static int
+do_test (void)
+{
+  volatile unsigned char array[15];
+  volatile unsigned char *ptr = &array[0];
+  unsigned long hwcap2;
+
+  hwcap2 = getauxval (AT_HWCAP2);
+  if ((hwcap2 & HWCAP2_MTE) == 0)
+    return EXIT_UNSUPPORTED;
+
+  setHandler();
+  use (ptr);
+
+  /* Write to memory beyond the 16 byte granule (offsest 0x10) MTE should
+     generate an exception If the offset is less than 0x10 no SIGSEGV will
+     occur.  */
+  ptr[0x10] = 0x55;
+  FAIL_EXIT1 ("MTE exception is not hit");
+}
+
+#include <support/test-driver.c>
-- 
2.53.0



More information about the Libc-alpha mailing list