From c791d16b7dcf9d985ebe0e852481142753603353 Mon Sep 17 00:00:00 2001 From: Aaron Merey Date: Fri, 8 Dec 2023 16:44:35 -0500 Subject: [PATCH] libdwfl: Correctly handle corefile non-contiguous segments It is possible for segments of different shared libaries to be interleaved in memory such that the segments of one library are located in between non-contiguous segments of another library. For example, this can be seen with firefox on RHEL 7.9 where multiple shared libraries could be mapped in between ld-2.17.so segments: [...] 7f0972082000-7f09720a4000 00000000 139264 /usr/lib64/ld-2.17.so 7f09720a4000-7f09720a5000 00000000 4096 /memfd:mozilla-ipc (deleted) 7f09720a5000-7f09720a7000 00000000 8192 /memfd:mozilla-ipc (deleted) 7f09720a7000-7f09720a9000 00000000 8192 /memfd:mozilla-ipc (deleted) 7f0972134000-7f0972136000 00000000 8192 /usr/lib64/firefox/libmozwayland.so 7f0972136000-7f0972137000 00002000 4096 /usr/lib64/firefox/libmozwayland.so 7f0972137000-7f0972138000 00003000 4096 /usr/lib64/firefox/libmozwayland.so 7f0972138000-7f0972139000 00003000 4096 /usr/lib64/firefox/libmozwayland.so 7f097213a000-7f0972147000 00000000 53248 /usr/lib64/firefox/libmozsqlite3.so 7f0972147000-7f097221e000 0000d000 880640 /usr/lib64/firefox/libmozsqlite3.so 7f097221e000-7f0972248000 000e4000 172032 /usr/lib64/firefox/libmozsqlite3.so 7f0972248000-7f0972249000 0010e000 4096 /usr/lib64/firefox/libmozsqlite3.so 7f0972249000-7f097224c000 0010e000 12288 /usr/lib64/firefox/libmozsqlite3.so 7f097224c000-7f0972250000 00111000 16384 /usr/lib64/firefox/libmozsqlite3.so 7f0972250000-7f0972253000 00000000 12288 /usr/lib64/firefox/liblgpllibs.so [...] 7f09722a3000-7f09722a4000 00021000 4096 /usr/lib64/ld-2.17.so 7f09722a4000-7f09722a5000 00022000 4096 /usr/lib64/ld-2.17.so dwfl_segment_report_module did not account for the possibility of interleaving non-contiguous segments, resulting in premature closure of modules as well as failing to report modules. Fix this by removing segment skipping in dwfl_segment_report_module. When dwfl_segment_report_module reported a module, it would return the index of the segment immediately following the end address of the current module. Since there's a chance that other modules might fall within this address range, dwfl_segment_report_module instead returns the index of the next segment. This patch also fixes premature module closure that can occur in dwfl_segment_report_module when interleaving non-contiguous segments are found. Previously modules with start and end addresses that overlap with the current segment would have their build-ids compared with the current segment's build-id. If there was a mismatch, that module would be closed. Avoid closing modules in this case when mismatching build-ids correspond to distinct modules. https://sourceware.org/bugzilla/show_bug.cgi?id=30975 Signed-off-by: Aaron Merey --- libdwfl/dwfl_segment_report_module.c | 37 +++++++++---- tests/Makefile.am | 8 ++- tests/dwfl-core-noncontig.c | 82 ++++++++++++++++++++++++++++ tests/run-dwfl-core-noncontig.sh | 63 +++++++++++++++++++++ 4 files changed, 176 insertions(+), 14 deletions(-) create mode 100644 tests/dwfl-core-noncontig.c create mode 100755 tests/run-dwfl-core-noncontig.sh diff --git a/libdwfl/dwfl_segment_report_module.c b/libdwfl/dwfl_segment_report_module.c index 3ef62a7..09ee37b 100644 --- a/libdwfl/dwfl_segment_report_module.c +++ b/libdwfl/dwfl_segment_report_module.c @@ -737,17 +737,34 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, && invalid_elf (module->elf, module->disk_file_has_build_id, &build_id)) { - elf_end (module->elf); - close (module->fd); - module->elf = NULL; - module->fd = -1; + /* If MODULE's build-id doesn't match the disk file's + build-id, close ELF only if MODULE and ELF refer to + different builds of files with the same name. This + prevents premature closure of the correct ELF in cases + where segments of a module are non-contiguous in memory. */ + if (name != NULL && module->name[0] != '\0' + && strcmp (basename (module->name), basename (name)) == 0) + { + elf_end (module->elf); + close (module->fd); + module->elf = NULL; + module->fd = -1; + } } - if (module->elf != NULL) + else if (module->elf != NULL) { - /* Ignore this found module if it would conflict in address - space with any already existing module of DWFL. */ + /* This module has already been reported. */ skip_this_module = true; } + else + { + /* Only report this module if we haven't already done so. */ + for (Dwfl_Module *mod = dwfl->modulelist; mod != NULL; + mod = mod->next) + if (mod->low_addr == module_start + && mod->high_addr == module_end) + skip_this_module = true; + } } if (skip_this_module) goto out; @@ -781,10 +798,6 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, } } - /* Our return value now says to skip the segments contained - within the module. */ - ndx = addr_segndx (dwfl, segment, module_end, true); - /* Examine its .dynamic section to get more interesting details. If it has DT_SONAME, we'll use that as the module name. If it has a DT_DEBUG, then it's actually a PIE rather than a DSO. @@ -929,6 +942,8 @@ dwfl_segment_report_module (Dwfl *dwfl, int ndx, const char *name, ndx = -1; goto out; } + else + ndx++; /* We have reported the module. Now let the caller decide whether we should read the whole thing in right now. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 7fb8efb..9f8f769 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -42,7 +42,7 @@ check_PROGRAMS = arextract arsymtest newfile saridx scnnames sectiondump \ dwfl-bug-addr-overflow arls dwfl-bug-fd-leak \ dwfl-addr-sect dwfl-bug-report early-offscn \ dwfl-bug-getmodules dwarf-getmacros dwarf-ranges addrcfi \ - dwarfcfi \ + dwfl-core-noncontig dwarfcfi \ test-flag-nobits dwarf-getstring rerequest_tag \ alldts typeiter typeiter2 low_high_pc \ test-elf_cntl_gelf_getshdr dwflsyms dwfllines \ @@ -212,7 +212,7 @@ TESTS = run-arextract.sh run-arsymtest.sh run-ar.sh newfile test-nlist \ $(asm_TESTS) run-disasm-bpf.sh run-low_high_pc-dw-form-indirect.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ run-readelf-dw-form-indirect.sh run-strip-largealign.sh \ - run-readelf-Dd.sh + run-readelf-Dd.sh run-dwfl-core-noncontig.sh if !BIARCH export ELFUTILS_DISABLE_BIARCH = 1 @@ -632,7 +632,8 @@ EXTRA_DIST = run-arextract.sh run-arsymtest.sh run-ar.sh \ run-nvidia-extended-linemap-libdw.sh run-nvidia-extended-linemap-readelf.sh \ testfile_nvidia_linemap.bz2 \ testfile-largealign.o.bz2 run-strip-largealign.sh \ - run-funcretval++11.sh + run-funcretval++11.sh \ + run-dwfl-core-noncontig.sh testcore-noncontig.bz2 if USE_VALGRIND @@ -738,6 +739,7 @@ dwfl_bug_fd_leak_LDADD = $(libeu) $(libdw) $(libebl) $(libelf) dwfl_bug_report_LDADD = $(libdw) $(libebl) $(libelf) dwfl_bug_getmodules_LDADD = $(libeu) $(libdw) $(libebl) $(libelf) dwfl_addr_sect_LDADD = $(libeu) $(libdw) $(libebl) $(libelf) $(argp_LDADD) +dwfl_core_noncontig_LDADD = $(libdw) $(libelf) dwarf_getmacros_LDADD = $(libdw) dwarf_ranges_LDADD = $(libdw) dwarf_getstring_LDADD = $(libdw) diff --git a/tests/dwfl-core-noncontig.c b/tests/dwfl-core-noncontig.c new file mode 100644 index 0000000..04558e2 --- /dev/null +++ b/tests/dwfl-core-noncontig.c @@ -0,0 +1,82 @@ +/* Test program for dwfl_getmodules bug. + Copyright (C) 2008 Red Hat, Inc. + This file is part of elfutils. + + This file is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + elfutils 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 General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . */ + +#include +#include +#include +#include +#include ELFUTILS_HEADER(dwfl) +#include ELFUTILS_HEADER(elf) + +static const Dwfl_Callbacks cb = +{ + NULL, + NULL, + NULL, + NULL, +}; + +int +main (int argc, char **argv) +{ + assert (argc == 2); + + Dwfl *dwfl = dwfl_begin (&cb); + + int fd = open (argv[1], O_RDONLY); + assert (fd != -1); + + Elf *elf = elf_begin (fd, ELF_C_READ, NULL); + (void) dwfl_core_file_report (dwfl, elf, argv[0]); + + /* testcore-noncontig contains a shared library mapped between + non-contiguous segments of another shared library: + + [...] + 7f14e458c000-7f14e45ae000 00000000 139264 /usr/lib64/ld-2.17.so (1) + 7f14e4795000-7f14e4798000 00000000 12288 /usr/lib64/firefox/liblgpllibs.so (2) + 7f14e4798000-7f14e479d000 00003000 20480 /usr/lib64/firefox/liblgpllibs.so + 7f14e479d000-7f14e479f000 00008000 8192 /usr/lib64/firefox/liblgpllibs.so + 7f14e479f000-7f14e47a0000 00009000 4096 /usr/lib64/firefox/liblgpllibs.so + 7f14e47a0000-7f14e47a1000 0000a000 4096 /usr/lib64/firefox/liblgpllibs.so (3) + 7f14e47ad000-7f14e47ae000 00021000 4096 /usr/lib64/ld-2.17.so (4) + 7f14e47ae000-7f14e47af000 00022000 4096 /usr/lib64/ld-2.17.so */ + + /* First segment of the non-contiguous module (1). */ + int seg = dwfl_addrsegment (dwfl, 0x7f14e458c000, NULL); + assert (seg == 32); + + /* First segment of the module within the non-contiguous module's address + range (2). */ + seg = dwfl_addrsegment (dwfl, 0x7f14e4795000, NULL); + assert (seg == 33); + + /* Last segment of the module within the non-contiguous module's + address range (3). */ + seg = dwfl_addrsegment (dwfl, 0x7f14e47a0000, NULL); + assert (seg == 37); + + /* First segment of non-contiguous module following its address space + gap (4). */ + seg = dwfl_addrsegment (dwfl, 0x7f14e47ad000, NULL); + assert (seg == 40); + + dwfl_end (dwfl); + elf_end (elf); + + return 0; +} diff --git a/tests/run-dwfl-core-noncontig.sh b/tests/run-dwfl-core-noncontig.sh new file mode 100755 index 0000000..1245b67 --- /dev/null +++ b/tests/run-dwfl-core-noncontig.sh @@ -0,0 +1,63 @@ +#! /bin/sh +# Copyright (C) 2023 Red Hat, Inc. +# This file is part of elfutils. +# +# This file is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 3 of the License, or +# (at your option) any later version. +# +# elfutils 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +. $srcdir/test-subr.sh + +# Test whether libdwfl can handle corefiles containing non-contiguous +# segments where multiple modules are contained within the address +# space of some other module. + +# testcore-noncontig was generated from the following program with +# systemd-coredump on RHEL 7.9 Workstation, kernel +# 3.10.0-1160.105.1.el7.x86_64. liblgpllibs.so was packaged with +# firefox-115.4.0-1.el7_9.x86_64.rpm. + +# #include +# #include +# +# int main () { +# dlopen ("/usr/lib64/firefox/liblgpllibs.so", RTLD_GLOBAL | RTLD_NOW); +# sleep (60); +# return 0; +# } +# +# gcc -ldl -o test test.c + +tempfiles out +testfiles testcore-noncontig + +testrun ${abs_builddir}/dwfl-core-noncontig testcore-noncontig + +# Remove parts of the output that could change depending on which +# libraries are locally installed. +testrun ${abs_top_builddir}/src/unstrip -n --core testcore-noncontig \ + | sed 's/+/ /g' | cut -d " " -f1,3 | sort > out + +testrun_compare cat out <<\EOF +0x400000 3a1748a544b40a38b3be3d2d13ffa34a2a5a71c0@0x400284 +0x7f14e357e000 edf51350c7f71496149d064aa8b1441f786df88a@0x7f14e357e1d8 +0x7f14e3794000 7615604eaf4a068dfae5085444d15c0dee93dfbd@0x7f14e37941d8 +0x7f14e3a96000 09cfb171310110bc7ea9f4476c9fa044d85baff4@0x7f14e3a96210 +0x7f14e3d9e000 e10cc8f2b932fc3daeda22f8dac5ebb969524e5b@0x7f14e3d9e248 +0x7f14e3fba000 fc4fa58e47a5acc137eadb7689bce4357c557a96@0x7f14e3fba280 +0x7f14e4388000 7f2e9cb0769d7e57bd669b485a74b537b63a57c4@0x7f14e43881d8 +0x7f14e458c000 62c449974331341bb08dcce3859560a22af1e172@0x7f14e458c1d8 +0x7f14e4795000 175efdcef445455872a86a6fbee7567ca16a513e@0x7f14e4795248 +0x7ffcfe59f000 80d79b32785868a2dc10047b39a80d1daec8923d@0x7ffcfe59f328 +EOF + +exit 0 -- 2.43.0