[PATCH v2 3/3] Linux: Add tests that check that TLS and rseq area are separate

Florian Weimer fweimer@redhat.com
Mon Jan 13 12:33:10 GMT 2025


The new test elf/tst-rseq-tls-range-4096-static reliably detected
the extra TLS allocation problem (tcb_offset was dropped from
the allocation size) on aarch64.  It also failed with a crash
in dlopen *before* the extra TLS changes, so TLS alignment with
static dlopen was already broken.
---
 sysdeps/unix/sysv/linux/Makefile              |  14 ++
 .../linux/tst-rseq-tls-range-4096-static.c    |   1 +
 .../unix/sysv/linux/tst-rseq-tls-range-4096.c |   1 +
 .../unix/sysv/linux/tst-rseq-tls-range-mod.c  |   1 +
 .../sysv/linux/tst-rseq-tls-range-static.c    |   1 +
 sysdeps/unix/sysv/linux/tst-rseq-tls-range.c  | 168 ++++++++++++++++++
 6 files changed, 186 insertions(+)
 create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c
 create mode 100644 sysdeps/unix/sysv/linux/tst-rseq-tls-range.c

diff --git a/sysdeps/unix/sysv/linux/Makefile b/sysdeps/unix/sysv/linux/Makefile
index c8d30cc405..395d2d6593 100644
--- a/sysdeps/unix/sysv/linux/Makefile
+++ b/sysdeps/unix/sysv/linux/Makefile
@@ -660,6 +660,20 @@ install-bin += \
 
 $(objpfx)pldd: $(objpfx)xmalloc.o
 
