]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
analyze: add inspect-elf verb to parse package metadata 21454/head
authorLuca Boccassi <luca.boccassi@microsoft.com>
Wed, 17 Nov 2021 01:45:07 +0000 (01:45 +0000)
committerLuca Boccassi <luca.boccassi@microsoft.com>
Tue, 30 Nov 2021 23:14:07 +0000 (23:14 +0000)
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
shell-completion/bash/systemd-analyze
shell-completion/zsh/_systemd-analyze
src/analyze/analyze-elf.c [new file with mode: 0644]
src/analyze/analyze-elf.h [new file with mode: 0644]
src/analyze/analyze.c
src/analyze/meson.build
test/units/testsuite-65.sh

index 6482fcfe485dc651a06323e571d231f015ef789d..8bc67a1ea89b4ce77ef50edfa3ca5bd8cee88e9d 100644 (file)
@@ -681,6 +681,39 @@ $ systemd-analyze verify /tmp/source:alias.service
 </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>
index ddee57b0e71db3bb4d8440674e45d77363d43889..f6dc972f031f234a6b452ed89bf72fc3724346bd 100644 (file)
@@ -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") )
index 639964f064583c9ba896cb113cfd24798180199c..9c33d73f981e36f0b9b3a56bbe9936290bae60a1 100644 (file)
@@ -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 (file)
index 0000000..741cd20
--- /dev/null
@@ -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 (file)
index 0000000..e0d4712
--- /dev/null
@@ -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);
index a641be4179b1441f825da6e74a8785403ecb1bef..dfbfda6009a3d5c65a2c44f001b417b6d900d75c 100644 (file)
@@ -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      },
                 {}
         };
 
index f796629cc2802204d7ffb686e60dba5ccee6a611..492b79069fbaec6e426b3c9b5da429c423e4f1ab 100644 (file)
@@ -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
index 245f74c5d93fe043b16e9697f56361c33642d39c..c04b404ea0a68ae7025064b2d66e0b1946d3483d 100755 (executable)
@@ -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