diff options
author | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2021-06-25 10:54:12 -0300 |
---|---|---|
committer | Adhemerval Zanella <adhemerval.zanella@linaro.org> | 2021-07-14 15:10:27 -0300 |
commit | ba33937be210da5d07f7f01709323743f66011ce (patch) | |
tree | 8de2c3d81913a4e6b40b1f903078ce190d2f17be /elf/tst-tls20.c | |
parent | 0e1f068108d94576321bbbd354cfb1b3b99389bf (diff) | |
download | glibc-ba33937be210da5d07f7f01709323743f66011ce.tar glibc-ba33937be210da5d07f7f01709323743f66011ce.tar.gz glibc-ba33937be210da5d07f7f01709323743f66011ce.tar.bz2 glibc-ba33937be210da5d07f7f01709323743f66011ce.zip |
elf: Fix DTV gap reuse logic (BZ #27135)
This is updated version of the 572bd547d57a (reverted by 40ebfd016ad2)
that fixes the _dl_next_tls_modid issues.
This issue with 572bd547d57a patch is the DTV entry will be only
update on dl_open_worker() with the update_tls_slotinfo() call after
all dependencies are being processed by _dl_map_object_deps(). However
_dl_map_object_deps() itself might call _dl_next_tls_modid(), and since
the _dl_tls_dtv_slotinfo_list::map is not yet set the entry will be
wrongly reused.
This patch fixes by renaming the _dl_next_tls_modid() function to
_dl_assign_tls_modid() and by passing the link_map so it can set
the slotinfo value so a subsequente _dl_next_tls_modid() call will
see the entry as allocated.
The intermediary value is cleared up on remove_slotinfo() for the case
a library fails to load with RTLD_NOW.
This patch fixes BZ #27135.
Checked on x86_64-linux-gnu.
Reviewed-by: Szabolcs Nagy <szabolcs.nagy@arm.com>
Diffstat (limited to 'elf/tst-tls20.c')
-rw-r--r-- | elf/tst-tls20.c | 275 |
1 files changed, 268 insertions, 7 deletions
diff --git a/elf/tst-tls20.c b/elf/tst-tls20.c index 9977ec8032..d8d04fe574 100644 --- a/elf/tst-tls20.c +++ b/elf/tst-tls20.c @@ -16,12 +16,14 @@ License along with the GNU C Library; if not, see <http://www.gnu.org/licenses/>. */ +#include <array_length.h> #include <dlfcn.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <support/check.h> #include <support/support.h> +#include <support/test-driver.h> #include <support/xdlfcn.h> #include <support/xthread.h> @@ -59,28 +61,75 @@ access (int i) char *buf = xasprintf ("tls_global_%02d", i); dlerror (); int *p = dlsym (mod[i], buf); - printf ("mod[%d]: &tls = %p\n", i, p); + if (test_verbose) + printf ("mod[%d]: &tls = %p\n", i, p); if (p == NULL) FAIL_EXIT1 ("dlsym failed: %s\n", dlerror ()); + TEST_COMPARE (*p, 0); ++*p; free (buf); } +static void +access_mod (const char *modname, void *mod, int i) +{ + char *modsym = xasprintf ("tls_global_%d", i); + dlerror (); + int *p = dlsym (mod, modsym); + if (test_verbose) + printf ("%s: &tls = %p\n", modname, p); + if (p == NULL) + FAIL_EXIT1 ("dlsym failed: %s\n", dlerror ()); + TEST_COMPARE (*p, 0); + ++*p; + free (modsym); +} + +static void +access_dep (int i) +{ + char *modname = xasprintf ("tst-tls-manydynamic%dmod-dep.so", i); + void *moddep = xdlopen (modname, RTLD_LAZY); + access_mod (modname, moddep, i); + free (modname); + xdlclose (moddep); +} + +struct start_args +{ + const char *modname; + void *mod; + int modi; + int ndeps; + const int *deps; +}; + static void * start (void *a) { + struct start_args *args = a; + for (int i = 0; i < NMOD; i++) if (mod[i] != NULL) access (i); + + if (args != NULL) + { + access_mod (args->modname, args->mod, args->modi); + for (int n = 0; n < args->ndeps; n++) + access_dep (args->deps[n]); + } + return 0; } -static int -do_test (void) +/* This test gaps with shared libraries with dynamic TLS that has no + dependencies. The DTV gap is set with by trying to load an invalid + module, the entry should be used on the dlopen. */ +static void +do_test_no_depedency (void) { - int i; - - for (i = 0; i < NMOD; i++) + for (int i = 0; i < NMOD; i++) { load_mod (i); /* Bump the generation of mod[0] without using new dtv slot. */ @@ -91,8 +140,220 @@ do_test (void) pthread_t t = xpthread_create (0, start, 0); xpthread_join (t); } - for (i = 0; i < NMOD; i++) + for (int i = 0; i < NMOD; i++) unload_mod (i); +} + +/* The following test check DTV gaps handling with shared libraries that has + dependencies. It defines 5 different sets: + + 1. Single dependency: + mod0 -> mod1 + 2. Double dependency: + mod2 -> [mod3,mod4] + 3. Double dependency with each dependency depent of another module: + mod5 -> [mod6,mod7] -> mod8 + 4. Long chain with one double dependency in the middle: + mod9 -> [mod10, mod11] -> mod12 -> mod13 + 5. Long chain with two double depedencies in the middle: + mod14 -> mod15 -> [mod16, mod17] + mod15 -> [mod18, mod19] + + This does not cover all the possible gaps and configuration, but it + should check if different dynamic shared sets are placed correctly in + different gaps configurations. */ + +static int +nmodules (uint32_t v) +{ + unsigned int r = 0; + while (v >>= 1) + r++; + return r + 1; +} + +static inline bool +is_mod_set (uint32_t g, uint32_t n) +{ + return (1U << (n - 1)) & g; +} + +static void +print_gap (uint32_t g) +{ + if (!test_verbose) + return; + printf ("gap: "); + int nmods = nmodules (g); + for (int n = 1; n <= nmods; n++) + printf ("%c", ((1 << (n - 1)) & g) == 0 ? 'G' : 'M'); + printf ("\n"); +} + +static void +do_test_dependency (void) +{ + /* Maps the module and its dependencies, use thread to access the TLS on + each loaded module. */ + static const int tlsmanydeps0[] = { 1 }; + static const int tlsmanydeps1[] = { 3, 4 }; + static const int tlsmanydeps2[] = { 6, 7, 8 }; + static const int tlsmanydeps3[] = { 10, 11, 12 }; + static const int tlsmanydeps4[] = { 15, 16, 17, 18, 19 }; + static const struct tlsmanydeps_t + { + int modi; + int ndeps; + const int *deps; + } tlsmanydeps[] = + { + { 0, array_length (tlsmanydeps0), tlsmanydeps0 }, + { 2, array_length (tlsmanydeps1), tlsmanydeps1 }, + { 5, array_length (tlsmanydeps2), tlsmanydeps2 }, + { 9, array_length (tlsmanydeps3), tlsmanydeps3 }, + { 14, array_length (tlsmanydeps4), tlsmanydeps4 }, + }; + + /* The gap configuration is defined as a bitmap: the bit set represents a + loaded module prior the tests execution, while a bit unsed is a module + unloaded. Not all permtation will show gaps, but it is simpler than + define each one independently. */ + for (uint32_t g = 0; g < 64; g++) + { + print_gap (g); + int nmods = nmodules (g); + + int mods[nmods]; + /* We use '0' as indication for a gap, to avoid the dlclose on iteration + cleanup. */ + for (int n = 1; n <= nmods; n++) + { + load_mod (n); + mods[n] = n; + } + for (int n = 1; n <= nmods; n++) + { + if (!is_mod_set (g, n)) + { + unload_mod (n); + mods[n] = 0; + } + } + + for (int t = 0; t < array_length (tlsmanydeps); t++) + { + char *moddepname = xasprintf ("tst-tls-manydynamic%dmod-dep.so", + tlsmanydeps[t].modi); + void *moddep = xdlopen (moddepname, RTLD_LAZY); + + /* Access TLS in all loaded modules. */ + struct start_args args = + { + moddepname, + moddep, + tlsmanydeps[t].modi, + tlsmanydeps[t].ndeps, + tlsmanydeps[t].deps + }; + pthread_t t = xpthread_create (0, start, &args); + xpthread_join (t); + + free (moddepname); + xdlclose (moddep); + } + + for (int n = 1; n <= nmods; n++) + if (mods[n] != 0) + unload_mod (n); + } +} + +/* The following test check DTV gaps handling with shared libraries that has + invalid dependencies. It defines 5 different sets: + + 1. Single dependency: + mod0 -> invalid + 2. Double dependency: + mod1 -> [mod2,invalid] + 3. Double dependency with each dependency depent of another module: + mod3 -> [mod4,mod5] -> invalid + 4. Long chain with one double dependency in the middle: + mod6 -> [mod7, mod8] -> mod12 -> invalid + 5. Long chain with two double depedencies in the middle: + mod10 -> mod11 -> [mod12, mod13] + mod12 -> [mod14, invalid] + + This does not cover all the possible gaps and configuration, but it + should check if different dynamic shared sets are placed correctly in + different gaps configurations. */ + +static void +do_test_invalid_dependency (bool bind_now) +{ + static const int tlsmanydeps[] = { 0, 1, 3, 6, 10 }; + + /* The gap configuration is defined as a bitmap: the bit set represents a + loaded module prior the tests execution, while a bit unsed is a module + unloaded. Not all permtation will show gaps, but it is simpler than + define each one independently. */ + for (uint32_t g = 0; g < 64; g++) + { + print_gap (g); + int nmods = nmodules (g); + + int mods[nmods]; + /* We use '0' as indication for a gap, to avoid the dlclose on iteration + cleanup. */ + for (int n = 1; n <= nmods; n++) + { + load_mod (n); + mods[n] = n; + } + for (int n = 1; n <= nmods; n++) + { + if (!is_mod_set (g, n)) + { + unload_mod (n); + mods[n] = 0; + } + } + + for (int t = 0; t < array_length (tlsmanydeps); t++) + { + char *moddepname = xasprintf ("tst-tls-manydynamic%dmod-dep-bad.so", + tlsmanydeps[t]); + void *moddep; + if (bind_now) + { + moddep = dlopen (moddepname, RTLD_NOW); + TEST_VERIFY (moddep == 0); + } + else + moddep = dlopen (moddepname, RTLD_LAZY); + + /* Access TLS in all loaded modules. */ + pthread_t t = xpthread_create (0, start, NULL); + xpthread_join (t); + + free (moddepname); + if (!bind_now) + xdlclose (moddep); + } + + for (int n = 1; n <= nmods; n++) + if (mods[n] != 0) + unload_mod (n); + } +} + +static int +do_test (void) +{ + do_test_no_depedency (); + do_test_dependency (); + do_test_invalid_dependency (true); + do_test_invalid_dependency (false); + return 0; } |