1
0
mirror of https://github.com/systemd/systemd.git synced 2025-06-02 17:07:47 +03:00

Merge pull request #21454 from bluca/inspect_elf

analyze: add inspect-elf verb to parse package metadata
This commit is contained in:
Zbigniew Jędrzejewski-Szmek 2021-12-06 12:45:25 +01:00 committed by GitHub
commit ec1574cd8e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 333 additions and 6 deletions

View 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>

View File

@ -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 = []

View 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") )

View 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
)

128
src/analyze/analyze-elf.c Normal file
View File

@ -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;
}

View File

@ -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);

View 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 },
{}
};

View 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

View File

@ -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;

View File

@ -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