From patchwork Wed Apr 28 18:01:43 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 43191 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 94A63393D036; Wed, 28 Apr 2021 18:01:26 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 94A63393D036 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1619632886; bh=hAh6MbMttHGVwiM2oOx91R0/F0b8XMJTTHa3rP0mo2U=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:Cc:From; b=HW73iEkNCBjMacqtPy4GAZcEscBllD06jXa/MvzvT0TqW+2i0Ut+ibWOkdXg5DVdJ EFtpsiv0k6QqoToG/6eh03E1pWjMNLamdPjRIMP3Rb9on6LwqJ78UrY+y7+zb5g/Jx 6HCJvcP0vgIt+7pyTbzk+ZISO73LlQ1X1TZlyRRw= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [216.205.24.124]) by sourceware.org (Postfix) with ESMTP id 59296393D011 for ; Wed, 28 Apr 2021 18:01:22 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.3.2 sourceware.org 59296393D011 Received: from mimecast-mx01.redhat.com (mimecast-mx01.redhat.com [209.132.183.4]) (Using TLS) by relay.mimecast.com with ESMTP id us-mta-121-CYACxy-SMlO85eK6INpWGw-1; Wed, 28 Apr 2021 14:01:15 -0400 X-MC-Unique: CYACxy-SMlO85eK6INpWGw-1 Received: from smtp.corp.redhat.com (int-mx04.intmail.prod.int.phx2.redhat.com [10.5.11.14]) (using TLSv1.2 with cipher AECDH-AES256-SHA (256/256 bits)) (No client certificate requested) by mimecast-mx01.redhat.com (Postfix) with ESMTPS id C8AF4106BAA7; Wed, 28 Apr 2021 18:01:14 +0000 (UTC) Received: from oldenburg.str.redhat.com (ovpn-113-20.ams2.redhat.com [10.36.113.20]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 857BA5D9C6; Wed, 28 Apr 2021 18:01:13 +0000 (UTC) To: libc-alpha@sourceware.org Subject: [RFC] elf: Implement filtering of symbols historically defined in libpthread Date: Wed, 28 Apr 2021 20:01:43 +0200 Message-ID: <87h7jqguew.fsf@oldenburg.str.redhat.com> User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/27.2 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 2.79 on 10.5.11.14 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-11.8 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_LOW, RCVD_IN_MSPIKE_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_PASS, TXREP, URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.2 X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-Patchwork-Original-From: Florian Weimer via Libc-alpha From: Florian Weimer Reply-To: Florian Weimer Cc: Andreas Schwab Errors-To: libc-alpha-bounces@sourceware.org Sender: "Libc-alpha" Definitions of these symbols in libc expose bugs in existing binaries linked against earlier glibc versions. Therefore, hide these symbols for old binaries. The symbol list in sysdeps/nptl/dl-pthread-weak.c contains some symbols which have not been moved yet, but that is harmless because the function is only invoked if the symbol is found in libc.so. The test suite passes on i686-gnu-linux, powerpc64-linux-gnu, x86_64-linux-gnu with these changes. Is this the direction we want to go in? Then I'm going to add test cases, probably using assembler. Personally I think it's not *too* bad, also not particularly nice either. elf/dl-pthread-weak.os brings in 2-3 KiB of code (but few run-time relocations). One possibility I have not mentioned in the comment is to put the moved symbols into a GLIBCPTHREAD_2.34 symbol version and use the presence of this version on the chain as an indicator that the symbol uses special treatment. This eliminates the separate string table. The downside is that we cannot easily add more symbols if we discover some are missing. This happened to me during development with pthread_mutexattr_gettype, which is a GLIBC_2.1 symbol and therefore not actually suitable for detecting the presence of libpthread (historically speaking). And it could happen again with thrd_exit (which is of course much younger). Thanks, Florian --- elf/Makefile | 2 +- elf/dl-lookup.c | 13 +++- elf/dl-open.c | 3 + elf/dl-pthread-weak.c | 20 +++++ elf/dl-version.c | 2 + sysdeps/generic/dl-pthread-weak.h | 67 +++++++++++++++++ sysdeps/nptl/dl-pthread-weak.c | 153 ++++++++++++++++++++++++++++++++++++++ sysdeps/nptl/dl-pthread-weak.h | 107 ++++++++++++++++++++++++++ 8 files changed, 363 insertions(+), 4 deletions(-) diff --git a/elf/Makefile b/elf/Makefile index f09988f7d2..0dd430366f 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -34,7 +34,7 @@ dl-routines = $(addprefix dl-,load lookup object reloc deps \ version profile tls origin scope \ execstack open close trampoline \ exception sort-maps lookup-direct \ - call-libc-early-init write \ + call-libc-early-init write pthread-weak \ thread_gscope_wait tls_init_tp) ifeq (yes,$(use-ldconfig)) dl-routines += dl-cache diff --git a/elf/dl-lookup.c b/elf/dl-lookup.c index eea217eb28..2a2b46f85c 100644 --- a/elf/dl-lookup.c +++ b/elf/dl-lookup.c @@ -29,6 +29,7 @@ #include #include #include +#include #include @@ -64,6 +65,7 @@ check_match (const char *const undef_name, const Elf_Symndx symidx, const char *const strtab, const struct link_map *const map, + const struct link_map *undef_map, const ElfW(Sym) **const versioned_sym, int *const num_versions) { @@ -142,6 +144,11 @@ check_match (const char *const undef_name, public interface should be returned. */ if (verstab != NULL) { + /* Check if this is a legacy pthread weak symbol reference. + If yes, then do not bind to this symbol. */ + if (dl_pthread_hide_symbol (undef_map, undef_name, ref, map)) + return NULL; + if ((verstab[symidx] & 0x7fff) >= ((flags & DL_LOOKUP_RETURN_NEWEST) ? 2 : 3)) { @@ -429,8 +436,8 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash, symidx = ELF_MACHINE_HASH_SYMIDX (map, hasharr); sym = check_match (undef_name, ref, version, flags, type_class, &symtab[symidx], symidx, - strtab, map, &versioned_sym, - &num_versions); + strtab, map, undef_map, + &versioned_sym, &num_versions); if (sym != NULL) goto found_it; } @@ -454,7 +461,7 @@ do_lookup_x (const char *undef_name, uint_fast32_t new_hash, { sym = check_match (undef_name, ref, version, flags, type_class, &symtab[symidx], symidx, - strtab, map, &versioned_sym, + strtab, map, undef_map, &versioned_sym, &num_versions); if (sym != NULL) goto found_it; diff --git a/elf/dl-open.c b/elf/dl-open.c index ab7aaa345e..4389159717 100644 --- a/elf/dl-open.c +++ b/elf/dl-open.c @@ -35,6 +35,7 @@ #include #include #include +#include #include #include @@ -743,6 +744,8 @@ dl_open_worker (void *a) on memory allocation failure. See bug 16134. */ update_tls_slotinfo (new); + dl_pthread_record_dlopen (new); + /* Notify the debugger all new objects have been relocated. */ if (relocation_in_progress) LIBC_PROBE (reloc_complete, 3, args->nsid, r, new); diff --git a/elf/dl-pthread-weak.c b/elf/dl-pthread-weak.c new file mode 100644 index 0000000000..aff80d4177 --- /dev/null +++ b/elf/dl-pthread-weak.c @@ -0,0 +1,20 @@ +/* Weak references to symbols formerly in libpthread. Generic version. + Copyright (C) 2021 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 + . */ + +/* The generic version is a header-only implementation. */ +#include diff --git a/elf/dl-version.c b/elf/dl-version.c index 914955c2a8..d4c3b24a76 100644 --- a/elf/dl-version.c +++ b/elf/dl-version.c @@ -24,6 +24,7 @@ #include #include #include <_itoa.h> +#include #include @@ -220,6 +221,7 @@ _dl_check_map_versions (struct link_map *map, int verbose, int trace_mode) strtab + aux->vna_name, needed->l_real, verbose, aux->vna_flags & VER_FLG_WEAK); + dl_pthread_record_version (map, aux); /* Compare the version index. */ if ((unsigned int) (aux->vna_other & 0x7fff) > ndx_high) diff --git a/sysdeps/generic/dl-pthread-weak.h b/sysdeps/generic/dl-pthread-weak.h new file mode 100644 index 0000000000..109cb4264b --- /dev/null +++ b/sysdeps/generic/dl-pthread-weak.h @@ -0,0 +1,67 @@ +/* Weak references to symbols formerly in libpthread. Generic version. + Copyright (C) 2021 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 + . */ + +/* A lot of applications contain code like this: + + if (pthread_mutexattr_gettype != NULL) + pthread_once (&once_control, initializer_function); + + pthread_mutexattr_gettype and pthread_once are declared as weak in + the application. Traditionally, link editors apply various forms + of relaxations to a call to a weak function symbol if the symbol is + undefined at static link time. This eliminates the symbol + reference, but the relevant code path cannot be executed anymore. + Such code paths become active after symbols like + pthread_mutexattr_gettype are moved into libc, so it is necessary + to mask the existence of the symbol for old binaries. */ + +#ifndef _DL_PTHREAD_WEAK_H +#define _DL_PTHREAD_WEAK_H + +#include +#include +#include + +/* Returns true if check_match in elf/dl-lookup.c should not resolve + the symbol. Called only if an unversioned symbol is about to be + bound to a versioned symbol. */ +static inline bool +dl_pthread_hide_symbol (const struct link_map *undef_map, + const char *undef_name, + const ElfW(Sym) *undef_sym, + const struct link_map *defining_map) +{ + return false; +} + +/* Called during dlopen in the base namespace. This can be used to + detect a reference to libpthread. */ +static inline void +dl_pthread_record_dlopen (const struct link_map *map) +{ +} + +/* Called for each needed version during symbol version information + processing as part of dlopen. */ +static inline void +dl_pthread_record_version (const struct link_map *map, + const ElfW(Vernaux) *aux) +{ +} + +#endif /* _DL_PTHREAD_WEAK_H */ diff --git a/sysdeps/nptl/dl-pthread-weak.c b/sysdeps/nptl/dl-pthread-weak.c new file mode 100644 index 0000000000..ff32939253 --- /dev/null +++ b/sysdeps/nptl/dl-pthread-weak.c @@ -0,0 +1,153 @@ +/* Weak references to symbols formerly in libpthread. NPTL version. + Copyright (C) 2021 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 + +#if DL_PTHREAD_WEAK_NEEDED +bool _dl_pthread_weak_symbols; + +/* There are several ways to implement the check to identify the + relevant symbols. For example, we could use the otherwise unused + weak symbol status within libc.so. The set representation is + reasonably small and fast. This function is called only for weak + unversioned symbol references already found in libc.so, which is an + unusual case and therefore not on the fast path for symbol + lookup. */ + + +/* Lexicographically ordered list of symbols originally at the + GLIBC_2.0 and GLIBC_2.1 versions. Later symbols (including C11 + symbols) will give false negatives on earlier glibc versions and + are thus unsuitable for libpthread detection. Even GLIBC_2.1 is + problematic in this regard, but actual binaries use + pthread_mutexattr_gettype as the detector symbol. */ +enum { maximum_length = 21 }; +static const char symbols[][maximum_length] = + { + "_cleanup_pop", + "_cleanup_pop_restore", + "_cleanup_push", + "_cleanup_push_defer", + "_getspecific", + "_key_create", + "_mutex_destroy", + "_mutex_init", + "_mutex_lock", + "_mutex_trylock", + "_mutex_unlock", + "_mutexattr_destroy", + "_mutexattr_init", + "_mutexattr_settype", + "_once", + "_setspecific", + "atfork", + "attr_getguardsize", + "attr_getstackaddr", + "attr_getstacksize", + "attr_setguardsize", + "attr_setstackaddr", + "attr_setstacksize", + "cancel", + "create", + "detach", + "getconcurrency", + "getspecific", + "join", + "key_create", + "key_delete", + "kill", + "kill_other_threads_np", + "mutex_trylock", + "mutexattr_destroy", + "mutexattr_getkind_np", + "mutexattr_gettype", + "mutexattr_init", + "mutexattr_setkind_np", + "mutexattr_settype", + "once", + "rwlock_destroy", + "rwlock_init", + "rwlock_rdlock", + "rwlock_tryrdlock", + "rwlock_trywrlock", + "rwlock_unlock", + "rwlock_wrlock", + "rwlockattr_destroy", + "rwlockattr_getkind_np", + "rwlockattr_getpshared", + "rwlockattr_init", + "rwlockattr_setkind_np", + "rwlockattr_setpshared", + "sem_destroy", + "sem_getvalue", + "sem_init", + "sem_post", + "sem_trywait", + "sem_wait", + "setconcurrency", + "setspecific", + "sigmask", + "testcancel", + }; + +static inline int +compare (const void *a, const void *b) +{ + return strncmp (a, b, maximum_length); +} + +bool +_dl_pthread_hidden_symbol (const char *undef_name) +{ + /* Turn the __pthread and _pthread prefixes into a _ prefix. This + allows us to use a single lookup table. (The difference between + __pthread_mutex_lock and pthread_mutex_lock is significant, for + example.) */ + const char *key = NULL; + if (strncmp (undef_name, "__pthread_", strlen ("__pthread_")) == 0) + key = undef_name + strlen ("__pthread"); + else if (strncmp (undef_name, "_pthread_", strlen ("_pthread_")) == 0) + key = undef_name + strlen ("_pthread"); + else if (strncmp (undef_name, "pthread_", strlen ("pthread_")) == 0) + key = undef_name + strlen ("pthread_"); + else if (strncmp (undef_name, "sem_", strlen ("sem_")) == 0) + /* Do not remove the sem_ prefix. This would result in false + matches for symbols such as pthread_sem_post, but no such + symbols exist. */ + key = undef_name; + + if (key == NULL || strlen (key) > maximum_length) + /* The prefix of undef_name is not recognized, or the string is + not in the table because it is too long. */ + return false; + + if (bsearch (key, symbols, array_length (symbols), maximum_length, + compare) != NULL) + { + if (__glibc_unlikely (GLRO (dl_debug_mask) & DL_DEBUG_BINDINGS)) + _dl_debug_printf ("\ +not binding legacy weak reference in main program to %s\n", + undef_name); + return true; + } + + return false; +} + +#endif /* DL_PTHREAD_WEAK_NEEDED */ diff --git a/sysdeps/nptl/dl-pthread-weak.h b/sysdeps/nptl/dl-pthread-weak.h new file mode 100644 index 0000000000..f252abcafe --- /dev/null +++ b/sysdeps/nptl/dl-pthread-weak.h @@ -0,0 +1,107 @@ +/* Weak references to symbols formerly in libpthread. NPTL version. + Copyright (C) 2021 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 +#if OTHER_SHLIB_COMPAT (libpthread, GLIBC_2_0, GLIBC_2_34) + +/* For context, refer to . */ + +# include +# include +# include +# include +# include + +/* The implementation file needs to be compiled. */ +#define DL_PTHREAD_WEAK_NEEDED 1 + +/* If false, weak symbols to old libpthread-only functions are hidden + from symbol resolution in the main program. Set to true if + libpthread is loaded by the main program (via + dl_pthread_record_dlopen), or if the main program references + GLIBC_2.34 (via dl_pthread_record_version). */ +extern bool _dl_pthread_weak_symbols attribute_hidden; + +/* Returns true if UNDEF_NAME refers to a libpthread symbol that is + hidden (in case of certain weak symbol references from the main + program). */ +bool _dl_pthread_hidden_symbol (const char *undef_name) attribute_hidden; + +static inline bool +dl_pthread_hide_symbol (const struct link_map *undef_map, + const char *undef_name, + const ElfW(Sym) *undef_sym, + const struct link_map *defining_map) +{ + /* Check if symbol hiding has been disabled. */ + if (_dl_pthread_weak_symbols) + return false; + + /* Symbol hiding only applies to weak symbol references. */ + if (undef_sym == NULL || ELFW(ST_BIND) (undef_sym->st_info) != STB_WEAK) + return false; + + /* Only symbols in the main map are potentially hidden. Shared + objects are compiled as PIC and are not affected by link editor + optimizations. This implies a check that we are in the base + namespace. */ + const struct link_map *main_map = GL (dl_ns)[LM_ID_BASE]._ns_loaded; + if (undef_map != main_map) + return false; + + /* Symbol hiding only applies to symbols in libc.so. */ + if (defining_map != GL (dl_ns)[LM_ID_BASE].libc_map) + return false; + + /* Delegate to the out-of-line name checking function. */ + return _dl_pthread_hidden_symbol (undef_name); +} + +static inline void +dl_pthread_record_dlopen (const struct link_map *map) +{ + /* This assumes that our libpthread has soname (and still exists as + a separate shared object). */ + const char *strtab = (const char *) D_PTR (map, l_info[DT_STRTAB]); + if (map->l_info[DT_SONAME] != NULL + && strcmp (strtab + map->l_info[DT_SONAME]->d_un.d_val, + LIBPTHREAD_SO) == 0) + _dl_pthread_weak_symbols = true; +} + +static inline void +dl_pthread_record_version (const struct link_map *map, + const ElfW(Vernaux) *aux) +{ + /* Only GLIBC_2.34 references from the main map disable weak symbol + hiding. */ + const struct link_map *main_map = GL (dl_ns)[LM_ID_BASE]._ns_loaded; + if (map != main_map) + return; + + const char *strtab = (const char *) D_PTR (map, l_info[DT_STRTAB]); + if (strcmp (strtab + aux->vna_name, "GLIBC_2.34") == 0) + _dl_pthread_weak_symbols = true; +} + +#else +/* For static build and glibc after 2.34, it is possible to use the + no-op default version. */ +# include +# define DL_PTHREAD_WEAK_NEEDED 0 +#endif