</programlisting>
</example>
</refsect2>
+
+ <refsect2>
+ <title><command>systemd-analyze inspect-elf <replaceable>FILE</replaceable>...</command></title>
+
+ <para>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 <ulink url="https://systemd.io/COREDUMP_PACKAGE_METADATA/">
+ Packaging Metadata</ulink> documentation for more information.</para>
+
+ <example>
+ <title>Table output</title>
+
+ <programlisting>$ 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"
+ }
+}
+ </programlisting>
+ </example>
+
+ </refsect2>
</refsect1>
<refsect1>
--- /dev/null
+/* 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;
+}
#include "alloc-util.h"
#include "analyze-condition.h"
+#include "analyze-elf.h"
#include "analyze-security.h"
#include "analyze-verify.h"
#include "bus-error.h"
/*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;
" 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"
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.");
{ "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 },
{}
};