From patchwork Tue Apr 26 19:38:32 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Adhemerval Zanella Netto X-Patchwork-Id: 53247 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 1D975385781F for ; Tue, 26 Apr 2022 19:39:01 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 1D975385781F DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=sourceware.org; s=default; t=1651001941; bh=ti+ZwJqyHIsZym1+sxO6puSgoPboKD8oO2USMOhRDSs=; h=To:Subject:Date:List-Id:List-Unsubscribe:List-Archive:List-Post: List-Help:List-Subscribe:From:Reply-To:From; b=IAqodQhTdVjGrKG6IecdnUUDyc9u/RjY8IR9SJbCFhLtUpmf6jaXbqWCMi+NKnTQU ATcTkkXzV4sPoeRZlvik/SvH7noNLKe3XX5BZiuyxuxljOVtHyHNLiHCjn9DUi48v4 3uMAI3P3sD8wKs1XiUT3WT1opICmwjn1imCTDoSY= X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from mail-oi1-x22c.google.com (mail-oi1-x22c.google.com [IPv6:2607:f8b0:4864:20::22c]) by sourceware.org (Postfix) with ESMTPS id 6F0943858C83 for ; Tue, 26 Apr 2022 19:38:38 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.1 sourceware.org 6F0943858C83 Received: by mail-oi1-x22c.google.com with SMTP id s131so2545131oie.1 for ; Tue, 26 Apr 2022 12:38:38 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20210112; h=x-gm-message-state:from:to:subject:date:message-id:mime-version :content-transfer-encoding; bh=ti+ZwJqyHIsZym1+sxO6puSgoPboKD8oO2USMOhRDSs=; b=NJoIlD3CKnd9Ka5LL0sW0Q0PvBK5eT780sf2z2KurTSCzOG5R+ZbUN+Hhuy+JEs871 0R18QLjb/faQ/+coO+qz5ylsSEm7o8Z1dc7gJzg7AFtJ+hK6fkhcgYs1K1CEHdqzdMph 2g6Gm+jpHxO/2nz5ouGZeqAJYShu3m6o/PSf/hNBpTXJiXJx9I2uUbT6Ft9eyzYwbHUd uQPlHnhsyOFIBRfNFte7agc3tzYLJSj4t8MG1AKzwLz5VV0NzoCSDKk66ljZojG9tKih um14m1n/Lck0AW6uz4amTUnnREelJRmfI6BRFzumORjaMRxa5GmQlGWWQPtrT2dO/yJh 9zSw== X-Gm-Message-State: AOAM533HGfwyH021r2pzKG2o+xonpNkDzhcMnagTJFlWZsG481625M50 2jRikefK0LyMXIkZndgfiepXPnnGc8ICaw== X-Google-Smtp-Source: ABdhPJzqJmrcIwJ9P4yN6QYsnMMYAhu7uMirjcEBdeoLvAhMne21SInQUp00TWs4DXSMYsIngNGGig== X-Received: by 2002:a05:6808:1589:b0:322:9102:5503 with SMTP id t9-20020a056808158900b0032291025503mr11636070oiw.68.1651001917210; Tue, 26 Apr 2022 12:38:37 -0700 (PDT) Received: from birita.. ([2804:431:c7ca:4214:745b:d03c:b667:123b]) by smtp.gmail.com with ESMTPSA id o4-20020a0568080bc400b0032571d1f27bsm344242oik.6.2022.04.26.12.38.35 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 26 Apr 2022 12:38:36 -0700 (PDT) To: libc-alpha@sourceware.org, Siddhesh Poyarekar Subject: [PATCH v6] elf: Fix DFS sorting algorithm for LD_TRACE_LOADED_OBJECTS with missing libraries (BZ #28868) Date: Tue, 26 Apr 2022 16:38:32 -0300 Message-Id: <20220426193832.1040685-1-adhemerval.zanella@linaro.org> X-Mailer: git-send-email 2.34.1 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_NONE, SPF_HELO_NONE, SPF_PASS, 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: Adhemerval Zanella via Libc-alpha From: Adhemerval Zanella Netto Reply-To: Adhemerval Zanella Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org Sender: "Libc-alpha" On _dl_map_object the underlying file is not opened in trace mode (in other cases where the underlying file can't be opened, _dl_map_object quits with an error). If there any missing libraries being processed, they will not be considered on final nlist size passed on _dl_sort_maps later in the function. And it is then used by _dl_sort_maps_dfs on the stack allocated working maps: 222 /* Array to hold RPO sorting results, before we copy back to maps[]. */ 223 struct link_map *rpo[nmaps]; 224 225 /* The 'head' position during each DFS iteration. Note that we start at 226 one past the last element due to first-decrement-then-store (see the 227 bottom of above dfs_traversal() routine). */ 228 struct link_map **rpo_head = &rpo[nmaps]; However while transversing the 'l_initfini' on dfs_traversal it will still considere the l_faked maps and thus update rpo more times than the allocated working 'rpo', overflowing the stack object. As suggested in bugzilla, one option would be to avoid sorting the maps for trace mode. However I think ignoring l_faked object does make sense (there is one less constraint to call the sorting function), it allows a slight less stack usage for trace, and it is slight simpler solution. The tests does trigger the stack overflow, however I tried to make it more generic to check different scenarios or missing objects. Checked on x86_64-linux-gnu. Reviewed-by: Siddhesh Poyarekar --- v6: Rebase to master, remove unused file, use {} instead of (). v5: Refactor tests make rules. v4: Use --soname to build the libraries, fixed typos. v3: Removed stamp files to avoid add them as linker depedencies and moved test evaluation to python script. v2: Added comments and fixed the default tst-trace1 --library-path. --- elf/Makefile | 54 ++++++++++++++++++++ elf/dl-deps.c | 2 + elf/dl-sort-maps.c | 4 +- elf/libtracemod1-1.c | 1 + elf/libtracemod2-1.c | 1 + elf/libtracemod3-1.c | 1 + elf/libtracemod4-1.c | 1 + elf/libtracemod5-1.c | 1 + elf/tst-trace1.exp | 4 ++ elf/tst-trace2.exp | 6 +++ elf/tst-trace3.exp | 6 +++ elf/tst-trace4.exp | 6 +++ elf/tst-trace5.exp | 6 +++ scripts/tst-ld-trace.py | 108 ++++++++++++++++++++++++++++++++++++++++ 14 files changed, 200 insertions(+), 1 deletion(-) create mode 100644 elf/libtracemod1-1.c create mode 100644 elf/libtracemod2-1.c create mode 100644 elf/libtracemod3-1.c create mode 100644 elf/libtracemod4-1.c create mode 100644 elf/libtracemod5-1.c create mode 100644 elf/tst-trace1.exp create mode 100644 elf/tst-trace2.exp create mode 100644 elf/tst-trace3.exp create mode 100644 elf/tst-trace4.exp create mode 100644 elf/tst-trace5.exp create mode 100755 scripts/tst-ld-trace.py diff --git a/elf/Makefile b/elf/Makefile index ad253defdd..80e8552c59 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -693,6 +693,11 @@ modules-names += \ libmarkermod5-3 \ libmarkermod5-4 \ libmarkermod5-5 \ + libtracemod1-1 \ + libtracemod2-1 \ + libtracemod3-1 \ + libtracemod4-1 \ + libtracemod5-1 \ ltglobmod1 \ ltglobmod2 \ neededobj1 \ @@ -1161,6 +1166,11 @@ tests-special += \ $(objpfx)tst-initorder2-cmp.out \ $(objpfx)tst-unused-dep-cmp.out \ $(objpfx)tst-unused-dep.out \ + $(objpfx)tst-trace1.out \ + $(objpfx)tst-trace2.out \ + $(objpfx)tst-trace3.out \ + $(objpfx)tst-trace4.out \ + $(objpfx)tst-trace5.out \ # tests-special endif @@ -2884,3 +2894,47 @@ $(objpfx)tst-relr-mod4a.so: $(objpfx)tst-relr-mod4a.os \ -shared -o $@.new $(filter-out $(map-file),$^) $(call after-link,$@.new) mv -f $@.new $@ + +LDFLAGS-libtracemod1-1.so += -Wl,-soname,libtracemod1.so +LDFLAGS-libtracemod2-1.so += -Wl,-soname,libtracemod2.so +LDFLAGS-libtracemod3-1.so += -Wl,-soname,libtracemod3.so +LDFLAGS-libtracemod4-1.so += -Wl,-soname,libtracemod4.so +LDFLAGS-libtracemod5-1.so += -Wl,-soname,libtracemod5.so + +$(objpfx)libtracemod1-1.so: $(objpfx)libtracemod2-1.so \ + $(objpfx)libtracemod3-1.so +$(objpfx)libtracemod2-1.so: $(objpfx)libtracemod4-1.so \ + $(objpfx)libtracemod5-1.so + +define libtracemod-x +$(objpfx)libtracemod$(1)/libtracemod$(1).so: $(objpfx)libtracemod$(1)-1.so + $$(make-target-directory) + cp $$< $$@ +endef +libtracemod-suffixes = 1 2 3 4 5 +$(foreach i,$(libtracemod-suffixes), $(eval $(call libtracemod-x,$(i)))) + +define tst-trace-skeleton +$(objpfx)tst-trace$(1).out: $(objpfx)libtracemod1/libtracemod1.so \ + $(objpfx)libtracemod2/libtracemod2.so \ + $(objpfx)libtracemod3/libtracemod3.so \ + $(objpfx)libtracemod4/libtracemod4.so \ + $(objpfx)libtracemod5/libtracemod5.so \ + $(..)scripts/tst-ld-trace.py \ + tst-trace$(1).exp + ${ $(PYTHON) $(..)scripts/tst-ld-trace.py \ + "$(test-wrapper-env) $(elf-objpfx)$(rtld-installed-name) \ + --library-path $(common-objpfx):$(strip $(2)) \ + $(objpfx)libtracemod1/libtracemod1.so" tst-trace$(1).exp \ + } > $$@; $$(evaluate-test) +endef + +$(eval $(call tst-trace-skeleton,1,)) +$(eval $(call tst-trace-skeleton,2,\ + $(objpfx)libtracemod2)) +$(eval $(call tst-trace-skeleton,3,\ + $(objpfx)libtracemod2:$(objpfx)libtracemod3)) +$(eval $(call tst-trace-skeleton,4,\ + $(objpfx)libtracemod2:$(objpfx)libtracemod3:$(objpfx)libtracemod4)) +$(eval $(call tst-trace-skeleton,5,\ + $(objpfx)libtracemod2:$(objpfx)libtracemod3:$(objpfx)libtracemod4:$(objpfx)libtracemod5)) diff --git a/elf/dl-deps.c b/elf/dl-deps.c index a2fc278256..06005a0cc8 100644 --- a/elf/dl-deps.c +++ b/elf/dl-deps.c @@ -473,6 +473,8 @@ _dl_map_object_deps (struct link_map *map, for (nlist = 0, runp = known; runp; runp = runp->next) { + /* _dl_sort_maps ignores l_faked object, so it is safe to not consider + them for nlist. */ if (__builtin_expect (trace_mode, 0) && runp->map->l_faked) /* This can happen when we trace the loading. */ --map->l_searchlist.r_nlist; diff --git a/elf/dl-sort-maps.c b/elf/dl-sort-maps.c index 9e9d53ec47..96638d7ed1 100644 --- a/elf/dl-sort-maps.c +++ b/elf/dl-sort-maps.c @@ -140,7 +140,9 @@ static void dfs_traversal (struct link_map ***rpo, struct link_map *map, bool *do_reldeps) { - if (map->l_visited) + /* _dl_map_object_deps ignores l_faked objects when calculating the + number of maps before calling _dl_sort_maps, ignore them as well. */ + if (map->l_visited || map->l_faked) return; map->l_visited = 1; diff --git a/elf/libtracemod1-1.c b/elf/libtracemod1-1.c new file mode 100644 index 0000000000..7c89c9a5a4 --- /dev/null +++ b/elf/libtracemod1-1.c @@ -0,0 +1 @@ +/* Empty */ diff --git a/elf/libtracemod2-1.c b/elf/libtracemod2-1.c new file mode 100644 index 0000000000..7c89c9a5a4 --- /dev/null +++ b/elf/libtracemod2-1.c @@ -0,0 +1 @@ +/* Empty */ diff --git a/elf/libtracemod3-1.c b/elf/libtracemod3-1.c new file mode 100644 index 0000000000..7c89c9a5a4 --- /dev/null +++ b/elf/libtracemod3-1.c @@ -0,0 +1 @@ +/* Empty */ diff --git a/elf/libtracemod4-1.c b/elf/libtracemod4-1.c new file mode 100644 index 0000000000..7c89c9a5a4 --- /dev/null +++ b/elf/libtracemod4-1.c @@ -0,0 +1 @@ +/* Empty */ diff --git a/elf/libtracemod5-1.c b/elf/libtracemod5-1.c new file mode 100644 index 0000000000..7c89c9a5a4 --- /dev/null +++ b/elf/libtracemod5-1.c @@ -0,0 +1 @@ +/* Empty */ diff --git a/elf/tst-trace1.exp b/elf/tst-trace1.exp new file mode 100644 index 0000000000..4a6f5211a6 --- /dev/null +++ b/elf/tst-trace1.exp @@ -0,0 +1,4 @@ +ld 1 +libc 1 +libtracemod2.so 0 +libtracemod3.so 0 diff --git a/elf/tst-trace2.exp b/elf/tst-trace2.exp new file mode 100644 index 0000000000..e13506e2eb --- /dev/null +++ b/elf/tst-trace2.exp @@ -0,0 +1,6 @@ +ld 1 +libc 1 +libtracemod2.so 1 +libtracemod3.so 0 +libtracemod4.so 0 +libtracemod5.so 0 diff --git a/elf/tst-trace3.exp b/elf/tst-trace3.exp new file mode 100644 index 0000000000..e574549d12 --- /dev/null +++ b/elf/tst-trace3.exp @@ -0,0 +1,6 @@ +ld 1 +libc 1 +libtracemod2.so 1 +libtracemod3.so 1 +libtracemod4.so 0 +libtracemod5.so 0 diff --git a/elf/tst-trace4.exp b/elf/tst-trace4.exp new file mode 100644 index 0000000000..31ca97b35b --- /dev/null +++ b/elf/tst-trace4.exp @@ -0,0 +1,6 @@ +ld 1 +libc 1 +libtracemod2.so 1 +libtracemod3.so 1 +libtracemod4.so 1 +libtracemod5.so 0 diff --git a/elf/tst-trace5.exp b/elf/tst-trace5.exp new file mode 100644 index 0000000000..5d7d953726 --- /dev/null +++ b/elf/tst-trace5.exp @@ -0,0 +1,6 @@ +ld 1 +libc 1 +libtracemod2.so 1 +libtracemod3.so 1 +libtracemod4.so 1 +libtracemod5.so 1 diff --git a/scripts/tst-ld-trace.py b/scripts/tst-ld-trace.py new file mode 100755 index 0000000000..f5a4028003 --- /dev/null +++ b/scripts/tst-ld-trace.py @@ -0,0 +1,108 @@ +#!/usr/bin/python3 +# Dump the output of LD_TRACE_LOADED_OBJECTS in architecture neutral format. +# Copyright (C) 2022 Free Software Foundation, Inc. +# Copyright The GNU Toolchain Authors. +# 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 +# . + +import argparse +import os +import subprocess +import sys + +try: + subprocess.run +except: + class _CompletedProcess: + def __init__(self, args, returncode, stdout=None, stderr=None): + self.args = args + self.returncode = returncode + self.stdout = stdout + self.stderr = stderr + + def _run(*popenargs, input=None, timeout=None, check=False, **kwargs): + assert(timeout is None) + with subprocess.Popen(*popenargs, **kwargs) as process: + try: + stdout, stderr = process.communicate(input) + except: + process.kill() + process.wait() + raise + returncode = process.poll() + if check and returncode: + raise subprocess.CalledProcessError(returncode, popenargs) + return _CompletedProcess(popenargs, returncode, stdout, stderr) + + subprocess.run = _run + +def is_vdso(lib): + return lib.startswith('linux-gate') or lib.startswith('linux-vdso') + + +def parse_trace(cmd, fref): + new_env = os.environ.copy() + new_env['LD_TRACE_LOADED_OBJECTS'] = '1' + trace_out = subprocess.run(cmd, stdout=subprocess.PIPE, check=True, + universal_newlines=True, env=new_env).stdout + trace = [] + for line in trace_out.splitlines(): + line = line.strip() + if is_vdso(line): + continue + fields = line.split('=>' if '=>' in line else ' ') + lib = os.path.basename(fields[0].strip()) + if lib.startswith('ld'): + lib = 'ld' + elif lib.startswith('libc'): + lib = 'libc' + found = 1 if fields[1].strip() != 'not found' else 0 + trace += ['{} {}'.format(lib, found)] + trace = sorted(trace) + + reference = sorted(line.replace('\n','') for line in fref.readlines()) + + ret = 0 if trace == reference else 1 + if ret != 0: + for i in reference: + if i not in trace: + print("Only in {}: {}".format(fref.name, i)) + for i in trace: + if i not in reference: + print("Only in trace: {}".format(i)) + + sys.exit(ret) + + +def get_parser(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('command', + help='comand to run') + parser.add_argument('reference', + help='reference file to compare') + return parser + + +def main(argv): + parser = get_parser() + opts = parser.parse_args(argv) + with open(opts.reference, 'r') as fref: + # Remove the initial 'env' command. + parse_trace(opts.command.split()[1:], fref) + + +if __name__ == '__main__': + main(sys.argv[1:])