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
+
+
+ systemd-analyze inspect-elf FILE...
+
+ 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/meson.build b/meson.build
index 60ed4d454e..43f5bf2c5a 100644
--- a/meson.build
+++ b/meson.build
@@ -1338,6 +1338,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/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 d5057bbe85..2293fcea6a 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/src/shared/elf-util.c b/src/shared/elf-util.c
index 084920a501..dafd219bb2 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;
@@ -610,7 +741,7 @@ int parse_elf_object(int fd, const char *executable, bool fork_disable_dump, cha
goto child_fail;
}
- 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;
diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh
index 867a77a1b7..dcd11161f4 100755
--- a/test/units/testsuite-65.sh
+++ b/test/units/testsuite-65.sh
@@ -596,6 +596,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