+tests += tst-rseq-tls-range tst-rseq-tls-range-4096
+tests-static += tst-rseq-tls-range-static tst-rseq-tls-range-4096-static
+modules-names += tst-rseq-tls-range-mod
+CFLAGS-tst-rseq-tls-range.c += -DMAIN_TLS_ALIGN=4
+CFLAGS-tst-rseq-tls-range-4096.c += -DMAIN_TLS_ALIGN=4096
+CFLAGS-tst-rseq-tls-range-static.c += -DMAIN_TLS_ALIGN=4
+CFLAGS-tst-rseq-tls-range-4096-static.c += -DMAIN_TLS_ALIGN=4096
+$(objpfx)tst-rseq-tls-range.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-4096.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-static.out: $(objpfx)tst-rseq-tls-range-mod.so
+$(objpfx)tst-rseq-tls-range-4096-static.out: $(objpfx)tst-rseq-tls-range-mod.so
+tst-rseq-tls-range-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
+tst-rseq-tls-range-4096-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
+
 test-internal-extras += tst-nolink-libc
 ifeq ($(run-built-tests),yes)
 tests-special += \
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096-static.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-4096.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c
new file mode 100644
index 0000000000..165e468eaa
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-mod.c
@@ -0,0 +1 @@
+__thread int mod_thread_var __attribute__ ((tls_model ("initial-exec")));
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c
new file mode 100644
index 0000000000..5ad2853847
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range-static.c
@@ -0,0 +1 @@
+#include "tst-rseq-tls-range.c"
diff --git a/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c b/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c
new file mode 100644
index 0000000000..5e9a2cd98f
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/tst-rseq-tls-range.c
@@ -0,0 +1,168 @@
+#include <array_length.h>
+#include <elf.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <sys/rseq.h>
+#include <thread_pointer.h>
+#include <unistd.h>
+
+/* Used to keep track of address ranges.  The ranges are sorted and
+   then checked for overlap.  */
+
+struct address_range
+{
+  const char *prefix;
+  const char *label;
+  uintptr_t start;
+  size_t length;
+};
+
+struct address_range ranges[20];
+size_t range_count;
+
+static void
+add_range (const char *prefix, const char *label,
+           const void *start, size_t length)
+{
+  TEST_VERIFY (start != NULL);
+  TEST_VERIFY (length > 0);
+  TEST_VERIFY_EXIT (range_count < array_length (ranges));
+  ranges[range_count].prefix = prefix;
+  ranges[range_count].label = label;
+  ranges[range_count].start = (uintptr_t) start;
+  ranges[range_count].length = length;
+  ++range_count;
+}
+
+static int
+range_compare (const void *a1, const void *b1)
+{
+  const struct address_range *a = a1;
+  const struct address_range *b = b1;
+  if (a->start < b->start)
+    return -1;
+  if (a->start > b->start)
+    return 1;
+  return 0;
+}
+
+static void
+check_for_overlap (void)
+{
+  qsort (ranges, range_count, sizeof (ranges[0]), range_compare);
+  uintptr_t previous_end = ranges[0].start + ranges[0].length - 1;
+  for (size_t i = 1; i < range_count; ++i)
+    {
+      uintptr_t this_end = ranges[i].start + ranges[i].length - 1;
+      if (ranges[i].start <= previous_end)
+        {
+          puts ("error: overlap between address ranges");
+          printf ("  %s%s: [0x%lx, 0x%lx)\n",
+                  ranges[i - 1].prefix, ranges[i - 1].label,
+                  (unsigned long int) ranges[i - 1].start,
+                  (unsigned long int) previous_end);
+          printf ("  %s%s: [0x%lx, 0x%lx)\n",
+                  ranges[i].prefix, ranges[i].label,
+                  (unsigned long int) ranges[i].start,
+                  (unsigned long int) this_end);
+        }
+      previous_end = this_end;
+    }
+}
+
+static void
+add_rseq (void)
+{
+  if (__rseq_size > 0)
+    add_range ("", "rseq area",
+               (char *) __thread_pointer () + __rseq_offset, __rseq_size);
+}
+
+/* These functions add the TLS data for all loaded modules to the
+   recorded address ranges.  */
+
+static int
+dlip_callback (struct dl_phdr_info *info, size_t size, void *ignored)
+{
+  /* If the dynamic linker does not provide TLS address information,
+     there is nothing to register.  */
+  if (info->dlpi_tls_data == NULL)
+    return 0;
+
+  for (int i = 0; i < info->dlpi_phnum; ++i)
+    {
+      if (info->dlpi_phdr[i].p_type == PT_TLS)
+        {
+          printf ("info: adding TLS range for \"%s\" (%zu bytes)\n",
+                  info->dlpi_name, (size_t) info->dlpi_phdr[i].p_memsz);
+          add_range ("TLS for ",
+                     info->dlpi_name[0] != '\0' ? info->dlpi_name : "main",
+                     info->dlpi_tls_data, info->dlpi_phdr[i].p_memsz);
+          break;
+        }
+    }
+  return 0;
+}
+
+/* Returns true if any TLS ranges were found.  */
+static void
+add_tls_ranges (void)
+{
+  dl_iterate_phdr (dlip_callback, NULL);
+}
+
+volatile __thread int thread_var __attribute__ ((aligned (MAIN_TLS_ALIGN)));
+
+static int
+do_test (void)
+{
+  void *original_brk = sbrk (0);
+  void *initial_allocation = malloc (16);
+
+  /* Ensure that the variable is not optimized away.  */
+  thread_var = 0;
+
+  printf ("info: rseq area size: %u\n", __rseq_size);
+
+  puts ("info: checking address ranges with initially loaded modules");
+  add_range ("", "program break", original_brk, 1);
+  add_range ("", "malloc allocation", initial_allocation, 16);
+  add_rseq ();
+  add_tls_ranges ();
+  printf ("info: %zu ranges found\n", range_count);
+  check_for_overlap ();
+  range_count = 0;
+
+  puts ("info: checking address ranges after dlopen");
+  void *handle = xdlopen ("tst-rseq-tls-range-mod.so", RTLD_NOW);
+  int *mod_thread_var = xdlsym (handle, "mod_thread_var");
+  add_range ("", "program break", original_brk, 1);
+  add_range ("", "malloc allocation", initial_allocation, 16);
+  add_rseq ();
+  add_tls_ranges ();
+  {
+    bool found_objects = false;
+    for (size_t i = 0; i < range_count; ++i)
+      if (strchr (ranges[i].label, '/') != NULL)
+        found_objects = true;
+    if (!found_objects)
+      /* __tls_get_addr does not fully work with static dlopen.
+         Add some fall-back test data.  */
+      add_range ("", "mod_thread_var",
+                 mod_thread_var, sizeof (*mod_thread_var));
+  }
+  printf ("info: %zu ranges found\n", range_count);
+  check_for_overlap ();
+  xdlclose (handle);
+
+  free (initial_allocation);
+  return 0;
+}
+
+#include <support/test-driver.c>
-- 
2.47.1



More information about the Libc-alpha mailing list