diff --git a/options/rtld/generic/linker.cpp b/options/rtld/generic/linker.cpp index 848f7dedc6..01035f277d 100644 --- a/options/rtld/generic/linker.cpp +++ b/options/rtld/generic/linker.cpp @@ -108,6 +108,7 @@ uintptr_t alignUp(uintptr_t address, size_t align) { ObjectRepository::ObjectRepository() : loadedObjects{getAllocator()}, + dependencyQueue{getAllocator()}, _nameMap{frg::hash{}, getAllocator()}, _destructQueue{getAllocator()} {} @@ -123,6 +124,8 @@ SharedObject *ObjectRepository::injectObjectFromDts(frg::string_view name, _parseDynamic(object); _parseVerdef(object); + object->wasVisited = true; + dependencyQueue.push_back(object); _addLoadedObject(object); _discoverDependencies(object, globalScope.get(), rts); _parseVerneed(object); @@ -142,6 +145,8 @@ SharedObject *ObjectRepository::injectObjectFromPhdrs(frg::string_view name, _parseDynamic(object); _parseVerdef(object); + object->wasVisited = true; + dependencyQueue.push_back(object); _addLoadedObject(object); _discoverDependencies(object, globalScope.get(), rts); _parseVerneed(object); @@ -285,10 +290,7 @@ frg::expected ObjectRepository::requestObjectWithNa _parseDynamic(object); _parseVerdef(object); - _addLoadedObject(object); - _discoverDependencies(object, localScope, rts); - _parseVerneed(object); return object; } @@ -332,14 +334,16 @@ frg::expected ObjectRepository::requestObjectAtPath _parseDynamic(object); _parseVerdef(object); - _addLoadedObject(object); - _discoverDependencies(object, localScope, rts); - _parseVerneed(object); return object; } +void ObjectRepository::discoverDependenciesFromLoadedObject(SharedObject *object) { + _discoverDependencies(object, object->localScope, object->objectRts); + _parseVerneed(object); +} + SharedObject *ObjectRepository::findCaller(void *addr) { uintptr_t target = reinterpret_cast(addr); @@ -990,7 +994,12 @@ void ObjectRepository::_discoverDependencies(SharedObject *object, if(verbose) mlibc::infoLogger() << "rtld: Preloading " << preload << frg::endlog; - object->dependencies.push_back(libraryResult.value()); + auto library = libraryResult.value(); + object->dependencies.push_back(library); + if (library->wasVisited) + continue; + library->wasVisited = true; + dependencyQueue.push_back(library); } } @@ -1003,11 +1012,17 @@ void ObjectRepository::_discoverDependencies(SharedObject *object, const char *library_str = (const char *)(object->baseAddress + object->stringTableOffset + dynamic->d_un.d_val); - auto library = requestObjectWithName(frg::string_view{library_str}, + auto libraryResult = requestObjectWithName(frg::string_view{library_str}, object, localScope, false, rts); - if(!library) + if(!libraryResult) mlibc::panicLogger() << "Could not satisfy dependency " << library_str << frg::endlog; - object->dependencies.push(library.value()); + + auto library = libraryResult.value(); + object->dependencies.push(library); + if (library->wasVisited) + continue; + library->wasVisited = true; + dependencyQueue.push_back(library); } } diff --git a/options/rtld/generic/linker.hpp b/options/rtld/generic/linker.hpp index 9bf147635a..b2dc29638f 100644 --- a/options/rtld/generic/linker.hpp +++ b/options/rtld/generic/linker.hpp @@ -70,6 +70,8 @@ struct ObjectRepository { frg::expected requestObjectAtPath(frg::string_view path, Scope *localScope, bool createScope, uint64_t rts); + void discoverDependenciesFromLoadedObject(SharedObject *object); + SharedObject *findCaller(void *address); SharedObject *findLoadedObject(frg::string_view name); @@ -80,6 +82,9 @@ struct ObjectRepository { // Used by dl_iterate_phdr: stores objects in the order they are loaded. frg::vector loadedObjects; + // Used for breadth-first searching dependencies. + frg::vector dependencyQueue; + private: void _fetchFromPhdrs(SharedObject *object, void *phdr_pointer, size_t phdr_entry_size, size_t num_phdrs, void *entry_pointer); @@ -226,6 +231,8 @@ struct SharedObject { bool wasDestroyed = false; + bool wasVisited = false; + // PHDR related stuff, we only set these for the main executable void *phdrPointer = nullptr; size_t phdrEntrySize = 0; diff --git a/options/rtld/generic/main.cpp b/options/rtld/generic/main.cpp index 34f62d4877..7d81ecf89a 100644 --- a/options/rtld/generic/main.cpp +++ b/options/rtld/generic/main.cpp @@ -535,6 +535,11 @@ extern "C" void *interpreterMain(uintptr_t *entry_stack) { // so we have to set the ldso path after loading both. ldso->path = executableSO->interpreterPath; + // We added the roots for the BFS, time for the actual BFS + for (size_t i = 0; i < initialRepository->dependencyQueue.size(); i++) { + auto current = initialRepository->dependencyQueue[i]; + initialRepository->discoverDependenciesFromLoadedObject(current); + } #else executableSO = initialRepository->injectStaticObject(execfn, frg::string{ execfn, getAllocator() }, diff --git a/tests/rtld/ld_library_path/meson.build b/tests/rtld/ld_library_path/meson.build index 953c48dce2..254b302fa7 100644 --- a/tests/rtld/ld_library_path/meson.build +++ b/tests/rtld/ld_library_path/meson.build @@ -5,8 +5,8 @@ test_rpath = '$ORIGIN/' test_ld_path = meson.build_root() / 'tests' / 'rtld' / test_name -test_env += ['LD_LIBRARY_PATH=' + test_ld_path] -test_native_env += ['LD_LIBRARY_PATH=' + test_ld_path] +test_env.set('LD_LIBRARY_PATH', test_ld_path) +test_native_env.set('LD_LIBRARY_PATH', test_ld_path) libfoo = shared_library('foo', 'libfoo.c', dependencies: libc_dep, diff --git a/tests/rtld/meson.build b/tests/rtld/meson.build index 2a8d852d59..b4a53b258f 100644 --- a/tests/rtld/meson.build +++ b/tests/rtld/meson.build @@ -13,6 +13,7 @@ rtld_test_cases = [ 'scope3', 'scope4', 'scope5', + 'search-order', 'tls_align', 'relr', 'symver', @@ -26,17 +27,30 @@ foreach test_name : rtld_test_cases test_rpath = meson.build_root() / 'tests' / 'rtld' / test_name / '' test_rpath += ':$ORIGIN/' # Workaround old and buggy qemu-user on CI - test_env = [] + test_env = environment() test_link_with = [] test_depends = [] - test_native_env = [] + test_native_env = environment() test_native_link_with = [] test_native_depends = [] test_additional_link_args = [] + test_skipped = false # Build the needed DSOs for the test. This sets the variables above. subdir(test_name) + if test_skipped + exec = executable('rtld-' + test_name, ['skipped.c', test_sources], + dependencies: libc_dep, + link_args: test_link_args, + ) + test(test_name, exec, suite: 'rtld') + if build_tests_host_libc and not host_libc_excluded_test_cases.contains(test_name) + test(test_name, exec, suite: ['host-libc', 'rtld']) + endif + continue + endif + exec = executable('rtld-' + test_name, [test_name / 'test.c', test_sources], link_with: test_link_with, dependencies: libc_dep, diff --git a/tests/rtld/search-order/libbar.c b/tests/rtld/search-order/libbar.c new file mode 100644 index 0000000000..22f09b0fb1 --- /dev/null +++ b/tests/rtld/search-order/libbar.c @@ -0,0 +1,19 @@ +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBFOO "libnative-foo.so" +#else +#define LIBFOO "libfoo.so" +#endif + +int foo(void); + +int bar(void) { + return foo() + 0xBABE; +} + +[[constructor]] void init(void) { + void *libfoo = dlopen(LIBFOO, RTLD_LOCAL | RTLD_NOW); + assert(libfoo); +} diff --git a/tests/rtld/search-order/libfoo.c b/tests/rtld/search-order/libfoo.c new file mode 100644 index 0000000000..d62656d0e5 --- /dev/null +++ b/tests/rtld/search-order/libfoo.c @@ -0,0 +1,3 @@ +int foo(void) { + return 0xCAFE0000; +} diff --git a/tests/rtld/search-order/meson.build b/tests/rtld/search-order/meson.build new file mode 100644 index 0000000000..07be86a72f --- /dev/null +++ b/tests/rtld/search-order/meson.build @@ -0,0 +1,37 @@ +patchelf = find_program('patchelf', required: false) + +if patchelf.found() + # Overwrite LD_LIBRARY_PATH to make meson not fix our shenanigans + # Otherwise, meson's LD_LIBRARY_PATH makes this test useless because libfoo + # will be found through it instead of failing when libfoo is not found + # test_env.unset('LD_LIBRARY_PATH') + # test_native_env.unset('LD_LIBRARY_PATH') + + libfoo = shared_library('foo', 'libfoo.c') + libbar_unpatched = shared_library('bar-unpatched', 'libbar.c', + dependencies: libc_dep, link_with: libfoo) + libbar = custom_target('patch-libbar', + command: [patchelf, + '--remove-rpath', + '--set-soname', 'libbar.so', + libbar_unpatched, + '--output', '@OUTPUT0@'], + output: ['libbar.so'], + ) + test_link_with = [libbar, libfoo] + + libfoo_native = shared_library('native-foo', 'libfoo.c', native: true) + libbar_native_unpatched = shared_library('native-bar-unpatched', 'libbar.c', + dependencies: rtlib_deps, link_with: libfoo_native, native: true) + libbar_native = custom_target('patch-libnative-bar', + command: [patchelf, + '--remove-rpath', + '--set-soname', 'libnative-bar.so', + libbar_native_unpatched, + '--output', '@OUTPUT0@'], + output: ['libnative-bar.so'], + ) + test_native_link_with = [libbar_native, libfoo_native] +else + test_skipped = true +endif diff --git a/tests/rtld/search-order/test.c b/tests/rtld/search-order/test.c new file mode 100644 index 0000000000..986311e0c8 --- /dev/null +++ b/tests/rtld/search-order/test.c @@ -0,0 +1,21 @@ +#include +#include + +#ifdef USE_HOST_LIBC +#define LIBBAR "libnative-bar.so" +#else +#define LIBBAR "libbar.so" +#endif + +int foo(); +int bar(); + +int main() { + void *libbar = dlopen(LIBBAR, RTLD_LOCAL | RTLD_NOW); + assert(libbar); + + assert(foo() == (int) 0xCAFE0000); + assert(bar() == (int) 0xCAFEBABE); + + return 0; +} diff --git a/tests/rtld/skipped.c b/tests/rtld/skipped.c new file mode 100644 index 0000000000..e7c74c561f --- /dev/null +++ b/tests/rtld/skipped.c @@ -0,0 +1,3 @@ +int main() { + return 77; +}