summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLiav A <liavalb@gmail.com>2022-12-18 16:52:22 +0200
committerAndrew Kaster <andrewdkaster@gmail.com>2022-12-31 05:06:39 -0700
commit658f9eec6a9284b7f2a5620630d2b3d5c726fcd9 (patch)
tree8b3ae0dc5c27a0858a1850c96a1affd914ae402d
parentefec3448038fccd1de7fd56c3b673f1e62e282df (diff)
downloadserenity-658f9eec6a9284b7f2a5620630d2b3d5c726fcd9.zip
Utilities: Introduce the ldd utility
This utility lets a user to figure out what are the dependency libraries for an ELF dynamic object, whether it's a dynamically loaded executable or dynamically loaded library.
-rw-r--r--Base/usr/share/man/man1/ldd.md41
-rw-r--r--Userland/Utilities/ldd.cpp151
2 files changed, 192 insertions, 0 deletions
diff --git a/Base/usr/share/man/man1/ldd.md b/Base/usr/share/man/man1/ldd.md
new file mode 100644
index 0000000000..d358f0b788
--- /dev/null
+++ b/Base/usr/share/man/man1/ldd.md
@@ -0,0 +1,41 @@
+## Name
+
+ldd - list dynamic dependencies
+
+## Synopsis
+
+```**sh
+$ ldd [-r] [-f] <path>
+```
+
+## Description
+
+`ldd` prints all dependency libraries of an ELF object.
+
+## Options
+
+* `-f`, `--force-without-valid-interpreter`: Force library resolving on ELF
+ object without a valid interpreter
+* `-r`, `--max-recursion`: Max library resolving recursion
+
+## Arguments
+
+* `path`: Path to ELF object
+
+## Security
+
+In contrast to other OS implementations, the `ldd` binary is completely safe for
+usage on untrusted binaries - we only use the `LibELF` code for doing library
+resolving, and the actual binary interpreter (when specified) is never called to
+decode the dependency information.
+
+## Examples
+
+```sh
+# List all dependency libraries for libc.so
+$ ldd -f /usr/lib/libc.so
+# List all dependency libraries for /bin/id
+$ ldd /bin/id
+# List all dependency libraries for /bin/WindowServer
+$ ldd /bin/WindowServer
+```
diff --git a/Userland/Utilities/ldd.cpp b/Userland/Utilities/ldd.cpp
new file mode 100644
index 0000000000..47fa61062b
--- /dev/null
+++ b/Userland/Utilities/ldd.cpp
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2022, Liav A. <liavalb@hotmail.co.il>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#include <AK/DeprecatedString.h>
+#include <AK/LexicalPath.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <LibCore/ArgsParser.h>
+#include <LibCore/File.h>
+#include <LibCore/MappedFile.h>
+#include <LibCore/System.h>
+#include <LibELF/DynamicLinker.h>
+#include <LibELF/DynamicLoader.h>
+#include <LibELF/DynamicObject.h>
+#include <LibELF/Image.h>
+#include <LibELF/Validation.h>
+#include <LibMain/Main.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static Vector<DeprecatedString> found_libraries;
+
+static ErrorOr<void> recusively_resolve_all_necessary_libraries(StringView interpreter_path, size_t recursive_iteration_max, size_t recursive_iteration, ELF::DynamicObject& object)
+{
+ if (recursive_iteration > recursive_iteration_max)
+ return ELOOP;
+
+ Vector<DeprecatedString> libraries;
+ object.for_each_needed_library([&libraries](StringView entry) {
+ libraries.append(DeprecatedString::formatted("{}", entry));
+ });
+ for (auto& library_name : libraries) {
+ auto possible_library_path = ELF::DynamicLinker::resolve_library(library_name, object);
+ if (!possible_library_path.has_value())
+ continue;
+ auto library_path = LexicalPath::absolute_path(TRY(Core::System::getcwd()), possible_library_path.value());
+ if (found_libraries.contains_slow(library_path))
+ continue;
+ auto file = TRY(Core::MappedFile::map(library_path));
+
+ auto elf_image_data = file->bytes();
+ ELF::Image elf_image(elf_image_data);
+ if (!elf_image.is_valid()) {
+ outln("Shared library is not valid ELF: {}", library_path);
+ continue;
+ }
+ if (!elf_image.is_dynamic()) {
+ outln("Shared library is not dynamic loaded object: {}", library_path);
+ continue;
+ }
+
+ int fd = TRY(Core::System::open(library_path, O_RDONLY));
+ auto result = ELF::DynamicLoader::try_create(fd, library_path);
+ if (result.is_error()) {
+ outln("{}", result.error().text);
+ continue;
+ }
+ auto& loader = result.value();
+ if (!loader->is_valid()) {
+ outln("{} is not a valid ELF dynamic shared object!", library_path);
+ continue;
+ }
+
+ RefPtr<ELF::DynamicObject> library_object = loader->map();
+ if (!library_object) {
+ outln("Failed to map dynamic ELF object {}", library_path);
+ continue;
+ }
+ outln("{} => {}", library_name, library_path);
+ recursive_iteration++;
+ found_libraries.append(library_path);
+ TRY(recusively_resolve_all_necessary_libraries(interpreter_path, recursive_iteration_max, recursive_iteration, *library_object));
+ }
+ return {};
+}
+
+ErrorOr<int> serenity_main(Main::Arguments arguments)
+{
+ TRY(Core::System::pledge("stdio rpath"));
+
+ DeprecatedString path {};
+ Optional<size_t> recursive_iteration_max;
+ bool force_without_valid_interpreter = false;
+
+ Core::ArgsParser args_parser;
+ args_parser.add_option(recursive_iteration_max, "Max library resolving recursion", "max-recursion", 'r', "max recursion-level");
+ args_parser.add_option(force_without_valid_interpreter, "Force library resolving on ELF object without valid interpreter", "force-without-valid-interpreter", 'f');
+ args_parser.add_positional_argument(path, "ELF path", "path");
+ args_parser.parse(arguments);
+
+ path = LexicalPath::absolute_path(TRY(Core::System::getcwd()), path);
+
+ auto file_or_error = Core::MappedFile::map(path);
+
+ if (file_or_error.is_error()) {
+ warnln("Unable to map file {}: {}", path, file_or_error.error());
+ return -1;
+ }
+
+ auto elf_image_data = file_or_error.value()->bytes();
+ ELF::Image elf_image(elf_image_data);
+
+ if (!elf_image.is_valid()) {
+ warnln("File is not a valid ELF object");
+ return -1;
+ }
+
+ StringBuilder interpreter_path_builder;
+ auto result_or_error = ELF::validate_program_headers(*(const ElfW(Ehdr)*)elf_image_data.data(), elf_image_data.size(), elf_image_data, &interpreter_path_builder);
+ if (result_or_error.is_error() || !result_or_error.value()) {
+ warnln("Invalid ELF headers");
+ return -1;
+ }
+ auto interpreter_path = interpreter_path_builder.string_view();
+
+ RefPtr<ELF::DynamicObject> object = nullptr;
+ if (elf_image.is_dynamic()) {
+ if (interpreter_path != "/usr/lib/Loader.so"sv && !force_without_valid_interpreter) {
+ warnln("ELF interpreter image is invalid");
+ return 1;
+ }
+
+ int fd = TRY(Core::System::open(path, O_RDONLY));
+ auto result = ELF::DynamicLoader::try_create(fd, path);
+ if (result.is_error()) {
+ outln("{}", result.error().text);
+ return 1;
+ }
+ auto& loader = result.value();
+ if (!loader->is_valid()) {
+ outln("{} is not a valid ELF dynamic shared object!", path);
+ return 1;
+ }
+
+ object = loader->map();
+ if (!object) {
+ outln("Failed to map dynamic ELF object {}", path);
+ return 1;
+ }
+ TRY(recusively_resolve_all_necessary_libraries(interpreter_path, recursive_iteration_max.value_or(10), 0, *object));
+ } else {
+ outln("ELF program is not dynamic loaded!");
+ return 1;
+ }
+ return 0;
+}