analyze: Add dlopen-metadata verb (#39457)

systemd-analyze dlopen-metadata will show dlopen metadata
in the ELF binary.
This commit is contained in:
Daan De Meyer
2025-10-30 13:56:01 +01:00
committed by GitHub
10 changed files with 248 additions and 51 deletions
+10
View File
@@ -915,6 +915,16 @@ alias.service:7: Unknown key name 'MysteryKey' in section 'Service', ignoring.
</example>
</refsect2>
<refsect2>
<title><command>systemd-analyze dlopen-metadata <replaceable>FILE</replaceable></command></title>
<para>This command will load the specified file, and if it is an ELF object (executables,
libraries, core files, etc.) it will parse the embedded dlopen metadata, if any, and print
it in a table or json format. See the
<ulink url="https://systemd.io/ELF_DLOPEN_METADATA/">
dlopen() Metadata for ELF Files</ulink> document for more information.</para>
</refsect2>
<refsect2>
<title><command>systemd-analyze fdstore <replaceable>UNIT</replaceable>...</command></title>
+72
View File
@@ -0,0 +1,72 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "sd-json.h"
#include "alloc-util.h"
#include "analyze.h"
#include "analyze-dlopen-metadata.h"
#include "chase.h"
#include "elf-util.h"
#include "fd-util.h"
#include "format-table.h"
#include "json-util.h"
#include "strv.h"
int verb_dlopen_metadata(int argc, char *argv[], void *userdata) {
int r;
_cleanup_free_ char *abspath = NULL;
_cleanup_close_ int fd = -EBADF;
fd = chase_and_open(argv[1], arg_root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC, &abspath);
if (fd < 0)
return log_error_errno(fd, "Could not open \"%s\": %m", argv[1]);
_cleanup_(sd_json_variant_unrefp) sd_json_variant *dlopen_metadata = NULL;
r = parse_elf_object(
fd,
abspath,
arg_root,
/* fork_disable_dump= */ false,
/* ret= */ NULL,
NULL,
&dlopen_metadata);
if (r < 0)
return log_error_errno(r, "Parsing \"%s\" as ELF object failed: %m", abspath);
if (!dlopen_metadata)
return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "%s does not contain any .note.dlopen sections", argv[1]);
if (sd_json_format_enabled(arg_json_format_flags))
return sd_json_variant_dump(dlopen_metadata, arg_json_format_flags, stdout, NULL);
_cleanup_(table_unrefp) Table *t = NULL;
t = table_new("feature", "description", "soname", "priority");
if (!t)
return log_oom();
table_set_ersatz_string(t, TABLE_ERSATZ_NA);
sd_json_variant *z;
JSON_VARIANT_ARRAY_FOREACH(z, dlopen_metadata) {
_cleanup_strv_free_ char **sonames = NULL;
r = sd_json_variant_strv(sd_json_variant_by_key(z, "soname"), &sonames);
if (r < 0)
return log_error_errno(r, "Failed to extract sonames from dlopen metadata: %m");
r = table_add_many(
t,
TABLE_STRING, sd_json_variant_string(sd_json_variant_by_key(z, "feature")),
TABLE_STRING, sd_json_variant_string(sd_json_variant_by_key(z, "description")),
TABLE_STRV_WRAPPED, sonames,
TABLE_STRING, sd_json_variant_string(sd_json_variant_by_key(z, "priority")));
if (r < 0)
return table_log_add_error(r);
}
r = table_print(t, NULL);
if (r < 0)
return table_log_print_error(r);
return 0;
}
+4
View File
@@ -0,0 +1,4 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#pragma once
int verb_dlopen_metadata(int argc, char *argv[], void *userdata);
+8 -1
View File
@@ -27,7 +27,14 @@ static int analyze_elf(char **filenames, sd_json_format_flags_t json_flags) {
if (fd < 0)
return log_error_errno(fd, "Could not open \"%s\": %m", *filename);
r = parse_elf_object(fd, abspath, arg_root, /* fork_disable_dump= */false, &stacktrace, &package_metadata);
r = parse_elf_object(
fd,
abspath,
arg_root,
/* fork_disable_dump= */ false,
&stacktrace,
&package_metadata,
/* ret_dlopen_metadata= */ NULL);
if (r < 0)
return log_error_errno(r, "Parsing \"%s\" as ELF object failed: %m", abspath);
+2
View File
@@ -22,6 +22,7 @@
#include "analyze-compare-versions.h"
#include "analyze-condition.h"
#include "analyze-critical-chain.h"
#include "analyze-dlopen-metadata.h"
#include "analyze-dot.h"
#include "analyze-dump.h"
#include "analyze-exit-status.h"
@@ -798,6 +799,7 @@ static int run(int argc, char *argv[]) {
{ "timespan", 2, VERB_ANY, 0, verb_timespan },
{ "security", VERB_ANY, VERB_ANY, 0, verb_security },
{ "inspect-elf", 2, VERB_ANY, 0, verb_elf_inspection },
{ "dlopen-metadata", 2, 2, 0, verb_dlopen_metadata },
{ "malloc", VERB_ANY, VERB_ANY, 0, verb_malloc },
{ "fdstore", 2, VERB_ANY, 0, verb_fdstore },
{ "image-policy", 2, 2, 0, verb_image_policy },
+1
View File
@@ -10,6 +10,7 @@ systemd_analyze_sources = files(
'analyze-compare-versions.c',
'analyze-condition.c',
'analyze-critical-chain.c',
'analyze-dlopen-metadata.c',
'analyze-dot.c',
'analyze-dump.c',
'analyze-exit-status.c',
+2 -1
View File
@@ -689,7 +689,8 @@ int coredump_submit(
root,
/* fork_disable_dump= */ skip, /* avoid loops */
&stacktrace,
&json_metadata);
&json_metadata,
/* ret_dlopen_metadata= */ NULL);
}
}
+131 -47
View File
@@ -20,6 +20,7 @@
#include "fileio.h"
#include "format-util.h"
#include "io-util.h"
#include "json-util.h"
#include "log.h"
#include "memstream-util.h"
#include "path-util.h"
@@ -186,6 +187,7 @@ typedef struct StackContext {
unsigned n_thread;
unsigned n_frame;
sd_json_variant **package_metadata;
sd_json_variant **dlopen_metadata;
Set **modules;
} StackContext;
@@ -352,8 +354,8 @@ static void report_module_metadata(StackContext *c, const char *name, sd_json_va
fputs("\n", c->m.f);
}
static int parse_package_metadata(const char *name, sd_json_variant *id_json, Elf *elf, bool *ret_interpreter_found, StackContext *c) {
bool interpreter_found = false;
static int parse_metadata(const char *name, sd_json_variant *id_json, Elf *elf, bool *ret_interpreter_found, StackContext *c) {
bool package_metadata_found = false, interpreter_found = false;
size_t n_program_headers;
int r;
@@ -408,7 +410,7 @@ static int parse_package_metadata(const char *name, sd_json_variant *id_json, El
/* Package metadata might have different owners, but the
* magic ID is always the same. */
if (note_header.n_type != ELF_PACKAGE_METADATA_ID)
if (!IN_SET(note_header.n_type, ELF_PACKAGE_METADATA_ID, ELF_NOTE_DLOPEN_TYPE))
continue;
_cleanup_free_ char *payload_0suffixed = NULL;
@@ -430,46 +432,59 @@ static int parse_package_metadata(const char *name, sd_json_variant *id_json, El
return log_error_errno(r, "json_parse on \"%s\" failed: %m", strnull(esc));
}
/* If we have a build-id, merge it in the same JSON object so that it appears all
* nicely together in the logs/metadata. */
if (id_json) {
r = sd_json_variant_merge_object(&v, id_json);
if (note_header.n_type == ELF_PACKAGE_METADATA_ID) {
/* If we have a build-id, merge it in the same JSON object so that it appears all
* nicely together in the logs/metadata. */
if (id_json) {
r = sd_json_variant_merge_object(&v, id_json);
if (r < 0)
return log_error_errno(r, "sd_json_variant_merge of package meta with buildId failed: %m");
}
/* Pretty-print to the buffer, so that the metadata goes as plaintext in the
* journal. */
report_module_metadata(c, name, v);
/* Then we build a new object using the module name as the key, and merge it
* with the previous parses, so that in the end it all fits together in a single
* JSON blob. */
r = sd_json_buildo(&w, SD_JSON_BUILD_PAIR(name, SD_JSON_BUILD_VARIANT(v)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
r = sd_json_variant_merge_object(c->package_metadata, w);
if (r < 0)
return log_error_errno(r, "sd_json_variant_merge of package meta with buildId failed: %m");
package_metadata_found = true;
} else if (c->dlopen_metadata) {
sd_json_variant *z;
JSON_VARIANT_ARRAY_FOREACH(z, v) {
r = sd_json_variant_append_array(c->dlopen_metadata, z);
if (r < 0)
return log_error_errno(r, "Failed to append entry to dlopen metadata: %m");
}
}
/* Pretty-print to the buffer, so that the metadata goes as plaintext in the
* journal. */
report_module_metadata(c, name, v);
/* Then we build a new object using the module name as the key, and merge it
* with the previous parses, so that in the end it all fits together in a single
* JSON blob. */
r = sd_json_buildo(&w, SD_JSON_BUILD_PAIR(name, SD_JSON_BUILD_VARIANT(v)));
if (r < 0)
return log_error_errno(r, "Failed to build JSON object: %m");
r = sd_json_variant_merge_object(c->package_metadata, w);
if (r < 0)
return log_error_errno(r, "sd_json_variant_merge of package meta with buildId failed: %m");
/* Finally stash the name, so we avoid double visits. */
r = set_put_strdup(c->modules, name);
if (r < 0)
return log_error_errno(r, "set_put_strdup failed: %m");
if (ret_interpreter_found)
*ret_interpreter_found = interpreter_found;
if (!c->dlopen_metadata) {
if (ret_interpreter_found)
*ret_interpreter_found = interpreter_found;
return 1;
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;
return c->dlopen_metadata ? 0 : package_metadata_found;
}
/* Get the build-id out of an ELF object or a dwarf core module. */
@@ -535,7 +550,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, NULL, c);
r = parse_metadata(name, id_json, elf, NULL, c);
if (r < 0)
return DWARF_CB_ABORT;
if (r > 0)
@@ -590,7 +605,8 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name,
_cleanup_(elf_endp) Elf *memelf = sym_elf_memory(data->d_buf, data->d_size);
if (!memelf)
continue;
r = parse_package_metadata(name, id_json, memelf, NULL, c);
r = parse_metadata(name, id_json, memelf, NULL, c);
if (r < 0)
return DWARF_CB_ABORT;
if (r > 0)
@@ -600,7 +616,12 @@ static int module_callback(Dwfl_Module *mod, void **userdata, const char *name,
return DWARF_CB_OK;
}
static int parse_core(int fd, const char *root, char **ret, sd_json_variant **ret_package_metadata) {
static int parse_core(
int fd,
const char *root,
char **ret,
sd_json_variant **ret_package_metadata,
sd_json_variant **ret_dlopen_metadata) {
const Dwfl_Callbacks callbacks = {
.find_elf = sym_dwfl_build_id_find_elf,
@@ -608,10 +629,11 @@ static int parse_core(int fd, const char *root, char **ret, sd_json_variant **re
.find_debuginfo = sym_dwfl_standard_find_debuginfo,
};
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL, *dlopen_metadata = NULL;
_cleanup_set_free_ Set *modules = NULL;
_cleanup_(stack_context_done) StackContext c = {
.package_metadata = &package_metadata,
.dlopen_metadata = ret_dlopen_metadata ? &dlopen_metadata : NULL,
.modules = &modules,
};
int r;
@@ -667,15 +689,24 @@ static int parse_core(int fd, const char *root, char **ret, sd_json_variant **re
if (ret_package_metadata)
*ret_package_metadata = TAKE_PTR(package_metadata);
if (ret_dlopen_metadata)
*ret_dlopen_metadata = TAKE_PTR(dlopen_metadata);
return 0;
}
static int parse_elf(int fd, const char *executable, const char *root, char **ret, sd_json_variant **ret_package_metadata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL, *elf_metadata = NULL;
static int parse_elf(
int fd,
const char *executable,
const char *root,
char **ret,
sd_json_variant **ret_package_metadata,
sd_json_variant **ret_dlopen_metadata) {
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL, *dlopen_metadata = NULL, *elf_metadata = NULL;
_cleanup_set_free_ Set *modules = NULL;
_cleanup_(stack_context_done) StackContext c = {
.package_metadata = &package_metadata,
.dlopen_metadata = ret_dlopen_metadata ? &dlopen_metadata : NULL,
.modules = &modules,
};
const char *elf_type;
@@ -702,7 +733,7 @@ static int parse_elf(int fd, const char *executable, const char *root, char **re
if (elf_header.e_type == ET_CORE) {
_cleanup_free_ char *out = NULL;
r = parse_core(fd, root, ret ? &out : NULL, &package_metadata);
r = parse_core(fd, root, ret ? &out : NULL, &package_metadata, &dlopen_metadata);
if (r < 0)
return log_warning_errno(r, "Failed to inspect core file: %m");
@@ -719,7 +750,7 @@ static int parse_elf(int fd, const char *executable, const char *root, char **re
if (r < 0)
return log_warning_errno(r, "Failed to parse build-id of ELF file: %m");
r = parse_package_metadata(e, id_json, c.elf, &interpreter_found, &c);
r = parse_metadata(e, id_json, c.elf, &interpreter_found, &c);
if (r < 0)
return log_warning_errno(r, "Failed to parse package metadata of ELF file: %m");
@@ -769,18 +800,28 @@ static int parse_elf(int fd, const char *executable, const char *root, char **re
if (ret_package_metadata)
*ret_package_metadata = TAKE_PTR(elf_metadata);
if (ret_dlopen_metadata)
*ret_dlopen_metadata = TAKE_PTR(dlopen_metadata);
return 0;
}
#endif
int parse_elf_object(int fd, const char *executable, const char *root, bool fork_disable_dump, char **ret, sd_json_variant **ret_package_metadata) {
int parse_elf_object(
int fd,
const char *executable,
const char *root,
bool fork_disable_dump,
char **ret,
sd_json_variant **ret_package_metadata,
sd_json_variant **ret_dlopen_metadata) {
#if HAVE_ELFUTILS
_cleanup_close_pair_ int error_pipe[2] = EBADF_PAIR,
return_pipe[2] = EBADF_PAIR,
json_pipe[2] = EBADF_PAIR;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL;
package_metadata_pipe[2] = EBADF_PAIR,
dlopen_metadata_pipe[2] = EBADF_PAIR;
_cleanup_(sd_json_variant_unrefp) sd_json_variant *package_metadata = NULL, *dlopen_metadata = NULL;
_cleanup_free_ char *buf = NULL;
int r;
@@ -805,7 +846,13 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
}
if (ret_package_metadata) {
r = RET_NERRNO(pipe2(json_pipe, O_CLOEXEC|O_NONBLOCK));
r = RET_NERRNO(pipe2(package_metadata_pipe, O_CLOEXEC|O_NONBLOCK));
if (r < 0)
return r;
}
if (ret_dlopen_metadata) {
r = RET_NERRNO(pipe2(dlopen_metadata_pipe, O_CLOEXEC|O_NONBLOCK));
if (r < 0)
return r;
}
@@ -818,8 +865,8 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
* the file descriptor and writing into these four pipes. */
r = safe_fork_full("(sd-parse-elf)",
NULL,
(int[]){ fd, error_pipe[1], return_pipe[1], json_pipe[1] },
4,
(int[]){ fd, error_pipe[1], return_pipe[1], package_metadata_pipe[1], dlopen_metadata_pipe[1] },
5,
FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_NEW_MOUNTNS|FORK_MOUNTNS_SLAVE|FORK_NEW_USERNS|FORK_WAIT|FORK_REOPEN_LOG,
NULL);
if (r < 0) {
@@ -846,7 +893,13 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
report_errno_and_exit(error_pipe[1], r);
}
r = parse_elf(fd, executable, root, ret ? &buf : NULL, ret_package_metadata ? &package_metadata : NULL);
r = parse_elf(
fd,
executable,
root,
ret ? &buf : NULL,
ret_package_metadata ? &package_metadata : NULL,
ret_dlopen_metadata ? &dlopen_metadata : NULL);
if (r < 0)
report_errno_and_exit(error_pipe[1], r);
@@ -879,9 +932,9 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
/* Bump the space for the returned string. We don't know how much space we'll need in
* advance, so we'll just try to write as much as possible and maybe fail later. */
(void) fcntl(json_pipe[1], F_SETPIPE_SZ, COREDUMP_PIPE_MAX);
(void) fcntl(package_metadata_pipe[1], F_SETPIPE_SZ, COREDUMP_PIPE_MAX);
json_out = take_fdopen(&json_pipe[1], "w");
json_out = take_fdopen(&package_metadata_pipe[1], "w");
if (!json_out)
report_errno_and_exit(error_pipe[1], -errno);
@@ -890,12 +943,29 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
log_warning_errno(r, "Failed to write JSON package metadata, ignoring: %m");
}
if (dlopen_metadata) {
_cleanup_fclose_ FILE *json_out = NULL;
/* Bump the space for the returned string. We don't know how much space we'll need in
* advance, so we'll just try to write as much as possible and maybe fail later. */
(void) fcntl(dlopen_metadata_pipe[1], F_SETPIPE_SZ, COREDUMP_PIPE_MAX);
json_out = take_fdopen(&dlopen_metadata_pipe[1], "w");
if (!json_out)
report_errno_and_exit(error_pipe[1], -errno);
r = sd_json_variant_dump(dlopen_metadata, SD_JSON_FORMAT_FLUSH, json_out, NULL);
if (r < 0)
log_warning_errno(r, "Failed to write JSON package metadata, ignoring: %m");
}
_exit(EXIT_SUCCESS);
}
error_pipe[1] = safe_close(error_pipe[1]);
return_pipe[1] = safe_close(return_pipe[1]);
json_pipe[1] = safe_close(json_pipe[1]);
package_metadata_pipe[1] = safe_close(package_metadata_pipe[1]);
dlopen_metadata_pipe[1] = safe_close(dlopen_metadata_pipe[1]);
if (ret) {
_cleanup_fclose_ FILE *in = NULL;
@@ -912,19 +982,33 @@ int parse_elf_object(int fd, const char *executable, const char *root, bool fork
if (ret_package_metadata) {
_cleanup_fclose_ FILE *json_in = NULL;
json_in = take_fdopen(&json_pipe[0], "r");
json_in = take_fdopen(&package_metadata_pipe[0], "r");
if (!json_in)
return -errno;
r = sd_json_parse_file(json_in, NULL, 0, &package_metadata, NULL, NULL);
if (r < 0 && r != -ENODATA) /* ENODATA: json was empty, so we got nothing, but that's ok */
log_warning_errno(r, "Failed to read or parse json metadata, ignoring: %m");
log_warning_errno(r, "Failed to read or parse package metadata, ignoring: %m");
}
if (ret_dlopen_metadata) {
_cleanup_fclose_ FILE *json_in = NULL;
json_in = take_fdopen(&dlopen_metadata_pipe[0], "r");
if (!json_in)
return -errno;
r = sd_json_parse_file(json_in, NULL, 0, &dlopen_metadata, NULL, NULL);
if (r < 0 && r != -ENODATA) /* ENODATA: json was empty, so we got nothing, but that's ok */
log_warning_errno(r, "Failed to read or parse dlopen metadata, ignoring: %m");
}
if (ret)
*ret = TAKE_PTR(buf);
if (ret_package_metadata)
*ret_package_metadata = TAKE_PTR(package_metadata);
if (ret_dlopen_metadata)
*ret_dlopen_metadata = TAKE_PTR(dlopen_metadata);
return 0;
#else
+8 -1
View File
@@ -9,4 +9,11 @@ int dlopen_elf(void);
/* Parse an ELF object in a forked process, so that errors while iterating over
* untrusted and potentially malicious data do not propagate to the main caller's process.
* If fork_disable_dump, the child process will not dump core if it crashes. */
int parse_elf_object(int fd, const char *executable, const char *root, bool fork_disable_dump, char **ret, sd_json_variant **ret_package_metadata);
int parse_elf_object(
int fd,
const char *executable,
const char *root,
bool fork_disable_dump,
char **ret,
sd_json_variant **ret_package_metadata,
sd_json_variant **ret_dlopen_metadata);
+10 -1
View File
@@ -18,7 +18,7 @@ systemd-analyze time || :
systemd-analyze critical-chain || :
# blame
systemd-analyze blame
systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame
systemd-run --wait --user --pipe -M testuser@.host systemd-analyze blame --no-pager
(! systemd-analyze blame --global)
# plot
systemd-analyze plot >/dev/null || :
@@ -1006,7 +1006,16 @@ systemd-analyze security --threshold=25 --offline=true \
rm /tmp/img/usr/lib/systemd/system/testfile.service
if systemd-analyze --version | grep -q -F "+ELFUTILS"; then
systemd-analyze inspect-elf /lib/systemd/systemd
systemd-analyze inspect-elf --json=short /lib/systemd/systemd | grep -q -F '"elfType":"executable"'
# For some unknown reason the .note.dlopen sections are removed when building with sanitizers, so only
# run this test if we're not running under sanitizers.
if [[ ! -v ASAN_OPTIONS ]]; then
shared="$(ldd /lib/systemd/systemd | grep shared | cut -d' ' -f3)"
systemd-analyze dlopen-metadata "$shared"
systemd-analyze dlopen-metadata --json=short "$shared"
fi
fi
systemd-analyze --threshold=90 security systemd-journald.service