glibc/tests/Regression/bz1717438-glibc-libc-freeres-under-valgrind-triggers/tst-libidl.c
2023-04-04 01:55:48 +02:00

103 lines
3.2 KiB
C

/*
To build:
With system libc:
gcc -o libdl_bug libdl_bug.c -ldl -pthread
With custom libc
libc_dir=/path/to/glibc/install
gcc -o libdl_bug libdl_bug.c -L$libc_dir/lib -Wl,--rpath=$libc_dir/lib \
-Wl,--dynamic-linker=$libc_dir/lib/ld-linux-x86-64.so.2 -ldl -pthread
./libdl_bug bad_free is just a sanity check that valgrind will notice if
|&do_not_free_this| is accidentally passed to |free|.
Otherwise, this tool will allocate a |pthread_key_t| (which will likely be zero)
and store |&do_not_free_this| in it. The |pthread_key_t| has no destructor, so
this is perfectly valid.
It will then optionally call |dlerror| (pass dlerror vs no_dlerror to the tool),
and then return from |main| cleanly. At this point, when running under valgrind,
vg_preloaded.c will call |__libc_freeres|.
In glibc after 2827ab990aefbb0e53374199b875d98f116d6390 (2.28 and later),
|__libc_freeres| will call |__libdl_freeres|, which calls |free_key_mem| in
dlerror.c. That function cleans up |dlerror|'s thread-local state, but has a
bug: if nothing has called |dlerror|, there is no thread-local state and the
|pthread_key_t| is uninitialized! It then blindly calls |free| on the zero key,
and hits our |&do_not_free_this|. That results in an error in valgrind:
$ valgrind -q ./libdl_bug dlerror
Initializing a pthread_key_t.
key = 0
Setting thread local to &do_not_free_this.
Calling dlerror.
Exiting
$ valgrind -q ./libdl_bug no_dlerror
Initializing a pthread_key_t.
key = 0
Setting thread local to &do_not_free_this.
Exiting
==139993== Invalid free() / delete / delete[] / realloc()
==139993== at 0x4C2FFA8: free (vg_replace_malloc.c:540)
==139993== by 0x4E3D6D9: free_key_mem (dlerror.c:223)
==139993== by 0x4E3D6D9: __dlerror_main_freeres (dlerror.c:239)
==139993== by 0x53BFDC9: __libc_freeres (in /[...]/lib/libc-2.29.9000.so)
==139993== by 0x4A296DB: _vgnU_freeres (vg_preloaded.c:77)
==139993== by 0x5296381: __run_exit_handlers (exit.c:132)
==139993== by 0x52963A9: exit (exit.c:139)
==139993== by 0x528105D: (below main) (libc-start.c:342)
==139993== Address 0x30a08c is 0 bytes inside data symbol "do_not_free_this"
==139993==
*/
#include <dlfcn.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static void usage(const char *prog_name) {
fprintf(stderr, "Usage: %s [bad_free|no_dlerror|dlerror]\n", prog_name);
exit(1);
}
static int do_not_free_this;
static pthread_key_t key;
int main(int argc, char **argv) {
if (argc != 2) {
usage(argv[0]);
}
if (strcmp(argv[1], "bad_free") == 0) {
printf("Calling free(&do_not_free_this). Valgrind should notice.\n");
free(&do_not_free_this);
} else if (strcmp(argv[1], "no_dlerror") == 0 ||
strcmp(argv[1], "dlerror") == 0) {
printf("Initializing a pthread_key_t.\n");
if (pthread_key_create(&key, NULL)) {
perror("pthread_key_create");
exit(1);
}
printf("key = %d\n", key);
printf("Setting thread local to &do_not_free_this.\n");
if (pthread_setspecific(key, &do_not_free_this) != 0) {
perror("pthread_setspecific");
exit(1);
}
if (strcmp(argv[1], "dlerror") == 0) {
printf("Calling dlerror.\n");
dlerror();
}
} else {
usage(argv[0]);
}
printf("Exiting\n");
return 0;
}