[Bug dynamic-link/32412] New: TLS realloc's DTV breaking invariant
woodard at redhat dot com
sourceware-bugzilla@sourceware.org
Tue Dec 3 18:52:14 GMT 2024
https://sourceware.org/bugzilla/show_bug.cgi?id=32412
Bug ID: 32412
Summary: TLS realloc's DTV breaking invariant
Product: glibc
Version: 2.41
Status: NEW
Severity: critical
Priority: P2
Component: dynamic-link
Assignee: unassigned at sourceware dot org
Reporter: woodard at redhat dot com
Target Milestone: ---
Created attachment 15818
--> https://sourceware.org/bugzilla/attachment.cgi?id=15818&action=edit
reproducer
When using LD_AUDIT the DTV gets realloc'd twice apparently breaking an
invariant in the dynamic linker.
This appears to affect all extant versions of glibc at least back to 2.32 and
may have been reported back in 2019 but in that case a workaround was found and
no further investigation was done until it bit another user.
The original reporter included the following analysis:
Depending on build-time constants for your GLIBC you may have to increase the
number of libraries specified in the Makefile to reproduce the crash. The
reproducer currently uses the minimum number of libraries (28) needed to
trigger on our system.
I haven’t deep-dived on all the details. At a high-level, ld.so is trying to
use libc.so’s realloc() on a memory allocated from ld.so’s rtld_malloc() when
resizing the DTV. The dynamic linker is trying to guard against this by keeping
track of a dl_initial_dtv pointer, which it points at the first allocation of
the DTV. It (correctly) assumes that the first DTV allocation will come from
rtld_malloc() or static memory, so if (dtv == dl_initial_dtv) in
_dl_resize_dtv() then it won’t try to use libc’s realloc() to grow the DTV.
The problem is that with LD_AUDIT enabled you can get a second resizing of the
DTV before libc.so is setup, which will still use rtld_malloc() (and I’m not
too clear on the why of that part yet—it’s something with the DTV’s generation
system). So on the subsequent third resizing of the DTV the check for (dtv ==
dl_initial_dtv) no longer hits, since we’re not resizing the first allocation,
but we still can’t safely use libc’s realloc() since the second allocation came
from rtld_malloc(). And thus we crash.
------
The backtrace from Fedora 41 with glibc-2.40-12.fc41.x86_64 is as follows:
$ gdb ./test
(gdb) set args 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15
(gdb) set env LD_AUDIT=/home/ben/Shared/Work/test/tlsfault/libminaudit.so
(gdb) r
Starting program: /home/ben/Shared/Work/test/tlsfault/test 01 02 03 04 05 06 07
08 09 10 11 12 13 14 15
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc4000
Downloading 6.69 M separate debug info for /lib64/libc.so.6
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib64/libthread_db.so.1".
realloc(): invalid pointer
Program received signal SIGABRT, Aborted.
Downloading source file
/usr/src/debug/glibc-2.40-12.fc41.x86_64/nptl/pthread_kill.c
__pthread_kill_implementation (threadid=<optimized out>, signo=signo@entry=6,
no_tid=no_tid@entry=0)
at pthread_kill.c:44
44 return INTERNAL_SYSCALL_ERROR_P (ret) ? INTERNAL_SYSCALL_ERRNO
(ret) : 0;
(gdb) bt
#0 __pthread_kill_implementation (threadid=<optimized out>,
signo=signo@entry=6,
no_tid=no_tid@entry=0) at pthread_kill.c:44
#1 0x00007ffff7bd6d03 in __pthread_kill_internal (threadid=<optimized out>,
signo=6)
at pthread_kill.c:78
#2 0x00007ffff7b7dd1e in __GI_raise (sig=sig@entry=6) at
../sysdeps/posix/raise.c:26
#3 0x00007ffff7b65942 in __GI_abort () at abort.c:79
#4 0x00007ffff7b667a7 in __libc_message_impl (fmt=fmt@entry=0x7ffff7d193cd
"%s\n")
at ../sysdeps/posix/libc_fatal.c:132
#5 0x00007ffff7be0e25 in malloc_printerr (str=str@entry=0x7ffff7d17269
"realloc(): invalid pointer")
at malloc.c:5772
#6 0x00007ffff7be617c in __GI___libc_realloc (oldmem=0x7ffff7b62510,
bytes=736) at malloc.c:3466
#7 0x00007ffff7fd727f in realloc (ptr=0x7ffff7b62510, size=736) at
../include/rtld-malloc.h:62
#8 _dl_resize_dtv (dtv=dtv@entry=0x7ffff7b62520, max_modid=max_modid@entry=30)
at ../elf/dl-tls.c:534
#9 0x00007ffff7fd7c62 in _dl_update_slotinfo (req_modid=30, new_gen=17) at
../elf/dl-tls.c:858
#10 0x00007ffff7fd7d94 in update_get_addr (ti=0x7ffff7b3dfc0, gen=<optimized
out>)
at ../elf/dl-tls.c:976
#11 0x00007ffff7fdaccc in __tls_get_addr () at
../sysdeps/x86_64/tls_get_addr.S:55
#12 0x00007ffff7b3b124 in dlopen_set_15 () from ./libdlopentls15.so
#13 0x000000000040126b in openlib (num=0x7fffffffd9b0 "15") at test.c:29
#14 0x00000000004012d4 in main (argc=16, argv=0x7fffffffd4b8) at test.c:41
At first glance this is consistent with the OP's analysis of the problem. It
appears like the resizing the DTV should not call the normal realloc() it
should call a special rtld_realloc() and/or the invariant that dtv ==
dl_initial_dtv should include some additional logic to handle the case where
dtv has already been realloc'd.
To run the reproducer simply untar the attached reproducer and run Make.
--
You are receiving this mail because:
You are on the CC list for the bug.
More information about the Glibc-bugs
mailing list