diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 384e3d4288..4559f7c7e5 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -91,7 +91,12 @@ and generally safe to run on the host without side effects. Ideally, every module in `src/basic/` and `src/shared/` should have a corresponding unit test under `src/test/`, exercising every helper function. -## Fuzzer test cases +## Fuzzing + +Fuzzers are a type of unit tests that execute code on an externally-supplied +input sample. Fuzzers are called `fuzz-*`. Fuzzers for `src/basic/` and +`src/shared` live under `src/fuzz/`, and those for other parts of the codebase +should be located next to the code they test. Files under `test/fuzz/` contain input data for fuzzers, one subdirectory for each fuzzer. Some of the files are "seed corpora", i.e. files that contain @@ -102,6 +107,16 @@ When adding new input samples under `test/fuzz/*/`, please use some short-but-meaningful names. Names of meson tests include the input file name and output looks awkward if they are too long. +Fuzzers are invoked primarily in three ways: firstly, each fuzzer is compiled +as a normal executable and executed for each of the input samples under +`test/fuzz/` as part of the test suite. Secondly, fuzzers may be instrumented +with sanitizers and invoked as part of the test suite (if `-Dfuzz-tests=true` +is configured). Thirdly, fuzzers are executed through fuzzing engines that try +to find new "interesting" inputs through coverage feedback and massive +parallelization; see the links for oss-fuzz in [Code +quality](https://systemd.io/CODE_QUALITY). For testing and debugging, fuzzers +can be executed as any other program, including under `valgrind` or `gdb`. + # Integration Tests Sources in `test/TEST-*` implement system-level testing for executables, diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 1f29577f00..b6bd1dfd65 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -558,39 +558,6 @@ static int status_variables(void) { return 0; } -static int boot_entry_file_check(const char *root, const char *p) { - _cleanup_free_ char *path = NULL; - - path = path_join(root, p); - if (!path) - return log_oom(); - - return RET_NERRNO(access(path, F_OK)); -} - -static void boot_entry_file_list(const char *field, const char *root, const char *p, int *ret_status) { - int status = boot_entry_file_check(root, p); - - printf("%13s%s ", strempty(field), field ? ":" : " "); - if (status < 0) { - errno = -status; - printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal()); - } else - printf("%s\n", p); - - if (*ret_status == 0 && status < 0) - *ret_status = status; -} - -static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = { - [BOOT_ENTRY_CONF] = "Boot Loader Specification Type #1 (.conf)", - [BOOT_ENTRY_UNIFIED] = "Boot Loader Specification Type #2 (.efi)", - [BOOT_ENTRY_LOADER] = "Reported by Boot Loader", - [BOOT_ENTRY_LOADER_AUTO] = "Automatic", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType); - static int boot_config_load_and_select( BootConfig *config, const char *esp_path, @@ -620,104 +587,6 @@ static int boot_config_load_and_select( return boot_config_select_special_entries(config); } -static int boot_entry_show( - const BootEntry *e, - bool show_as_default, - bool show_as_selected, - bool show_reported) { - - int status = 0; - - /* Returns 0 on success, negative on processing error, and positive if something is wrong with the - boot entry itself. */ - - assert(e); - - printf(" type: %s\n", - boot_entry_type_to_string(e->type)); - - printf(" title: %s%s%s", - ansi_highlight(), boot_entry_title(e), ansi_normal()); - - if (show_as_default) - printf(" %s(default)%s", - ansi_highlight_green(), ansi_normal()); - - if (show_as_selected) - printf(" %s(selected)%s", - ansi_highlight_magenta(), ansi_normal()); - - if (show_reported) { - if (e->type == BOOT_ENTRY_LOADER) - printf(" %s(reported/absent)%s", - ansi_highlight_red(), ansi_normal()); - else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO) - printf(" %s(not reported/new)%s", - ansi_highlight_green(), ansi_normal()); - } - - putchar('\n'); - - if (e->id) - printf(" id: %s\n", e->id); - if (e->path) { - _cleanup_free_ char *link = NULL; - - /* Let's urlify the link to make it easy to view in an editor, but only if it is a text - * file. Unified images are binary ELFs, and EFI variables are not pure text either. */ - if (e->type == BOOT_ENTRY_CONF) - (void) terminal_urlify_path(e->path, NULL, &link); - - printf(" source: %s\n", link ?: e->path); - } - if (e->sort_key) - printf(" sort-key: %s\n", e->sort_key); - if (e->version) - printf(" version: %s\n", e->version); - if (e->machine_id) - printf(" machine-id: %s\n", e->machine_id); - if (e->architecture) - printf(" architecture: %s\n", e->architecture); - if (e->kernel) - boot_entry_file_list("linux", e->root, e->kernel, &status); - - STRV_FOREACH(s, e->initrd) - boot_entry_file_list(s == e->initrd ? "initrd" : NULL, - e->root, - *s, - &status); - - if (!strv_isempty(e->options)) { - _cleanup_free_ char *t = NULL, *t2 = NULL; - _cleanup_strv_free_ char **ts = NULL; - - t = strv_join(e->options, " "); - if (!t) - return log_oom(); - - ts = strv_split_newlines(t); - if (!ts) - return log_oom(); - - t2 = strv_join(ts, "\n "); - if (!t2) - return log_oom(); - - printf(" options: %s\n", t2); - } - - if (e->device_tree) - boot_entry_file_list("devicetree", e->root, e->device_tree, &status); - - STRV_FOREACH(s, e->device_tree_overlay) - boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL, - e->root, - *s, - &status); - - return -status; -} - static int status_entries( const BootConfig *config, const char *esp_path, @@ -752,7 +621,7 @@ static int status_entries( else { printf("Default Boot Loader Entry:\n"); - r = boot_entry_show( + r = show_boot_entry( boot_config_default_entry(config), /* show_as_default= */ false, /* show_as_selected= */ false, @@ -1861,65 +1730,13 @@ static int verb_list(int argc, char *argv[], void *userdata) { if (r < 0) return r; - if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { - - pager_open(arg_pager_flags); - - for (size_t i = 0; i < config.n_entries; i++) { - _cleanup_free_ char *opts = NULL; - BootEntry *e = config.entries + i; - _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; - - if (!strv_isempty(e->options)) { - opts = strv_join(e->options, " "); - if (!opts) - return log_oom(); - } - - r = json_build(&v, JSON_BUILD_OBJECT( - JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), - JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), - JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), - JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), - JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), - JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), - JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), - JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), - JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), - JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), - JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), - JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), - JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), - JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); - if (r < 0) - return log_oom(); - - json_variant_dump(v, arg_json_format_flags, stdout, NULL); - } - - } else if (config.n_entries == 0) + if (config.n_entries == 0 && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) { log_info("No boot loader entries found."); - else { - pager_open(arg_pager_flags); - - printf("Boot Loader Entries:\n"); - - for (size_t n = 0; n < config.n_entries; n++) { - r = boot_entry_show( - config.entries + n, - /* show_as_default= */ n == (size_t) config.default_entry, - /* show_as_selected= */ n == (size_t) config.selected_entry, - /* show_discovered= */ true); - if (r < 0) - return r; - - if (n+1 < config.n_entries) - putchar('\n'); - } + return 0; } - return 0; + pager_open(arg_pager_flags); + return show_boot_entries(&config, arg_json_format_flags); } static int install_random_seed(const char *esp) { diff --git a/src/fuzz/fuzz-bootspec-gen.py b/src/fuzz/fuzz-bootspec-gen.py new file mode 100644 index 0000000000..99af3f5f69 --- /dev/null +++ b/src/fuzz/fuzz-bootspec-gen.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later + +"""Generate sample input for fuzz-bootspec""" + +import json +import os +import sys + +config = open(sys.argv[1]).read() +loader = [entry for entry in open(sys.argv[2], encoding='utf-16-le').read().split('\0') + if len(entry) > 2] # filter out fluff from bad decoding +entries = [(os.path.basename(name), open(name).read()) + for name in sys.argv[3:]] + +data = { + 'config': config, + 'entries': entries, + 'loader': loader, +} + +print(json.dumps(data, indent=4)) diff --git a/src/fuzz/fuzz-bootspec.c b/src/fuzz/fuzz-bootspec.c new file mode 100644 index 0000000000..b59e67c24e --- /dev/null +++ b/src/fuzz/fuzz-bootspec.c @@ -0,0 +1,122 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include + +#include "bootspec.h" +#include "env-util.h" +#include "escape.h" +#include "fuzz.h" +#include "fd-util.h" +#include "json.h" + +static int json_dispatch_config(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + BootConfig *config = ASSERT_PTR(userdata); + + const char *s = json_variant_string(variant); + if (!s) + return -EINVAL; + + _cleanup_fclose_ FILE *f = NULL; + assert_se(f = data_to_file((const uint8_t*) s, strlen(s))); + + (void) boot_loader_read_conf(config, f, "memstream"); + return 0; +} + +static int json_dispatch_entries(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + BootConfig *config = ASSERT_PTR(userdata); + JsonVariant *entry; + + JSON_VARIANT_ARRAY_FOREACH(entry, variant) { + if (!json_variant_is_array(entry) || + json_variant_elements(entry) < 1) + return -EINVAL; + + JsonVariant *v; + const char *id = NULL, *raw = NULL; + _cleanup_free_ char *data = NULL; + ssize_t len = -ENODATA; + + v = json_variant_by_index(entry, 0); + if (v) + id = json_variant_string(v); + if (!id) + continue; + + v = json_variant_by_index(entry, 1); + if (v) + raw = json_variant_string(v); + if (raw) + len = cunescape(raw, UNESCAPE_RELAX | UNESCAPE_ACCEPT_NUL, &data); + if (len >= 0) { + _cleanup_fclose_ FILE *f = NULL; + assert_se(f = data_to_file((const uint8_t*) data, len)); + + assert_se(boot_config_load_type1(config, f, "/", "/entries", id) != -ENOMEM); + } + } + + return 0; +} + +static int json_dispatch_loader(const char *name, JsonVariant *variant, JsonDispatchFlags flags, void *userdata) { + BootConfig *config = ASSERT_PTR(userdata); + _cleanup_strv_free_ char **entries = NULL; + int r; + + r = json_dispatch_strv(name, variant, flags, &entries); + if (r < 0) + return r; + + (void) boot_config_augment_from_loader(config, entries, false); + return 0; +} + +static const JsonDispatch data_dispatch[] = { + { "config", JSON_VARIANT_STRING, json_dispatch_config, 0, 0 }, + { "entries", JSON_VARIANT_ARRAY, json_dispatch_entries, 0, 0 }, + { "loader", JSON_VARIANT_ARRAY, json_dispatch_loader, 0, 0 }, + {} +}; + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + _cleanup_free_ const char *datadup = NULL; + _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; + int r; + + /* Disable most logging if not running standalone */ + if (!getenv("SYSTEMD_LOG_LEVEL")) + log_set_max_level(LOG_CRIT); + + assert_se(datadup = memdup_suffix0(data, size)); + + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + r = json_parse(datadup, 0, &v, NULL, NULL); + if (r < 0) + return 0; + + r = json_dispatch(v, data_dispatch, NULL, 0, &config); + if (r < 0) + return 0; + + assert_se(boot_config_finalize(&config) >= 0); + + (void) boot_config_select_special_entries(&config); + + _cleanup_close_ int orig_stdout_fd = -1; + if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) { + orig_stdout_fd = fcntl(fileno(stdout), F_DUPFD_CLOEXEC, 3); + if (orig_stdout_fd < 0) + log_warning_errno(orig_stdout_fd, "Failed to duplicate fd 1: %m"); + else + assert_se(freopen("/dev/null", "w", stdout)); + } + + (void) show_boot_entries(&config, JSON_FORMAT_OFF); + (void) show_boot_entries(&config, JSON_FORMAT_PRETTY); + + if (orig_stdout_fd >= 0) + assert_se(freopen(FORMAT_PROC_FD_PATH(orig_stdout_fd), "w", stdout)); + + return 0; +} diff --git a/src/fuzz/meson.build b/src/fuzz/meson.build index d987f32b08..6f36536f1b 100644 --- a/src/fuzz/meson.build +++ b/src/fuzz/meson.build @@ -1,23 +1,25 @@ # SPDX-License-Identifier: LGPL-2.1-or-later fuzzers += [ - [files('fuzz-catalog.c')], - - [files('fuzz-json.c')], - - [files('fuzz-varlink.c')], - - [files('fuzz-udev-database.c')], - - [files('fuzz-compress.c')], + [files('fuzz-bootspec.c')], [files('fuzz-bus-label.c')], + [files('fuzz-calendarspec.c')], + + [files('fuzz-catalog.c')], + + [files('fuzz-compress.c')], + [files('fuzz-env-file.c')], [files('fuzz-hostname-setup.c')], - [files('fuzz-calendarspec.c')], + [files('fuzz-json.c')], [files('fuzz-time-util.c')], + + [files('fuzz-udev-database.c')], + + [files('fuzz-varlink.c')], ] diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 91cb605fb1..2111a024fb 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -2,23 +2,36 @@ #include -#include "bootspec.h" #include "bootspec-fundamental.h" +#include "bootspec.h" #include "conf-files.h" #include "devnum-util.h" #include "dirent-util.h" #include "efi-loader.h" #include "env-file.h" +#include "errno-util.h" #include "fd-util.h" #include "fileio.h" #include "find-esp.h" #include "path-util.h" #include "pe-header.h" +#include "pretty-print.h" #include "recurse-dir.h" #include "sort-util.h" +#include "string-table.h" #include "strv.h" +#include "terminal-util.h" #include "unaligned.h" +static const char* const boot_entry_type_table[_BOOT_ENTRY_TYPE_MAX] = { + [BOOT_ENTRY_CONF] = "Boot Loader Specification Type #1 (.conf)", + [BOOT_ENTRY_UNIFIED] = "Boot Loader Specification Type #2 (.efi)", + [BOOT_ENTRY_LOADER] = "Reported by Boot Loader", + [BOOT_ENTRY_LOADER_AUTO] = "Automatic", +}; + +DEFINE_STRING_TABLE_LOOKUP_TO_STRING(boot_entry_type, BootEntryType); + static void boot_entry_free(BootEntry *entry) { assert(entry); @@ -114,6 +127,13 @@ static int boot_entry_load_type1( continue; } + if (isempty(p)) { + /* Some fields can reasonably have an empty value. In other cases warn. */ + if (!STR_IN_SET(field, "options", "devicetree-overlay")) + log_warning("%s:%u: Field %s without value", tmp.path, line, field); + continue; + } + if (streq(field, "title")) r = free_and_strdup(&tmp.title, p); else if (streq(field, "sort-key")) @@ -155,6 +175,31 @@ static int boot_entry_load_type1( return 0; } +int boot_config_load_type1( + BootConfig *config, + FILE *f, + const char *root, + const char *dir, + const char *id) { + int r; + + assert(config); + assert(f); + assert(root); + assert(dir); + assert(id); + + if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1)) + return log_oom(); + + r = boot_entry_load_type1(f, root, dir, id, config->entries + config->n_entries); + if (r < 0) + return r; + + config->n_entries++; + return 0; +} + void boot_config_free(BootConfig *config) { assert(config); @@ -178,27 +223,19 @@ void boot_config_free(BootConfig *config) { set_free(config->inodes_seen); } -static int boot_loader_read_conf(const char *path, BootConfig *config) { - _cleanup_fclose_ FILE *f = NULL; +int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path) { unsigned line = 1; int r; - assert(path); assert(config); - - f = fopen(path, "re"); - if (!f) { - if (errno == ENOENT) - return 0; - - return log_error_errno(errno, "Failed to open \"%s\": %m", path); - } + assert(file); + assert(path); for (;;) { _cleanup_free_ char *buf = NULL, *field = NULL; const char *p; - r = read_line(f, LONG_LINE_MAX, &buf); + r = read_line(file, LONG_LINE_MAX, &buf); if (r == 0) break; if (r == -ENOBUFS) @@ -249,6 +286,23 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) { return 1; } +static int boot_loader_read_conf_path(BootConfig *config, const char *path) { + _cleanup_fclose_ FILE *f = NULL; + + assert(config); + assert(path); + + f = fopen(path, "re"); + if (!f) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open \"%s\": %m", path); + } + + return boot_loader_read_conf(config, f, path); +} + static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { int r; @@ -375,14 +429,9 @@ static int boot_entries_find_type1( if (r == 0) /* inode already seen or otherwise not relevant */ continue; - if (!GREEDY_REALLOC0(config->entries, config->n_entries + 1)) - return log_oom(); - - r = boot_entry_load_type1(f, root, dir, de->d_name, config->entries + config->n_entries); - if (r < 0) - continue; - - config->n_entries++; + r = boot_config_load_type1(config, f, root, dir, de->d_name); + if (r == -ENOMEM) + return r; } return 0; @@ -834,6 +883,18 @@ int boot_config_select_special_entries(BootConfig *config) { return 0; } +int boot_config_finalize(BootConfig *config) { + int r; + + typesafe_qsort(config->entries, config->n_entries, boot_entry_compare); + + r = boot_entries_uniquify(config->entries, config->n_entries); + if (r < 0) + return log_error_errno(r, "Failed to uniquify boot entries: %m"); + + return 0; +} + int boot_config_load( BootConfig *config, const char *esp_path, @@ -846,7 +907,7 @@ int boot_config_load( if (esp_path) { p = strjoina(esp_path, "/loader/loader.conf"); - r = boot_loader_read_conf(p, config); + r = boot_loader_read_conf_path(config, p); if (r < 0) return r; @@ -873,13 +934,7 @@ int boot_config_load( return r; } - typesafe_qsort(config->entries, config->n_entries, boot_entry_compare); - - r = boot_entries_uniquify(config->entries, config->n_entries); - if (r < 0) - return log_error_errno(r, "Failed to uniquify boot entries: %m"); - - return 0; + return boot_config_finalize(config); } int boot_config_load_auto( @@ -936,6 +991,7 @@ int boot_config_augment_from_loader( "auto-efi-shell", "EFI Shell", "auto-efi-default", "EFI Default Loader", "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface", + NULL, }; assert(config); @@ -987,6 +1043,16 @@ int boot_config_augment_from_loader( return 0; } +static int boot_entry_file_check(const char *root, const char *p) { + _cleanup_free_ char *path = NULL; + + path = path_join(root, p); + if (!path) + return log_oom(); + + return RET_NERRNO(access(path, F_OK)); +} + BootEntry* boot_config_find_entry(BootConfig *config, const char *id) { assert(config); assert(id); @@ -998,3 +1064,172 @@ BootEntry* boot_config_find_entry(BootConfig *config, const char *id) { return NULL; } + +static void boot_entry_file_list(const char *field, const char *root, const char *p, int *ret_status) { + int status = boot_entry_file_check(root, p); + + printf("%13s%s ", strempty(field), field ? ":" : " "); + if (status < 0) { + errno = -status; + printf("%s%s%s (%m)\n", ansi_highlight_red(), p, ansi_normal()); + } else + printf("%s\n", p); + + if (*ret_status == 0 && status < 0) + *ret_status = status; +} + +int show_boot_entry( + const BootEntry *e, + bool show_as_default, + bool show_as_selected, + bool show_reported) { + + int status = 0; + + /* Returns 0 on success, negative on processing error, and positive if something is wrong with the + boot entry itself. */ + + assert(e); + + printf(" type: %s\n", + boot_entry_type_to_string(e->type)); + + printf(" title: %s%s%s", + ansi_highlight(), boot_entry_title(e), ansi_normal()); + + if (show_as_default) + printf(" %s(default)%s", + ansi_highlight_green(), ansi_normal()); + + if (show_as_selected) + printf(" %s(selected)%s", + ansi_highlight_magenta(), ansi_normal()); + + if (show_reported) { + if (e->type == BOOT_ENTRY_LOADER) + printf(" %s(reported/absent)%s", + ansi_highlight_red(), ansi_normal()); + else if (!e->reported_by_loader && e->type != BOOT_ENTRY_LOADER_AUTO) + printf(" %s(not reported/new)%s", + ansi_highlight_green(), ansi_normal()); + } + + putchar('\n'); + + if (e->id) + printf(" id: %s\n", e->id); + if (e->path) { + _cleanup_free_ char *link = NULL; + + /* Let's urlify the link to make it easy to view in an editor, but only if it is a text + * file. Unified images are binary ELFs, and EFI variables are not pure text either. */ + if (e->type == BOOT_ENTRY_CONF) + (void) terminal_urlify_path(e->path, NULL, &link); + + printf(" source: %s\n", link ?: e->path); + } + if (e->sort_key) + printf(" sort-key: %s\n", e->sort_key); + if (e->version) + printf(" version: %s\n", e->version); + if (e->machine_id) + printf(" machine-id: %s\n", e->machine_id); + if (e->architecture) + printf(" architecture: %s\n", e->architecture); + if (e->kernel) + boot_entry_file_list("linux", e->root, e->kernel, &status); + + STRV_FOREACH(s, e->initrd) + boot_entry_file_list(s == e->initrd ? "initrd" : NULL, + e->root, + *s, + &status); + + if (!strv_isempty(e->options)) { + _cleanup_free_ char *t = NULL, *t2 = NULL; + _cleanup_strv_free_ char **ts = NULL; + + t = strv_join(e->options, " "); + if (!t) + return log_oom(); + + ts = strv_split_newlines(t); + if (!ts) + return log_oom(); + + t2 = strv_join(ts, "\n "); + if (!t2) + return log_oom(); + + printf(" options: %s\n", t2); + } + + if (e->device_tree) + boot_entry_file_list("devicetree", e->root, e->device_tree, &status); + + STRV_FOREACH(s, e->device_tree_overlay) + boot_entry_file_list(s == e->device_tree_overlay ? "devicetree-overlay" : NULL, + e->root, + *s, + &status); + + return -status; +} + +int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { + int r; + + if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) { + for (size_t i = 0; i < config->n_entries; i++) { + _cleanup_free_ char *opts = NULL; + const BootEntry *e = config->entries + i; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + + if (!strv_isempty(e->options)) { + opts = strv_join(e->options, " "); + if (!opts) + return log_oom(); + } + + r = json_build(&v, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR_CONDITION(e->id, "id", JSON_BUILD_STRING(e->id)), + JSON_BUILD_PAIR_CONDITION(e->path, "path", JSON_BUILD_STRING(e->path)), + JSON_BUILD_PAIR_CONDITION(e->root, "root", JSON_BUILD_STRING(e->root)), + JSON_BUILD_PAIR_CONDITION(e->title, "title", JSON_BUILD_STRING(e->title)), + JSON_BUILD_PAIR_CONDITION(boot_entry_title(e), "showTitle", JSON_BUILD_STRING(boot_entry_title(e))), + JSON_BUILD_PAIR_CONDITION(e->sort_key, "sortKey", JSON_BUILD_STRING(e->sort_key)), + JSON_BUILD_PAIR_CONDITION(e->version, "version", JSON_BUILD_STRING(e->version)), + JSON_BUILD_PAIR_CONDITION(e->machine_id, "machineId", JSON_BUILD_STRING(e->machine_id)), + JSON_BUILD_PAIR_CONDITION(e->architecture, "architecture", JSON_BUILD_STRING(e->architecture)), + JSON_BUILD_PAIR_CONDITION(opts, "options", JSON_BUILD_STRING(opts)), + JSON_BUILD_PAIR_CONDITION(e->kernel, "linux", JSON_BUILD_STRING(e->kernel)), + JSON_BUILD_PAIR_CONDITION(e->efi, "efi", JSON_BUILD_STRING(e->efi)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->initrd), "initrd", JSON_BUILD_STRV(e->initrd)), + JSON_BUILD_PAIR_CONDITION(e->device_tree, "devicetree", JSON_BUILD_STRING(e->device_tree)), + JSON_BUILD_PAIR_CONDITION(!strv_isempty(e->device_tree_overlay), "devicetreeOverlay", JSON_BUILD_STRV(e->device_tree_overlay)))); + if (r < 0) + return log_oom(); + + json_variant_dump(v, json_format, stdout, NULL); + } + + } else { + printf("Boot Loader Entries:\n"); + + for (size_t n = 0; n < config->n_entries; n++) { + r = show_boot_entry( + config->entries + n, + /* show_as_default= */ n == (size_t) config->default_entry, + /* show_as_selected= */ n == (size_t) config->selected_entry, + /* show_discovered= */ true); + if (r < 0) + return r; + + if (n+1 < config->n_entries) + putchar('\n'); + } + } + + return 0; +} diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index 93fb22e9d4..ff54cc2e84 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -7,6 +7,7 @@ #include #include +#include "json.h" #include "set.h" #include "string-util.h" @@ -69,6 +70,8 @@ typedef struct BootConfig { .selected_entry = -1, \ } +const char* boot_entry_type_to_string(BootEntryType); + BootEntry* boot_config_find_entry(BootConfig *config, const char *id); static inline const BootEntry* boot_config_default_entry(const BootConfig *config) { @@ -83,6 +86,16 @@ static inline const BootEntry* boot_config_default_entry(const BootConfig *confi void boot_config_free(BootConfig *config); +int boot_loader_read_conf(BootConfig *config, FILE *file, const char *path); + +int boot_config_load_type1( + BootConfig *config, + FILE *f, + const char *root, + const char *dir, + const char *id); + +int boot_config_finalize(BootConfig *config); int boot_config_load(BootConfig *config, const char *esp_path, const char *xbootldr_path); int boot_config_load_auto(BootConfig *config, const char *override_esp_path, const char *override_xbootldr_path); int boot_config_augment_from_loader(BootConfig *config, char **list, bool only_auto); @@ -92,5 +105,14 @@ int boot_config_select_special_entries(BootConfig *config); static inline const char* boot_entry_title(const BootEntry *entry) { assert(entry); - return entry->show_title ?: entry->title ?: entry->id; + return ASSERT_PTR(entry->show_title ?: entry->title ?: entry->id); } + +int show_boot_entry( + const BootEntry *e, + bool show_as_default, + bool show_as_selected, + bool show_reported); +int show_boot_entries( + const BootConfig *config, + JsonFormatFlags json_format); diff --git a/test/fuzz/fuzz-bootspec/crash-autoentry b/test/fuzz/fuzz-bootspec/crash-autoentry new file mode 100644 index 0000000000..04d78ef377 --- /dev/null +++ b/test/fuzz/fuzz-bootspec/crash-autoentry @@ -0,0 +1,21 @@ +{ + "config": "timeout 3\nconsole-mode 2\n# default 08a5690afeedfeedaaac0a5d2e3cf6b0-*\n# default auto-reboot-to-firmware-setup\n", + "entries": [ + [ + "08a5690afeedfeedaaac0a5d2e3cf6b0-5.15.14-200.fc35.x86_64.conf", + "title Fedora Linux 35 (Workstation Edition)\nversion 5.15.14-200.fc35.x86_64\nmachine-id 08a5690afeedfeedaaac0a5d2e3cf6b0\noptions root=/dev/mapper/fedora_krowka-root rw rd.lvm.lv=fedora_krowka/root rd.luks.uuid=luks-2d9b648a-15b1-4204-988b-ec085089f8ce rd.lvm.lv=fedora_krowka/swap LANG=en_US.UTF-8 selinux=0 resume=/dev/mapper/fedora_krowka-swap systemd.show-status systemd.unit-status-format=name quiet\nlinux /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/linux\ninitrd /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/initrd\n" + ], + [ + "08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", + "title Fedora Linux 36 (Workstation Edition Prerelease)\nversion 5.17.0-0.rc5.102.fc36.x86_64\nmachine-id 08a5690afeedfeedaaac0a5d2e3cf6b0\noptions root=/dev/mapper/fedora_krowka-root rw rd.lvm.lv=fedora_krowka/root rd.luks.uuid=luks-2d9b648a-15b1-4204-988b-ec085089f8ce rd.lvm.lv=fedora_krowka/swap LANG=en_US.UTF-8 selinux=0 resume=/dev/mapper/fedora_krowka-swap systemd.show-status systemd.unit-status-format=name quiet\nlinux /08a5690afeedfeedaaac0a5d2e3cf6b0/5.17.0-0.rc5.102.fc36.x86_64/linux\ninitrd /08a5690afeedfeedaaac0a5d2e3cf6b0/5.17.0-0.rc5.102.fc36.x86_64/initrd\n" + ], + [ + "listing.conf", + "title Title\nversion 3.7.2-201.fc18.x86_64\nmachine-id 4098b3f648d74c13b1f04ccfba7798e8\nsort-key 666\nlinux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux\ninitrd /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/initrd\nefi /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/efi\noptions one two three four\ndevicetree 6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.armv7hl/tegra20-paz00.dtb\ndevicetree-overlay /6a9857a393724b7a981ebb5b8495b9ea/overlays/overlay_A.dtbo /6a9857a393724b7a981ebb5b8495b9ea/overlays/overlay_B.dtbo\narchitecture IA32\narchitecture x64\narchitecture IA64\narchitecture ARM\narchitecture AA64\n" + ] + ], + "loader": [ + "08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", + "08a5690afeedfeedaaac0a5d2e3cfkstation Edition Prereleaa_krowse)\nversion 5.17.0-0.rc5.102.fc36.xtup" + ] +} diff --git a/test/fuzz/fuzz-bootspec/crash-empty-value b/test/fuzz/fuzz-bootspec/crash-empty-value new file mode 100644 index 0000000000..5436e44987 --- /dev/null +++ b/test/fuzz/fuzz-bootspec/crash-empty-value @@ -0,0 +1 @@ +{"config": "timeout 3\nconsole-mode 2\n# default 08a5690afeedfeedaaac0a5d2e3cf6b0-*\n# default auto-reboot-to-firmware-set*up\n", "entries": [["08a5690afeedfeedaaac0a5d2e3cf6b0-5.15.14-200.fc35.x86_64.conf", "title Fedora Linux 35 (Workstation Edition)\nversio' 5.15.14-200.fc35.x86_64\nmachine-id 08a5690afeedfeedaaac0a5d2e3cf6b0\noptions root=/dev/mapper/fedora_krowka-root rw rd.lvm.lv=fedora_krowka/root rd.luks.uuid=luks-2d9b648a-15b1-4204-988b-ec085085f8ce rd.lvm.lv=fedora_krowka/swap LANG=en_US.UTF-8 selinux=0 resume='dev/mapper/fedora_krowka-swap systemd.show-status systemd.unit-status-format=name quiet\nlinux /08a5690md.unit-status-format=name quiet\nlinux /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/linux\ninitrd /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/titled\n"], ["08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.47cf92ac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", "title Fedora Lin38 6724b7a987ebb5b8495b9ea/3.8.0-2.fc19.armv7hl/tegra20-paz00.dtb\ndevicetree-overlay ", "title Fedora Lin3x 6724b7a987ebb5b8495_64/initrd\n"], ["08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", "title Fedora Lin3x 6724b7a987ebb5b8495b9ea/3.8.0-2.fc19.armv7hl/tegra20-paz00.dtb\ndevicetree-overlay /6a9857a393'24b7a981ebb5b8495b9ea/overlays/overlay_A.dtbo /6a9857a393724b7a981ebb5b8495b9ea/overlays/overlay_B.dtbo\narchitecture IA32\narchitecture x64\narchitecture IA64\narcetithcure ARM\narchitecture AA64\n"]]} diff --git a/test/fuzz/fuzz-bootspec/crash-json-dispatch b/test/fuzz/fuzz-bootspec/crash-json-dispatch new file mode 100644 index 0000000000..fc2f0903b9 --- /dev/null +++ b/test/fuzz/fuzz-bootspec/crash-json-dispatch @@ -0,0 +1 @@ +{"config": "timeout ,3\nconsole-mode 2\n#o-firmwapa-setup\n", "entries": [["08a5690afeedfeedaaac0a5d2e3cf6b0-5.15.14mapper/fedod47cf92ac0a\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\3d2e5cf6b0/5.17.0-0.rc5.102.fc3uie4/initrd\n"], ["08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.0-0.rc5.102.86_64/initrd\n"], ["08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.WSL.rc5.102.fc36.x86_64.conf", "title Fedora Lays/overlay_A.dtbo nux /6a9857a393724b7a981eb-15b1-420ition)\nversioedod47cf92ac0a\\\\\\null\\\\\\\\\\\rmv7h)/tegra20-paz00.dtb\ndevicetree-overlay /6/9857a393'24b7a981ebb5ersioedod47cf92ac0a\\\\\\null\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\3d2earchb05/.17.0-0.rc5.102.fc3uiet\nlinux /0iet\nlinux /08a5690afeedfeedaaac0a5d2e3085089fdora Linux 35 (Workstationlirchitecture AA64\n"]]} diff --git a/test/fuzz/fuzz-bootspec/sample1 b/test/fuzz/fuzz-bootspec/sample1 new file mode 100644 index 0000000000..1231c864df --- /dev/null +++ b/test/fuzz/fuzz-bootspec/sample1 @@ -0,0 +1,22 @@ +{ + "config": "timeout 3\nconsole-mode 2\n# default 08a5690afeedfeedaaac0a5d2e3cf6b0-*\n# default auto-reboot-to-firmware-setup\n", + "entries": [ + [ + "08a5690afeedfeedaaac0a5d2e3cf6b0-5.15.14-200.fc35.x86_64.conf", + "title Fedora Linux 35 (Workstation Edition)\nversion 5.15.14-200.fc35.x86_64\nmachine-id 08a5690afeedfeedaaac0a5d2e3cf6b0\noptions root=/dev/mapper/fedora_krowka-root rw rd.lvm.lv=fedora_krowka/root rd.luks.uuid=luks-2d9b648a-15b1-4204-988b-ec085089f8ce rd.lvm.lv=fedora_krowka/swap LANG=en_US.UTF-8 selinux=0 resume=/dev/mapper/fedora_krowka-swap systemd.show-status systemd.unit-status-format=name quiet\nlinux /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/linux\ninitrd /08a5690afeedfeedaaac0a5d2e3cf6b0/5.15.14-200.fc35.x86_64/initrd\n" + ], + [ + "08a5690afeedfeedaaac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", + "title Fedora Linux 36 (Workstation Edition Prerelease)\nversion 5.17.0-0.rc5.102.fc36.x86_64\nmachine-id 08a5690afeedfeedaaac0a5d2e3cf6b0\noptions root=/dev/mapper/fedora_krowka-root rw rd.lvm.lv=fedora_krowka/root rd.luks.uuid=luks-2d9b648a-15b1-4204-988b-ec085089f8ce rd.lvm.lv=fedora_krowka/swap LANG=en_US.UTF-8 selinux=0 resume=/dev/mapper/fedora_krowka-swap systemd.show-status systemd.unit-status-format=name quiet\nlinux /08a5690afeedfeedaaac0a5d2e3cf6b0/5.17.0-0.rc5.102.fc36.x86_64/linux\ninitrd /08a5690afeedfeedaaac0a5d2e3cf6b0/5.17.0-0.rc5.102.fc36.x86_64/initrd\n" + ], + [ + "listing.conf", + "title Title\nversion 3.7.2-201.fc18.x86_64\nmachine-id 4098b3f648d74c13b1f04ccfba7798e8\nsort-key 666\nlinux /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/linux\ninitrd /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/initrd\nefi /6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.x86_64/efi\noptions one two three four\ndevicetree 6a9857a393724b7a981ebb5b8495b9ea/3.8.0-2.fc19.armv7hl/tegra20-paz00.dtb\ndevicetree-overlay /6a9857a393724b7a981ebb5b8495b9ea/overlays/overlay_A.dtbo /6a9857a393724b7a981ebb5b8495b9ea/overlays/overlay_B.dtbo\narchitecture IA32\narchitecture x64\narchitecture IA64\narchitecture ARM\narchitecture AA64\n" + ] + ], + "loader": [ + "08a5690a2eed47cf92ac0a5d2e3cf6b0-5.17.0-0.rc5.102.fc36.x86_64.conf", + "08a5690a2eed47cf92ac0a5d2e3cf6b0-5.15.14-200.fc35.x86_64.conf", + "auto-reboot-to-firmware-setup" + ] +}