]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
elf-util: Add support for parsing dlopen metadata
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Sun, 26 Oct 2025 18:33:30 +0000 (19:33 +0100)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 30 Oct 2025 07:41:43 +0000 (08:41 +0100)
Then we can add support for showing dlopen metadata to systemd-analyze.

src/analyze/analyze-inspect-elf.c
src/coredump/coredump-submit.c
src/shared/elf-util.c
src/shared/elf-util.h

index ccb5a987200459d548ae30cf17242c81c7e41765..d84601dfabe09814cd21ca656d5ce5a058d262f8 100644 (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);
 
index a4c9c424cf51de078dd26aab9de720a5c694c650..1079dfd7b7fec70a395a7080ebed2a26a5c7b959 100644 (file)
@@ -689,7 +689,8 @@ int coredump_submit(
                                                 root,
                                                 /* fork_disable_dump= */ skip, /* avoid loops */
                                                 &stacktrace,
-                                                &json_metadata);
+                                                &json_metadata,
+                                                /* ret_dlopen_metadata= */ NULL);
                 }
         }
 
index 9315562bbada08bb6df9aa78d5cbd1cae3e7f495..25fb217149e264ff2f021d7e621e0e3f4ba3dd91 100644 (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, "sd_json_variant_merge of package meta with buildId failed: %m");
-                        }
+                                        return log_error_errno(r, "Failed to build JSON object: %m");
 
-                        /* Pretty-print to the buffer, so that the metadata goes as plaintext in the
-                         * journal. */
-                        report_module_metadata(c, name, v);
+                                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");
 
-                        /* 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");
+                                package_metadata_found = true;
+                        } else if (c->dlopen_metadata) {
+                                sd_json_variant *z;
 
-                        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");
+                                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");
+                                }
+                        }
 
                         /* 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
index 19c40c210744a32f832cfd84e7ca707adc5fd81a..b5c3db80ee217984763e017e6cbc1340b50f4163 100644 (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);