From d48c2721b6b94a7aae470dcfcdc0578c971dc556 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Thu, 18 Nov 2021 00:03:48 +0000 Subject: [PATCH 1/2] elf-util: add function to parse metadata out of ELF objects Parse the packaging metadata from an ELF object, if any, and print a pretty table following the spec defined at: https://systemd.io/COREDUMP_PACKAGE_METADATA/ --- meson.build | 4 ++ src/shared/elf-util.c | 141 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 140 insertions(+), 5 deletions(-) diff --git a/meson.build b/meson.build index c80750c3b5..10efa4bc31 100644 --- a/meson.build +++ b/meson.build @@ -1329,6 +1329,10 @@ if want_elfutils != 'false' and not skip_deps libdw = dependency('libdw', required : want_elfutils == 'true') have = libdw.found() + + # New in elfutils 0.177 + conf.set10('HAVE_DWELF_ELF_E_MACHINE_STRING', + have and cc.has_function('dwelf_elf_e_machine_string', dependencies : libdw)) else have = false libdw = [] diff --git a/src/shared/elf-util.c b/src/shared/elf-util.c index e5fb8cb2e3..256744ce95 100644 --- a/src/shared/elf-util.c +++ b/src/shared/elf-util.c @@ -40,6 +40,9 @@ const char *(*sym_dwarf_formstring)(Dwarf_Attribute *); int (*sym_dwarf_getscopes)(Dwarf_Die *, Dwarf_Addr, Dwarf_Die **); int (*sym_dwarf_getscopes_die)(Dwarf_Die *, Dwarf_Die **); Elf *(*sym_dwelf_elf_begin)(int); +#if HAVE_DWELF_ELF_E_MACHINE_STRING +const char *(*sym_dwelf_elf_e_machine_string)(int); +#endif ssize_t (*sym_dwelf_elf_gnu_build_id)(Elf *, const void **); int (*sym_dwarf_tag)(Dwarf_Die *); Dwfl_Module *(*sym_dwfl_addrmodule)(Dwfl *, Dwarf_Addr); @@ -90,6 +93,9 @@ static int dlopen_dw(void) { DLSYM_ARG(dwarf_diename), DLSYM_ARG(dwelf_elf_gnu_build_id), DLSYM_ARG(dwelf_elf_begin), +#if HAVE_DWELF_ELF_E_MACHINE_STRING + DLSYM_ARG(dwelf_elf_e_machine_string), +#endif DLSYM_ARG(dwfl_addrmodule), DLSYM_ARG(dwfl_frame_pc), DLSYM_ARG(dwfl_module_addrdie), @@ -260,7 +266,8 @@ static int thread_callback(Dwfl_Thread *thread, void *userdata) { return DWARF_CB_OK; } -static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *elf, StackContext *c) { +static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *elf, bool *ret_interpreter_found, StackContext *c) { + bool interpreter_found = false; size_t n_program_headers; int r; @@ -286,9 +293,14 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e /* Package metadata is in PT_NOTE headers. */ program_header = sym_gelf_getphdr(elf, i, &mem); - if (!program_header || program_header->p_type != PT_NOTE) + if (!program_header || (program_header->p_type != PT_NOTE && program_header->p_type != PT_INTERP)) continue; + if (program_header->p_type == PT_INTERP) { + interpreter_found = true; + continue; + } + /* Fortunately there is an iterator we can use to walk over the * elements of a PT_NOTE program header. We are interested in the * note with type. */ @@ -348,11 +360,17 @@ static int parse_package_metadata(const char *name, JsonVariant *id_json, Elf *e if (r < 0) return log_error_errno(r, "set_put_strdup failed: %m"); + if (ret_interpreter_found) + *ret_interpreter_found = interpreter_found; + return 1; } } } + if (ret_interpreter_found) + *ret_interpreter_found = interpreter_found; + /* Didn't find package metadata for this module - that's ok, just go to the next. */ return 0; } @@ -426,7 +444,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, * to the ELF object first. We might be lucky and just get it from elfutils. */ elf = sym_dwfl_module_getelf(mod, &bias); if (elf) { - r = parse_package_metadata(name, id_json, elf, c); + r = parse_package_metadata(name, id_json, elf, NULL, c); if (r < 0) return DWARF_CB_ABORT; if (r > 0) @@ -468,7 +486,7 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name, _cleanup_(sym_elf_endp) Elf *memelf = sym_elf_memory(data->d_buf, data->d_size); if (!memelf) continue; - r = parse_package_metadata(name, id_json, memelf, c); + r = parse_package_metadata(name, id_json, memelf, NULL, c); if (r < 0) return DWARF_CB_ABORT; if (r > 0) @@ -546,6 +564,119 @@ static int parse_core(int fd, const char *executable, char **ret, JsonVariant ** return 0; } +static int parse_elf(int fd, const char *executable, char **ret, JsonVariant **ret_package_metadata) { + _cleanup_(json_variant_unrefp) JsonVariant *package_metadata = NULL, *elf_metadata = NULL; + _cleanup_(set_freep) Set *modules = NULL; + _cleanup_free_ char *buf = NULL; /* buf should be freed last, c.f closed first (via stack_context_destroy) */ + _cleanup_(stack_context_destroy) StackContext c = { + .package_metadata = &package_metadata, + .modules = &modules, + }; + const char *elf_architecture = NULL, *elf_type; + GElf_Ehdr elf_header; + size_t sz = 0; + int r; + + assert(fd >= 0); + + if (lseek(fd, 0, SEEK_SET) == (off_t) -1) + return log_warning_errno(errno, "Failed to seek to beginning of the ELF file: %m"); + + if (ret) { + c.f = open_memstream_unlocked(&buf, &sz); + if (!c.f) + return log_oom(); + } + + sym_elf_version(EV_CURRENT); + + c.elf = sym_elf_begin(fd, ELF_C_READ_MMAP, NULL); + if (!c.elf) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not parse ELF file, elf_begin() failed: %s", sym_elf_errmsg(sym_elf_errno())); + + if (!sym_gelf_getehdr(c.elf, &elf_header)) + return log_warning_errno(SYNTHETIC_ERRNO(EINVAL), "Could not parse ELF file, gelf_getehdr() failed: %s", sym_elf_errmsg(sym_elf_errno())); + + if (elf_header.e_type == ET_CORE) { + _cleanup_free_ char *out = NULL; + + r = parse_core(fd, executable, ret ? &out : NULL, &package_metadata); + if (r < 0) + return log_warning_errno(r, "Failed to inspect core file: %m"); + + if (out) + fprintf(c.f, "%s", out); + + elf_type = "coredump"; + } else { + _cleanup_(json_variant_unrefp) JsonVariant *id_json = NULL; + bool interpreter_found = false; + + r = parse_buildid(NULL, c.elf, executable, &c, &id_json); + if (r < 0) + return log_warning_errno(r, "Failed to parse build-id of ELF file: %m"); + + r = parse_package_metadata(executable, id_json, c.elf, &interpreter_found, &c); + if (r < 0) + return log_warning_errno(r, "Failed to parse package metadata of ELF file: %m"); + + /* If we found a build-id and nothing else, return at least that. */ + if (!package_metadata && id_json) { + r = json_build(&package_metadata, JSON_BUILD_OBJECT(JSON_BUILD_PAIR(executable, JSON_BUILD_VARIANT(id_json)))); + if (r < 0) + return log_warning_errno(r, "Failed to build JSON object: %m"); + } + + if (interpreter_found) + elf_type = "executable"; + else + elf_type = "library"; + } + + /* Note that e_type is always DYN for both executables and libraries, so we can't tell them apart from the header, + * but we will search for the PT_INTERP section when parsing the metadata. */ + r = json_build(&elf_metadata, JSON_BUILD_OBJECT(JSON_BUILD_PAIR("elfType", JSON_BUILD_STRING(elf_type)))); + if (r < 0) + return log_warning_errno(r, "Failed to build JSON object: %m"); + +#if HAVE_DWELF_ELF_E_MACHINE_STRING + elf_architecture = sym_dwelf_elf_e_machine_string(elf_header.e_machine); +#endif + if (elf_architecture) { + _cleanup_(json_variant_unrefp) JsonVariant *json_architecture = NULL; + + r = json_build(&json_architecture, + JSON_BUILD_OBJECT(JSON_BUILD_PAIR("elfArchitecture", JSON_BUILD_STRING(elf_architecture)))); + if (r < 0) + return log_warning_errno(r, "Failed to build JSON object: %m"); + + r = json_variant_merge(&elf_metadata, json_architecture); + if (r < 0) + return log_warning_errno(r, "Failed to merge JSON objects: %m"); + + if (ret) + fprintf(c.f, "ELF object binary architecture: %s\n", elf_architecture); + } + + /* We always at least have the ELF type, so merge that (and possibly the arch). */ + r = json_variant_merge(&elf_metadata, package_metadata); + if (r < 0) + return log_warning_errno(r, "Failed to merge JSON objects: %m"); + + if (ret) { + r = fflush_and_check(c.f); + if (r < 0) + return log_warning_errno(r, "Could not parse ELF file, flushing file buffer failed: %m"); + + c.f = safe_fclose(c.f); + *ret = TAKE_PTR(buf); + } + if (ret_package_metadata) + *ret_package_metadata = TAKE_PTR(elf_metadata); + + return 0; +} + int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, char **ret, JsonVariant **ret_package_metadata) { _cleanup_close_pair_ int error_pipe[2] = { -1, -1 }, return_pipe[2] = { -1, -1 }, json_pipe[2] = { -1, -1 }; _cleanup_(json_variant_unrefp) JsonVariant *package_metadata = NULL; @@ -607,7 +738,7 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha if (fork_disable_dump) prctl(PR_SET_DUMPABLE, 0); - r = parse_core(fd, executable, ret ? &buf : NULL, ret_package_metadata ? &package_metadata : NULL); + r = parse_elf(fd, executable, ret ? &buf : NULL, ret_package_metadata ? &package_metadata : NULL); if (r < 0) goto child_fail; From 917e655457cb2e0730d4c7baf4b65cd26986f663 Mon Sep 17 00:00:00 2001 From: Luca Boccassi Date: Wed, 17 Nov 2021 01:45:07 +0000 Subject: [PATCH 2/2] analyze: add inspect-elf verb to parse package metadata Parses and prints package metadata from executables, libraries and core files $ systemd-analyze inspect-elf /tmp/core ../fsverity-utils/fsverityb /bin/bash --json=off --no-pager __________________________ path: /tmp/core elfType: coredump elfArchitecture: AMD x86-64 module name: /tmp/crash type: deb name: hello version: 1.0 architecture: amd64 os: debian osVersion: 11 buildId: b33541096a09c29a0ba4ec5c69364a2711b7c269 module name: /usr/lib/x86_64-linux-gnu/libc-2.31.so type: deb name: hello version: 1.0 architecture: amd64 os: debian osVersion: 11 buildId: 54eef5ce96cf37cb175b0d93186836ca1caf470c module name: /usr/lib/x86_64-linux-gnu/ld-2.31.so type: deb name: hello version: 1.0 architecture: amd64 os: debian osVersion: 11 buildId: 32438eb3b034da54caf58c7a65446639f7cfe274 __________________________________________________________________ path: /home/luca/git/systemd/../fsverity-utils/fsverity elfType: executable elfArchitecture: AMD x86-64 type: deb name: fsverity-utils version: 1.3-1 architecture: amd64 os: debian debugInfoUrl: https://debuginfod.debian.net buildId: 05b899e6ee0d3653e20458719b202ed3ca8d566f _________________________ path: /bin/bash elfType: executable elfArchitecture: AMD x86-64 buildId: 4fef260f60e257d2dbd4126bf8add83837aea190 $ $ systemd-analyze inspect-elf /tmp/core ../fsverity-utils/fsverity /bin/bash /tmp/core.test-condition.1000.f9b9a84a9fd1482c9702d6afa6f6934b.37640.1637083078000000 --json=pretty --no-pager { "elfType" : "coredump", "elfArchitecture" : "AMD x86-64", "/home/bluca/git/fsverity-utils/fsverity" : { "type" : "deb", "name" : "fsverity-utils", "version" : "1.3-1", "buildId" : "7c895ecd2a271f93e96268f479fdc3c64a2ec4ee" }, "/home/bluca/git/fsverity-utils/libfsverity.so.0" : { "type" : "deb", "name" : "fsverity-utils", "version" : "1.3-1", "buildId" : "b5e428254abf14237b0ae70ed85fffbb98a78f88" } } { "elfType" : "executable", "elfArchitecture" : "AMD x86-64", "/home/bluca/git/systemd/../fsverity-utils/fsverity" : { "type" : "deb", "name" : "fsverity-utils", "version" : "1.3-1", "buildId" : "7c895ecd2a271f93e96268f479fdc3c64a2ec4ee" } } { "elfType" : "executable", "elfArchitecture" : "AMD x86-64", "/bin/bash" : { "buildId" : "3313b4cb119dcce16927a9b6cc61dcd97dfc4d59" } } { "elfType" : "coredump", "elfArchitecture" : "AMD x86-64" } --- man/systemd-analyze.xml | 33 +++++++ shell-completion/bash/systemd-analyze | 9 ++ shell-completion/zsh/_systemd-analyze | 1 + src/analyze/analyze-elf.c | 128 ++++++++++++++++++++++++++ src/analyze/analyze-elf.h | 6 ++ src/analyze/analyze.c | 11 ++- src/analyze/meson.build | 2 + test/units/testsuite-65.sh | 4 + 8 files changed, 193 insertions(+), 1 deletion(-) create mode 100644 src/analyze/analyze-elf.c create mode 100644 src/analyze/analyze-elf.h diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml index 6482fcfe48..8bc67a1ea8 100644 --- a/man/systemd-analyze.xml +++ b/man/systemd-analyze.xml @@ -681,6 +681,39 @@ $ systemd-analyze verify /tmp/source:alias.service + + + <command>systemd-analyze inspect-elf <replaceable>FILE</replaceable>...</command> + + This command will load the specified file(s), and if they are ELF objects (executables, + libraries, core files, etc.) it will parse the embedded packaging metadata, if any, and print + it in a table or json format. See the + Packaging Metadata documentation for more information. + + + Table output + + $ systemd-analyze inspect-elf --json=pretty /tmp/core.fsverity.1000.f77dac5dc161402aa44e15b7dd9dcf97.58561.1637106137000000 +{ + "elfType" : "coredump", + "elfArchitecture" : "AMD x86-64", + "/home/bluca/git/fsverity-utils/fsverity" : { + "type" : "deb", + "name" : "fsverity-utils", + "version" : "1.3-1", + "buildId" : "7c895ecd2a271f93e96268f479fdc3c64a2ec4ee" + }, + "/home/bluca/git/fsverity-utils/libfsverity.so.0" : { + "type" : "deb", + "name" : "fsverity-utils", + "version" : "1.3-1", + "buildId" : "b5e428254abf14237b0ae70ed85fffbb98a78f88" + } +} + + + + diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze index ddee57b0e7..f6dc972f03 100644 --- a/shell-completion/bash/systemd-analyze +++ b/shell-completion/bash/systemd-analyze @@ -63,6 +63,7 @@ _systemd_analyze() { [CAT_CONFIG]='cat-config' [SECURITY]='security' [CONDITION]='condition' + [INSPECT_ELF]='inspect-elf' ) local CONFIGS='systemd/bootchart.conf systemd/coredump.conf systemd/journald.conf @@ -169,6 +170,14 @@ _systemd_analyze() { fi comps=$( __get_services $mode ) fi + + elif __contains_word "$verb" ${VERBS[INSPECT_ELF]}; then + if [[ $cur = -* ]]; then + comps='--help --version --json=off --json=pretty --json=short' + else + comps=$( compgen -A file -- "$cur" ) + compopt -o filenames + fi fi COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) diff --git a/shell-completion/zsh/_systemd-analyze b/shell-completion/zsh/_systemd-analyze index 639964f064..9c33d73f98 100644 --- a/shell-completion/zsh/_systemd-analyze +++ b/shell-completion/zsh/_systemd-analyze @@ -54,6 +54,7 @@ 'timestamp:Parse a systemd syntax timestamp' 'timespan:Parse a systemd syntax timespan' 'security:Analyze security settings of a service' + 'inspect-elf:Parse and print ELF package metadata' # log-level, log-target, service-watchdogs have been deprecated ) diff --git a/src/analyze/analyze-elf.c b/src/analyze/analyze-elf.c new file mode 100644 index 0000000000..741cd20f99 --- /dev/null +++ b/src/analyze/analyze-elf.c @@ -0,0 +1,128 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "analyze-elf.h" +#include "elf-util.h" +#include "errno-util.h" +#include "fd-util.h" +#include "format-table.h" +#include "format-util.h" +#include "json.h" +#include "path-util.h" +#include "strv.h" + +int analyze_elf(char **filenames, JsonFormatFlags json_flags) { + char **filename; + int r; + + STRV_FOREACH(filename, filenames) { + _cleanup_(json_variant_unrefp) JsonVariant *package_metadata = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + _cleanup_free_ char *abspath = NULL; + _cleanup_close_ int fd = -1; + + r = path_make_absolute_cwd(*filename, &abspath); + if (r < 0) + return log_error_errno(r, "Could not make an absolute path out of \"%s\": %m", *filename); + + path_simplify(abspath); + + fd = RET_NERRNO(open(abspath, O_RDONLY|O_CLOEXEC)); + if (fd < 0) + return log_error_errno(fd, "Could not open \"%s\": %m", abspath); + + r = parse_elf_object(fd, abspath, /* fork_disable_dump= */false, NULL, &package_metadata); + if (r < 0) + return log_error_errno(r, "Parsing \"%s\" as ELF object failed: %m", abspath); + + t = table_new("", ""); + if (!t) + return log_oom(); + + r = table_set_align_percent(t, TABLE_HEADER_CELL(0), 100); + if (r < 0) + return table_log_add_error(r); + + r = table_add_many( + t, + TABLE_STRING, "path:", + TABLE_STRING, abspath); + if (r < 0) + return table_log_add_error(r); + + if (package_metadata) { + JsonVariant *module_json; + const char *module_name; + + JSON_VARIANT_OBJECT_FOREACH(module_name, module_json, package_metadata) { + const char *field_name; + JsonVariant *field; + + /* The ELF type and architecture are added as top-level objects, + * since they are only parsed for the file itself, but the packaging + * metadata is parsed recursively in core files, so there might be + * multiple modules. */ + if (STR_IN_SET(module_name, "elfType", "elfArchitecture")) { + _cleanup_free_ char *suffixed = NULL; + + suffixed = strjoin(module_name, ":"); + if (!suffixed) + return log_oom(); + + r = table_add_many( + t, + TABLE_STRING, suffixed, + TABLE_STRING, json_variant_string(module_json)); + if (r < 0) + return table_log_add_error(r); + + continue; + } + + /* path/elfType/elfArchitecture come first just once per file, + * then we might have multiple modules, so add a separator between + * them to make the output more readable. */ + r = table_add_many(t, TABLE_EMPTY, TABLE_EMPTY); + if (r < 0) + return table_log_add_error(r); + + /* In case of core files the module name will be the executable, + * but for binaries/libraries it's just the path, so don't print it + * twice. */ + if (!streq(abspath, module_name)) { + r = table_add_many( + t, + TABLE_STRING, "module name:", + TABLE_STRING, module_name); + if (r < 0) + return table_log_add_error(r); + } + + JSON_VARIANT_OBJECT_FOREACH(field_name, field, module_json) + if (json_variant_is_string(field)) { + _cleanup_free_ char *suffixed = NULL; + + suffixed = strjoin(field_name, ":"); + if (!suffixed) + return log_oom(); + + r = table_add_many( + t, + TABLE_STRING, suffixed, + TABLE_STRING, json_variant_string(field)); + if (r < 0) + return table_log_add_error(r); + } + } + } + if (json_flags & JSON_FORMAT_OFF) { + (void) table_set_header(t, true); + + r = table_print(t, NULL); + if (r < 0) + return table_log_print_error(r); + } else + json_variant_dump(package_metadata, json_flags, stdout, NULL); + } + + return 0; +} diff --git a/src/analyze/analyze-elf.h b/src/analyze/analyze-elf.h new file mode 100644 index 0000000000..e0d4712e5a --- /dev/null +++ b/src/analyze/analyze-elf.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "json.h" + +int analyze_elf(char **filenames, JsonFormatFlags json_flags); diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c index a641be4179..dfbfda6009 100644 --- a/src/analyze/analyze.c +++ b/src/analyze/analyze.c @@ -13,6 +13,7 @@ #include "alloc-util.h" #include "analyze-condition.h" +#include "analyze-elf.h" #include "analyze-security.h" #include "analyze-verify.h" #include "bus-error.h" @@ -2431,6 +2432,12 @@ static int do_security(int argc, char *argv[], void *userdata) { /*flags=*/ 0); } +static int do_elf_inspection(int argc, char *argv[], void *userdata) { + pager_open(arg_pager_flags); + + return analyze_elf(strv_skip(argv, 1), arg_json_format_flags); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL, *dot_link = NULL; int r; @@ -2473,6 +2480,7 @@ static int help(int argc, char *argv[], void *userdata) { " timestamp TIMESTAMP... Validate a timestamp\n" " timespan SPAN... Validate a time span\n" " security [UNIT...] Analyze security of unit\n" + " inspect-elf FILE... Parse and print ELF package metadata\n" "\nOptions:\n" " --recursive-errors=MODE Control which units are verified\n" " --offline=BOOL Perform a security review on unit file(s)\n" @@ -2759,7 +2767,7 @@ static int parse_argv(int argc, char *argv[]) { return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --offline= is only supported for security right now."); - if (arg_json_format_flags != JSON_FORMAT_OFF && !streq_ptr(argv[optind], "security")) + if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf")) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --json= is only supported for security right now."); @@ -2835,6 +2843,7 @@ static int run(int argc, char *argv[]) { { "timestamp", 2, VERB_ANY, 0, test_timestamp }, { "timespan", 2, VERB_ANY, 0, dump_timespan }, { "security", VERB_ANY, VERB_ANY, 0, do_security }, + { "inspect-elf", 2, VERB_ANY, 0, do_elf_inspection }, {} }; diff --git a/src/analyze/meson.build b/src/analyze/meson.build index f796629cc2..492b79069f 100644 --- a/src/analyze/meson.build +++ b/src/analyze/meson.build @@ -4,6 +4,8 @@ systemd_analyze_sources = files(''' analyze.c analyze-condition.c analyze-condition.h + analyze-elf.c + analyze-elf.h analyze-verify.c analyze-verify.h analyze-security.c diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh index 245f74c5d9..c04b404ea0 100755 --- a/test/units/testsuite-65.sh +++ b/test/units/testsuite-65.sh @@ -595,6 +595,10 @@ set -e rm /tmp/img/usr/lib/systemd/system/testfile.service +if systemd-analyze --version | grep -q -F "+ELFUTILS"; then + systemd-analyze inspect-elf --json=short /lib/systemd/systemd | grep -q -F '"elfType":"executable"' +fi + systemd-analyze log-level info echo OK >/testok