From patchwork Tue Sep 14 19:09:19 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Siddhesh Poyarekar X-Patchwork-Id: 44996 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 B9B5E385801D for ; Tue, 14 Sep 2021 19:09:56 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org B9B5E385801D DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1631646596; bh=EDrraS5T0rVezsQQsRlhS/1D1np9uPGthYQ/ETghypA=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:Cc:From; b=ACgouNHUq5AFI+yo6aW17LlfgfYcntuv+vVVRFM3bSKGgAkjB6j0zRs1lYftkwRYx 8t0OuprYIAgz4mugFGJBNi/kVRGbbgVYfUx+D+0DrTFLWF+1zLutFJt8ltfFIWk5wh ucz9cu45ziU/qfSetvEmLfDvYSvBxc4GdnEi99nU= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from brown.birch.relay.mailchannels.net (brown.birch.relay.mailchannels.net [23.83.209.23]) by sourceware.org (Postfix) with ESMTPS id 4B07C3858402 for ; Tue, 14 Sep 2021 19:09:33 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 4B07C3858402 X-Sender-Id: dreamhost|x-authsender|siddhesh@gotplt.org Received: from relay.mailchannels.net (localhost [127.0.0.1]) by relay.mailchannels.net (Postfix) with ESMTP id 858812E045F; Tue, 14 Sep 2021 19:09:31 +0000 (UTC) Received: from pdx1-sub0-mail-a17.g.dreamhost.com (unknown [127.0.0.6]) (Authenticated sender: dreamhost) by relay.mailchannels.net (Postfix) with ESMTPA id 8F9A02E0C7C; Tue, 14 Sep 2021 19:09:30 +0000 (UTC) X-Sender-Id: dreamhost|x-authsender|siddhesh@gotplt.org Received: from pdx1-sub0-mail-a17.g.dreamhost.com (pop.dreamhost.com [64.90.62.162]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384) by 100.112.147.92 (trex/6.4.3); Tue, 14 Sep 2021 19:09:31 +0000 X-MC-Relay: Junk X-MailChannels-SenderId: dreamhost|x-authsender|siddhesh@gotplt.org X-MailChannels-Auth-Id: dreamhost X-Bitter-Versed: 24b68a1d6c84dc97_1631646571084_586124345 X-MC-Loop-Signature: 1631646571084:3578806590 X-MC-Ingress-Time: 1631646571083 Received: from pdx1-sub0-mail-a17.g.dreamhost.com (localhost [127.0.0.1]) by pdx1-sub0-mail-a17.g.dreamhost.com (Postfix) with ESMTP id 4DD3D8348B; Tue, 14 Sep 2021 12:09:30 -0700 (PDT) Received: from rhbox.intra.reserved-bit.com (unknown [1.186.224.23]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) (Authenticated sender: siddhesh@gotplt.org) by pdx1-sub0-mail-a17.g.dreamhost.com (Postfix) with ESMTPSA id A84888342C; Tue, 14 Sep 2021 12:09:26 -0700 (PDT) X-DH-BACKEND: pdx1-sub0-mail-a17 To: libc-alpha@sourceware.org Subject: [PATCH] ld.so: Handle read-only dynamic section gracefully [BZ #28340] Date: Wed, 15 Sep 2021 00:39:19 +0530 Message-Id: <20210914190919.1728320-1-siddhesh@sourceware.org> X-Mailer: git-send-email 2.31.1 MIME-Version: 1.0 X-Spam-Status: No, score=-3495.6 required=5.0 tests=BAYES_00, GIT_PATCH_0, JMQ_SPF_NEUTRAL, KAM_DMARC_NONE, KAM_DMARC_STATUS, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H2, SPF_HELO_NONE, SPF_NEUTRAL, TXREP autolearn=ham autolearn_force=no version=3.4.4 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) 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: Siddhesh Poyarekar via Libc-alpha From: Siddhesh Poyarekar Reply-To: Siddhesh Poyarekar Cc: fweimer@redhat.com Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" ld.so on x86_64 (and possibly for all other architectures that don't have a read-only dynamic section by default but I haven't checked on all of them), when invoked with a DSO that has a read-only .dynamic section, crashes when trying to update gotplt, symtab, etc. entries with the load address. This crash happens even during verification, i.e. when it is invoked with --verify or with LD_TRACE_LOADED_OBJECTS. This is seen with vdso64.so from the Linux kernel on x86_64 for example. Handle this case more gracefully by bailing out early when a read-only PT_DYNAMIC segment is encountered and if the dynamic section has entries that would need to be updated on relocation. A test case has also been added to verify that BZ #28340 has been fixed. --- elf/Makefile | 11 +++++++++-- elf/dl-load.c | 14 ++++++++++++++ elf/get-dynamic-info.h | 41 ++++++++++++++++++++++++++++++++++++++++ elf/rtld.c | 12 ++++++++++++ elf/tst-ro-dynamic-mod.c | 1 + elf/tst-ro-dynamic.map | 13 +++++++++++++ elf/tst-ro-dynamic.sh | 36 +++++++++++++++++++++++++++++++++++ 7 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 elf/tst-ro-dynamic-mod.c create mode 100644 elf/tst-ro-dynamic.map create mode 100644 elf/tst-ro-dynamic.sh diff --git a/elf/Makefile b/elf/Makefile index 9f3fadc37e..0bed622040 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -223,7 +223,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \ tst-tls-ie tst-tls-ie-dlmopen argv0test \ tst-glibc-hwcaps tst-glibc-hwcaps-prepend tst-glibc-hwcaps-mask \ tst-tls20 tst-tls21 tst-dlmopen-dlerror tst-dlmopen-gethostbyname \ - tst-dl-is_dso + tst-dl-is_dso tst-ro-dynamic # reldep9 tests-internal += loadtest unload unload2 circleload1 \ neededtest neededtest2 neededtest3 neededtest4 \ @@ -359,7 +359,7 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \ libmarkermod4-1 libmarkermod4-2 libmarkermod4-3 libmarkermod4-4 \ tst-tls20mod-bad tst-tls21mod tst-dlmopen-dlerror-mod \ tst-auxvalmod \ - tst-dlmopen-gethostbyname-mod \ + tst-dlmopen-gethostbyname-mod tst-ro-dynamic-mod \ # Most modules build with _ISOMAC defined, but those filtered out # depend on internal headers. @@ -1908,3 +1908,10 @@ $(objpfx)tst-getauxval-static.out: $(objpfx)tst-auxvalmod.so tst-getauxval-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx) $(objpfx)tst-dlmopen-gethostbyname.out: $(objpfx)tst-dlmopen-gethostbyname-mod.so + +LDFLAGS-tst-ro-dynamic-mod = -Wl,--version-script=tst-ro-dynamic.map +$(objpfx)tst-ro-dynamic-mod.so: tst-ro-dynamic.map +$(objpfx)tst-ro-dynamic.out: tst-ro-dynamic.sh $(objpfx)tst-ro-dynamic-mod.so \ + $(objpfx)ld.so + $(SHELL) $^ '$(test-wrapper-env)' '$(run_program_env)' > $@; \ + $(evaluate-test) diff --git a/elf/dl-load.c b/elf/dl-load.c index 650e4edc35..5bac007a01 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -1124,6 +1124,9 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, * executable. Other platforms default to a nonexecutable stack and don't * need PT_GNU_STACK to do so. */ uint_fast16_t stack_flags = DEFAULT_STACK_PERMS; +#ifndef DL_RO_DYN_SECTION + bool readonly_dynamic = false; +#endif { /* Scan the program header table, collecting its load commands. */ @@ -1149,6 +1152,9 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, such a segment to avoid a crash later. */ l->l_ld = (void *) ph->p_vaddr; l->l_ldnum = ph->p_memsz / sizeof (ElfW(Dyn)); +#ifndef DL_RO_DYN_SECTION + readonly_dynamic = (ph->p_flags & PF_W) == 0; +#endif } break; @@ -1292,6 +1298,14 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd, else l->l_ld = (ElfW(Dyn) *) ((ElfW(Addr)) l->l_ld + l->l_addr); +#ifndef DL_RO_DYN_SECTION + if (readonly_dynamic && elf_dynamic_info_needs_fixup (l)) + { + errstring = N_("dynamic section of object file not writable"); + goto lose; + } +#endif + elf_get_dynamic_info (l, NULL); /* Make sure we are not dlopen'ing an object that has the diff --git a/elf/get-dynamic-info.h b/elf/get-dynamic-info.h index d8ec32377d..6072696afb 100644 --- a/elf/get-dynamic-info.h +++ b/elf/get-dynamic-info.h @@ -22,6 +22,47 @@ #include #include +#ifndef RESOLVE_MAP +static +#else +auto +#endif +#ifndef DL_RO_DYN_SECTION +inline bool __attribute__ ((unused, always_inline)) +elf_dynamic_info_needs_fixup (struct link_map *l) +{ +#if __ELF_NATIVE_CLASS == 32 + typedef Elf32_Word d_tag_utype; +#elif __ELF_NATIVE_CLASS == 64 + typedef Elf64_Xword d_tag_utype; +#endif + + if (l->l_addr == 0) + return false; + + for (ElfW(Dyn) *dyn = l->l_ld; dyn->d_tag != DT_NULL; dyn++) + switch ((d_tag_utype) dyn->d_tag) + { + case DT_HASH: + case DT_PLTGOT: + case DT_STRTAB: + case DT_SYMTAB: +# if !ELF_MACHINE_NO_RELA + case DT_RELA: +# endif +# if !ELF_MACHINE_NO_REL + case DT_REL: +# endif + case DT_JMPREL: + case VERSYMIDX (DT_VERSYM): + case ADDRIDX (DT_GNU_HASH): + return true; + } + + return false; +} +#endif + #ifndef RESOLVE_MAP static #else diff --git a/elf/rtld.c b/elf/rtld.c index 878e6480f4..8067a01088 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -1456,6 +1456,10 @@ dl_main (const ElfW(Phdr) *phdr, /* And it was opened directly. */ ++main_map->l_direct_opencount; +#ifndef DL_RO_DYN_SECTION + bool readonly_dynamic = false; +#endif + /* Scan the program header table for the dynamic section. */ for (ph = phdr; ph < &phdr[phnum]; ++ph) switch (ph->p_type) @@ -1468,6 +1472,9 @@ dl_main (const ElfW(Phdr) *phdr, /* This tells us where to find the dynamic section, which tells us everything we need to do. */ main_map->l_ld = (void *) main_map->l_addr + ph->p_vaddr; +#ifndef DL_RO_DYN_SECTION + readonly_dynamic = (ph->p_flags & PF_W) == 0; +#endif break; case PT_INTERP: /* This "interpreter segment" was used by the program loader to @@ -1612,6 +1619,11 @@ dl_main (const ElfW(Phdr) *phdr, if (! rtld_is_main) { +#ifndef DL_RO_DYN_SECTION + if (readonly_dynamic && elf_dynamic_info_needs_fixup (main_map)) + _dl_fatal_printf ("dynamic section of executable is not writable\n"); +#endif + /* Extract the contents of the dynamic section for easy access. */ elf_get_dynamic_info (main_map, NULL); diff --git a/elf/tst-ro-dynamic-mod.c b/elf/tst-ro-dynamic-mod.c new file mode 100644 index 0000000000..2bc87d0c3c --- /dev/null +++ b/elf/tst-ro-dynamic-mod.c @@ -0,0 +1 @@ +int foo = 0; diff --git a/elf/tst-ro-dynamic.map b/elf/tst-ro-dynamic.map new file mode 100644 index 0000000000..dac92e0b94 --- /dev/null +++ b/elf/tst-ro-dynamic.map @@ -0,0 +1,13 @@ +SECTIONS +{ + .data : { *(.data) } :text + .bss : { *(.bss) } :text + .dynamic : { *(.dynamic) } :text :dynamic + .text : { *(.text) } :text +} + +PHDRS +{ + text PT_LOAD FLAGS(5) FILEHDR PHDRS; + dynamic PT_DYNAMIC FLAGS(4); +} diff --git a/elf/tst-ro-dynamic.sh b/elf/tst-ro-dynamic.sh new file mode 100644 index 0000000000..3d7144e0e2 --- /dev/null +++ b/elf/tst-ro-dynamic.sh @@ -0,0 +1,36 @@ +#!/bin/sh +# Ensure ld.so does not crash when verifying DSO with read-only dynamic +# section. +# 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 +# . + +dso=$1 +rtld=$2 +test_wrapper_env=$3 +run_program_env=$4 + +${test_wrapper_env} \ +${run_program_env} \ +$rtld --verify ${dso} + +ret=$? + +# ld.so should fail gracefully by returning 1. +if [ $ret -ne 1 ]; then + echo "ld.so returned $ret" + exit 1 +fi