From patchwork Wed May 6 20:28:53 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 134584 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 0F0FF4BA23E4 for ; Wed, 6 May 2026 20:31:18 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 0F0FF4BA23E4 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=xPSzOEzC X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-vs1-xe31.google.com (mail-vs1-xe31.google.com [IPv6:2607:f8b0:4864:20::e31]) by sourceware.org (Postfix) with ESMTPS id 62BB74BA2E3D for ; Wed, 6 May 2026 20:29:10 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 62BB74BA2E3D Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=linaro.org Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=linaro.org ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 62BB74BA2E3D Authentication-Results: sourceware.org; arc=none smtp.remote-ip=2607:f8b0:4864:20::e31 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778099350; cv=none; b=LkhBTLPG1yXF1GSZKisXo2MSBknYsYeB+WrJ/VJUTjipB9e+l+xWtdXn1lcQaW8h0d5p9XCvRqQYP99O/drsuUprVvxKdiQ2RyZ8/mhSrAxyEKWpgenGv1kiUlbLSkUeFWFczX1ozF23fbNvHT484zMeQi8J2pOkGFyHrwbldE4= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1778099350; c=relaxed/simple; bh=Ta+CKfZ0ft7AdLH+e9eTF9i8AJiLpwB8XwOPIc/PDOw=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=pO/hHzeVG/HGqK9qVHrdCT70+Mhot4VELGs3sU0zQFRfwz5jF1hlc06YtGmiQwVquwKKLU3rqMquKvNJR1GtJcRFQtyiSNDOwGu0BbtaplLnwlkg5n3ThVajgEmFoghSa9LV6BT4IZlOFS+OWcg/yjcTxDCnLImqYX5WrMOcYuo= ARC-Authentication-Results: i=1; sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=linaro.org header.i=@linaro.org header.a=rsa-sha256 header.s=google header.b=xPSzOEzC DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 62BB74BA2E3D Received: by mail-vs1-xe31.google.com with SMTP id ada2fe7eead31-62f4c4e6694so81141137.3 for ; Wed, 06 May 2026 13:29:10 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linaro.org; s=google; t=1778099349; x=1778704149; darn=sourceware.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=03qzu5SC5e6fagKw5yrWseAJnwDqxTeRlouwDMh1Mus=; b=xPSzOEzC1gbi2YuRm4se8xZ0t+WZrUTWrcnvYSnv5ZLiTw7b+fRl/oxXVSBekzTVjn liLyaU7kwp4Tm4jFejwrmoSFo+6BeYM4YdLOpdMR+FNaNADgANKi0zj25S04hBH3GFO7 2u1bmPqSTOshq+1mD985BEaDlvLLN05j9h0kKymsxVE32wZZP/Sf9CDeGXFRBGQ9IPAs g7wcwOeJXQ2gU30hB3oaI6PnA6QeV5wQ6DXq7MOrERzwhAym/izMfomWzd7Xd/N8Vp9I oXAB/eO0KRjE2fPTFgOoNnIa490XYaknRJaMO7G8UWye9em/xDJ6cZ9tQBnMAr/szwRC Z/7A== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778099349; x=1778704149; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=03qzu5SC5e6fagKw5yrWseAJnwDqxTeRlouwDMh1Mus=; b=f41k7/ZNZ9zrbzgSkybsfm4ZU/lrv73BM0QLrnzjXJI4nVv0K+ZUettTbGg1S5O1yq nwmn3UvFZSuhLqETa0feRgSAE5RF3M+MkPX9jL5yPlDMEl7VgvqqI6HoDsOf6lIWb3Qr ssMnAMWlZkOso4ZwgvSQVpyAqTccxYtC88UdapEghxRKeJkQMQwMNJDXFGlJb8mMl4Jl Z8hAmy4Bt7wIKwc5vvCARvIMvfI/0DgyAMBekWcC0WCJ/g0GuIRaAmy7E0pLwZ2QPXgs dXyTwdlGSAWdmMuAHO/2WmOOfwR+PzD9/784LD6vTTxw7qYSkS90Wq5iuagEsKKm23Qc 5DwA== X-Gm-Message-State: AOJu0YwEdhnL3xwQT1nBvEzNWBCFUn0W0j7KvJOlfYAdvu6wT1x6CJce Mvsz+XiMh1fzb3eBHpxMJ9VBB3+FOcoxNOPKB46UNvn59W4LiBQNthjyI6iA9mn4sSy6xOT18dm dP+zL X-Gm-Gg: AeBDies25fETlQdVzdwaH5Dfu5EkXqwVa97OOT4gneMqihb8xZbgw1a3Pc5H06QG10w cCoaXtb47+4oKL+XjCWGjd0LsSD7ILN1QwHKWvLGM/Rgek7No/MM0HdzQdQ7NCo7qeU1n0+ahiy OOdZbU7GvU+8ttX3zRXYMf2heVP91JHNuEjLIWkNpS5Wj4BwNqUkF87YZEiyV5eeiU67i7bJyKx 5Quv0QHqCgBumXtpgCoie7oLSYQMrtUIhg/1eaEdDnl2IROVlNBuZnpLvN75QBaPrB8zx+kHroh +w1I/ASeA/bFHNOEm7KU+4eTqouiyOfmJ17a5PxqztoUJwTsBQBdYDei6XvIT1dEyNyAUGzm03j lL88IppHUJE/cop8y6HKbh0dPDe8Fnh2DDvoY3adr2EOySd0lNRJ+w56xtLwhtRracDe7hG4rMC iFRmkLrIvRdzd21H9UE6Qbs1vbz62RidXmP2upItoCm3hN X-Received: by 2002:a05:6102:14a3:b0:62f:3025:bf7 with SMTP id ada2fe7eead31-630f9018972mr2852927137.18.1778099349218; Wed, 06 May 2026 13:29:09 -0700 (PDT) Received: from mandiga.. ([2804:1b3:a7c1:364:eaaa:4019:b33c:5884]) by smtp.gmail.com with ESMTPSA id a1e0cc1a2514c-95ce08efbcesm10174494241.4.2026.05.06.13.29.06 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 06 May 2026 13:29:08 -0700 (PDT) From: Adhemerval Zanella To: libc-alpha@sourceware.org Cc: Arjun Shankar , =?utf-8?q?Gra=C5=BEvydas_Ignotas?= , Florian Weimer Subject: [PATCH v4] dlfcn: Preserve destructor order when dlclose is called from atexit handler (BZ 33598) Date: Wed, 6 May 2026 17:28:53 -0300 Message-ID: <20260506202903.691892-1-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.43.0 MIME-Version: 1.0 X-Spam-Status: No, score=-12.6 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces~patchwork=sourceware.org@sourceware.org When a shared library loaded via dlopen has both a thread_local variable and a C++ static global destructor that calls dlclose on another library, the process crashes with a SIGSEGV during exit. The cause it due __run_exit_handlers calling __call_tls_dtors first, decrementing l_tls_dtor_count to zero for all libraries. atexit callbacks then fire in reverse registration order: library destructors registered via __cxa_atexit at dlopen time run before _dl_fini, which was registered at startup by __libc_start_main. Using the example provided by BZ 33598, when lib1's destructor (~c1) calls dlclose(lib2), _dl_close_worker scans the namespace and finds both lib1 and lib2 with l_direct_opencount==0 and l_tls_dtor_count==0. Both are marked for unloading and unmapped and the code then returns from dlclose into the now-unmapped lib1 (note that _dl_fini would have prevented this by bumping l_direct_opencount before calling finalizers, but it runs too late in the atexit order). A new rtld_global field, _dl_exit_cxa_dso_handle (void *), is set in __run_exit_handlers to f->func.cxa.dso_handle before each ef_cxa callback fires and cleared to NULL after it returns. In _dl_close_worker, the marking loop reads this value once as cxa_dso (under dl_load_lock). For each candidate map with l_direct_opencount==0 and l_tls_dtor_count==0, if cxa_dso falls within [l_map_start, l_map_end) the map is given IDX_STILL_USED. All other maps are subject to the normal reference-count rules. The __libc_freeres path (do_dlclose) needs no special treatment: it runs outside any __cxa_atexit callback, so _dl_exit_cxa_dso_handle is NULL and _dl_close_worker behaves normally, performing real unmaps for valgrind leak detection. This scheme preserves the destructor ordering for both dclose call during destructors and .initfini calls. Two tests are added. Each loads lib1 (which has a thread_local and a static destructor that calls dlclose on lib2) and verifies: - No crash (the original bug): dlclose from a __cxa_atexit destructor that was preceded by __call_tls_dtors does not segfault. - Destructor ordering: lib2 exports set_fini_flag(int *p); its destructor writes *p=1 through the stored pointer. lib1 registers the address of a local flag after dlopen, then asserts the flag is set immediately after dlclose returns. This confirms that lib2's destructor ran during the dlclose call itself, not deferred to _dl_fini. tst-thrlocal-dlclose2 extends the scenario to lib2 itself opening a third library (libm) during init and closing it during cleanup, covering the case of nested dynamic dependencies being released from an atexit handler. Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu. --- dlfcn/Makefile | 25 ++++++++++++ dlfcn/tst-thrlocal-dlclose1-lib1.cc | 50 +++++++++++++++++++++++ dlfcn/tst-thrlocal-dlclose1-lib2.cc | 36 +++++++++++++++++ dlfcn/tst-thrlocal-dlclose1.c | 29 ++++++++++++++ dlfcn/tst-thrlocal-dlclose2-lib1.cc | 62 +++++++++++++++++++++++++++++ dlfcn/tst-thrlocal-dlclose2-lib2.c | 47 ++++++++++++++++++++++ dlfcn/tst-thrlocal-dlclose2.c | 29 ++++++++++++++ elf/dl-close.c | 11 ++++- elf/dl-support.c | 2 + stdlib/exit.c | 8 ++++ sysdeps/generic/ldsodefs.h | 5 +++ 11 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 dlfcn/tst-thrlocal-dlclose1-lib1.cc create mode 100644 dlfcn/tst-thrlocal-dlclose1-lib2.cc create mode 100644 dlfcn/tst-thrlocal-dlclose1.c create mode 100644 dlfcn/tst-thrlocal-dlclose2-lib1.cc create mode 100644 dlfcn/tst-thrlocal-dlclose2-lib2.c create mode 100644 dlfcn/tst-thrlocal-dlclose2.c diff --git a/dlfcn/Makefile b/dlfcn/Makefile index 00341dd476f..fcfdda2b9b0 100644 --- a/dlfcn/Makefile +++ b/dlfcn/Makefile @@ -68,9 +68,19 @@ tests = \ tst-dladdr \ tst-dlinfo \ tst-rec-dlopen \ + tst-thrlocal-dlclose1 \ + tst-thrlocal-dlclose2 \ tstatexit \ tstcxaatexit \ # tests + +ifneq ($(have-cxx-thread_local),yes) +tests-unsupported += \ + tst-thrlocal-dlclose1 \ + tst-thrlocal-dlclose2 + # tests-unsupported +endif + endif modules-names = \ bug-atexit1-lib \ @@ -90,8 +100,13 @@ modules-names = \ modcxaatexit \ moddummy1 \ moddummy2 \ + tst-thrlocal-dlclose1-lib1 \ + tst-thrlocal-dlclose1-lib2 \ + tst-thrlocal-dlclose2-lib1 \ + tst-thrlocal-dlclose2-lib2 \ # modules-names + failtestmod.so-no-z-defs = yes glreflib2.so-no-z-defs = yes errmsg1mod.so-no-z-defs = yes @@ -197,3 +212,13 @@ $(objpfx)bug-dl-leaf.out: $(objpfx)bug-dl-leaf-lib-cb.so $(objpfx)bug-dl-leaf-lib-cb.so: $(objpfx)bug-dl-leaf-lib.so $(objpfx)tst-rec-dlopen.out: $(objpfx)moddummy1.so $(objpfx)moddummy2.so + +CFLAGS-tst-thrlocal-dlclose1-lib1.o += -std=gnu++11 +LDLIBS-tst-thrlocal-dlclose1-lib1.so = -lstdc++ +$(objpfx)tst-thrlocal-dlclose1.out: $(objpfx)tst-thrlocal-dlclose1-lib1.so \ + $(objpfx)tst-thrlocal-dlclose1-lib2.so + +CFLAGS-tst-thrlocal-dlclose2-lib1.o += -std=gnu++11 +LDLIBS-tst-thrlocal-dlclose2-lib1.so = -lstdc++ +$(objpfx)tst-thrlocal-dlclose2.out: $(objpfx)tst-thrlocal-dlclose2-lib1.so \ + $(objpfx)tst-thrlocal-dlclose2-lib2.so diff --git a/dlfcn/tst-thrlocal-dlclose1-lib1.cc b/dlfcn/tst-thrlocal-dlclose1-lib1.cc new file mode 100644 index 00000000000..50e7450f535 --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose1-lib1.cc @@ -0,0 +1,50 @@ +/* Module for tst-thrlocal-dlclose test. + Copyright (C) 2025 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 + . */ + +#include +#include +#include + +thread_local std::unique_ptr tlp; + +static struct c1 +{ + void *h; + int lib2_fini_ran = 0; + + c1 () + { + h = dlopen ("tst-thrlocal-dlclose1-lib2.so", RTLD_NOW); + assert (h != NULL); + + void (*set_fini_flag)(int *) = + (void (*)(int *)) dlsym (h, "set_fini_flag"); + assert (set_fini_flag != NULL); + set_fini_flag (&lib2_fini_ran); + + tlp = std::make_unique(); + } + + ~c1 () + { + assert (dlclose (h) == 0); + /* Verify lib2's destructor ran during dlclose, not deferred to + _dl_fini. */ + assert (lib2_fini_ran == 1); + } +} c1; diff --git a/dlfcn/tst-thrlocal-dlclose1-lib2.cc b/dlfcn/tst-thrlocal-dlclose1-lib2.cc new file mode 100644 index 00000000000..fecd1895038 --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose1-lib2.cc @@ -0,0 +1,36 @@ +/* Module for tst-thrlocal-dlclose test. + Copyright (C) 2025 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 + . */ + +static int *fini_flag; + +extern "C" void +set_fini_flag (int *p) +{ + fini_flag = p; +} + +static struct lib2_dtor +{ + ~lib2_dtor () + { + if (fini_flag != nullptr) + *fini_flag = 1; + } +} lib2_dtor_obj; + +int foo (void) { return 42; } diff --git a/dlfcn/tst-thrlocal-dlclose1.c b/dlfcn/tst-thrlocal-dlclose1.c new file mode 100644 index 00000000000..602e4ac32c9 --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose1.c @@ -0,0 +1,29 @@ +/* Check if thread local destructor dclose does not fail (BZ 33598) + Copyright (C) 2025 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 + . */ + +#include + +int +do_test (void) +{ + xdlclose (xdlopen ("tst-thrlocal-dlclose1-lib1.so", RTLD_NOW)); + + return 0; +} + +#include diff --git a/dlfcn/tst-thrlocal-dlclose2-lib1.cc b/dlfcn/tst-thrlocal-dlclose2-lib1.cc new file mode 100644 index 00000000000..62c31873b6c --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose2-lib1.cc @@ -0,0 +1,62 @@ +/* Module for tst-thrlocal-dlclose test. + Copyright (C) 2025 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 + . */ + +#include +#include +#include + +thread_local std::unique_ptr tlp; + +static struct c1 +{ + void *h; + + void (*init)(void); + void (*cleanup)(void); + int lib2_fini_ran = 0; + + c1 () + { + h = dlopen ("tst-thrlocal-dlclose2-lib2.so", RTLD_NOW); + assert (h != NULL); + + init = (void (*)(void)) dlsym (h, "init"); + assert (init); + + cleanup = (void (*)(void)) dlsym (h, "cleanup"); + assert (cleanup); + + void (*set_fini_flag)(int *) = + (void (*)(int *)) dlsym (h, "set_fini_flag"); + assert (set_fini_flag != NULL); + set_fini_flag (&lib2_fini_ran); + + init (); + + tlp = std::make_unique(); + } + + ~c1 () + { + cleanup (); + assert (dlclose (h) == 0); + /* Verify lib2's destructor ran during dlclose, not deferred to + _dl_fini. */ + assert (lib2_fini_ran == 1); + } +} c1; diff --git a/dlfcn/tst-thrlocal-dlclose2-lib2.c b/dlfcn/tst-thrlocal-dlclose2-lib2.c new file mode 100644 index 00000000000..9f688935d7b --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose2-lib2.c @@ -0,0 +1,47 @@ +/* Module for tst-thrlocal-dlclose test. + Copyright (C) 2025 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 + . */ + +#include +#include + +static void *h; +static int *fini_flag; + +void +set_fini_flag (int *p) +{ + fini_flag = p; +} + +static void __attribute__ ((destructor)) +lib2_fini (void) +{ + if (fini_flag != NULL) + *fini_flag = 1; +} + +void init (void) +{ + h = dlopen ("libm.so.6", RTLD_NOW); + assert(h); +} + +void cleanup (void) +{ + dlclose (h); +} diff --git a/dlfcn/tst-thrlocal-dlclose2.c b/dlfcn/tst-thrlocal-dlclose2.c new file mode 100644 index 00000000000..ad1802df42f --- /dev/null +++ b/dlfcn/tst-thrlocal-dlclose2.c @@ -0,0 +1,29 @@ +/* Check if thread local destructor dclose does not fail (BZ 33598) + Copyright (C) 2025 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 + . */ + +#include + +int +do_test (void) +{ + xdlclose (xdlopen ("tst-thrlocal-dlclose2-lib1.so", RTLD_NOW)); + + return 0; +} + +#include diff --git a/elf/dl-close.c b/elf/dl-close.c index 92bce07c7a4..00c9f565ac3 100644 --- a/elf/dl-close.c +++ b/elf/dl-close.c @@ -144,6 +144,10 @@ _dl_close_worker (struct link_map *map, bool force) bool any_tls = false; const unsigned int nloaded = ns->_ns_nloaded; struct link_map *maps[nloaded]; + /* If a __cxa_atexit callback is currently running, its library must not + be unloaded while still on the call stack. Get the address of the DSO + handle to identify which map to protect in the loop below. */ + ElfW(Addr) cxa_dso = (ElfW(Addr)) GL(dl_exit_cxa_dso_handle); /* Run over the list and assign indexes to the link maps and enter them into the MAPS array. */ @@ -185,7 +189,12 @@ _dl_close_worker (struct link_map *map, bool force) /* See CONCURRENCY NOTES in cxa_thread_atexit_impl.c to know why acquire is sufficient and correct. */ && atomic_load_acquire (&l->l_tls_dtor_count) == 0 - && !l->l_map_used) + && !l->l_map_used + /* Protect the library whose __cxa_atexit callback is currently + executing: its pages must not be unmapped while still in use. */ + && (cxa_dso == 0 + || cxa_dso < l->l_map_start + || cxa_dso >= l->l_map_end)) continue; /* We need this object and we handle it now. */ diff --git a/elf/dl-support.c b/elf/dl-support.c index 0508d6113b5..50d3bbf5897 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -169,6 +169,8 @@ fpu_control_t _dl_fpu_control = _FPU_DEFAULT; /* Required flags used for stack allocation. */ int _dl_stack_prot_flags = DEFAULT_STACK_PROT_PERMS; +void *_dl_exit_cxa_dso_handle = NULL; + #if __PTHREAD_NPTL list_t _dl_stack_used; list_t _dl_stack_user; diff --git a/stdlib/exit.c b/stdlib/exit.c index 114657a8fb8..eb37a9eae3a 100644 --- a/stdlib/exit.c +++ b/stdlib/exit.c @@ -21,6 +21,7 @@ #include #include #include +#include #include "exit.h" /* Initialize the flag that indicates exit function processing @@ -113,10 +114,17 @@ __run_exit_handlers (int status, struct exit_function_list **listp, arg = f->func.cxa.arg; PTR_DEMANGLE (cxafct); + /* Track the DSO handle of the currently-executing callback so + that _dl_close_worker can protect it from being unloaded if a + destructor calls dlclose on another library (BZ 33598). */ + GL(dl_exit_cxa_dso_handle) = f->func.cxa.dso_handle; + /* Unlock the list while we call a foreign function. */ __libc_lock_unlock (__exit_funcs_lock); cxafct (arg, status); __libc_lock_lock (__exit_funcs_lock); + + GL(dl_exit_cxa_dso_handle) = NULL; break; } diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 15c46598539..fb378c1aec4 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -441,6 +441,11 @@ struct rtld_global /* Generation counter for the dtv. */ EXTERN size_t _dl_tls_generation; + /* DSO handle of the __cxa_atexit callback currently executing, or NULL. + Used in _dl_close_worker to protect the executing library from being + unloaded while its destructor is still on the call stack (BZ 33598). */ + EXTERN void *_dl_exit_cxa_dso_handle; + /* Scopes to free after next THREAD_GSCOPE_WAIT (). */ EXTERN struct dl_scope_free_list {