Просмотр исходного кода

ldso: make the _dl_tls_get_addr_soft DTV check microblaze-specific

_dl_tls_get_addr_soft serves dl_iterate_phdr, which fills
dl_phdr_info::dlpi_tls_data for every caller.  In practice that field is
only consumed by libsanitizer, called from a normal thread where the
thread pointer - and thus THREAD_DTV() - is valid, so the DTV is fine.
The trouble is that dl_iterate_phdr fills dlpi_tls_data unconditionally,
including for the DWARF unwinder that walks the stack during pthread
cancellation and never looks at the field.

Commit d8ae8c688 ("ldso: harden _dl_tls_get_addr_soft against an
unusable DTV") rejects a bogus DTV pointer for every target with
"(uintptr_t) dtv < 4096".  The problem it works around is in fact
microblaze-specific: microblaze keeps the thread pointer in r21, an
ordinary callee-saved GPR that is not globally reserved, so low-level
asm on the stack (libgcc's unwinder arithmetic, libc helpers) can leave
a scratch value in r21 before calling into dl_iterate_phdr.  THREAD_DTV()
then returns a bogus pointer whose value is well above 4096 (e.g. 0x15c3)
and gets dereferenced -> SIGSEGV during pthread cancellation.

Every other target uses a reserved thread pointer, so THREAD_DTV() is
reliable there and a plain NULL check is enough.  Gate the plausibility
check under __microblaze__ and, while there, tighten it: a real DTV is a
heap-allocated, aligned object, so also require the pointer to be above
0x10000 and aligned to dtv_t.  This is not foolproof - an aligned scratch
value above 0x10000 would still slip through - but it catches the values
seen in practice without imposing the magnitude assumption (which would
be wrong for low-loaded noMMU images) on other architectures.

Signed-off-by: Ramin Moussavi <ramin.moussavi@yacoub.de>
ramin 2 дней назад
Родитель
Сommit
26063ed16a
1 измененных файлов с 13 добавлено и 7 удалено
  1. 13 7
      ldso/ldso/dl-tls.c

+ 13 - 7
ldso/ldso/dl-tls.c

@@ -900,15 +900,21 @@ _dl_tls_get_addr_soft (struct link_map *map)
   if (map->l_tls_modid == 0)
     return NULL;
 
-  /* This is called from dl_iterate_phdr to fill dl_phdr_info::dlpi_tls_data,
-     which the unwinder invokes during stack unwinding (e.g. pthread
-     cancellation).  In such contexts the calling thread may not have a
-     usable thread pointer / DTV yet (or any more), so THREAD_DTV() can
-     come back as a near-null bogus pointer.  dlpi_tls_data is optional,
-     so bail out instead of dereferencing it.  */
   dtv = THREAD_DTV ();
-  if (dtv == NULL || (uintptr_t) dtv < 4096)
+#if defined(__microblaze__)
+  /* microblaze keeps the thread pointer in r21, a non-reserved GPR, so when
+     the unwinder calls dl_iterate_phdr THREAD_DTV() can return a bogus
+     pointer.  Reject anything that does not look like a real (high, aligned)
+     DTV.  Not foolproof: an aligned scratch value above 0x10000 still slips
+     through.  Other targets use a reserved thread pointer; the NULL check
+     suffices there.  */
+  if (dtv == NULL || (uintptr_t) dtv < 0x10000
+      || ((uintptr_t) dtv & (__alignof__ (dtv_t) - 1)) != 0)
     return NULL;
+#else
+  if (dtv == NULL)
+    return NULL;
+#endif
   if (map->l_tls_modid > (size_t) dtv[-1].counter)
     return NULL;