Avoid a dlopen abort on an invalid TLS alignment

If the alignment of a TLS segment in a shared object is invalid, return
an error through dlerror() rather than aborting the process.

Bug: http://b/78026329
Test: bionic unit tests
Change-Id: I60e589ddd8ca897f485d55af089f08bd3ff5b1fa
diff --git a/libc/bionic/bionic_elf_tls.cpp b/libc/bionic/bionic_elf_tls.cpp
index 19efff2..691293d 100644
--- a/libc/bionic/bionic_elf_tls.cpp
+++ b/libc/bionic/bionic_elf_tls.cpp
@@ -41,24 +41,13 @@
 // Search for a TLS segment in the given phdr table. Returns true if it has a
 // TLS segment and false otherwise.
 bool __bionic_get_tls_segment(const ElfW(Phdr)* phdr_table, size_t phdr_count,
-                              ElfW(Addr) load_bias, const char* mod_name,
-                              TlsSegment* out) {
+                              ElfW(Addr) load_bias, TlsSegment* out) {
   for (size_t i = 0; i < phdr_count; ++i) {
     const ElfW(Phdr)& phdr = phdr_table[i];
     if (phdr.p_type == PT_TLS) {
-      // N.B. The size does not need to be a multiple of the alignment. With
-      // ld.bfd (or after using binutils' strip), the TLS segment's size isn't
-      // rounded up.
-      size_t alignment = phdr.p_align;
-      if (alignment == 0 || !powerof2(alignment)) {
-        async_safe_fatal("error: \"%s\": TLS segment alignment is not a power of 2: %zu",
-                         mod_name, alignment);
-      }
-      // Bionic only respects TLS alignment up to one page.
-      alignment = MIN(alignment, PAGE_SIZE);
       *out = TlsSegment {
         phdr.p_memsz,
-        alignment,
+        phdr.p_align,
         reinterpret_cast<void*>(load_bias + phdr.p_vaddr),
         phdr.p_filesz,
       };
@@ -68,6 +57,20 @@
   return false;
 }
 
+// Return true if the alignment of a TLS segment is a valid power-of-two. Also
+// cap the alignment if it's too high.
+bool __bionic_check_tls_alignment(size_t* alignment) {
+  // N.B. The size does not need to be a multiple of the alignment. With
+  // ld.bfd (or after using binutils' strip), the TLS segment's size isn't
+  // rounded up.
+  if (*alignment == 0 || !powerof2(*alignment)) {
+    return false;
+  }
+  // Bionic only respects TLS alignment up to one page.
+  *alignment = MIN(*alignment, PAGE_SIZE);
+  return true;
+}
+
 // Reserves space for the Bionic TCB and the executable's TLS segment. Returns
 // the offset of the executable's TLS segment.
 size_t StaticTlsLayout::reserve_exe_segment_and_tcb(const TlsSegment* exe_segment,
diff --git a/libc/bionic/libc_init_static.cpp b/libc/bionic/libc_init_static.cpp
index 1920727..8fbc20e 100644
--- a/libc/bionic/libc_init_static.cpp
+++ b/libc/bionic/libc_init_static.cpp
@@ -91,13 +91,17 @@
   ElfW(Phdr)* phdr_start = reinterpret_cast<ElfW(Phdr)*>(getauxval(AT_PHDR));
   size_t phdr_ct = getauxval(AT_PHNUM);
 
-  static TlsModule module;
-  if (__bionic_get_tls_segment(phdr_start, phdr_ct, 0, progname, &module.segment)) {
-    module.static_offset = layout.reserve_exe_segment_and_tcb(&module.segment, progname);
-    module.first_generation = 1;
+  static TlsModule mod;
+  if (__bionic_get_tls_segment(phdr_start, phdr_ct, 0, &mod.segment)) {
+    if (!__bionic_check_tls_alignment(&mod.segment.alignment)) {
+      async_safe_fatal("error: TLS segment alignment in \"%s\" is not a power of 2: %zu\n",
+                       progname, mod.segment.alignment);
+    }
+    mod.static_offset = layout.reserve_exe_segment_and_tcb(&mod.segment, progname);
+    mod.first_generation = 1;
     __libc_shared_globals()->tls_modules.generation = 1;
     __libc_shared_globals()->tls_modules.module_count = 1;
-    __libc_shared_globals()->tls_modules.module_table = &module;
+    __libc_shared_globals()->tls_modules.module_table = &mod;
   } else {
     layout.reserve_exe_segment_and_tcb(nullptr, progname);
   }