diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/basic/env-file.c | 2 | ||||
-rw-r--r-- | src/basic/fs-util.c | 15 | ||||
-rw-r--r-- | src/basic/fs-util.h | 2 | ||||
-rw-r--r-- | src/boot/bless-boot.c | 191 | ||||
-rw-r--r-- | src/boot/bootctl.c | 394 | ||||
-rw-r--r-- | src/boot/efi/boot.c | 202 | ||||
-rw-r--r-- | src/boot/efi/crc32.c | 142 | ||||
-rw-r--r-- | src/boot/efi/crc32.h | 8 | ||||
-rw-r--r-- | src/boot/efi/meson.build | 4 | ||||
-rw-r--r-- | src/boot/efi/stub.c | 15 | ||||
-rw-r--r-- | src/boot/efi/util.c | 11 | ||||
-rw-r--r-- | src/boot/efi/util.h | 7 | ||||
-rw-r--r-- | src/gpt-auto-generator/gpt-auto-generator.c | 86 | ||||
-rw-r--r-- | src/shared/bootspec.c | 934 | ||||
-rw-r--r-- | src/shared/bootspec.h | 8 | ||||
-rw-r--r-- | src/shared/dissect-image.c | 104 | ||||
-rw-r--r-- | src/shared/dissect-image.h | 1 | ||||
-rw-r--r-- | src/shared/gpt.h | 1 | ||||
-rw-r--r-- | src/shared/meson.build | 1 | ||||
-rw-r--r-- | src/shared/pe-header.h | 59 | ||||
-rw-r--r-- | src/systemctl/systemctl.c | 24 |
21 files changed, 1806 insertions, 405 deletions
diff --git a/src/basic/env-file.c b/src/basic/env-file.c index 7f10f9ad39..a1f1308a54 100644 --- a/src/basic/env-file.c +++ b/src/basic/env-file.c @@ -559,6 +559,6 @@ int write_env_file(const char *fname, char **l) { r = -errno; } - unlink(p); + (void) unlink(p); return r; } diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 3ff8615797..0d631093b2 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -1330,6 +1330,21 @@ int fsync_path_at(int at_fd, const char *path) { return 0; } +int syncfs_path(int atfd, const char *path) { + _cleanup_close_ int fd = -1; + + assert(path); + + fd = openat(atfd, path, O_CLOEXEC|O_RDONLY|O_NONBLOCK); + if (fd < 0) + return -errno; + + if (syncfs(fd) < 0) + return -errno; + + return 0; +} + int open_parent(const char *path, int flags, mode_t mode) { _cleanup_free_ char *parent = NULL; int fd; diff --git a/src/basic/fs-util.h b/src/basic/fs-util.h index 7ad030be5d..9c9044669d 100644 --- a/src/basic/fs-util.h +++ b/src/basic/fs-util.h @@ -108,4 +108,6 @@ int unlinkat_deallocate(int fd, const char *name, int flags); int fsync_directory_of_file(int fd); int fsync_path_at(int at_fd, const char *path); +int syncfs_path(int atfd, const char *path); + int open_parent(const char *path, int flags, mode_t mode); diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index 42b9618aa9..b5d110f422 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -16,9 +16,9 @@ #include "verbs.h" #include "virt.h" -static char *arg_path = NULL; +static char **arg_path = NULL; -STATIC_DESTRUCTOR_REGISTER(arg_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_path, strv_freep); static int help(int argc, char *argv[], void *userdata) { @@ -27,7 +27,7 @@ static int help(int argc, char *argv[], void *userdata) { "Mark the boot process as good or bad.\n\n" " -h --help Show this help\n" " --version Print version\n" - " --path=PATH Path to the EFI System Partition (ESP)\n" + " --path=PATH Path to the $BOOT partition (may be used multiple times)\n" "\n" "Commands:\n" " good Mark this boot as good\n" @@ -67,7 +67,7 @@ static int parse_argv(int argc, char *argv[]) { return version(); case ARG_PATH: - r = free_and_strdup(&arg_path, optarg); + r = strv_extend(&arg_path, optarg); if (r < 0) return log_oom(); break; @@ -82,20 +82,42 @@ static int parse_argv(int argc, char *argv[]) { return 1; } -static int acquire_esp(void) { - _cleanup_free_ char *np = NULL; +static int acquire_path(void) { + _cleanup_free_ char *esp_path = NULL, *xbootldr_path = NULL; + char **a; int r; - r = find_esp_and_warn(arg_path, false, &np, NULL, NULL, NULL, NULL); - if (r == -ENOKEY) /* find_esp_and_warn() doesn't warn in this one error case, but in all others */ - return log_error_errno(r, - "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n" - "Alternatively, use --path= to specify path to mount point."); - if (r < 0) + if (!strv_isempty(arg_path)) + return 0; + + r = find_esp_and_warn(NULL, false, &esp_path, NULL, NULL, NULL, NULL); + if (r < 0 && r != -ENOKEY) /* ENOKEY means not found, and is the only error the function won't log about on its own */ + return r; + + r = find_xbootldr_and_warn(NULL, false, &xbootldr_path, NULL); + if (r < 0 && r != -ENOKEY) return r; - free_and_replace(arg_path, np); - log_debug("Using EFI System Partition at %s.", arg_path); + if (!esp_path && !xbootldr_path) + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), + "Couldn't find $BOOT partition. It is recommended to mount it to /boot.\n" + "Alternatively, use --path= to specify path to mount point."); + + if (esp_path) + a = strv_new(esp_path, xbootldr_path); + else + a = strv_new(xbootldr_path); + if (!a) + return log_oom(); + + strv_free_and_replace(arg_path, a); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *j; + + j = strv_join(arg_path, ":"); + log_debug("Using %s as boot loader drop-in search path.", j); + } return 0; } @@ -282,10 +304,9 @@ static const char *skip_slash(const char *path) { } static int verb_status(int argc, char *argv[], void *userdata) { - _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL; - _cleanup_close_ int fd = -1; uint64_t left, done; + char **p; int r; r = acquire_boot_count_path(&path, &prefix, &left, &done, &suffix); @@ -296,7 +317,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = acquire_esp(); + r = acquire_path(); if (r < 0) return r; @@ -308,50 +329,61 @@ static int verb_status(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - log_debug("Booted file: %s%s\n" - "The same modified for 'good': %s%s\n" - "The same modified for 'bad': %s%s\n", - arg_path, path, - arg_path, good, - arg_path, bad); + log_debug("Booted file: %s\n" + "The same modified for 'good': %s\n" + "The same modified for 'bad': %s\n", + path, + good, + bad); log_debug("Tries left: %" PRIu64"\n" "Tries done: %" PRIu64"\n", left, done); - fd = open(arg_path, O_DIRECTORY|O_CLOEXEC|O_RDONLY); - if (fd < 0) - return log_error_errno(errno, "Failed to open ESP '%s': %m", arg_path); + STRV_FOREACH(p, arg_path) { + _cleanup_close_ int fd = -1; - if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) { - puts("indeterminate"); - return 0; - } - if (errno != ENOENT) - return log_error_errno(errno, "Failed to check if '%s' exists: %m", path); + fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (fd < 0) { + if (errno == ENOENT) + continue; - if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) { - puts("good"); - return 0; - } - if (errno != ENOENT) - return log_error_errno(errno, "Failed to check if '%s' exists: %m", good); + return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); + } - if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) { - puts("bad"); - return 0; + if (faccessat(fd, skip_slash(path), F_OK, 0) >= 0) { + puts("indeterminate"); + return 0; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", path); + + if (faccessat(fd, skip_slash(good), F_OK, 0) >= 0) { + puts("good"); + return 0; + } + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", good); + + if (faccessat(fd, skip_slash(bad), F_OK, 0) >= 0) { + puts("bad"); + return 0; + } + if (errno != ENOENT) + return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad); + + /* We didn't find any of the three? If so, let's try the next directory, before we give up. */ } - if (errno != ENOENT) - return log_error_errno(errno, "Failed to check if '%s' exists: %m", bad); - return log_error_errno(errno, "Couldn't determine boot state: %m"); + return log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Couldn't determine boot state: %m"); } static int verb_set(int argc, char *argv[], void *userdata) { _cleanup_free_ char *path = NULL, *prefix = NULL, *suffix = NULL, *good = NULL, *bad = NULL, *parent = NULL; const char *target, *source1, *source2; - _cleanup_close_ int fd = -1; uint64_t done; + char **p; int r; r = acquire_boot_count_path(&path, &prefix, NULL, &done, &suffix); @@ -360,7 +392,7 @@ static int verb_set(int argc, char *argv[], void *userdata) { if (r < 0) return r; - r = acquire_esp(); + r = acquire_path(); if (r < 0) return r; @@ -372,10 +404,6 @@ static int verb_set(int argc, char *argv[], void *userdata) { if (r < 0) return log_oom(); - fd = open(arg_path, O_DIRECTORY|O_CLOEXEC|O_RDONLY); - if (fd < 0) - return log_error_errno(errno, "Failed to open ESP '%s': %m", arg_path); - /* Figure out what rename to what */ if (streq(argv[0], "good")) { target = good; @@ -392,45 +420,58 @@ static int verb_set(int argc, char *argv[], void *userdata) { source2 = bad; } - r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target)); - if (r == -EEXIST) - goto exists; - else if (r == -ENOENT) { + STRV_FOREACH(p, arg_path) { + _cleanup_close_ int fd = -1; + + fd = open(*p, O_DIRECTORY|O_CLOEXEC|O_RDONLY); + if (fd < 0) + return log_error_errno(errno, "Failed to open $BOOT partition '%s': %m", *p); - r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target)); + r = rename_noreplace(fd, skip_slash(source1), fd, skip_slash(target)); if (r == -EEXIST) goto exists; else if (r == -ENOENT) { - if (access(target, F_OK) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ + r = rename_noreplace(fd, skip_slash(source2), fd, skip_slash(target)); + if (r == -EEXIST) goto exists; + else if (r == -ENOENT) { + + if (faccessat(fd, skip_slash(target), F_OK, 0) >= 0) /* Hmm, if we can't find either source file, maybe the destination already exists? */ + goto exists; + + if (errno != ENOENT) + return log_error_errno(errno, "Failed to determine if %s already exists: %m", target); + + /* We found none of the snippets here, try the next directory */ + continue; + } else if (r < 0) + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target); + else + log_debug("Successfully renamed '%s' to '%s'.", source2, target); - return log_error_errno(r, "Can't find boot counter source file for '%s': %m", target); } else if (r < 0) - return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source2, target); + return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target); else - log_debug("Successfully renamed '%s' to '%s'.", source2, target); - - } else if (r < 0) - return log_error_errno(r, "Failed to rename '%s' to '%s': %m", source1, target); - else - log_debug("Successfully renamed '%s' to '%s'.", source1, target); + log_debug("Successfully renamed '%s' to '%s'.", source1, target); - /* First, fsync() the directory these files are located in */ - parent = dirname_malloc(path); - if (!parent) - return log_oom(); + /* First, fsync() the directory these files are located in */ + parent = dirname_malloc(target); + if (!parent) + return log_oom(); - r = fsync_path_at(fd, skip_slash(parent)); - if (r < 0) - log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m"); + r = fsync_path_at(fd, skip_slash(parent)); + if (r < 0) + log_debug_errno(errno, "Failed to synchronize image directory, ignoring: %m"); - /* Secondly, syncfs() the whole file system these files are located in */ - if (syncfs(fd) < 0) - log_debug_errno(errno, "Failed to synchronize ESP, ignoring: %m"); + /* Secondly, syncfs() the whole file system these files are located in */ + if (syncfs(fd) < 0) + log_debug_errno(errno, "Failed to synchronize $BOOT partition, ignoring: %m"); - log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done); + log_info("Marked boot as '%s'. (Boot attempt counter is at %" PRIu64".)", argv[0], done); + } + log_error_errno(SYNTHETIC_ERRNO(EBUSY), "Can't find boot counter source file for '%s': %m", target); return 1; exists: diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index 323806a534..1e0d115fe3 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -45,12 +45,20 @@ #include "verbs.h" #include "virt.h" -static char *arg_path = NULL; -static bool arg_print_path = false; +static char *arg_esp_path = NULL; +static char *arg_xbootldr_path = NULL; +static bool arg_print_esp_path = false; +static bool arg_print_dollar_boot_path = false; static bool arg_touch_variables = true; static PagerFlags arg_pager_flags = 0; -STATIC_DESTRUCTOR_REGISTER(arg_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); +STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); + +static const char *arg_dollar_boot_path(void) { + /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ + return arg_xbootldr_path ?: arg_esp_path; +} static int acquire_esp( bool unprivileged_mode, @@ -62,24 +70,44 @@ static int acquire_esp( char *np; int r; - /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on its own, - * except for ENOKEY (which is good, we want to show our own message in that case, suggesting use of --path=) - * and EACCESS (only when we request unprivileged mode; in this case we simply eat up the error here, so that - * --list and --status work too, without noise about this). */ + /* Find the ESP, and log about errors. Note that find_esp_and_warn() will log in all error cases on + * its own, except for ENOKEY (which is good, we want to show our own message in that case, + * suggesting use of --esp-path=) and EACCESS (only when we request unprivileged mode; in this case + * we simply eat up the error here, so that --list and --status work too, without noise about + * this). */ - r = find_esp_and_warn(arg_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); + r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid); if (r == -ENOKEY) return log_error_errno(r, "Couldn't find EFI system partition. It is recommended to mount it to /boot or /efi.\n" - "Alternatively, use --path= to specify path to mount point."); + "Alternatively, use --esp-path= to specify path to mount point."); if (r < 0) return r; - free_and_replace(arg_path, np); + free_and_replace(arg_esp_path, np); + log_debug("Using EFI System Partition at %s.", arg_esp_path); - log_debug("Using EFI System Partition at %s.", arg_path); + return 1; +} - return 0; +static int acquire_xbootldr(bool unprivileged_mode, sd_id128_t *ret_uuid) { + char *np; + int r; + + r = find_xbootldr_and_warn(arg_xbootldr_path, unprivileged_mode, &np, ret_uuid); + if (r == -ENOKEY) { + log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + return 0; + } + if (r < 0) + return r; + + free_and_replace(arg_xbootldr_path, np); + log_debug("Using XBOOTLDR partition at %s as $BOOT.", arg_xbootldr_path); + + return 1; } /* search for "#### LoaderInfo: systemd-boot 218 ####" string inside the binary */ @@ -96,6 +124,10 @@ static int get_file_version(int fd, char **v) { if (fstat(fd, &st) < 0) return log_error_errno(errno, "Failed to stat EFI binary: %m"); + r = stat_verify_regular(&st); + if (r < 0) + return log_error_errno(errno, "EFI binary is not a regular file: %m"); + if (st.st_size < 27) { *v = NULL; return 0; @@ -112,8 +144,7 @@ static int get_file_version(int fd, char **v) { e = memmem(s, st.st_size - (s - buf), " ####", 5); if (!e || e - s < 3) { - log_error("Malformed version string."); - r = -EINVAL; + r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Malformed version string."); goto finish; } @@ -131,10 +162,13 @@ finish: } static int enumerate_binaries(const char *esp_path, const char *path, const char *prefix) { - char *p; _cleanup_closedir_ DIR *d = NULL; struct dirent *de; - int r = 0, c = 0; + int c = 0, r; + char *p; + + assert(esp_path); + assert(path); p = strjoina(esp_path, "/", path); d = opendir(p); @@ -146,8 +180,8 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char } FOREACH_DIRENT(de, d, break) { - _cleanup_close_ int fd = -1; _cleanup_free_ char *v = NULL; + _cleanup_close_ int fd = -1; if (!endswith_no_case(de->d_name, ".efi")) continue; @@ -166,6 +200,7 @@ static int enumerate_binaries(const char *esp_path, const char *path, const char printf(" File: %s/%s/%s (%s%s%s)\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name, ansi_highlight(), v, ansi_normal()); else printf(" File: %s/%s/%s\n", special_glyph(SPECIAL_GLYPH_TREE_RIGHT), path, de->d_name); + c++; } @@ -188,20 +223,22 @@ static int status_binaries(const char *esp_path, sd_id128_t partition) { printf("\n"); r = enumerate_binaries(esp_path, "EFI/systemd", NULL); + if (r < 0) + goto finish; if (r == 0) log_info("systemd-boot not installed in ESP."); - else if (r < 0) - return r; r = enumerate_binaries(esp_path, "EFI/BOOT", "boot"); + if (r < 0) + goto finish; if (r == 0) log_info("No default/fallback boot loader installed in ESP."); - else if (r < 0) - return r; - printf("\n"); + r = 0; - return 0; +finish: + printf("\n"); + return r; } static int print_efi_option(uint16_t id, bool in_order) { @@ -316,11 +353,34 @@ static int boot_entry_show(const BootEntry *e, bool show_as_default) { return 0; } -static int status_entries(const char *esp_path, sd_id128_t partition) { +static int status_entries( + const char *esp_path, + sd_id128_t esp_partition_uuid, + const char *xbootldr_path, + sd_id128_t xbootldr_partition_uuid) { + _cleanup_(boot_config_free) BootConfig config = {}; + sd_id128_t dollar_boot_partition_uuid; + const char *dollar_boot_path; int r; - r = boot_entries_load_config(esp_path, &config); + assert(esp_path || xbootldr_path); + + if (xbootldr_path) { + dollar_boot_path = xbootldr_path; + dollar_boot_partition_uuid = xbootldr_partition_uuid; + } else { + dollar_boot_path = esp_path; + dollar_boot_partition_uuid = esp_partition_uuid; + } + + printf("Boot Loader Entries:\n" + " $BOOT: %s", dollar_boot_path); + if (!sd_id128_is_null(dollar_boot_partition_uuid)) + printf(" (/dev/disk/by-partuuid/%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x)", SD_ID128_FORMAT_VAL(dollar_boot_partition_uuid)); + printf("\n\n"); + + r = boot_entries_load_config(esp_path, xbootldr_path, &config); if (r < 0) return r; @@ -386,10 +446,8 @@ static int version_check(int fd_from, const char *from, int fd_to, const char *t "Skipping \"%s\", since it's owned by another boot loader.", to); - if (compare_version(a, b) < 0) { - log_warning("Skipping \"%s\", since a newer boot loader version exists already.", to); - return -ESTALE; - } + if (compare_version(a, b) < 0) + return log_warning_errno(SYNTHETIC_ERRNO(ESTALE), "Skipping \"%s\", since a newer boot loader version exists already.", to); return 0; } @@ -468,20 +526,21 @@ static int mkdir_one(const char *prefix, const char *suffix) { return 0; } -static const char *efi_subdirs[] = { +static const char *const esp_subdirs[] = { "EFI", "EFI/systemd", "EFI/BOOT", "loader", - "loader/entries", + /* Note that "/loader/entries" is not listed here, since it should be placed in $BOOT, which might + * not necessarily be the ESP */ NULL }; -static int create_dirs(const char *esp_path) { - const char **i; +static int create_esp_subdirs(const char *esp_path) { + const char *const *i; int r; - STRV_FOREACH(i, efi_subdirs) { + STRV_FOREACH(i, esp_subdirs) { r = mkdir_one(esp_path, *i); if (r < 0) return r; @@ -520,22 +579,11 @@ static int install_binaries(const char *esp_path, bool force) { _cleanup_closedir_ DIR *d = NULL; int r = 0; - if (force) { - /* Don't create any of these directories when we are - * just updating. When we update we'll drop-in our - * files (unless there are newer ones already), but we - * won't create the directories for them in the first - * place. */ - r = create_dirs(esp_path); - if (r < 0) - return r; - } - d = opendir(BOOTLIBDIR); if (!d) return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); - FOREACH_DIRENT(de, d, break) { + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \""BOOTLIBDIR"\": %m")) { int k; if (!endswith_no_case(de->d_name, ".efi")) @@ -751,18 +799,36 @@ static int rmdir_one(const char *prefix, const char *suffix) { p = strjoina(prefix, "/", suffix); if (rmdir(p) < 0) { - if (!IN_SET(errno, ENOENT, ENOTEMPTY)) - return log_error_errno(errno, "Failed to remove \"%s\": %m", p); + bool ignore = IN_SET(errno, ENOENT, ENOTEMPTY); + + log_full_errno(ignore ? LOG_DEBUG : LOG_ERR, errno, + "Failed to remove directory \"%s\": %m", p); + if (!ignore) + return -errno; } else log_info("Removed \"%s\".", p); return 0; } +static int remove_esp_subdirs(const char *esp_path) { + size_t i; + int r = 0; + + for (i = ELEMENTSOF(esp_subdirs)-1; i > 0; i--) { + int q; + + q = rmdir_one(esp_path, esp_subdirs[i-1]); + if (q < 0 && r >= 0) + r = q; + } + + return r; +} + static int remove_binaries(const char *esp_path) { char *p; int r, q; - unsigned i; p = strjoina(esp_path, "/EFI/systemd"); r = rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL); @@ -771,15 +837,31 @@ static int remove_binaries(const char *esp_path) { if (q < 0 && r == 0) r = q; - for (i = ELEMENTSOF(efi_subdirs)-1; i > 0; i--) { - q = rmdir_one(esp_path, efi_subdirs[i-1]); - if (q < 0 && r == 0) - r = q; - } - return r; } +static int remove_loader_config(const char *esp_path) { + const char *p; + + assert(esp_path); + + p = strjoina(esp_path, "/loader/loader.conf"); + if (unlink(p) < 0) { + log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, "Failed to unlink file \"%s\": %m", p); + if (errno != ENOENT) + return -errno; + } else + log_info("Removed \"%s\".", p); + + return 0; +} + +static int remove_entries_directory(const char *dollar_boot_path) { + assert(dollar_boot_path); + + return rmdir_one(dollar_boot_path, "/loader/entries"); +} + static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; @@ -802,7 +884,6 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { } static int install_loader_config(const char *esp_path) { - char machine_string[SD_ID128_STRING_MAX]; _cleanup_(unlink_and_freep) char *t = NULL; _cleanup_fclose_ FILE *f = NULL; @@ -810,15 +891,14 @@ static int install_loader_config(const char *esp_path) { const char *p; int r, fd; - r = sd_id128_get_machine(&machine_id); - if (r < 0) - return log_error_errno(r, "Failed to get machine id: %m"); - p = strjoina(esp_path, "/loader/loader.conf"); - if (access(p, F_OK) >= 0) /* Silently skip creation if the file already exists (early check) */ return 0; + r = sd_id128_get_machine(&machine_id); + if (r < 0) + return log_error_errno(r, "Failed to get machine id: %m"); + fd = open_tmpfile_linkable(p, O_WRONLY|O_CLOEXEC, &t); if (fd < 0) return log_error_errno(fd, "Failed to open \"%s\" for writing: %m", p); @@ -844,10 +924,15 @@ static int install_loader_config(const char *esp_path) { return log_error_errno(r, "Failed to move \"%s\" into place: %m", p); t = mfree(t); - return 1; } +static int install_entries_directory(const char *dollar_boot_path) { + assert(dollar_boot_path); + + return mkdir_one(dollar_boot_path, "/loader/entries"); +} + static int help(int argc, char *argv[], void *userdata) { _cleanup_free_ char *link = NULL; int r; @@ -858,21 +943,23 @@ static int help(int argc, char *argv[], void *userdata) { printf("%s [COMMAND] [OPTIONS...]\n\n" "Install, update or remove the systemd-boot EFI boot manager.\n\n" - " -h --help Show this help\n" - " --version Print version\n" - " --path=PATH Path to the EFI System Partition (ESP)\n" - " -p --print-path Print path to the EFI partition\n" - " --no-variables Don't touch EFI variables\n" - " --no-pager Do not pipe output into a pager\n" + " -h --help Show this help\n" + " --version Print version\n" + " --esp-path=PATH Path to the EFI System Partition (ESP)\n" + " --boot-path=PATH Path to the $BOOT partition\n" + " -p --print-esp-path Print path to the EFI System Partition\n" + " --print-boot-path Print path to the $BOOT partition\n" + " --no-variables Don't touch EFI variables\n" + " --no-pager Do not pipe output into a pager\n" "\nBoot Loader Commands:\n" - " status Show status of installed systemd-boot and EFI variables\n" - " install Install systemd-boot to the ESP and EFI variables\n" - " update Update systemd-boot in the ESP and EFI variables\n" - " remove Remove systemd-boot from the ESP and EFI variables\n" + " status Show status of installed systemd-boot and EFI variables\n" + " install Install systemd-boot to the ESP and EFI variables\n" + " update Update systemd-boot in the ESP and EFI variables\n" + " remove Remove systemd-boot from the ESP and EFI variables\n" "\nBoot Loader Entries Commands:\n" - " list List boot loader entries\n" - " set-default ID Set default boot loader entry\n" - " set-oneshot ID Set default boot loader entry, for next boot only\n" + " list List boot loader entries\n" + " set-default ID Set default boot loader entry\n" + " set-oneshot ID Set default boot loader entry, for next boot only\n" "\nSee the %s for details.\n" , program_invocation_short_name , link); @@ -882,19 +969,25 @@ static int help(int argc, char *argv[], void *userdata) { static int parse_argv(int argc, char *argv[]) { enum { - ARG_PATH = 0x100, + ARG_ESP_PATH = 0x100, + ARG_BOOT_PATH, + ARG_PRINT_BOOT_PATH, ARG_VERSION, ARG_NO_VARIABLES, ARG_NO_PAGER, }; static const struct option options[] = { - { "help", no_argument, NULL, 'h' }, - { "version", no_argument, NULL, ARG_VERSION }, - { "path", required_argument, NULL, ARG_PATH }, - { "print-path", no_argument, NULL, 'p' }, - { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, - { "no-pager", no_argument, NULL, ARG_NO_PAGER }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "esp-path", required_argument, NULL, ARG_ESP_PATH }, + { "path", required_argument, NULL, ARG_ESP_PATH }, /* Compatibility alias */ + { "boot-path", required_argument, NULL, ARG_BOOT_PATH }, + { "print-esp-path", no_argument, NULL, 'p' }, + { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ + { "print-boot-path", no_argument, NULL, ARG_PRINT_BOOT_PATH }, + { "no-variables", no_argument, NULL, ARG_NO_VARIABLES }, + { "no-pager", no_argument, NULL, ARG_NO_PAGER }, {} }; @@ -913,14 +1006,24 @@ static int parse_argv(int argc, char *argv[]) { case ARG_VERSION: return version(); - case ARG_PATH: - r = free_and_strdup(&arg_path, optarg); + case ARG_ESP_PATH: + r = free_and_strdup(&arg_esp_path, optarg); + if (r < 0) + return log_oom(); + break; + + case ARG_BOOT_PATH: + r = free_and_strdup(&arg_xbootldr_path, optarg); if (r < 0) return log_oom(); break; case 'p': - arg_print_path = true; + arg_print_esp_path = true; + break; + + case ARG_PRINT_BOOT_PATH: + arg_print_dollar_boot_path = true; break; case ARG_NO_VARIABLES: @@ -950,23 +1053,33 @@ static void read_loader_efi_var(const char *name, char **var) { } static int verb_status(int argc, char *argv[], void *userdata) { - - sd_id128_t uuid = SD_ID128_NULL; + sd_id128_t esp_uuid = SD_ID128_NULL, xbootldr_uuid = SD_ID128_NULL; int r, k; - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &uuid); - - if (arg_print_path) { + r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &esp_uuid); + if (arg_print_esp_path) { if (r == -EACCES) /* If we couldn't acquire the ESP path, log about access errors (which is the only * error the find_esp_and_warn() won't log on its own) */ - return log_error_errno(r, "Failed to determine ESP: %m"); + return log_error_errno(r, "Failed to determine ESP location: %m"); if (r < 0) return r; - puts(arg_path); - return 0; + puts(arg_esp_path); } + r = acquire_xbootldr(geteuid() != 0, &xbootldr_uuid); + if (arg_print_dollar_boot_path) { + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR location: %m"); + if (r < 0) + return r; + + puts(arg_dollar_boot_path()); + } + + if (arg_print_esp_path || arg_print_dollar_boot_path) + return 0; + r = 0; /* If we couldn't determine the path, then don't consider that a problem from here on, just show what we * can show */ @@ -1037,8 +1150,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { } else printf("System:\n Not booted with EFI\n\n"); - if (arg_path) { - k = status_binaries(arg_path, uuid); + if (arg_esp_path) { + k = status_binaries(arg_esp_path, esp_uuid); if (k < 0) r = k; } @@ -1049,8 +1162,8 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = k; } - if (arg_path) { - k = status_entries(arg_path, uuid); + if (arg_esp_path || arg_xbootldr_path) { + k = status_entries(arg_esp_path, esp_uuid, arg_xbootldr_path, xbootldr_uuid); if (k < 0) r = k; } @@ -1061,20 +1174,25 @@ static int verb_status(int argc, char *argv[], void *userdata) { static int verb_list(int argc, char *argv[], void *userdata) { _cleanup_(boot_config_free) BootConfig config = {}; _cleanup_free_ char **found_by_loader = NULL; - sd_id128_t uuid = SD_ID128_NULL; int r; /* If we lack privileges we invoke find_esp_and_warn() in "unprivileged mode" here, which does two things: turn * off logging about access errors and turn off potentially privileged device probing. Here we're interested in * the latter but not the former, hence request the mode, and log about EACCES. */ - r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, &uuid); + r = acquire_esp(geteuid() != 0, NULL, NULL, NULL, NULL); if (r == -EACCES) /* We really need the ESP path for this call, hence also log about access errors */ return log_error_errno(r, "Failed to determine ESP: %m"); if (r < 0) return r; - r = boot_entries_load_config(arg_path, &config); + r = acquire_xbootldr(geteuid() != 0, NULL); + if (r == -EACCES) + return log_error_errno(r, "Failed to determine XBOOTLDR partition: %m"); + if (r < 0) + return r; + + r = boot_entries_load_config(arg_esp_path, arg_xbootldr_path, &config); if (r < 0) return r; @@ -1114,24 +1232,25 @@ static int verb_list(int argc, char *argv[], void *userdata) { return 0; } -static int sync_esp(void) { - _cleanup_close_ int fd = -1; +static int sync_everything(void) { + int ret = 0, k; - if (!arg_path) - return 0; - - fd = open(arg_path, O_CLOEXEC|O_DIRECTORY|O_RDONLY); - if (fd < 0) - return log_error_errno(errno, "Couldn't open ESP '%s' for synchronization: %m", arg_path); + if (arg_esp_path) { + k = syncfs_path(AT_FDCWD, arg_esp_path); + if (k < 0) + ret = log_error_errno(k, "Failed to synchronize the ESP '%s': %m", arg_esp_path); + } - if (syncfs(fd) < 0) - return log_error_errno(errno, "Failed to synchronize the ESP '%s': %m", arg_path); + if (arg_xbootldr_path) { + k = syncfs_path(AT_FDCWD, arg_xbootldr_path); + if (k < 0) + ret = log_error_errno(k, "Failed to synchronize $BOOT '%s': %m", arg_xbootldr_path); + } - return 1; + return ret; } static int verb_install(int argc, char *argv[], void *userdata) { - sd_id128_t uuid = SD_ID128_NULL; uint64_t pstart = 0, psize = 0; uint32_t part = 0; @@ -1142,24 +1261,41 @@ static int verb_install(int argc, char *argv[], void *userdata) { if (r < 0) return r; + r = acquire_xbootldr(false, NULL); + if (r < 0) + return r; + install = streq(argv[0], "install"); RUN_WITH_UMASK(0002) { - r = install_binaries(arg_path, install); + if (install) { + /* Don't create any of these directories when we are just updating. When we update + * we'll drop-in our files (unless there are newer ones already), but we won't create + * the directories for them in the first place. */ + r = create_esp_subdirs(arg_esp_path); + if (r < 0) + return r; + } + + r = install_binaries(arg_esp_path, install); if (r < 0) return r; if (install) { - r = install_loader_config(arg_path); + r = install_loader_config(arg_esp_path); + if (r < 0) + return r; + + r = install_entries_directory(arg_dollar_boot_path()); if (r < 0) return r; } } - (void) sync_esp(); + (void) sync_everything(); if (arg_touch_variables) - r = install_variables(arg_path, + r = install_variables(arg_esp_path, part, pstart, psize, uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", install); @@ -1169,21 +1305,35 @@ static int verb_install(int argc, char *argv[], void *userdata) { static int verb_remove(int argc, char *argv[], void *userdata) { sd_id128_t uuid = SD_ID128_NULL; - int r; + int r, q; r = acquire_esp(false, NULL, NULL, NULL, &uuid); if (r < 0) return r; - r = remove_binaries(arg_path); + r = acquire_xbootldr(false, NULL); + if (r < 0) + return r; - (void) sync_esp(); + r = remove_binaries(arg_esp_path); - if (arg_touch_variables) { - int q; + q = remove_loader_config(arg_esp_path); + if (q < 0 && r >= 0) + r = q; + + q = remove_entries_directory(arg_dollar_boot_path()); + if (q < 0 && r >= 0) + r = q; + q = remove_esp_subdirs(arg_esp_path); + if (q < 0 && r >= 0) + r = q; + + (void) sync_everything(); + + if (arg_touch_variables) { q = remove_variables(uuid, "/EFI/systemd/systemd-boot" EFI_MACHINE_TYPE_NAME ".efi", true); - if (q < 0 && r == 0) + if (q < 0 && r >= 0) r = q; } diff --git a/src/boot/efi/boot.c b/src/boot/efi/boot.c index 9bf6895831..c7ba088761 100644 --- a/src/boot/efi/boot.c +++ b/src/boot/efi/boot.c @@ -1,9 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1+ */ #include <efi.h> +#include <efigpt.h> #include <efilib.h> #include "console.h" +#include "crc32.h" #include "disk.h" #include "graphics.h" #include "linux.h" @@ -1316,7 +1318,7 @@ static VOID config_entry_add_from_file( entry->loader = stra_to_path(value); /* do not add an entry for ourselves */ - if (StriCmp(entry->loader, loaded_image_path) == 0) { + if (loaded_image_path && StriCmp(entry->loader, loaded_image_path) == 0) { entry->type = LOADER_UNDEFINED; break; } @@ -1836,7 +1838,7 @@ static VOID config_entry_add_osx(Config *config) { static VOID config_entry_add_linux( Config *config, - EFI_LOADED_IMAGE *loaded_image, + EFI_HANDLE *device, EFI_FILE *root_dir) { EFI_FILE_HANDLE linux_dir; @@ -1926,7 +1928,7 @@ static VOID config_entry_add_linux( conf = PoolPrint(L"%s-%s", os_id, os_version ? : os_build); path = PoolPrint(L"\\EFI\\Linux\\%s", f->FileName); - entry = config_entry_add_loader(config, loaded_image->DeviceHandle, LOADER_LINUX, conf, 'l', os_name, path); + entry = config_entry_add_loader(config, device, LOADER_LINUX, conf, 'l', os_name, path); FreePool(content); content = NULL; @@ -1955,6 +1957,193 @@ static VOID config_entry_add_linux( uefi_call_wrapper(linux_dir->Close, 1, linux_dir); } +/* Note that this is in GUID format, i.e. the first 32bit, and the following pair of 16bit are byteswapped. */ +static const UINT8 xbootldr_guid[16] = { + 0xff, 0xc2, 0x13, 0xbc, 0xe6, 0x59, 0x62, 0x42, 0xa3, 0x52, 0xb2, 0x75, 0xfd, 0x6f, 0x71, 0x72 +}; + +EFI_DEVICE_PATH *path_parent(EFI_DEVICE_PATH *path, EFI_DEVICE_PATH *node) { + EFI_DEVICE_PATH *parent; + UINTN len; + + len = (UINT8*) NextDevicePathNode(node) - (UINT8*) path; + parent = (EFI_DEVICE_PATH*) AllocatePool(len + sizeof(EFI_DEVICE_PATH)); + CopyMem(parent, path, len); + CopyMem((UINT8*) parent + len, EndDevicePath, sizeof(EFI_DEVICE_PATH)); + + return parent; +} + +static VOID config_load_xbootldr( + Config *config, + EFI_HANDLE *device) { + + EFI_DEVICE_PATH *partition_path, *node, *disk_path, *copy; + UINT32 found_partition_number = (UINT32) -1; + UINT64 found_partition_start = (UINT64) -1; + UINT64 found_partition_size = (UINT64) -1; + UINT8 found_partition_signature[16] = {}; + EFI_HANDLE new_device; + EFI_FILE *root_dir; + EFI_STATUS r; + + partition_path = DevicePathFromHandle(device); + if (!partition_path) + return; + + for (node = partition_path; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) { + EFI_HANDLE disk_handle; + EFI_BLOCK_IO *block_io; + EFI_DEVICE_PATH *p; + UINTN nr; + + /* First, Let's look for the SCSI/SATA/USB/… device path node, i.e. one above the media + * devices */ + if (DevicePathType(node) != MESSAGING_DEVICE_PATH) + continue; + + /* Determine the device path one level up */ + disk_path = path_parent(partition_path, node); + p = disk_path; + r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, &p, &disk_handle); + if (EFI_ERROR(r)) + continue; + + r = uefi_call_wrapper(BS->HandleProtocol, 3, disk_handle, &BlockIoProtocol, (VOID **)&block_io); + if (EFI_ERROR(r)) + continue; + + /* Filter out some block devices early. (We only care about block devices that aren't + * partitions themselves — we look for GPT partition tables to parse after all —, and only + * those which contain a medium and have at least 2 blocks.) */ + if (block_io->Media->LogicalPartition || + !block_io->Media->MediaPresent || + block_io->Media->LastBlock <= 1) + continue; + + /* Try both copies of the GPT header, in case one is corrupted */ + for (nr = 0; nr < 2; nr++) { + _cleanup_freepool_ EFI_PARTITION_ENTRY* entries = NULL; + union { + EFI_PARTITION_TABLE_HEADER gpt_header; + uint8_t space[((sizeof(EFI_PARTITION_TABLE_HEADER) + 511) / 512) * 512]; + } gpt_header_buffer; + UINT64 where; + UINTN i, sz; + UINT32 c; + + if (nr == 0) + where = 1; /* Read the first copy at LBA 1 */ + else + where = block_io->Media->LastBlock; /* Read the second copy at the very last LBA of this block device */ + + /* Read the GPT header */ + r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, where, sizeof(gpt_header_buffer), &gpt_header_buffer); + if (EFI_ERROR(r)) + continue; + + /* Some superficial validation of the GPT header */ + if (CompareMem(&gpt_header_buffer.gpt_header.Header.Signature, "EFI PART", sizeof(gpt_header_buffer.gpt_header.Header.Signature)) != 0) + continue; + + if (gpt_header_buffer.gpt_header.Header.HeaderSize < 92 || gpt_header_buffer.gpt_header.Header.HeaderSize > 512) + continue; + + if (gpt_header_buffer.gpt_header.Header.Revision != 0x00010000U) + continue; + + /* Calculate CRC check */ + c = ~crc32_exclude_offset((UINT32) -1, (const UINT8*) &gpt_header_buffer, gpt_header_buffer.gpt_header.Header.HeaderSize, + OFFSETOF(EFI_PARTITION_TABLE_HEADER, Header.CRC32), sizeof(gpt_header_buffer.gpt_header.Header.CRC32)); + if (c != gpt_header_buffer.gpt_header.Header.CRC32) + continue; + + if (gpt_header_buffer.gpt_header.MyLBA != where) + continue; + + if (gpt_header_buffer.gpt_header.SizeOfPartitionEntry < sizeof(EFI_PARTITION_ENTRY)) + continue; + + if (gpt_header_buffer.gpt_header.NumberOfPartitionEntries <= 0 || gpt_header_buffer.gpt_header.NumberOfPartitionEntries > 1024) + continue; + + /* Now load the GPT entry table */ + sz = ((gpt_header_buffer.gpt_header.SizeOfPartitionEntry * gpt_header_buffer.gpt_header.NumberOfPartitionEntries + 511) / 512) * 512; + entries = AllocatePool(sz); + + r = uefi_call_wrapper(block_io->ReadBlocks, 5, block_io, block_io->Media->MediaId, gpt_header_buffer.gpt_header.PartitionEntryLBA, sz, entries); + if (EFI_ERROR(r)) + continue; + + /* Calculate CRC of entries array, too */ + c = ~crc32((UINT32) -1, entries, sz); + if (c != gpt_header_buffer.gpt_header.PartitionEntryArrayCRC32) + continue; + + for (i = 0; i < gpt_header_buffer.gpt_header.NumberOfPartitionEntries; i++) { + EFI_PARTITION_ENTRY *entry; + + entry = (EFI_PARTITION_ENTRY*) ((UINT8*) entries + gpt_header_buffer.gpt_header.SizeOfPartitionEntry * i); + + if (CompareMem(&entry->PartitionTypeGUID, xbootldr_guid, 16) == 0) { + UINT64 end; + + /* Let's use memcpy(), in case the structs are not aligned (they really should be though) */ + CopyMem(&found_partition_start, &entry->StartingLBA, sizeof(found_partition_start)); + CopyMem(&end, &entry->EndingLBA, sizeof(end)); + + if (end < found_partition_start) /* Bogus? */ + continue; + + found_partition_size = end - found_partition_start + 1; + CopyMem(found_partition_signature, &entry->UniquePartitionGUID, sizeof(found_partition_signature)); + + found_partition_number = i + 1; + goto found; + } + } + + break; /* This GPT was fully valid, but we didn't find what we are looking for. This + * means there's no reason to check the second copy of the GPT header */ + } + } + + return; /* Not found */ + +found: + copy = DuplicateDevicePath(partition_path); + + /* Patch in the data we found */ + for (node = copy; !IsDevicePathEnd(node); node = NextDevicePathNode(node)) { + HARDDRIVE_DEVICE_PATH *hd; + + if (DevicePathType(node) != MEDIA_DEVICE_PATH) + continue; + + if (DevicePathSubType(node) != MEDIA_HARDDRIVE_DP) + continue; + + hd = (HARDDRIVE_DEVICE_PATH*) node; + hd->PartitionNumber = found_partition_number; + hd->PartitionStart = found_partition_start; + hd->PartitionSize = found_partition_size; + CopyMem(hd->Signature, found_partition_signature, sizeof(hd->Signature)); + hd->MBRType = MBR_TYPE_EFI_PARTITION_TABLE_HEADER; + hd->SignatureType = SIGNATURE_TYPE_GUID; + } + + r = uefi_call_wrapper(BS->LocateDevicePath, 3, &BlockIoProtocol, ©, &new_device); + if (EFI_ERROR(r)) + return; + + root_dir = LibOpenRoot(new_device); + if (!root_dir) + return; + + config_entry_add_linux(config, new_device, root_dir); + config_load_entries(config, new_device, root_dir, NULL); +} + static EFI_STATUS image_start( EFI_HANDLE parent_image, const Config *config, @@ -2121,7 +2310,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { root_dir = LibOpenRoot(loaded_image->DeviceHandle); if (!root_dir) { - Print(L"Unable to open root directory: %r ", err); + Print(L"Unable to open root directory."); uefi_call_wrapper(BS->Stall, 1, 3 * 1000 * 1000); return EFI_LOAD_ERROR; } @@ -2142,11 +2331,14 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { config_load_defaults(&config, root_dir); /* scan /EFI/Linux/ directory */ - config_entry_add_linux(&config, loaded_image, root_dir); + config_entry_add_linux(&config, loaded_image->DeviceHandle, root_dir); /* scan /loader/entries/\*.conf files */ config_load_entries(&config, loaded_image->DeviceHandle, root_dir, loaded_image_path); + /* Similar, but on any XBOOTLDR partition */ + config_load_xbootldr(&config, loaded_image->DeviceHandle); + /* sort entries after version number */ config_sort_entries(&config); diff --git a/src/boot/efi/crc32.c b/src/boot/efi/crc32.c new file mode 100644 index 0000000000..46b9aeea90 --- /dev/null +++ b/src/boot/efi/crc32.c @@ -0,0 +1,142 @@ +/* This is copied from util-linux, which in turn copied in the version from Gary S. Brown */ + +/* + * COPYRIGHT (C) 1986 Gary S. Brown. You may use this program, or + * code or tables extracted from it, as desired without restriction. + * + * First, the polynomial itself and its table of feedback terms. The + * polynomial is + * X^32+X^26+X^23+X^22+X^16+X^12+X^11+X^10+X^8+X^7+X^5+X^4+X^2+X^1+X^0 + * + * Note that we take it "backwards" and put the highest-order term in + * the lowest-order bit. The X^32 term is "implied"; the LSB is the + * X^31 term, etc. The X^0 term (usually shown as "+1") results in + * the MSB being 1. + * + * Note that the usual hardware shift register implementation, which + * is what we're using (we're merely optimizing it by doing eight-bit + * chunks at a time) shifts bits into the lowest-order term. In our + * implementation, that means shifting towards the right. Why do we + * do it this way? Because the calculated CRC must be transmitted in + * order from highest-order term to lowest-order term. UARTs transmit + * characters in order from LSB to MSB. By storing the CRC this way, + * we hand it to the UART in the order low-byte to high-byte; the UART + * sends each low-bit to high-bit; and the result is transmission bit + * by bit from highest- to lowest-order term without requiring any bit + * shuffling on our part. Reception works similarly. + * + * The feedback terms table consists of 256, 32-bit entries. Notes + * + * The table can be generated at runtime if desired; code to do so + * is shown later. It might not be obvious, but the feedback + * terms simply represent the results of eight shift/xor opera- + * tions for all combinations of data and CRC register values. + * + * The values must be right-shifted by eight bits by the "updcrc" + * logic; the shift must be unsigned (bring in zeroes). On some + * hardware you could probably optimize the shift in assembler by + * using byte-swap instructions. + * polynomial $edb88320 + * + */ + +#include "crc32.h" + +static const UINT32 crc32_tab[] = { + 0x00000000L, 0x77073096L, 0xee0e612cL, 0x990951baL, 0x076dc419L, + 0x706af48fL, 0xe963a535L, 0x9e6495a3L, 0x0edb8832L, 0x79dcb8a4L, + 0xe0d5e91eL, 0x97d2d988L, 0x09b64c2bL, 0x7eb17cbdL, 0xe7b82d07L, + 0x90bf1d91L, 0x1db71064L, 0x6ab020f2L, 0xf3b97148L, 0x84be41deL, + 0x1adad47dL, 0x6ddde4ebL, 0xf4d4b551L, 0x83d385c7L, 0x136c9856L, + 0x646ba8c0L, 0xfd62f97aL, 0x8a65c9ecL, 0x14015c4fL, 0x63066cd9L, + 0xfa0f3d63L, 0x8d080df5L, 0x3b6e20c8L, 0x4c69105eL, 0xd56041e4L, + 0xa2677172L, 0x3c03e4d1L, 0x4b04d447L, 0xd20d85fdL, 0xa50ab56bL, + 0x35b5a8faL, 0x42b2986cL, 0xdbbbc9d6L, 0xacbcf940L, 0x32d86ce3L, + 0x45df5c75L, 0xdcd60dcfL, 0xabd13d59L, 0x26d930acL, 0x51de003aL, + 0xc8d75180L, 0xbfd06116L, 0x21b4f4b5L, 0x56b3c423L, 0xcfba9599L, + 0xb8bda50fL, 0x2802b89eL, 0x5f058808L, 0xc60cd9b2L, 0xb10be924L, + 0x2f6f7c87L, 0x58684c11L, 0xc1611dabL, 0xb6662d3dL, 0x76dc4190L, + 0x01db7106L, 0x98d220bcL, 0xefd5102aL, 0x71b18589L, 0x06b6b51fL, + 0x9fbfe4a5L, 0xe8b8d433L, 0x7807c9a2L, 0x0f00f934L, 0x9609a88eL, + 0xe10e9818L, 0x7f6a0dbbL, 0x086d3d2dL, 0x91646c97L, 0xe6635c01L, + 0x6b6b51f4L, 0x1c6c6162L, 0x856530d8L, 0xf262004eL, 0x6c0695edL, + 0x1b01a57bL, 0x8208f4c1L, 0xf50fc457L, 0x65b0d9c6L, 0x12b7e950L, + 0x8bbeb8eaL, 0xfcb9887cL, 0x62dd1ddfL, 0x15da2d49L, 0x8cd37cf3L, + 0xfbd44c65L, 0x4db26158L, 0x3ab551ceL, 0xa3bc0074L, 0xd4bb30e2L, + 0x4adfa541L, 0x3dd895d7L, 0xa4d1c46dL, 0xd3d6f4fbL, 0x4369e96aL, + 0x346ed9fcL, 0xad678846L, 0xda60b8d0L, 0x44042d73L, 0x33031de5L, + 0xaa0a4c5fL, 0xdd0d7cc9L, 0x5005713cL, 0x270241aaL, 0xbe0b1010L, + 0xc90c2086L, 0x5768b525L, 0x206f85b3L, 0xb966d409L, 0xce61e49fL, + 0x5edef90eL, 0x29d9c998L, 0xb0d09822L, 0xc7d7a8b4L, 0x59b33d17L, + 0x2eb40d81L, 0xb7bd5c3bL, 0xc0ba6cadL, 0xedb88320L, 0x9abfb3b6L, + 0x03b6e20cL, 0x74b1d29aL, 0xead54739L, 0x9dd277afL, 0x04db2615L, + 0x73dc1683L, 0xe3630b12L, 0x94643b84L, 0x0d6d6a3eL, 0x7a6a5aa8L, + 0xe40ecf0bL, 0x9309ff9dL, 0x0a00ae27L, 0x7d079eb1L, 0xf00f9344L, + 0x8708a3d2L, 0x1e01f268L, 0x6906c2feL, 0xf762575dL, 0x806567cbL, + 0x196c3671L, 0x6e6b06e7L, 0xfed41b76L, 0x89d32be0L, 0x10da7a5aL, + 0x67dd4accL, 0xf9b9df6fL, 0x8ebeeff9L, 0x17b7be43L, 0x60b08ed5L, + 0xd6d6a3e8L, 0xa1d1937eL, 0x38d8c2c4L, 0x4fdff252L, 0xd1bb67f1L, + 0xa6bc5767L, 0x3fb506ddL, 0x48b2364bL, 0xd80d2bdaL, 0xaf0a1b4cL, + 0x36034af6L, 0x41047a60L, 0xdf60efc3L, 0xa867df55L, 0x316e8eefL, + 0x4669be79L, 0xcb61b38cL, 0xbc66831aL, 0x256fd2a0L, 0x5268e236L, + 0xcc0c7795L, 0xbb0b4703L, 0x220216b9L, 0x5505262fL, 0xc5ba3bbeL, + 0xb2bd0b28L, 0x2bb45a92L, 0x5cb36a04L, 0xc2d7ffa7L, 0xb5d0cf31L, + 0x2cd99e8bL, 0x5bdeae1dL, 0x9b64c2b0L, 0xec63f226L, 0x756aa39cL, + 0x026d930aL, 0x9c0906a9L, 0xeb0e363fL, 0x72076785L, 0x05005713L, + 0x95bf4a82L, 0xe2b87a14L, 0x7bb12baeL, 0x0cb61b38L, 0x92d28e9bL, + 0xe5d5be0dL, 0x7cdcefb7L, 0x0bdbdf21L, 0x86d3d2d4L, 0xf1d4e242L, + 0x68ddb3f8L, 0x1fda836eL, 0x81be16cdL, 0xf6b9265bL, 0x6fb077e1L, + 0x18b74777L, 0x88085ae6L, 0xff0f6a70L, 0x66063bcaL, 0x11010b5cL, + 0x8f659effL, 0xf862ae69L, 0x616bffd3L, 0x166ccf45L, 0xa00ae278L, + 0xd70dd2eeL, 0x4e048354L, 0x3903b3c2L, 0xa7672661L, 0xd06016f7L, + 0x4969474dL, 0x3e6e77dbL, 0xaed16a4aL, 0xd9d65adcL, 0x40df0b66L, + 0x37d83bf0L, 0xa9bcae53L, 0xdebb9ec5L, 0x47b2cf7fL, 0x30b5ffe9L, + 0xbdbdf21cL, 0xcabac28aL, 0x53b39330L, 0x24b4a3a6L, 0xbad03605L, + 0xcdd70693L, 0x54de5729L, 0x23d967bfL, 0xb3667a2eL, 0xc4614ab8L, + 0x5d681b02L, 0x2a6f2b94L, 0xb40bbe37L, 0xc30c8ea1L, 0x5a05df1bL, + 0x2d02ef8dL +}; + +static inline UINT32 crc32_add_char(UINT32 crc, UINT8 c) { + return crc32_tab[(crc ^ c) & 0xff] ^ (crc >> 8); +} + +/* + * This a generic crc32() function, it takes seed as an argument, + * and does __not__ xor at the end. Then individual users can do + * whatever they need. + */ +UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len) { + const UINT8 *p = buf; + UINT32 crc = seed; + + while (len > 0) { + crc = crc32_add_char(crc, *p++); + len--; + } + + return crc; +} + +UINT32 crc32_exclude_offset( + UINT32 seed, + const VOID *buf, + UINTN len, + UINTN exclude_off, + UINTN exclude_len) { + + const UINT8 *p = buf; + UINT32 crc = seed; + UINTN i; + + for (i = 0; i < len; i++) { + UINT8 x = *p++; + + if (i >= exclude_off && i < exclude_off + exclude_len) + x = 0; + + crc = crc32_add_char(crc, x); + } + + return crc; +} diff --git a/src/boot/efi/crc32.h b/src/boot/efi/crc32.h new file mode 100644 index 0000000000..64150ee948 --- /dev/null +++ b/src/boot/efi/crc32.h @@ -0,0 +1,8 @@ +/* SPDX-License-Identifier: LGPL-2.1+ */ +#pragma once + +#include <efi.h> +#include <efilib.h> + +UINT32 crc32(UINT32 seed, const VOID *buf, UINTN len); +UINT32 crc32_exclude_offset(UINT32 seed, const VOID *buf, UINTN len, UINTN exclude_off, UINTN exclude_len); diff --git a/src/boot/efi/meson.build b/src/boot/efi/meson.build index 2140151844..0ae3191635 100644 --- a/src/boot/efi/meson.build +++ b/src/boot/efi/meson.build @@ -2,14 +2,15 @@ efi_headers = files(''' console.h + crc32.h disk.h graphics.h linux.h measure.h pe.h + shim.h splash.h util.h - shim.h '''.split()) common_sources = ''' @@ -24,6 +25,7 @@ systemd_boot_sources = ''' boot.c console.c shim.c + crc32.c '''.split() stub_sources = ''' diff --git a/src/boot/efi/stub.c b/src/boot/efi/stub.c index 2a60f38bf7..26c204fb43 100644 --- a/src/boot/efi/stub.c +++ b/src/boot/efi/stub.c @@ -87,12 +87,13 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { #endif } - /* export the device path this image is started from */ - if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) - efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); + /* Export the device path this image is started from, if it's not set yet */ + if (efivar_get_raw(&loader_guid, L"LoaderDevicePartUUID", NULL, NULL) != EFI_SUCCESS) + if (disk_get_part_uuid(loaded_image->DeviceHandle, uuid) == EFI_SUCCESS) + efivar_set(L"LoaderDevicePartUUID", uuid, FALSE); /* if LoaderImageIdentifier is not set, assume the image with this stub was loaded directly from UEFI */ - if (efivar_get_raw(&global_guid, L"LoaderImageIdentifier", &b, &size) != EFI_SUCCESS) { + if (efivar_get_raw(&loader_guid, L"LoaderImageIdentifier", NULL, NULL) != EFI_SUCCESS) { _cleanup_freepool_ CHAR16 *s; s = DevicePathToStr(loaded_image->FilePath); @@ -100,7 +101,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* if LoaderFirmwareInfo is not set, let's set it */ - if (efivar_get_raw(&global_guid, L"LoaderFirmwareInfo", &b, &size) != EFI_SUCCESS) { + if (efivar_get_raw(&loader_guid, L"LoaderFirmwareInfo", NULL, NULL) != EFI_SUCCESS) { _cleanup_freepool_ CHAR16 *s; s = PoolPrint(L"%s %d.%02d", ST->FirmwareVendor, ST->FirmwareRevision >> 16, ST->FirmwareRevision & 0xffff); @@ -108,7 +109,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* ditto for LoaderFirmwareType */ - if (efivar_get_raw(&global_guid, L"LoaderFirmwareType", &b, &size) != EFI_SUCCESS) { + if (efivar_get_raw(&loader_guid, L"LoaderFirmwareType", NULL, NULL) != EFI_SUCCESS) { _cleanup_freepool_ CHAR16 *s; s = PoolPrint(L"UEFI %d.%02d", ST->Hdr.Revision >> 16, ST->Hdr.Revision & 0xffff); @@ -116,7 +117,7 @@ EFI_STATUS efi_main(EFI_HANDLE image, EFI_SYSTEM_TABLE *sys_table) { } /* add StubInfo */ - if (efivar_get_raw(&global_guid, L"StubInfo", &b, &size) != EFI_SUCCESS) + if (efivar_get_raw(&loader_guid, L"StubInfo", NULL, NULL) != EFI_SUCCESS) efivar_set(L"StubInfo", L"systemd-stub " GIT_VERSION, FALSE); if (szs[3] > 0) diff --git a/src/boot/efi/util.c b/src/boot/efi/util.c index f1f1674c59..bc1d4ae5c4 100644 --- a/src/boot/efi/util.c +++ b/src/boot/efi/util.c @@ -117,6 +117,9 @@ EFI_STATUS efivar_get(const CHAR16 *name, CHAR16 **value) { if ((size % 2) != 0) return EFI_INVALID_PARAMETER; + if (!value) + return EFI_SUCCESS; + /* Return buffer directly if it happens to be NUL terminated already */ if (size >= 2 && buf[size-2] == 0 && buf[size-1] == 0) { *value = (CHAR16*) buf; @@ -141,7 +144,7 @@ EFI_STATUS efivar_get_int(const CHAR16 *name, UINTN *i) { EFI_STATUS err; err = efivar_get(name, &val); - if (!EFI_ERROR(err)) + if (!EFI_ERROR(err) && i) *i = Atoi(val); return err; @@ -159,8 +162,10 @@ EFI_STATUS efivar_get_raw(const EFI_GUID *vendor, const CHAR16 *name, CHAR8 **bu err = uefi_call_wrapper(RT->GetVariable, 5, (CHAR16*) name, (EFI_GUID *)vendor, NULL, &l, buf); if (!EFI_ERROR(err)) { - *buffer = buf; - buf = NULL; + + if (buffer) + *buffer = TAKE_PTR(buf); + if (size) *size = l; } diff --git a/src/boot/efi/util.h b/src/boot/efi/util.h index 4ba7050a91..8c5e35ad25 100644 --- a/src/boot/efi/util.h +++ b/src/boot/efi/util.h @@ -55,3 +55,10 @@ const EFI_GUID loader_guid; #define UINTN_MAX (~(UINTN)0) #define INTN_MAX ((INTN)(UINTN_MAX>>1)) + +#define TAKE_PTR(ptr) \ + ({ \ + typeof(ptr) _ptr_ = (ptr); \ + (ptr) = NULL; \ + _ptr_; \ + }) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 65c504e10f..0f1e184eea 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -323,7 +323,6 @@ static int add_swap(const char *path) { return generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, "wants", name); } -#if ENABLE_EFI static int add_automount( const char *id, const char *what, @@ -385,8 +384,43 @@ static int add_automount( return generator_add_symlink(arg_dest, SPECIAL_LOCAL_FS_TARGET, "wants", unit); } -static int add_esp(DissectedPartition *p) { - const char *esp; +static int add_xbootldr(DissectedPartition *p) { + int r; + + assert(p); + + if (in_initrd()) { + log_debug("In initrd, ignoring the XBOOTLDR partition."); + return 0; + } + + r = fstab_is_mount_point("/boot"); + if (r < 0) + return log_error_errno(r, "Failed to parse fstab: %m"); + if (r > 0) { + log_debug("/boot specified in fstab, ignoring XBOOTLDR partition."); + return 0; + } + + r = path_is_busy("/boot"); + if (r < 0) + return r; + if (r > 0) + return 0; + + return add_automount("boot", + p->node, + "/boot", + p->fstype, + true, + "umask=0077", + "Boot Loader Partition", + 120 * USEC_PER_SEC); +} + +#if ENABLE_EFI +static int add_esp(DissectedPartition *p, bool has_xbootldr) { + const char *esp_path = NULL, *id = NULL; int r; assert(p); @@ -396,21 +430,37 @@ static int add_esp(DissectedPartition *p) { return 0; } - /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */ - esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot"; + /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice, but + * only if there's no explicit XBOOTLDR partition around. */ + if (access("/efi", F_OK) < 0) { + if (errno != ENOENT) + return log_error_errno(errno, "Failed to determine whether /efi exists: %m"); + + /* Use /boot as fallback, but only if there's no XBOOTLDR partition */ + if (!has_xbootldr) { + esp_path = "/boot"; + id = "boot"; + } + } + if (!esp_path) + esp_path = "/efi"; + if (!id) + id = "efi"; /* We create an .automount which is not overridden by the .mount from the fstab generator. */ - r = fstab_is_mount_point(esp); + r = fstab_is_mount_point(esp_path); if (r < 0) return log_error_errno(r, "Failed to parse fstab: %m"); if (r > 0) { - log_debug("%s specified in fstab, ignoring.", esp); + log_debug("%s specified in fstab, ignoring.", esp_path); return 0; } - r = path_is_busy(esp); - if (r != 0) - return r < 0 ? r : 0; + r = path_is_busy(esp_path); + if (r < 0) + return r; + if (r > 0) + return 0; if (is_efi_boot()) { sd_id128_t loader_uuid; @@ -426,15 +476,15 @@ static int add_esp(DissectedPartition *p) { return log_error_errno(r, "Failed to read ESP partition UUID: %m"); if (!sd_id128_equal(p->uuid, loader_uuid)) { - log_debug("Partition for %s does not appear to be the partition we are booted from.", esp); + log_debug("Partition for %s does not appear to be the partition we are booted from.", p->node); return 0; } } else log_debug("Not an EFI boot, skipping ESP check."); - return add_automount("boot", + return add_automount(id, p->node, - esp, + esp_path, p->fstype, true, "umask=0077", @@ -442,7 +492,7 @@ static int add_esp(DissectedPartition *p) { 120 * USEC_PER_SEC); } #else -static int add_esp(DissectedPartition *p) { +static int add_esp(DissectedPartition *p, bool has_xbootldr) { return 0; } #endif @@ -570,8 +620,14 @@ static int enumerate_partitions(dev_t devnum) { r = k; } + if (m->partitions[PARTITION_XBOOTLDR].found) { + k = add_xbootldr(m->partitions + PARTITION_XBOOTLDR); + if (k < 0) + r = k; + } + if (m->partitions[PARTITION_ESP].found) { - k = add_esp(m->partitions + PARTITION_ESP); + k = add_esp(m->partitions + PARTITION_ESP, m->partitions[PARTITION_XBOOTLDR].found); if (k < 0) r = k; } diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 7e276f1edf..53ab042404 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -3,6 +3,7 @@ #include <stdio.h> #include <linux/magic.h> +#include "sd-device.h" #include "sd-id128.h" #include "alloc-util.h" @@ -11,15 +12,19 @@ #include "conf-files.h" #include "def.h" #include "device-nodes.h" +#include "dirent-util.h" #include "efivars.h" +#include "env-file.h" #include "env-util.h" #include "fd-util.h" #include "fileio.h" #include "parse-util.h" #include "path-util.h" +#include "pe-header.h" #include "stat-util.h" #include "string-util.h" #include "strv.h" +#include "unaligned.h" #include "virt.h" static void boot_entry_free(BootEntry *entry) { @@ -27,6 +32,7 @@ static void boot_entry_free(BootEntry *entry) { free(entry->id); free(entry->path); + free(entry->root); free(entry->title); free(entry->show_title); free(entry->version); @@ -39,21 +45,24 @@ static void boot_entry_free(BootEntry *entry) { free(entry->device_tree); } -static int boot_entry_load(const char *path, BootEntry *entry) { +static int boot_entry_load( + const char *root, + const char *path, + BootEntry *entry) { + _cleanup_(boot_entry_free) BootEntry tmp = {}; _cleanup_fclose_ FILE *f = NULL; unsigned line = 1; char *b, *c; int r; + assert(root); assert(path); assert(entry); c = endswith_no_case(path, ".conf"); - if (!c) { - log_error("Invalid loader entry filename: %s", path); - return -EINVAL; - } + if (!c) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path); b = basename(path); tmp.id = strndup(b, c - b); @@ -64,6 +73,10 @@ static int boot_entry_load(const char *path, BootEntry *entry) { if (!tmp.path) return log_oom(); + tmp.root = strdup(root); + if (!tmp.root) + return log_oom(); + f = fopen(path, "re"); if (!f) return log_error_errno(errno, "Failed to open \"%s\": %m", path); @@ -115,7 +128,7 @@ static int boot_entry_load(const char *path, BootEntry *entry) { else if (streq(field, "devicetree")) r = free_and_strdup(&tmp.device_tree, p); else { - log_notice("%s:%u: Unknown line \"%s\"", path, line, field); + log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field); continue; } if (r < 0) @@ -203,7 +216,7 @@ static int boot_loader_read_conf(const char *path, BootConfig *config) { else if (streq(field, "console-mode")) r = free_and_strdup(&config->console_mode, p); else { - log_notice("%s:%u: Unknown line \"%s\"", path, line, field); + log_notice("%s:%u: Unknown line \"%s\", ignoring.", path, line, field); continue; } if (r < 0) @@ -217,36 +230,270 @@ static int boot_entry_compare(const BootEntry *a, const BootEntry *b) { return str_verscmp(a->id, b->id); } -static int boot_entries_find(const char *dir, BootEntry **ret_entries, size_t *ret_n_entries) { +static int boot_entries_find( + const char *root, + const char *dir, + BootEntry **entries, + size_t *n_entries) { + _cleanup_strv_free_ char **files = NULL; + size_t n_allocated = *n_entries; char **f; int r; - BootEntry *array = NULL; - size_t n_allocated = 0, n = 0; + assert(root); assert(dir); - assert(ret_entries); - assert(ret_n_entries); + assert(entries); + assert(n_entries); r = conf_files_list(&files, ".conf", NULL, 0, dir, NULL); if (r < 0) return log_error_errno(r, "Failed to list files in \"%s\": %m", dir); STRV_FOREACH(f, files) { - if (!GREEDY_REALLOC0(array, n_allocated, n + 1)) + if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1)) return log_oom(); - r = boot_entry_load(*f, array + n); + r = boot_entry_load(root, *f, *entries + *n_entries); if (r < 0) continue; - n++; + (*n_entries) ++; } - typesafe_qsort(array, n, boot_entry_compare); + return 0; +} + +static int boot_entry_load_unified( + const char *root, + const char *path, + const char *osrelease, + const char *cmdline, + BootEntry *ret) { - *ret_entries = array; - *ret_n_entries = n; + _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL; + _cleanup_(boot_entry_free) BootEntry tmp = {}; + _cleanup_fclose_ FILE *f = NULL; + const char *k; + int r; + + assert(root); + assert(path); + assert(osrelease); + + k = path_startswith(path, root); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path is not below root: %s", path); + + f = fmemopen((void*) osrelease, strlen(osrelease), "r"); + if (!f) + return log_error_errno(errno, "Failed to open os-release buffer: %m"); + + r = parse_env_file(f, "os-release", + "PRETTY_NAME", &os_pretty_name, + "ID", &os_id, + "VERSION_ID", &version_id, + "BUILD_ID", &build_id); + if (r < 0) + return log_error_errno(r, "Failed to parse os-release data from unified kernel image %s: %m", path); + + if (!os_pretty_name || !os_id || !(version_id || build_id)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Missing fields in os-release data from unified kernel image %s, refusing.", path); + + tmp.id = strjoin(os_id, "-", version_id ?: build_id); + if (!tmp.id) + return log_oom(); + + tmp.path = strdup(path); + if (!tmp.path) + return log_oom(); + + tmp.root = strdup(root); + if (!tmp.root) + return log_oom(); + + tmp.kernel = strdup(skip_leading_chars(k, "/")); + if (!tmp.kernel) + return log_oom(); + + tmp.options = strv_new(skip_leading_chars(cmdline, WHITESPACE)); + if (!tmp.options) + return log_oom(); + + delete_trailing_chars(tmp.options[0], WHITESPACE); + + tmp.title = TAKE_PTR(os_pretty_name); + + *ret = tmp; + tmp = (BootEntry) {}; + return 0; +} + +/* Maximum PE section we are willing to load (Note that sections we are not interested in may be larger, but + * the ones we do care about and we are willing to load into memory have this size limit.) */ +#define PE_SECTION_SIZE_MAX (4U*1024U*1024U) + +static int find_sections( + int fd, + char **ret_osrelease, + char **ret_cmdline) { + + _cleanup_free_ struct PeSectionHeader *sections = NULL; + _cleanup_free_ char *osrelease = NULL, *cmdline = NULL; + size_t i, n_sections; + struct DosFileHeader dos; + struct PeHeader pe; + uint64_t start; + ssize_t n; + + n = pread(fd, &dos, sizeof(dos), 0); + if (n < 0) + return log_error_errno(errno, "Failed read DOS header: %m"); + if (n != sizeof(dos)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading DOS header, refusing."); + + if (dos.Magic[0] != 'M' || dos.Magic[1] != 'Z') + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "DOS executable magic missing, refusing."); + + start = unaligned_read_le32(&dos.ExeHeader); + n = pread(fd, &pe, sizeof(pe), start); + if (n < 0) + return log_error_errno(errno, "Failed to read PE header: %m"); + if (n != sizeof(pe)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading PE header, refusing."); + + if (pe.Magic[0] != 'P' || pe.Magic[1] != 'E' || pe.Magic[2] != 0 || pe.Magic[3] != 0) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE executable magic missing, refusing."); + + n_sections = unaligned_read_le16(&pe.FileHeader.NumberOfSections); + if (n_sections > 96) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "PE header has too many sections, refusing."); + + sections = new(struct PeSectionHeader, n_sections); + if (!sections) + return log_oom(); + + n = pread(fd, sections, + n_sections * sizeof(struct PeSectionHeader), + start + sizeof(pe) + unaligned_read_le16(&pe.FileHeader.SizeOfOptionalHeader)); + if (n < 0) + return log_error_errno(errno, "Failed to read section data: %m"); + if ((size_t) n != n_sections * sizeof(struct PeSectionHeader)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading sections, refusing."); + + for (i = 0; i < n_sections; i++) { + _cleanup_free_ char *k = NULL; + uint32_t offset, size; + char **b; + + if (strneq((char*) sections[i].Name, ".osrel", sizeof(sections[i].Name))) + b = &osrelease; + else if (strneq((char*) sections[i].Name, ".cmdline", sizeof(sections[i].Name))) + b = &cmdline; + else + continue; + + if (*b) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Duplicate section %s, refusing.", sections[i].Name); + + offset = unaligned_read_le32(§ions[i].PointerToRawData); + size = unaligned_read_le32(§ions[i].VirtualSize); + + if (size > PE_SECTION_SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section %s too large, refusing.", sections[i].Name); + + k = new(char, size+1); + if (!k) + return log_oom(); + + n = pread(fd, k, size, offset); + if (n < 0) + return log_error_errno(errno, "Failed to read section payload: %m"); + if (n != size) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:"); + + /* Allow one trailing NUL byte, but nothing more. */ + if (size > 0 && memchr(k, 0, size - 1)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Section contains embedded NUL byte: %m"); + + k[size] = 0; + *b = TAKE_PTR(k); + } + + if (!osrelease) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Image lacks .osrel section, refusing."); + + if (ret_osrelease) + *ret_osrelease = TAKE_PTR(osrelease); + if (ret_cmdline) + *ret_cmdline = TAKE_PTR(cmdline); + + return 0; +} + +static int boot_entries_find_unified( + const char *root, + const char *dir, + BootEntry **entries, + size_t *n_entries) { + + _cleanup_(closedirp) DIR *d = NULL; + size_t n_allocated = *n_entries; + struct dirent *de; + int r; + + assert(root); + assert(dir); + assert(entries); + assert(n_entries); + + d = opendir(dir); + if (!d) { + if (errno == ENOENT) + return 0; + + return log_error_errno(errno, "Failed to open %s: %m", dir); + } + + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read %s: %m", dir)) { + _cleanup_free_ char *j = NULL, *osrelease = NULL, *cmdline = NULL; + _cleanup_close_ int fd = -1; + + if (!dirent_is_file(de)) + continue; + + if (!endswith_no_case(de->d_name, ".efi")) + continue; + + if (!GREEDY_REALLOC0(*entries, n_allocated, *n_entries + 1)) + return log_oom(); + + fd = openat(dirfd(d), de->d_name, O_RDONLY|O_CLOEXEC|O_NONBLOCK); + if (fd < 0) { + log_warning_errno(errno, "Failed to open %s/%s, ignoring: %m", dir, de->d_name); + continue; + } + + r = fd_verify_regular(fd); + if (r < 0) { + log_warning_errno(errno, "File %s/%s is not regular, ignoring: %m", dir, de->d_name); + continue; + } + + r = find_sections(fd, &osrelease, &cmdline); + if (r < 0) + continue; + + j = path_join(dir, de->d_name); + if (!j) + return log_oom(); + + r = boot_entry_load_unified(root, j, osrelease, cmdline, *entries + *n_entries); + if (r < 0) + continue; + + (*n_entries) ++; + } return 0; } @@ -358,22 +605,46 @@ static int boot_entries_select_default(const BootConfig *config) { return config->n_entries - 1; /* -1 means "no default" */ } -int boot_entries_load_config(const char *esp_path, BootConfig *config) { +int boot_entries_load_config( + const char *esp_path, + const char *xbootldr_path, + BootConfig *config) { + const char *p; int r; - assert(esp_path); assert(config); - p = strjoina(esp_path, "/loader/loader.conf"); - r = boot_loader_read_conf(p, config); - if (r < 0) - return r; + if (esp_path) { + p = strjoina(esp_path, "/loader/loader.conf"); + r = boot_loader_read_conf(p, config); + if (r < 0) + return r; - p = strjoina(esp_path, "/loader/entries"); - r = boot_entries_find(p, &config->entries, &config->n_entries); - if (r < 0) - return r; + p = strjoina(esp_path, "/loader/entries"); + r = boot_entries_find(esp_path, p, &config->entries, &config->n_entries); + if (r < 0) + return r; + + p = strjoina(esp_path, "/EFI/Linux/"); + r = boot_entries_find_unified(esp_path, p, &config->entries, &config->n_entries); + if (r < 0) + return r; + } + + if (xbootldr_path) { + p = strjoina(xbootldr_path, "/loader/entries"); + r = boot_entries_find(xbootldr_path, p, &config->entries, &config->n_entries); + if (r < 0) + return r; + + p = strjoina(xbootldr_path, "/EFI/Linux/"); + r = boot_entries_find_unified(xbootldr_path, p, &config->entries, &config->n_entries); + if (r < 0) + return r; + } + + typesafe_qsort(config->entries, config->n_entries, boot_entry_compare); r = boot_entries_uniquify(config->entries, config->n_entries); if (r < 0) @@ -395,87 +666,32 @@ int boot_entries_load_config(const char *esp_path, BootConfig *config) { /********************************************************************************/ -static int verify_esp( - const char *p, +static int verify_esp_blkid( + dev_t devid, bool searching, - bool unprivileged_mode, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid) { + + sd_id128_t uuid = SD_ID128_NULL; + uint64_t pstart = 0, psize = 0; + uint32_t part = 0; + #if HAVE_BLKID _cleanup_(blkid_free_probep) blkid_probe b = NULL; _cleanup_free_ char *node = NULL; const char *v; -#endif - uint64_t pstart = 0, psize = 0; - struct stat st, st2; - const char *t2; - struct statfs sfs; - sd_id128_t uuid = SD_ID128_NULL; - uint32_t part = 0; - bool relax_checks; int r; - assert(p); - - relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; - - /* Non-root user can only check the status, so if an error occured in the following, it does not cause any - * issues. Let's also, silence the error messages. */ - - if (!relax_checks) { - if (statfs(p, &sfs) < 0) { - /* If we are searching for the mount point, don't generate a log message if we can't find the path */ - if (errno == ENOENT && searching) - return -ENOENT; - - return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to check file system type of \"%s\": %m", p); - } - - if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) { - if (searching) - return -EADDRNOTAVAIL; - - log_error("File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - } - - if (stat(p, &st) < 0) - return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to determine block device node of \"%s\": %m", p); - - if (major(st.st_dev) == 0) { - log_error("Block device node of %p is invalid.", p); - return -ENODEV; - } - - t2 = strjoina(p, "/.."); - r = stat(t2, &st2); - if (r < 0) - return log_full_errno(unprivileged_mode && errno == EACCES ? LOG_DEBUG : LOG_ERR, errno, - "Failed to determine block device node of parent of \"%s\": %m", p); - - if (st.st_dev == st2.st_dev) { - log_error("Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p); - return -ENODEV; - } - - /* In a container we don't have access to block devices, skip this part of the verification, we trust the - * container manager set everything up correctly on its own. Also skip the following verification for non-root user. */ - if (detect_container() > 0 || unprivileged_mode || relax_checks) - goto finish; - -#if HAVE_BLKID - r = device_path_make_major_minor(S_IFBLK, st.st_dev, &node); + r = device_path_make_major_minor(S_IFBLK, devid, &node); if (r < 0) return log_error_errno(r, "Failed to format major/minor device path: %m"); + errno = 0; b = blkid_new_probe_from_filename(node); if (!b) - return log_error_errno(errno ?: ENOMEM, "Failed to open file system \"%s\": %m", p); + return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node); blkid_probe_enable_superblocks(b, 1); blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); @@ -484,56 +700,52 @@ static int verify_esp( errno = 0; r = blkid_do_safeprobe(b); - if (r == -2) { - log_error("File system \"%s\" is ambiguous.", p); - return -ENODEV; - } else if (r == 1) { - log_error("File system \"%s\" does not contain a label.", p); - return -ENODEV; - } else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe file system \"%s\": %m", p); + if (r == -2) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node); + else if (r == 1) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node); + else if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node); errno = 0; r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe file system type \"%s\": %m", p); - if (!streq(v, "vfat")) { - log_error("File system \"%s\" is not FAT.", p); - return -ENODEV; - } + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system type of \"%s\": %m", node); + if (!streq(v, "vfat")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" is not FAT.", node); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition scheme \"%s\": %m", p); - if (!streq(v, "gpt")) { - log_error("File system \"%s\" is not on a GPT partition table.", p); - return -ENODEV; - } + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node); + if (!streq(v, "gpt")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" is not on a GPT partition table.", node); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID \"%s\": %m", p); - if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) { - log_error("File system \"%s\" has wrong type for an EFI System Partition (ESP).", p); - return -ENODEV; - } + return log_error_errno(errno ?: EIO, "Failed to probe partition type UUID of \"%s\": %m", node); + if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition entry UUID \"%s\": %m", p); + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node); r = sd_id128_from_string(v, &uuid); - if (r < 0) { - log_error("Partition \"%s\" has invalid UUID \"%s\".", p, v); - return -EIO; - } + if (r < 0) + return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition number \"%s\": m", p); + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition number of \"%s\": m", node); r = safe_atou32(v, &part); if (r < 0) return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); @@ -541,7 +753,7 @@ static int verify_esp( errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition offset \"%s\": %m", p); + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition offset of \"%s\": %m", node); r = safe_atou64(v, &pstart); if (r < 0) return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); @@ -549,13 +761,12 @@ static int verify_esp( errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe partition size \"%s\": %m", p); + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition size of \"%s\": %m", node); r = safe_atou64(v, &psize); if (r < 0) return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); #endif -finish: if (ret_part) *ret_part = part; if (ret_pstart) @@ -568,6 +779,234 @@ finish: return 0; } +static int verify_esp_udev( + dev_t devid, + bool searching, + uint32_t *ret_part, + uint64_t *ret_pstart, + uint64_t *ret_psize, + sd_id128_t *ret_uuid) { + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + _cleanup_free_ char *node = NULL; + sd_id128_t uuid = SD_ID128_NULL; + uint64_t pstart = 0, psize = 0; + uint32_t part = 0; + const char *v; + int r; + + r = device_path_make_major_minor(S_IFBLK, devid, &node); + if (r < 0) + return log_error_errno(r, "Failed to format major/minor device path: %m"); + + r = sd_device_new_from_devnum(&d, 'b', devid); + if (r < 0) + return log_error_errno(r, "Failed to get device from device number: %m"); + + r = sd_device_get_property_value(d, "ID_FS_TYPE", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + if (!streq(v, "vfat")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" is not FAT.", node ); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + if (!streq(v, "gpt")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" is not on a GPT partition table.", node); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + if (!streq(v, "c12a7328-f81f-11d2-ba4b-00a0c93ec93b")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" has wrong type for an EFI System Partition (ESP).", node); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + r = sd_id128_from_string(v, &uuid); + if (r < 0) + return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_NUMBER", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + r = safe_atou32(v, &part); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_NUMBER field."); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_OFFSET", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + r = safe_atou64(v, &pstart); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_OFFSET field."); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_SIZE", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + r = safe_atou64(v, &psize); + if (r < 0) + return log_error_errno(r, "Failed to parse PART_ENTRY_SIZE field."); + + if (ret_part) + *ret_part = part; + if (ret_pstart) + *ret_pstart = pstart; + if (ret_psize) + *ret_psize = psize; + if (ret_uuid) + *ret_uuid = uuid; + + return 0; +} + +static int verify_fsroot_dir( + const char *path, + bool searching, + bool unprivileged_mode, + dev_t *ret_dev) { + + struct stat st, st2; + const char *t2, *trigger; + int r; + + assert(path); + assert(ret_dev); + + /* So, the ESP and XBOOTLDR partition are commonly located on an autofs mount. stat() on the + * directory won't trigger it, if it is not mounted yet. Let's hence explicitly trigger it here, + * before stat()ing */ + trigger = strjoina(path, "/trigger"); /* Filename doesn't matter... */ + (void) access(trigger, F_OK); + + if (stat(path, &st) < 0) + return log_full_errno((searching && errno == ENOENT) || + (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno, + "Failed to determine block device node of \"%s\": %m", path); + + if (major(st.st_dev) == 0) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Block device node of \"%s\" is invalid.", path); + + t2 = strjoina(path, "/.."); + if (stat(t2, &st2) < 0) { + if (errno != EACCES) + r = -errno; + else { + _cleanup_free_ char *parent = NULL; + + /* If going via ".." didn't work due to EACCESS, then let's determine the parent path + * directly instead. It's not as good, due to symlinks and such, but we can't do + * anything better here. */ + + parent = dirname_malloc(path); + if (!parent) + return log_oom(); + + if (stat(parent, &st2) < 0) + r = -errno; + else + r = 0; + } + + if (r < 0) + return log_full_errno(unprivileged_mode && r == -EACCES ? LOG_DEBUG : LOG_ERR, r, + "Failed to determine block device node of parent of \"%s\": %m", path); + } + + if (st.st_dev == st2.st_dev) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "Directory \"%s\" is not the root of the file system.", path); + + if (ret_dev) + *ret_dev = st.st_dev; + + return 0; +} + +static int verify_esp( + const char *p, + bool searching, + bool unprivileged_mode, + uint32_t *ret_part, + uint64_t *ret_pstart, + uint64_t *ret_psize, + sd_id128_t *ret_uuid) { + + bool relax_checks; + dev_t devid; + int r; + + assert(p); + + /* This logs about all errors, except: + * + * -ENOENT → if 'searching' is set, and the dir doesn't exist + * -EADDRNOTAVAIL → if 'searching' is set, and the dir doesn't look like an ESP + * -EACESS → if 'unprivileged_mode' is set, and we have trouble acessing the thing + */ + + relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; + + /* Non-root user can only check the status, so if an error occured in the following, it does not cause any + * issues. Let's also, silence the error messages. */ + + if (!relax_checks) { + struct statfs sfs; + + if (statfs(p, &sfs) < 0) + /* If we are searching for the mount point, don't generate a log message if we can't find the path */ + return log_full_errno((searching && errno == ENOENT) || + (unprivileged_mode && errno == EACCES) ? LOG_DEBUG : LOG_ERR, errno, + "Failed to check file system type of \"%s\": %m", p); + + if (!F_TYPE_EQUAL(sfs.f_type, MSDOS_SUPER_MAGIC)) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), + "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p); + } + + r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid); + if (r < 0) + return r; + + /* In a container we don't have access to block devices, skip this part of the verification, we trust + * the container manager set everything up correctly on its own. */ + if (detect_container() > 0 || relax_checks) + goto finish; + + /* If we are unprivileged we ask udev for the metadata about the partition. If we are privileged we + * use blkid instead. Why? Because this code is called from 'bootctl' which is pretty much an + * emergency recovery tool that should also work when udev isn't up (i.e. from the emergency shell), + * however blkid can't work if we have no privileges to access block devices directly, which is why + * we use udev in that case. */ + if (unprivileged_mode) + return verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid); + else + return verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid); + +finish: + if (ret_part) + *ret_part = 0; + if (ret_pstart) + *ret_pstart = 0; + if (ret_psize) + *ret_psize = 0; + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + + return 0; +} + int find_esp_and_warn( const char *path, bool unprivileged_mode, @@ -631,35 +1070,260 @@ found: return 0; } +static int verify_xbootldr_blkid( + dev_t devid, + bool searching, + sd_id128_t *ret_uuid) { + + sd_id128_t uuid = SD_ID128_NULL; + +#if HAVE_BLKID + _cleanup_(blkid_free_probep) blkid_probe b = NULL; + _cleanup_free_ char *node = NULL; + const char *v; + int r; + + r = device_path_make_major_minor(S_IFBLK, devid, &node); + if (r < 0) + return log_error_errno(r, "Failed to format major/minor device path: %m"); + errno = 0; + b = blkid_new_probe_from_filename(node); + if (!b) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(ENOMEM), "Failed to open file system \"%s\": %m", node); + + blkid_probe_enable_partitions(b, 1); + blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + + errno = 0; + r = blkid_do_safeprobe(b); + if (r == -2) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" is ambiguous.", node); + else if (r == 1) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "File system \"%s\" does not contain a label.", node); + else if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe file system \"%s\": %m", node); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition scheme of \"%s\": %m", node); + if (streq(v, "gpt")) { + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node); + if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" has wrong type for extended boot loader partition.", node); + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition entry UUID of \"%s\": %m", node); + r = sd_id128_from_string(v, &uuid); + if (r < 0) + return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v); + + } else if (streq(v, "dos")) { + + errno = 0; + r = blkid_probe_lookup_value(b, "PART_ENTRY_TYPE", &v, NULL); + if (r != 0) + return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "Failed to probe partition type UUID of \"%s\": %m", node); + if (!streq(v, "0xea")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" has wrong type for extended boot loader partition.", node); + + } else + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" is not on a GPT or DOS partition table.", node); +#endif + + if (ret_uuid) + *ret_uuid = uuid; + + return 0; +} + +static int verify_xbootldr_udev( + dev_t devid, + bool searching, + sd_id128_t *ret_uuid) { + + _cleanup_(sd_device_unrefp) sd_device *d = NULL; + _cleanup_free_ char *node = NULL; + sd_id128_t uuid = SD_ID128_NULL; + const char *v; + int r; + + r = device_path_make_major_minor(S_IFBLK, devid, &node); + if (r < 0) + return log_error_errno(r, "Failed to format major/minor device path: %m"); + + r = sd_device_new_from_devnum(&d, 'b', devid); + if (r < 0) + return log_error_errno(r, "Failed to get device from device number: %m"); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_SCHEME", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + + if (streq(v, "gpt")) { + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + if (!streq(v, "bc13c2ff-59e6-4262-a352-b275fd6f7172")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" has wrong type for extended boot loader partition.", node); + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_UUID", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + r = sd_id128_from_string(v, &uuid); + if (r < 0) + return log_error_errno(r, "Partition \"%s\" has invalid UUID \"%s\".", node, v); + + } else if (streq(v, "dos")) { + + r = sd_device_get_property_value(d, "ID_PART_ENTRY_TYPE", &v); + if (r < 0) + return log_error_errno(r, "Failed to get device property: %m"); + if (!streq(v, "0xea")) + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" has wrong type for extended boot loader partition.", node); + } else + return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, + searching ? SYNTHETIC_ERRNO(EADDRNOTAVAIL) : SYNTHETIC_ERRNO(ENODEV), + "File system \"%s\" is not on a GPT or DOS partition table.", node); + + if (ret_uuid) + *ret_uuid = uuid; + + return 0; +} + +static int verify_xbootldr( + const char *p, + bool searching, + bool unprivileged_mode, + sd_id128_t *ret_uuid) { + + bool relax_checks; + dev_t devid; + int r; + + assert(p); + + relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0; + + r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid); + if (r < 0) + return r; + + if (detect_container() > 0 || relax_checks) + goto finish; + + if (unprivileged_mode) + return verify_xbootldr_udev(devid, searching, ret_uuid); + else + return verify_xbootldr_blkid(devid, searching, ret_uuid); + +finish: + if (ret_uuid) + *ret_uuid = SD_ID128_NULL; + + return 0; +} + +int find_xbootldr_and_warn( + const char *path, + bool unprivileged_mode, + char **ret_path, + sd_id128_t *ret_uuid) { + + int r; + + /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */ + + if (path) { + r = verify_xbootldr(path, false, unprivileged_mode, ret_uuid); + if (r < 0) + return r; + + goto found; + } + + path = getenv("SYSTEMD_XBOOTLDR_PATH"); + if (path) { + if (!path_is_valid(path) || !path_is_absolute(path)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s", + path); + + goto found; + } + + r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid); + if (r >= 0) { + path = "/boot"; + goto found; + } + if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ + return r; + + return -ENOKEY; + +found: + if (ret_path) { + char *c; + + c = strdup(path); + if (!c) + return log_oom(); + + *ret_path = c; + } + + return 0; +} + int find_default_boot_entry( const char *esp_path, - char **esp_where, + const char *xbootldr_path, BootConfig *config, const BootEntry **e) { - _cleanup_free_ char *where = NULL; + _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL; int r; assert(config); assert(e); - r = find_esp_and_warn(esp_path, false, &where, NULL, NULL, NULL, NULL); + r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL); if (r < 0) return r; - r = boot_entries_load_config(where, config); + 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 bootspec config from \"%s/loader\": %m", where); + 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 entry suitable as default, refusing to guess."); + "No boot loader entry suitable as default, refusing to guess."); *e = &config->entries[config->default_entry]; - log_debug("Found default boot entry in file \"%s\"", (*e)->path); - - if (esp_where) - *esp_where = TAKE_PTR(where); + log_debug("Found default boot loader entry in file \"%s\"", (*e)->path); return 0; } diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ed576210fe..f184902558 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -10,7 +10,8 @@ typedef struct BootEntry { char *id; /* This is the file basename without extension */ - char *path; /* This is the full path to the file */ + char *path; /* This is the full path to the drop-in file */ + char *root; /* The root path in which the drop-in was found, i.e. to which 'kernel', 'efi' and 'initrd' are relative */ char *title; char *show_title; char *version; @@ -40,12 +41,13 @@ typedef struct BootConfig { } BootConfig; void boot_config_free(BootConfig *config); -int boot_entries_load_config(const char *esp_path, BootConfig *config); +int boot_entries_load_config(const char *esp_path, const char *xbootldr_path, BootConfig *config); static inline const char* boot_entry_title(const BootEntry *entry) { return entry->show_title ?: entry->title ?: entry->id; } int find_esp_and_warn(const char *path, bool unprivileged_mode, char **ret_path, uint32_t *ret_part, uint64_t *ret_pstart, uint64_t *ret_psize, sd_id128_t *ret_uuid); +int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path,sd_id128_t *ret_uuid); -int find_default_boot_entry(const char *esp_path, char **esp_where, BootConfig *config, const BootEntry **e); +int find_default_boot_entry(const char *esp_path, const char *xbootldr_path, BootConfig *config, const BootEntry **e); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d340487025..201a515589 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -343,10 +343,8 @@ int dissect_image( errno = 0; r = blkid_do_safeprobe(b); - if (IN_SET(r, -2, 1)) { - log_debug("Failed to identify any partition table."); - return -ENOPKG; - } + if (IN_SET(r, -2, 1)) + return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to identify any partition table."); if (r != 0) return -errno ?: -EIO; @@ -497,6 +495,14 @@ int dissect_image( designator = PARTITION_ESP; fstype = "vfat"; + + } else if (sd_id128_equal(type_id, GPT_XBOOTLDR)) { + + if (pflags & GPT_FLAG_NO_AUTO) + continue; + + designator = PARTITION_XBOOTLDR; + rw = !(pflags & GPT_FLAG_READ_ONLY); } #ifdef GPT_ROOT_NATIVE else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE)) { @@ -612,21 +618,53 @@ int dissect_image( } else if (is_mbr) { - if (pflags != 0x80) /* Bootable flag */ - continue; + switch (blkid_partition_get_type(pp)) { - if (blkid_partition_get_type(pp) != 0x83) /* Linux partition */ - continue; + case 0x83: /* Linux partition */ + + if (pflags != 0x80) /* Bootable flag */ + continue; + + if (generic_node) + multiple_generic = true; + else { + generic_nr = nr; + generic_rw = true; + generic_node = strdup(node); + if (!generic_node) + return -ENOMEM; + } + + break; + + case 0xEA: { /* Boot Loader Spec extended $BOOT partition */ + _cleanup_free_ char *n = NULL; + sd_id128_t id = SD_ID128_NULL; + const char *sid; + + /* First one wins */ + if (m->partitions[PARTITION_XBOOTLDR].found) + continue; + + sid = blkid_partition_get_uuid(pp); + if (sid) + (void) sd_id128_from_string(sid, &id); - if (generic_node) - multiple_generic = true; - else { - generic_nr = nr; - generic_rw = true; - generic_node = strdup(node); - if (!generic_node) + n = strdup(node); + if (!n) return -ENOMEM; - } + + m->partitions[PARTITION_XBOOTLDR] = (DissectedPartition) { + .found = true, + .partno = nr, + .rw = true, + .architecture = _ARCHITECTURE_INVALID, + .node = TAKE_PTR(n), + .uuid = id, + }; + + break; + }} } } @@ -819,11 +857,15 @@ static int mount_partition( return -ENOMEM; } - return mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); + r = mount_verbose(LOG_DEBUG, node, p, fstype, MS_NODEV|(rw ? 0 : MS_RDONLY), options); + if (r < 0) + return r; + + return 1; } int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, DissectImageFlags flags) { - int r; + int r, boot_mounted; assert(m); assert(where); @@ -856,21 +898,26 @@ int dissected_image_mount(DissectedImage *m, const char *where, uid_t uid_shift, if (r < 0) return r; + boot_mounted = mount_partition(m->partitions + PARTITION_XBOOTLDR, where, "/boot", uid_shift, flags); + if (boot_mounted < 0) + return boot_mounted; + if (m->partitions[PARTITION_ESP].found) { - const char *mp; + /* Mount the ESP to /efi if it exists. If it doesn't exist, use /boot instead, but only if it + * exists and is empty, and we didn't already mount the XBOOTLDR partition into it. */ - /* Mount the ESP to /efi if it exists and is empty. If it doesn't exist, use /boot instead. */ + r = chase_symlinks("/efi", where, CHASE_PREFIX_ROOT, NULL); + if (r >= 0) { + r = mount_partition(m->partitions + PARTITION_ESP, where, "/efi", uid_shift, flags); + if (r < 0) + return r; - FOREACH_STRING(mp, "/efi", "/boot") { + } else if (boot_mounted <= 0) { _cleanup_free_ char *p = NULL; - r = chase_symlinks(mp, where, CHASE_PREFIX_ROOT, &p); - if (r < 0) - continue; - - r = dir_is_empty(p); - if (r > 0) { - r = mount_partition(m->partitions + PARTITION_ESP, where, mp, uid_shift, flags); + r = chase_symlinks("/boot", where, CHASE_PREFIX_ROOT, &p); + if (r >= 0 && dir_is_empty(p) > 0) { + r = mount_partition(m->partitions + PARTITION_ESP, where, "/boot", uid_shift, flags); if (r < 0) return r; } @@ -1499,6 +1546,7 @@ static const char *const partition_designator_table[] = { [PARTITION_HOME] = "home", [PARTITION_SRV] = "srv", [PARTITION_ESP] = "esp", + [PARTITION_XBOOTLDR] = "xbootldr", [PARTITION_SWAP] = "swap", [PARTITION_ROOT_VERITY] = "root-verity", [PARTITION_ROOT_SECONDARY_VERITY] = "root-secondary-verity", diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index f50b40ea11..ded43f331a 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -29,6 +29,7 @@ enum { PARTITION_HOME, PARTITION_SRV, PARTITION_ESP, + PARTITION_XBOOTLDR, PARTITION_SWAP, PARTITION_ROOT_VERITY, /* verity data for the PARTITION_ROOT partition */ PARTITION_ROOT_SECONDARY_VERITY, /* verity data for the PARTITION_ROOT_SECONDARY partition */ diff --git a/src/shared/gpt.h b/src/shared/gpt.h index fd953fabc7..31e01bd5a5 100644 --- a/src/shared/gpt.h +++ b/src/shared/gpt.h @@ -15,6 +15,7 @@ #define GPT_ROOT_ARM_64 SD_ID128_MAKE(b9,21,b0,45,1d,f0,41,c3,af,44,4c,6f,28,0d,3f,ae) #define GPT_ROOT_IA64 SD_ID128_MAKE(99,3d,8d,3d,f8,0e,42,25,85,5a,9d,af,8e,d7,ea,97) #define GPT_ESP SD_ID128_MAKE(c1,2a,73,28,f8,1f,11,d2,ba,4b,00,a0,c9,3e,c9,3b) +#define GPT_XBOOTLDR SD_ID128_MAKE(bc,13,c2,ff,59,e6,42,62,a3,52,b2,75,fd,6f,71,72) #define GPT_SWAP SD_ID128_MAKE(06,57,fd,6d,a4,ab,43,c4,84,e5,09,33,c8,4b,4f,4f) #define GPT_HOME SD_ID128_MAKE(93,3a,c7,e1,2e,b4,4f,13,b8,44,0e,14,e2,ae,f9,15) #define GPT_SRV SD_ID128_MAKE(3b,8f,84,25,20,e0,4f,3b,90,7f,1a,25,a7,6f,98,e8) diff --git a/src/shared/meson.build b/src/shared/meson.build index 99d6ba14f1..1a5ba8c0f4 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -117,6 +117,7 @@ shared_sources = files(''' pager.h path-lookup.c path-lookup.h + pe-header.h pretty-print.c pretty-print.h ptyfwd.c diff --git a/src/shared/pe-header.h b/src/shared/pe-header.h new file mode 100644 index 0000000000..a362917523 --- /dev/null +++ b/src/shared/pe-header.h @@ -0,0 +1,59 @@ +#pragma once + +#include <inttypes.h> + +#include "macro.h" +#include "sparse-endian.h" + +struct DosFileHeader { + uint8_t Magic[2]; + le16_t LastSize; + le16_t nBlocks; + le16_t nReloc; + le16_t HdrSize; + le16_t MinAlloc; + le16_t MaxAlloc; + le16_t ss; + le16_t sp; + le16_t Checksum; + le16_t ip; + le16_t cs; + le16_t RelocPos; + le16_t nOverlay; + le16_t reserved[4]; + le16_t OEMId; + le16_t OEMInfo; + le16_t reserved2[10]; + le32_t ExeHeader; +} _packed_; + +#define PE_HEADER_MACHINE_I386 0x014cU +#define PE_HEADER_MACHINE_X64 0x8664U + +struct PeFileHeader { + le16_t Machine; + le16_t NumberOfSections; + le32_t TimeDateStamp; + le32_t PointerToSymbolTable; + le32_t NumberOfSymbols; + le16_t SizeOfOptionalHeader; + le16_t Characteristics; +} _packed_; + +struct PeHeader { + uint8_t Magic[4]; + struct PeFileHeader FileHeader; +} _packed_; + +struct PeSectionHeader { + uint8_t Name[8]; + le32_t VirtualSize; + le32_t VirtualAddress; + le32_t SizeOfRawData; + le32_t PointerToRawData; + le32_t PointerToRelocations; + le32_t PointerToLinenumbers; + le16_t NumberOfRelocations; + le16_t NumberOfLinenumbers; + le32_t Characteristics; + } _packed_; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 63dae2c872..57e3111b8d 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -134,7 +134,6 @@ static const char *arg_kill_who = NULL; static int arg_signal = SIGTERM; static char *arg_root = NULL; static usec_t arg_when = 0; -static char *arg_esp_path = NULL; static char *argv_cmdline = NULL; static enum action { ACTION_SYSTEMCTL, @@ -3519,7 +3518,7 @@ static int prepare_firmware_setup(void) { static int load_kexec_kernel(void) { _cleanup_(boot_config_free) BootConfig config = {}; - _cleanup_free_ char *where = NULL, *kernel = NULL, *initrd = NULL, *options = NULL; + _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; const BootEntry *e; pid_t pid; int r; @@ -3532,21 +3531,27 @@ static int load_kexec_kernel(void) { if (access(KEXEC, X_OK) < 0) return log_error_errno(errno, KEXEC" is not available: %m"); - r = find_default_boot_entry(arg_esp_path, &where, &config, &e); + r = find_default_boot_entry(NULL, NULL, &config, &e); if (r == -ENOKEY) /* find_default_boot_entry() doesn't warn about this case */ return log_error_errno(r, "Cannot find the ESP partition mount point."); if (r < 0) /* But it logs about all these cases, hence don't log here again */ return r; - if (strv_length(e->initrd) > 1) { - log_error("Boot entry specifies multiple initrds, which is not supported currently."); - return -EINVAL; + if (strv_length(e->initrd) > 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Boot entry specifies multiple initrds, which is not supported currently."); + + kernel = path_join(e->root, e->kernel); + if (!kernel) + return log_oom(); + + if (!strv_isempty(e->initrd)) { + initrd = path_join(e->root, *e->initrd); + if (!initrd) + return log_oom(); } - kernel = path_join(where, e->kernel); - if (!strv_isempty(e->initrd)) - initrd = path_join(where, *e->initrd); options = strv_join(e->options, " "); if (!options) return log_oom(); @@ -8814,7 +8819,6 @@ finish: strv_free(arg_wall); free(arg_root); - free(arg_esp_path); /* Note that we return r here, not 0, so that we can implement the LSB-like return codes */ return r; |