diff --git a/libexec/rtld-elf/map_object.c b/libexec/rtld-elf/map_object.c --- a/libexec/rtld-elf/map_object.c +++ b/libexec/rtld-elf/map_object.c @@ -454,6 +454,12 @@ STAILQ_REMOVE_HEAD(&obj->dagmembers, link); free(elm); } + while (!STAILQ_EMPTY(&obj->extra_refs)) { + elm = STAILQ_FIRST(&obj->extra_refs); + STAILQ_REMOVE_HEAD(&obj->extra_refs, link); + elm->obj->refcount--; + free(elm); + } if (obj->vertab) free(obj->vertab); if (obj->origin_path) @@ -477,6 +483,7 @@ obj = CNEW(Obj_Entry); STAILQ_INIT(&obj->dldags); STAILQ_INIT(&obj->dagmembers); + STAILQ_INIT(&obj->extra_refs); STAILQ_INIT(&obj->names); return obj; } diff --git a/libexec/rtld-elf/rtld.h b/libexec/rtld-elf/rtld.h --- a/libexec/rtld-elf/rtld.h +++ b/libexec/rtld-elf/rtld.h @@ -278,6 +278,7 @@ struct link_map linkmap; /* For GDB and dlinfo() */ Objlist dldags; /* Object belongs to these dlopened DAGs (%) */ Objlist dagmembers; /* DAG has these members (%) */ + Objlist extra_refs; /* Extra refs between objects (%) */ dev_t dev; /* Object's filesystem's device */ ino_t ino; /* Object's inode number */ void *priv; /* Platform-dependent */ @@ -290,13 +291,14 @@ #define RTLD_STATIC_TLS_EXTRA 128 -/* Flags to be passed into symlook_ family of functions. */ +/* SymLook::flags */ #define SYMLOOK_IN_PLT 0x01 /* Lookup for PLT symbol */ #define SYMLOOK_DLSYM 0x02 /* Return newest versioned symbol. Used by dlsym. */ #define SYMLOOK_EARLY 0x04 /* Symlook is done during initialization. */ #define SYMLOOK_IFUNC 0x08 /* Allow IFUNC processing in reloc_non_plt(). */ +#define SYMLOOK_ADD_REF 0x10 /* ++refcount when doing relocations. */ /* Flags for load_object(). */ #define RTLD_LO_NOLOAD 0x01 /* dlopen() specified RTLD_NOLOAD. */ @@ -380,7 +382,7 @@ uintptr_t rtld_round_page(uintptr_t); uintptr_t rtld_trunc_page(uintptr_t); unsigned long elf_hash(const char *); -const Elf_Sym *find_symdef(unsigned long, const Obj_Entry *, +const Elf_Sym *find_symdef(unsigned long, Obj_Entry *, const Obj_Entry **, int, SymCache *, struct Struct_RtldLockState *); void lockdflt_init(void); void digest_notes(Obj_Entry *, Elf_Addr, Elf_Addr); diff --git a/libexec/rtld-elf/rtld.c b/libexec/rtld-elf/rtld.c --- a/libexec/rtld-elf/rtld.c +++ b/libexec/rtld-elf/rtld.c @@ -1963,13 +1963,13 @@ * defining object via the reference parameter DEFOBJ_OUT. */ const Elf_Sym * -find_symdef(unsigned long symnum, const Obj_Entry *refobj, +find_symdef(unsigned long symnum, Obj_Entry *refobj, const Obj_Entry **defobj_out, int flags, SymCache *cache, RtldLockState *lockstate) { const Elf_Sym *ref; const Elf_Sym *def; - const Obj_Entry *defobj; + Obj_Entry *defobj; const Ver_Entry *ve; SymLook req; const char *name; @@ -2012,7 +2012,16 @@ res = symlook_default(&req, refobj); if (res == 0) { def = req.sym_out; - defobj = req.defobj_out; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-qual" + defobj = (Obj_Entry *)req.defobj_out; +#pragma clang diagnostic pop + if ((req.flags & SYMLOOK_ADD_REF) != 0) { + if (objlist_find(&refobj->extra_refs, defobj) == NULL) { + objlist_push_tail(&refobj->extra_refs, defobj); + defobj->refcount++; + } + } } } else { def = ref; @@ -4561,6 +4570,7 @@ { DoneList donelist; const Objlist_Entry *elm; + const Obj_Entry *dlopened_defobj; SymLook req1; int res; @@ -4592,7 +4602,7 @@ if (res == 0 && (req->sym_out == NULL || ELF_ST_BIND(req1.sym_out->st_info) != STB_WEAK)) { req->sym_out = req1.sym_out; - req->defobj_out = req1.defobj_out; + req->defobj_out = dlopened_defobj = req1.defobj_out; assert(req->defobj_out != NULL); } } @@ -4612,7 +4622,20 @@ } } - return (req->sym_out != NULL ? 0 : ESRCH); + if (req->sym_out == NULL) + return (ESRCH); + + if (req->defobj_out == dlopened_defobj) { + /* + * The defining object was dlopened. Tell the caller to record a + * reference to it from the referencing object. This will ensure + * that dlclose does not destroy the defining object prematurely. + * See the dlopen_common_global ATF test for such a use case. + */ + req->flags |= SYMLOOK_ADD_REF; + } + + return (0); } static int diff --git a/libexec/rtld-elf/tests/Makefile b/libexec/rtld-elf/tests/Makefile --- a/libexec/rtld-elf/tests/Makefile +++ b/libexec/rtld-elf/tests/Makefile @@ -11,6 +11,15 @@ SRCS.$t= $t.c common.c .endfor +ATF_TESTS_C+= dlopen_common_global + +SUBDIR+= libdlocg +SUBDIR+= nss_dlocg +SUBDIR+= pam_dlocg + +SUBDIR_DEPEND_nss_dlocg= libdlocg +SUBDIR_DEPEND_pam_dlocg= libdlocg + WARNS?= 3 .include diff --git a/libexec/rtld-elf/tests/Makefile.inc b/libexec/rtld-elf/tests/Makefile.inc new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/Makefile.inc @@ -0,0 +1 @@ +TESTSDIR= /usr/tests/libexec/rtld-elf diff --git a/libexec/rtld-elf/tests/dlopen_common_global.c b/libexec/rtld-elf/tests/dlopen_common_global.c new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/dlopen_common_global.c @@ -0,0 +1,76 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Dell Inc. + * Author: Eric van Gyzen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#include + +ATF_TC_WITHOUT_HEAD(dlopen_common_global); + +// See dlopen_common_global.md for a detailed description. + +ATF_TC_BODY(dlopen_common_global, tc) +{ + char libpath[PATH_MAX]; + const char *srcdir; + void *nss, *pam; + void (*pam_func)(void); + + srcdir = atf_tc_get_config_var(tc, "srcdir"); + + snprintf(libpath, sizeof(libpath), "%s/%s", srcdir, "nss_dlocg.so"); + nss = dlopen(libpath, RTLD_LAZY|RTLD_LOCAL); + ATF_REQUIRE_MSG(nss != NULL, "dlopen nss: %s", dlerror()); + + snprintf(libpath, sizeof(libpath), "%s/%s", srcdir, "pam_dlocg.so"); + pam = dlopen(libpath, RTLD_LAZY|RTLD_LOCAL); + ATF_REQUIRE_MSG(pam != NULL, "dlopen pam: %s", dlerror()); + + pam_func = dlsym(pam, "pam_func"); + ATF_REQUIRE_MSG(pam_func != NULL, "dlsym pam_func: %s", dlerror()); + + ATF_REQUIRE_EQ_MSG(dlclose(nss), 0, "dlclose nss: %s", dlerror()); + + /* + * This has the same effect as the atexit handler described in + * dlopen_common_global.md, but it keeps the code simple. + */ + pam_func(); + + // If we get here without a segfault, the test passed. + + ATF_REQUIRE_EQ_MSG(dlclose(pam), 0, "dlclose pam: %s", dlerror()); +} + +ATF_TP_ADD_TCS(tp) +{ + ATF_TP_ADD_TC(tp, dlopen_common_global); + + return atf_no_error(); +} diff --git a/libexec/rtld-elf/tests/dlopen_common_global.md b/libexec/rtld-elf/tests/dlopen_common_global.md new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/dlopen_common_global.md @@ -0,0 +1,25 @@ +Consider an nss module, a pam module, and a shared library needed by both. +When they are built with code coverage instrumentation, all three +define a common set of global variables. With the current toolchain, +`writeout_fn_list` in `compiler-rt` is a variable of interest. + +Applications that authenticate users commonly load both modules. +nss and pam both use dlopen in RTLD\_LOCAL mode, so each DAG of modules +and their needed libraries get their own copy of the common globals...almost. +dlopen loads the nss module, which defines the variables. It then loads +the library, which finds and uses the variables defined by the nss module, +because that module was loaded earlier in the same DAG. +Note that the library now refers to the nss module's bss segment, but rtld +did not track that reference; this was +the bug that prompted this test. dlopen then loads the pam module. +It finds the library already loaded, and records a reference to it from the +pam module. + +Later, dlclose unloads the nss module. The library stays loaded +because it is still referenced by the pam module. The library now +refers to unmapped memory. Then dlclose unloads the pam module and +the library, which is no longer needed. Due to the code coverage +instrumentation, the library has an atexit handler to write the +coverage counters to a file. This handler runs as the library is +unloaded. That handler uses the global variables that were provided +by the nss module and are no longer mapped, causing a segfault. diff --git a/libexec/rtld-elf/tests/libdlocg/Makefile b/libexec/rtld-elf/tests/libdlocg/Makefile new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/libdlocg/Makefile @@ -0,0 +1,7 @@ +SHLIB= dlocg +SRCS= libdlocg.c +SHLIB_MAJOR= 0 +LIBDIR= ${TESTSDIR} +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include diff --git a/libexec/rtld-elf/tests/libdlocg/libdlocg.c b/libexec/rtld-elf/tests/libdlocg/libdlocg.c new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/libdlocg/libdlocg.c @@ -0,0 +1,37 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Dell Inc. + * Author: Eric van Gyzen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +int dlopen_common_global; + +void libdlocg_func(void); + +void +libdlocg_func(void) +{ + ++dlopen_common_global; +} diff --git a/libexec/rtld-elf/tests/nss_dlocg/Makefile b/libexec/rtld-elf/tests/nss_dlocg/Makefile new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/nss_dlocg/Makefile @@ -0,0 +1,8 @@ +SHLIB= nss_dlocg +SRCS= nss_dlocg.c +SHLIB_NAME= nss_dlocg.so +LIBDIR= ${TESTSDIR} +LDADD= -L../libdlocg -Wl,-rpath,${LIBDIR} -ldlocg +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include diff --git a/libexec/rtld-elf/tests/nss_dlocg/nss_dlocg.c b/libexec/rtld-elf/tests/nss_dlocg/nss_dlocg.c new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/nss_dlocg/nss_dlocg.c @@ -0,0 +1,29 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Dell Inc. + * Author: Eric van Gyzen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +int dlopen_common_global; diff --git a/libexec/rtld-elf/tests/pam_dlocg/Makefile b/libexec/rtld-elf/tests/pam_dlocg/Makefile new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/pam_dlocg/Makefile @@ -0,0 +1,8 @@ +SHLIB= pam_dlocg +SRCS= pam_dlocg.c +SHLIB_NAME= pam_dlocg.so +LIBDIR= ${TESTSDIR} +LDADD= -L../libdlocg -Wl,-rpath,${LIBDIR} -ldlocg +NO_WMISSING_VARIABLE_DECLARATIONS= + +.include diff --git a/libexec/rtld-elf/tests/pam_dlocg/pam_dlocg.c b/libexec/rtld-elf/tests/pam_dlocg/pam_dlocg.c new file mode 100644 --- /dev/null +++ b/libexec/rtld-elf/tests/pam_dlocg/pam_dlocg.c @@ -0,0 +1,38 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2022 Dell Inc. + * Author: Eric van Gyzen + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +int dlopen_common_global; + +void libdlocg_func(void); +void pam_func(void); + +void +pam_func(void) +{ + libdlocg_func(); +}