]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/bootspec.c
Add fmemopen_unlocked() and use unlocked ops in fuzzers and some other tests
[thirdparty/systemd.git] / src / shared / bootspec.c
index 53ab042404674a208dac87b21a134055cc751ff0..b2f8936038bf529cc5564d376140ee25903812e3 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <stdio.h>
 #include <linux/magic.h>
+#include <unistd.h>
 
 #include "sd-device.h"
 #include "sd-id128.h"
 #include "parse-util.h"
 #include "path-util.h"
 #include "pe-header.h"
+#include "sort-util.h"
 #include "stat-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 #include "unaligned.h"
+#include "util.h"
 #include "virt.h"
 
 static void boot_entry_free(BootEntry *entry) {
@@ -50,7 +54,10 @@ static int boot_entry_load(
                 const char *path,
                 BootEntry *entry) {
 
-        _cleanup_(boot_entry_free) BootEntry tmp = {};
+        _cleanup_(boot_entry_free) BootEntry tmp = {
+                .type = BOOT_ENTRY_CONF,
+        };
+
         _cleanup_fclose_ FILE *f = NULL;
         unsigned line = 1;
         char *b, *c;
@@ -62,13 +69,16 @@ static int boot_entry_load(
 
         c = endswith_no_case(path, ".conf");
         if (!c)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", path);
 
         b = basename(path);
         tmp.id = strndup(b, c - b);
         if (!tmp.id)
                 return log_oom();
 
+        if (!efi_loader_entry_name_valid(tmp.id))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+
         tmp.path = strdup(path);
         if (!tmp.path)
                 return log_oom();
@@ -150,6 +160,7 @@ void boot_config_free(BootConfig *config) {
         free(config->editor);
         free(config->auto_entries);
         free(config->auto_firmware);
+        free(config->console_mode);
 
         free(config->entry_oneshot);
         free(config->entry_default);
@@ -272,7 +283,9 @@ static int boot_entry_load_unified(
                 BootEntry *ret) {
 
         _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
-        _cleanup_(boot_entry_free) BootEntry tmp = {};
+        _cleanup_(boot_entry_free) BootEntry tmp = {
+                .type = BOOT_ENTRY_UNIFIED,
+        };
         _cleanup_fclose_ FILE *f = NULL;
         const char *k;
         int r;
@@ -285,7 +298,7 @@ static int boot_entry_load_unified(
         if (!k)
                 return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path);
 
-        f = fmemopen((void*) osrelease, strlen(osrelease), "r");
+        f = fmemopen_unlocked((void*) osrelease, strlen(osrelease), "r");
         if (!f)
                 return log_error_errno(errno, "Failed to open os-release buffer: %m");
 
@@ -304,6 +317,9 @@ static int boot_entry_load_unified(
         if (!tmp.id)
                 return log_oom();
 
+        if (!efi_loader_entry_name_valid(tmp.id))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry: %s", tmp.id);
+
         tmp.path = strdup(path);
         if (!tmp.path)
                 return log_oom();
@@ -409,7 +425,7 @@ static int find_sections(
                 n = pread(fd, k, size, offset);
                 if (n < 0)
                         return log_error_errno(errno, "Failed to read section payload: %m");
-                if (n != size)
+                if ((size_t) n != size)
                         return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
 
                 /* Allow one trailing NUL byte, but nothing more. */
@@ -476,7 +492,7 @@ static int boot_entries_find_unified(
 
                 r = fd_verify_regular(fd);
                 if (r < 0) {
-                        log_warning_errno(errno, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
+                        log_warning_errno(r, "File %s/%s is not regular, ignoring: %m", dir, de->d_name);
                         continue;
                 }
 
@@ -572,6 +588,12 @@ static int boot_entries_select_default(const BootConfig *config) {
         int i;
 
         assert(config);
+        assert(config->entries || config->n_entries == 0);
+
+        if (config->n_entries == 0) {
+                log_debug("Found no default boot entry :(");
+                return -1; /* -1 means "no default" */
+        }
 
         if (config->entry_oneshot)
                 for (i = config->n_entries - 1; i >= 0; i--)
@@ -597,12 +619,8 @@ static int boot_entries_select_default(const BootConfig *config) {
                                 return i;
                         }
 
-        if (config->n_entries > 0)
-                log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
-        else
-                log_debug("Found no default boot entry :(");
-
-        return config->n_entries - 1; /* -1 means "no default" */
+        log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
+        return config->n_entries - 1;
 }
 
 int boot_entries_load_config(
@@ -652,18 +670,130 @@ int boot_entries_load_config(
 
         if (is_efi_boot()) {
                 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
+                if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
+                        log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\": %m");
+                        if (r == -ENOMEM)
+                                return r;
+                }
 
                 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
+                if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
+                        log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\": %m");
+                        if (r == -ENOMEM)
+                                return r;
+                }
         }
 
         config->default_entry = boot_entries_select_default(config);
         return 0;
 }
 
+int boot_entries_load_config_auto(
+                const char *override_esp_path,
+                const char *override_xbootldr_path,
+                BootConfig *config) {
+
+        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
+        int r;
+
+        assert(config);
+
+        /* This function is similar to boot_entries_load_config(), however we automatically search for the
+         * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
+         * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
+         * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
+         * it). This allows other boot loaders to pass boot loader entry information to our tools if they
+         * want to. */
+
+        if (!override_esp_path && !override_xbootldr_path) {
+                if (access("/run/boot-loader-entries/", F_OK) >= 0)
+                        return boot_entries_load_config("/run/boot-loader-entries/", NULL, config);
+
+                if (errno != ENOENT)
+                        return log_error_errno(errno,
+                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
+        }
+
+        r = find_esp_and_warn(override_esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
+        if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
+                return r;
+
+        r = find_xbootldr_and_warn(override_xbootldr_path, false, &xbootldr_where, NULL);
+        if (r < 0 && r != -ENOKEY)
+                return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
+
+        return boot_entries_load_config(esp_where, xbootldr_where, config);
+}
+
+int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
+
+        static const char * const title_table[] = {
+                /* Pretty names for a few well-known automatically discovered entries. */
+                "auto-osx",                      "macOS",
+                "auto-windows",                  "Windows Boot Manager",
+                "auto-efi-shell",                "EFI Shell",
+                "auto-efi-default",              "EFI Default Loader",
+                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
+        };
+
+        _cleanup_strv_free_ char **found_by_loader = NULL;
+        size_t n_allocated;
+        char **i;
+        int r;
+
+        assert(config);
+
+        /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
+         * already included there. */
+
+        r = efi_loader_get_entries(&found_by_loader);
+        if (IN_SET(r, -ENOENT, -EOPNOTSUPP))
+                return log_debug_errno(r, "Boot loader reported no entries.");
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
+
+        n_allocated = config->n_entries;
+
+        STRV_FOREACH(i, found_by_loader) {
+                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
+                char **a, **b;
+
+                if (boot_config_has_entry(config, *i))
+                        continue;
+
+                if (only_auto && !startswith(*i, "auto-"))
+                        continue;
+
+                c = strdup(*i);
+                if (!c)
+                        return log_oom();
+
+                STRV_FOREACH_PAIR(a, b, (char**) title_table)
+                        if (streq(*a, *i)) {
+                                t = strdup(*b);
+                                if (!t)
+                                        return log_oom();
+                                break;
+                        }
+
+                p = efi_variable_path(EFI_VENDOR_LOADER, "LoaderEntries");
+                if (!p)
+                        return log_oom();
+
+                if (!GREEDY_REALLOC0(config->entries, n_allocated, config->n_entries + 1))
+                        return log_oom();
+
+                config->entries[config->n_entries++] = (BootEntry) {
+                        .type = BOOT_ENTRY_LOADER,
+                        .id = TAKE_PTR(c),
+                        .title = TAKE_PTR(t),
+                        .path = TAKE_PTR(p),
+                };
+        }
+
+        return 0;
+}
+
 /********************************************************************************/
 
 static int verify_esp_blkid(
@@ -1293,37 +1423,3 @@ found:
 
         return 0;
 }
-
-int find_default_boot_entry(
-                const char *esp_path,
-                const char *xbootldr_path,
-                BootConfig *config,
-                const BootEntry **e) {
-
-        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
-        int r;
-
-        assert(config);
-        assert(e);
-
-        r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
-        if (r < 0)
-                return r;
-
-        r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
-        if (r < 0 && r != -ENOKEY)
-                return r;
-
-        r = boot_entries_load_config(esp_where, xbootldr_where, config);
-        if (r < 0)
-                return log_error_errno(r, "Failed to load boot loader entries: %m");
-
-        if (config->default_entry < 0)
-                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
-                                       "No boot loader entry suitable as default, refusing to guess.");
-
-        *e = &config->entries[config->default_entry];
-        log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
-
-        return 0;
-}