diff --git a/src/track/heaptrack_inject.cpp b/src/track/heaptrack_inject.cpp --- a/src/track/heaptrack_inject.cpp +++ b/src/track/heaptrack_inject.cpp @@ -230,29 +230,38 @@ }; using elf_string_table = elftable; +using elf_rela_table = elftable; using elf_jmprel_table = elftable; using elf_symbol_table = elftable; +template +void try_overwrite_elftable(const T& jumps, const elf_string_table& strings, const elf_symbol_table& symbols, + const Elf::Addr base, const bool restore) noexcept +{ + const auto rela_end = reinterpret_cast(reinterpret_cast(jumps.table) + jumps.size); + for (auto rela = jumps.table; rela < rela_end; rela++) { + const auto index = ELF_R_SYM(rela->r_info); + const char* symname = strings.table + symbols.table[index].st_name; + auto addr = rela->r_offset + base; + hooks::apply(symname, addr, restore); + } +} + void try_overwrite_symbols(const Elf::Dyn* dyn, const Elf::Addr base, const bool restore) noexcept { elf_symbol_table symbols; + elf_rela_table relas; elf_jmprel_table jmprels; elf_string_table strings; // initialize the elf tables for (; dyn->d_tag != DT_NULL; ++dyn) { - symbols.consume(dyn) || jmprels.consume(dyn) || strings.consume(dyn); + symbols.consume(dyn) || relas.consume(dyn) || jmprels.consume(dyn) || strings.consume(dyn); } // find symbols to overwrite - const auto rela_end = reinterpret_cast(reinterpret_cast(jmprels.table) + jmprels.size); - for (auto rela = jmprels.table; rela < rela_end; rela++) { - const auto index = ELF_R_SYM(rela->r_info); - const char* symname = strings.table + symbols.table[index].st_name; - auto addr = rela->r_offset + base; - - hooks::apply(symname, addr, restore); - } + try_overwrite_elftable(relas, strings, symbols, base, restore); + try_overwrite_elftable(jmprels, strings, symbols, base, restore); } int iterate_phdrs(dl_phdr_info* info, size_t /*size*/, void* data) noexcept diff --git a/tests/manual/run_linkage_tests.sh b/tests/manual/run_linkage_tests.sh new file mode 100755 --- /dev/null +++ b/tests/manual/run_linkage_tests.sh @@ -0,0 +1,34 @@ +#!/bin/bash + +input_file=$(readlink -f test_linkage.c) +cd /tmp + +function run { + echo "compiling with $@" + gcc $@ -O0 -g $input_file -o test_linkage || return + readelf -a test_linkage | grep free + ./test_linkage & + p=$! + sleep .1 + heaptrack -p $p |& tee test_linkage.log + if ! grep -qP "^\s+allocations:\s+10$" test_linkage.log; then + echo "FAILED: wrong allocation count (compiled with $@)" + fi + if ! grep -qP "^\s+leaked allocations:\s+0$" test_linkage.log; then + echo "FAILED: wrong allocation count (compiled with $@)" + fi + if ! grep -qP "^\s+temporary allocations:\s+10$" test_linkage.log; then + echo "FAILED: wrong temporary allocation count (compiled with $@)" + fi +} + +# set -o xtrace + +for linker in bfd gold; do + run -fuse-ld=$linker + run -fuse-ld=$linker -Wl,-z,now + run -fuse-ld=$linker -DTAKE_ADDR + run -fuse-ld=$linker -DUSE_FREEPTR +done + +rm heaptrack.test_linkage.*.gz test_linkage test_linkage.log diff --git a/tests/manual/test_linkage.c b/tests/manual/test_linkage.c new file mode 100644 --- /dev/null +++ b/tests/manual/test_linkage.c @@ -0,0 +1,102 @@ +/* + * Copyright 2018 Ivan Middleton + * Copyright 2018 Milian Wolff + * + * This 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. + * + * This 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 this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + By default, the linker uses lazy binding (function calls aren't + resolved until the first time the function is called). + + Relevant sections of the executable for lazy binding: + .plt (trampoline code) + .got.plt (function addresses cached here) + .rela.plt (relocation entries associating each function name + with its storage location in .got.plt) + + But symbols can also be bound right away when the executable or + shared library is started or loaded. + + Relevant sections for immediate binding: + .plt.got (trampoline code) + .got (function addresses stored here) + .rela.dyn (relocation entries) + + Immediate binding can be triggered in a couple different ways: + + (1) The linker option "-z now" makes all symbols use immediate + binding. Compile this file as follows to see this in action: + + gcc -Wl,-z,now -o testbind testbind.c + + Why might this linker option be used? See: + + https://wiki.debian.org/Hardening#DEB_BUILD_HARDENING_BINDNOW_.28ld_-z_now.29 + + Note that this seems to be platform dependant and is not always reproducible. + + (2) If a particular function has a pointer to it passed around, + then it must be bound immediately. Define TAKE_ADDR, i.e. compile with + + gcc -g -O0 -fuse-ld=bfd -DTAKE_ADDR -o testbind testbind.c + + to see this behavior. Note that ld.gold does not show this behavior. + + The heaptrack_inject function needs to look in both .rela.plt + (DT_JMPREL) and .rela.dyn (DT_RELA) in order to find all + malloc/free function pointers, lazily-bound or no. + + There is also another option which is currently not handled by heaptrack: + When do not rewrite data segments, which would be required to catch accessing + a given symbol through a function pointer (-DUSE_FREEPTR). + + Use the run_linkage_tests.sh bash script to check the behavior in an automated fashion. +*/ + +#include +#include + +void escape(void *p) +{ + asm volatile("" : : "g"(p) : "memory"); +} + +int main() +{ + int i = 0; + +#if defined(TAKE_ADDR) || defined(USE_FREEPTR) + void (*freePtr)(void*) = &free; + + escape(freePtr); +#endif + + sleep(1); + + for (i = 0; i < 10; ++i) { + void* foo = malloc(256); + escape(foo); + + usleep(200); +#if defined(USE_FREEPTR) + freePtr(foo); +#else + free(foo); +#endif + usleep(200); + } + return 0; +}