You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
510 lines
17 KiB
510 lines
17 KiB
commit ede8d94d154157d269b18f3601440ac576c1f96a |
|
Author: Florian Weimer <fweimer@redhat.com> |
|
Date: Mon May 16 18:41:43 2022 +0200 |
|
|
|
csu: Implement and use _dl_early_allocate during static startup |
|
|
|
This implements mmap fallback for a brk failure during TLS |
|
allocation. |
|
|
|
scripts/tls-elf-edit.py is updated to support the new patching method. |
|
The script no longer requires that in the input object is of ET_DYN |
|
type. |
|
|
|
Reviewed-by: Adhemerval Zanella <adhemerval.zanella@linaro.org> |
|
(cherry picked from commit f787e138aa0bf677bf74fa2a08595c446292f3d7) |
|
|
|
Conflicts: |
|
elf/Makefile |
|
(missing ld.so static execve backport upstream) |
|
sysdeps/generic/ldsodefs.h |
|
(missing ld.so dependency sorting optimization upstream) |
|
|
|
diff --git a/csu/libc-tls.c b/csu/libc-tls.c |
|
index d83e69f6257ae981..738f59f46b62c31c 100644 |
|
--- a/csu/libc-tls.c |
|
+++ b/csu/libc-tls.c |
|
@@ -145,11 +145,16 @@ __libc_setup_tls (void) |
|
_dl_allocate_tls_storage (in elf/dl-tls.c) does using __libc_memalign |
|
and dl_tls_static_align. */ |
|
tcb_offset = roundup (memsz + GLRO(dl_tls_static_surplus), max_align); |
|
- tlsblock = __sbrk (tcb_offset + TLS_INIT_TCB_SIZE + max_align); |
|
+ tlsblock = _dl_early_allocate (tcb_offset + TLS_INIT_TCB_SIZE + max_align); |
|
+ if (tlsblock == NULL) |
|
+ _startup_fatal ("Fatal glibc error: Cannot allocate TLS block\n"); |
|
#elif TLS_DTV_AT_TP |
|
tcb_offset = roundup (TLS_INIT_TCB_SIZE, align ?: 1); |
|
- tlsblock = __sbrk (tcb_offset + memsz + max_align |
|
- + TLS_PRE_TCB_SIZE + GLRO(dl_tls_static_surplus)); |
|
+ tlsblock = _dl_early_allocate (tcb_offset + memsz + max_align |
|
+ + TLS_PRE_TCB_SIZE |
|
+ + GLRO(dl_tls_static_surplus)); |
|
+ if (tlsblock == NULL) |
|
+ _startup_fatal ("Fatal glibc error: Cannot allocate TLS block\n"); |
|
tlsblock += TLS_PRE_TCB_SIZE; |
|
#else |
|
/* In case a model with a different layout for the TCB and DTV |
|
diff --git a/elf/Makefile b/elf/Makefile |
|
index 6423ebbdd7708a14..ea1512549be3f628 100644 |
|
--- a/elf/Makefile |
|
+++ b/elf/Makefile |
|
@@ -33,6 +33,7 @@ routines = \ |
|
$(all-dl-routines) \ |
|
dl-addr \ |
|
dl-addr-obj \ |
|
+ dl-early_allocate \ |
|
dl-error \ |
|
dl-iteratephdr \ |
|
dl-libc \ |
|
@@ -104,6 +105,7 @@ all-dl-routines = $(dl-routines) $(sysdep-dl-routines) |
|
# But they are absent from the shared libc, because that code is in ld.so. |
|
elide-routines.os = \ |
|
$(all-dl-routines) \ |
|
+ dl-early_allocate \ |
|
dl-exception \ |
|
dl-origin \ |
|
dl-reloc-static-pie \ |
|
@@ -264,6 +266,7 @@ tests-static-normal := \ |
|
tst-linkall-static \ |
|
tst-single_threaded-pthread-static \ |
|
tst-single_threaded-static \ |
|
+ tst-tls-allocation-failure-static \ |
|
tst-tlsalign-extern-static \ |
|
tst-tlsalign-static \ |
|
# tests-static-normal |
|
@@ -1101,6 +1104,10 @@ $(objpfx)tst-glibcelf.out: tst-glibcelf.py elf.h $(..)/scripts/glibcelf.py \ |
|
--cc="$(CC) $(patsubst -DMODULE_NAME=%,-DMODULE_NAME=testsuite,$(CPPFLAGS))" \ |
|
< /dev/null > $@ 2>&1; $(evaluate-test) |
|
|
|
+ifeq ($(run-built-tests),yes) |
|
+tests-special += $(objpfx)tst-tls-allocation-failure-static-patched.out |
|
+endif |
|
+ |
|
# The test requires shared _and_ PIE because the executable |
|
# unit test driver must be able to link with the shared object |
|
# that is going to eventually go into an installed DSO. |
|
@@ -2637,3 +2644,15 @@ $(objpfx)tst-ro-dynamic-mod.so: $(objpfx)tst-ro-dynamic-mod.os \ |
|
$(objpfx)tst-ro-dynamic-mod.os |
|
|
|
$(objpfx)tst-rtld-run-static.out: $(objpfx)/ldconfig |
|
+ |
|
+$(objpfx)tst-tls-allocation-failure-static-patched: \ |
|
+ $(objpfx)tst-tls-allocation-failure-static $(..)scripts/tst-elf-edit.py |
|
+ cp $< $@ |
|
+ $(PYTHON) $(..)scripts/tst-elf-edit.py --maximize-tls-size $@ |
|
+ |
|
+$(objpfx)tst-tls-allocation-failure-static-patched.out: \ |
|
+ $(objpfx)tst-tls-allocation-failure-static-patched |
|
+ $< > $@ 2>&1; echo "status: $$?" >> $@ |
|
+ grep -q '^Fatal glibc error: Cannot allocate TLS block$$' $@ \ |
|
+ && grep -q '^status: 127$$' $@; \ |
|
+ $(evaluate-test) |
|
diff --git a/elf/dl-early_allocate.c b/elf/dl-early_allocate.c |
|
new file mode 100644 |
|
index 0000000000000000..61677aaa0364c209 |
|
--- /dev/null |
|
+++ b/elf/dl-early_allocate.c |
|
@@ -0,0 +1,30 @@ |
|
+/* Early memory allocation for the dynamic loader. Generic version. |
|
+ Copyright (C) 2022 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 <ldsodefs.h> |
|
+#include <stddef.h> |
|
+#include <unistd.h> |
|
+ |
|
+void * |
|
+_dl_early_allocate (size_t size) |
|
+{ |
|
+ void *result = __sbrk (size); |
|
+ if (result == (void *) -1) |
|
+ result = NULL; |
|
+ return result; |
|
+} |
|
diff --git a/elf/tst-tls-allocation-failure-static.c b/elf/tst-tls-allocation-failure-static.c |
|
new file mode 100644 |
|
index 0000000000000000..8de831b2469ba390 |
|
--- /dev/null |
|
+++ b/elf/tst-tls-allocation-failure-static.c |
|
@@ -0,0 +1,31 @@ |
|
+/* Base for test program with impossiblyh large PT_TLS segment. |
|
+ Copyright (C) 2022 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/>. */ |
|
+ |
|
+/* The test actual binary is patched using scripts/tst-elf-edit.py |
|
+ --maximize-tls-size, and this introduces the expected test |
|
+ allocation failure due to an excessive PT_LS p_memsz value. |
|
+ |
|
+ Patching the binary is required because on some 64-bit targets, TLS |
|
+ relocations can only cover a 32-bit range, and glibc-internal TLS |
|
+ variables such as errno end up outside that range. */ |
|
+ |
|
+int |
|
+main (void) |
|
+{ |
|
+ return 0; |
|
+} |
|
diff --git a/scripts/tst-elf-edit.py b/scripts/tst-elf-edit.py |
|
new file mode 100644 |
|
index 0000000000000000..0e19ce1e7392f3ca |
|
--- /dev/null |
|
+++ b/scripts/tst-elf-edit.py |
|
@@ -0,0 +1,226 @@ |
|
+#!/usr/bin/python3 |
|
+# ELF editor for load align tests. |
|
+# Copyright (C) 2022 Free Software Foundation, Inc. |
|
+# Copyright The GNU Toolchain Authors. |
|
+# 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/>. |
|
+ |
|
+import argparse |
|
+import os |
|
+import sys |
|
+import struct |
|
+ |
|
+EI_NIDENT=16 |
|
+ |
|
+EI_MAG0=0 |
|
+ELFMAG0=b'\x7f' |
|
+EI_MAG1=1 |
|
+ELFMAG1=b'E' |
|
+EI_MAG2=2 |
|
+ELFMAG2=b'L' |
|
+EI_MAG3=3 |
|
+ELFMAG3=b'F' |
|
+ |
|
+EI_CLASS=4 |
|
+ELFCLASSNONE=b'0' |
|
+ELFCLASS32=b'\x01' |
|
+ELFCLASS64=b'\x02' |
|
+ |
|
+EI_DATA=5 |
|
+ELFDATA2LSB=b'\x01' |
|
+ELFDATA2MSB=b'\x02' |
|
+ |
|
+ET_EXEC=2 |
|
+ET_DYN=3 |
|
+ |
|
+PT_LOAD=1 |
|
+PT_TLS=7 |
|
+ |
|
+def elf_types_fmts(e_ident): |
|
+ endian = '<' if e_ident[EI_DATA] == ELFDATA2LSB else '>' |
|
+ addr = 'I' if e_ident[EI_CLASS] == ELFCLASS32 else 'Q' |
|
+ off = 'I' if e_ident[EI_CLASS] == ELFCLASS32 else 'Q' |
|
+ return (endian, addr, off) |
|
+ |
|
+class Elf_Ehdr: |
|
+ def __init__(self, e_ident): |
|
+ endian, addr, off = elf_types_fmts(e_ident) |
|
+ self.fmt = '{0}HHI{1}{2}{2}IHHHHHH'.format(endian, addr, off) |
|
+ self.len = struct.calcsize(self.fmt) |
|
+ |
|
+ def read(self, f): |
|
+ buf = f.read(self.len) |
|
+ if not buf: |
|
+ error('{}: header too small'.format(f.name)) |
|
+ data = struct.unpack(self.fmt, buf) |
|
+ self.e_type = data[0] |
|
+ self.e_machine = data[1] |
|
+ self.e_version = data[2] |
|
+ self.e_entry = data[3] |
|
+ self.e_phoff = data[4] |
|
+ self.e_shoff = data[5] |
|
+ self.e_flags = data[6] |
|
+ self.e_ehsize = data[7] |
|
+ self.e_phentsize= data[8] |
|
+ self.e_phnum = data[9] |
|
+ self.e_shstrndx = data[10] |
|
+ |
|
+ |
|
+class Elf_Phdr: |
|
+ def __init__(self, e_ident): |
|
+ endian, addr, off = elf_types_fmts(e_ident) |
|
+ self.ei_class = e_ident[EI_CLASS] |
|
+ if self.ei_class == ELFCLASS32: |
|
+ self.fmt = '{0}I{2}{1}{1}IIII'.format(endian, addr, off) |
|
+ else: |
|
+ self.fmt = '{0}II{2}{1}{1}QQQ'.format(endian, addr, off) |
|
+ self.len = struct.calcsize(self.fmt) |
|
+ |
|
+ def read(self, f): |
|
+ buf = f.read(self.len) |
|
+ if len(buf) < self.len: |
|
+ error('{}: program header too small'.format(f.name)) |
|
+ data = struct.unpack(self.fmt, buf) |
|
+ if self.ei_class == ELFCLASS32: |
|
+ self.p_type = data[0] |
|
+ self.p_offset = data[1] |
|
+ self.p_vaddr = data[2] |
|
+ self.p_paddr = data[3] |
|
+ self.p_filesz = data[4] |
|
+ self.p_memsz = data[5] |
|
+ self.p_flags = data[6] |
|
+ self.p_align = data[7] |
|
+ else: |
|
+ self.p_type = data[0] |
|
+ self.p_flags = data[1] |
|
+ self.p_offset = data[2] |
|
+ self.p_vaddr = data[3] |
|
+ self.p_paddr = data[4] |
|
+ self.p_filesz = data[5] |
|
+ self.p_memsz = data[6] |
|
+ self.p_align = data[7] |
|
+ |
|
+ def write(self, f): |
|
+ if self.ei_class == ELFCLASS32: |
|
+ data = struct.pack(self.fmt, |
|
+ self.p_type, |
|
+ self.p_offset, |
|
+ self.p_vaddr, |
|
+ self.p_paddr, |
|
+ self.p_filesz, |
|
+ self.p_memsz, |
|
+ self.p_flags, |
|
+ self.p_align) |
|
+ else: |
|
+ data = struct.pack(self.fmt, |
|
+ self.p_type, |
|
+ self.p_flags, |
|
+ self.p_offset, |
|
+ self.p_vaddr, |
|
+ self.p_paddr, |
|
+ self.p_filesz, |
|
+ self.p_memsz, |
|
+ self.p_align) |
|
+ f.write(data) |
|
+ |
|
+ |
|
+def error(msg): |
|
+ print(msg, file=sys.stderr) |
|
+ sys.exit(1) |
|
+ |
|
+ |
|
+def elf_edit_align(phdr, align): |
|
+ if align == 'half': |
|
+ phdr.p_align = phdr.p_align >> 1 |
|
+ else: |
|
+ phdr.p_align = int(align) |
|
+ |
|
+def elf_edit_maximize_tls_size(phdr, elfclass): |
|
+ if elfclass == ELFCLASS32: |
|
+ # It is possible that the kernel can allocate half of the |
|
+ # address space, so use something larger. |
|
+ phdr.p_memsz = 0xfff00000 |
|
+ else: |
|
+ phdr.p_memsz = 1 << 63 |
|
+ |
|
+def elf_edit(f, opts): |
|
+ ei_nident_fmt = 'c' * EI_NIDENT |
|
+ ei_nident_len = struct.calcsize(ei_nident_fmt) |
|
+ |
|
+ data = f.read(ei_nident_len) |
|
+ if len(data) < ei_nident_len: |
|
+ error('{}: e_nident too small'.format(f.name)) |
|
+ e_ident = struct.unpack(ei_nident_fmt, data) |
|
+ |
|
+ if e_ident[EI_MAG0] != ELFMAG0 \ |
|
+ or e_ident[EI_MAG1] != ELFMAG1 \ |
|
+ or e_ident[EI_MAG2] != ELFMAG2 \ |
|
+ or e_ident[EI_MAG3] != ELFMAG3: |
|
+ error('{}: bad ELF header'.format(f.name)) |
|
+ |
|
+ if e_ident[EI_CLASS] != ELFCLASS32 \ |
|
+ and e_ident[EI_CLASS] != ELFCLASS64: |
|
+ error('{}: unsupported ELF class: {}'.format(f.name, e_ident[EI_CLASS])) |
|
+ |
|
+ if e_ident[EI_DATA] != ELFDATA2LSB \ |
|
+ and e_ident[EI_DATA] != ELFDATA2MSB: \ |
|
+ error('{}: unsupported ELF data: {}'.format(f.name, e_ident[EI_DATA])) |
|
+ |
|
+ ehdr = Elf_Ehdr(e_ident) |
|
+ ehdr.read(f) |
|
+ if ehdr.e_type not in (ET_EXEC, ET_DYN): |
|
+ error('{}: not an executable or shared library'.format(f.name)) |
|
+ |
|
+ phdr = Elf_Phdr(e_ident) |
|
+ maximize_tls_size_done = False |
|
+ for i in range(0, ehdr.e_phnum): |
|
+ f.seek(ehdr.e_phoff + i * phdr.len) |
|
+ phdr.read(f) |
|
+ if phdr.p_type == PT_LOAD and opts.align is not None: |
|
+ elf_edit_align(phdr, opts.align) |
|
+ f.seek(ehdr.e_phoff + i * phdr.len) |
|
+ phdr.write(f) |
|
+ break |
|
+ if phdr.p_type == PT_TLS and opts.maximize_tls_size: |
|
+ elf_edit_maximize_tls_size(phdr, e_ident[EI_CLASS]) |
|
+ f.seek(ehdr.e_phoff + i * phdr.len) |
|
+ phdr.write(f) |
|
+ maximize_tls_size_done = True |
|
+ break |
|
+ |
|
+ if opts.maximize_tls_size and not maximize_tls_size_done: |
|
+ error('{}: TLS maximum size was not updated'.format(f.name)) |
|
+ |
|
+def get_parser(): |
|
+ parser = argparse.ArgumentParser(description=__doc__) |
|
+ parser.add_argument('-a', dest='align', |
|
+ help='How to set the LOAD alignment') |
|
+ parser.add_argument('--maximize-tls-size', action='store_true', |
|
+ help='Set maximum PT_TLS size') |
|
+ parser.add_argument('output', |
|
+ help='ELF file to edit') |
|
+ return parser |
|
+ |
|
+ |
|
+def main(argv): |
|
+ parser = get_parser() |
|
+ opts = parser.parse_args(argv) |
|
+ with open(opts.output, 'r+b') as fout: |
|
+ elf_edit(fout, opts) |
|
+ |
|
+ |
|
+if __name__ == '__main__': |
|
+ main(sys.argv[1:]) |
|
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h |
|
index a38de94bf7ea8e93..87ad2f3f4d89eb7d 100644 |
|
--- a/sysdeps/generic/ldsodefs.h |
|
+++ b/sysdeps/generic/ldsodefs.h |
|
@@ -1238,6 +1238,11 @@ extern struct link_map * _dl_get_dl_main_map (void) |
|
/* Initialize the DSO sort algorithm to use. */ |
|
extern void _dl_sort_maps_init (void) attribute_hidden; |
|
|
|
+/* Perform early memory allocation, avoding a TCB dependency. |
|
+ Terminate the process if allocation fails. May attempt to use |
|
+ brk. */ |
|
+void *_dl_early_allocate (size_t size) attribute_hidden; |
|
+ |
|
/* Initialization of libpthread for statically linked applications. |
|
If libpthread is not linked in, this is an empty function. */ |
|
void __pthread_initialize_minimal (void) weak_function; |
|
diff --git a/sysdeps/unix/sysv/linux/dl-early_allocate.c b/sysdeps/unix/sysv/linux/dl-early_allocate.c |
|
new file mode 100644 |
|
index 0000000000000000..52c538e85afa8522 |
|
--- /dev/null |
|
+++ b/sysdeps/unix/sysv/linux/dl-early_allocate.c |
|
@@ -0,0 +1,82 @@ |
|
+/* Early memory allocation for the dynamic loader. Generic version. |
|
+ Copyright (C) 2022 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/>. */ |
|
+ |
|
+/* Mark symbols hidden in static PIE for early self relocation to work. */ |
|
+#if BUILD_PIE_DEFAULT |
|
+# pragma GCC visibility push(hidden) |
|
+#endif |
|
+#include <startup.h> |
|
+ |
|
+#include <ldsodefs.h> |
|
+#include <stddef.h> |
|
+#include <string.h> |
|
+#include <sysdep.h> |
|
+#include <unistd.h> |
|
+ |
|
+#include <brk_call.h> |
|
+#include <mmap_call.h> |
|
+ |
|
+/* Defined in brk.c. */ |
|
+extern void *__curbrk; |
|
+ |
|
+void * |
|
+_dl_early_allocate (size_t size) |
|
+{ |
|
+ void *result; |
|
+ |
|
+ if (__curbrk != NULL) |
|
+ /* If the break has been initialized, brk must have run before, |
|
+ so just call it once more. */ |
|
+ { |
|
+ result = __sbrk (size); |
|
+ if (result == (void *) -1) |
|
+ result = NULL; |
|
+ } |
|
+ else |
|
+ { |
|
+ /* If brk has not been invoked, there is no need to update |
|
+ __curbrk. The first call to brk will take care of that. */ |
|
+ void *previous = __brk_call (0); |
|
+ result = __brk_call (previous + size); |
|
+ if (result == previous) |
|
+ result = NULL; |
|
+ else |
|
+ result = previous; |
|
+ } |
|
+ |
|
+ /* If brk fails, fall back to mmap. This can happen due to |
|
+ unfortunate ASLR layout decisions and kernel bugs, particularly |
|
+ for static PIE. */ |
|
+ if (result == NULL) |
|
+ { |
|
+ long int ret; |
|
+ int prot = PROT_READ | PROT_WRITE; |
|
+ int flags = MAP_PRIVATE | MAP_ANONYMOUS; |
|
+#ifdef __NR_mmap2 |
|
+ ret = MMAP_CALL_INTERNAL (mmap2, 0, size, prot, flags, -1, 0); |
|
+#else |
|
+ ret = MMAP_CALL_INTERNAL (mmap, 0, size, prot, flags, -1, 0); |
|
+#endif |
|
+ if (INTERNAL_SYSCALL_ERROR_P (ret)) |
|
+ result = NULL; |
|
+ else |
|
+ result = (void *) ret; |
|
+ } |
|
+ |
|
+ return result; |
|
+}
|
|
|