diff --git a/TODO b/TODO index a84573cc36..6a4f27b511 100644 --- a/TODO +++ b/TODO @@ -762,8 +762,8 @@ Features: mapping to $RUNTIME_DIRECTORY, $STATE_DIRECTORY and such * All tools that support --root= should also learn --image= so that they can - operate on disk images directly. Specifically: bootctl, systemctl, - coredumpctl. (Already done: systemd-nspawn, systemd-firstboot, + operate on disk images directly. Specifically: systemctl, coredumpctl. + (Already done: bootctl, systemd-nspawn, systemd-firstboot, systemd-repart, systemd-tmpfiles, systemd-sysusers, journalctl) * seccomp: by default mask x32 ABI system wide on x86-64. it's on its way out diff --git a/man/bootctl.xml b/man/bootctl.xml index 8c88238383..1664f68157 100644 --- a/man/bootctl.xml +++ b/man/bootctl.xml @@ -237,6 +237,37 @@ Loader partition to /boot/, if possible. + + + Takes a directory path as an argument. All + paths will be prefixed with the given alternate + root path, including config search + paths. + + + + + + Takes a path to a disk image file or block device node. If specified all operations + are applied to file system in the indicated disk image. This is similar to + but operates on file systems stored in disk images or block devices. The disk image should either + contain just a file system or a set of file systems within a GPT partition table, following the + Discoverable Partitions + Specification. For further information on supported disk images, see + systemd-nspawn1's + switch of the same name. + + + + + When installing binaries with or + , selects where to source them from. Takes one of auto + (the default), image or host. With auto + binaries will be picked from the specified directory or image, and if not found they will be picked + from the host. With image or host no fallback search will be + performed if the binaries are not found in the selected source. + + diff --git a/shell-completion/bash/bootctl b/shell-completion/bash/bootctl index 9214af5350..0b7cef7871 100644 --- a/shell-completion/bash/bootctl +++ b/shell-completion/bash/bootctl @@ -32,7 +32,7 @@ _bootctl() { local cur=${COMP_WORDS[COMP_CWORD]} prev=${COMP_WORDS[COMP_CWORD-1]} local -A OPTS=( [STANDALONE]='-h --help -p --print-esp-path -x --print-boot-path --version --no-variables --no-pager --graceful' - [ARG]='--esp-path --boot-path --make-machine-id-directory' + [ARG]='--esp-path --boot-path --make-machine-id-directory --root --image --install-source' ) if __contains_word "$prev" ${OPTS[ARG]}; then @@ -48,6 +48,13 @@ _bootctl() { --make-machine-id-directory) comps="yes no auto" ;; + --image|--root) + compopt -o nospace + comps=$( compgen -A file -- "$cur" ) + ;; + --install-source) + comps="image host auto" + ;; esac COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) return 0 diff --git a/shell-completion/zsh/_bootctl b/shell-completion/zsh/_bootctl index eef48d71d9..8634e8b9bc 100644 --- a/shell-completion/zsh/_bootctl +++ b/shell-completion/zsh/_bootctl @@ -73,4 +73,7 @@ _arguments \ '--no-variables[Do not touch EFI variables]' \ '--no-pager[Do not pipe output into a pager]' \ '--graceful[Do not fail when locating ESP or writing fails]' \ + '--root=[Operate under the specified directory]:PATH' \ + '--image=[Operate on the specified image]:PATH' \ + '--install-source[Where to pick files when using --root=/--image=]:options:(image host auto)' \ '*::bootctl command:_bootctl_commands' diff --git a/src/boot/bless-boot.c b/src/boot/bless-boot.c index d9c901d73b..315a1a37ed 100644 --- a/src/boot/bless-boot.c +++ b/src/boot/bless-boot.c @@ -108,11 +108,11 @@ static int acquire_path(void) { if (!strv_isempty(arg_path)) return 0; - r = find_esp_and_warn(NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &esp_path, NULL, NULL, NULL, NULL, &esp_devid); 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, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn(NULL, NULL, /* unprivileged_mode= */ false, &xbootldr_path, NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c index e50f96cb2f..c09c305b3f 100644 --- a/src/boot/bootctl.c +++ b/src/boot/bootctl.c @@ -15,9 +15,11 @@ #include "alloc-util.h" #include "blkid-util.h" #include "bootspec.h" +#include "chase-symlinks.h" #include "copy.h" #include "devnum-util.h" #include "dirent-util.h" +#include "dissect-image.h" #include "efi-api.h" #include "efi-loader.h" #include "efivars.h" @@ -31,6 +33,7 @@ #include "glyph-util.h" #include "main-func.h" #include "mkdir.h" +#include "mount-util.h" #include "os-util.h" #include "pager.h" #include "parse-argument.h" @@ -75,11 +78,20 @@ static enum { static char *arg_entry_token = NULL; static JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; static bool arg_arch_all = false; +static char *arg_root = NULL; +static char *arg_image = NULL; +static enum { + ARG_INSTALL_SOURCE_IMAGE, + ARG_INSTALL_SOURCE_HOST, + ARG_INSTALL_SOURCE_AUTO, +} arg_install_source = ARG_INSTALL_SOURCE_AUTO; STATIC_DESTRUCTOR_REGISTER(arg_esp_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_xbootldr_path, freep); STATIC_DESTRUCTOR_REGISTER(arg_install_layout, freep); STATIC_DESTRUCTOR_REGISTER(arg_entry_token, freep); +STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +STATIC_DESTRUCTOR_REGISTER(arg_image, freep); static const char *arg_dollar_boot_path(void) { /* $BOOT shall be the XBOOTLDR partition if it exists, and otherwise the ESP */ @@ -104,7 +116,7 @@ static int acquire_esp( * we simply eat up the error here, so that --list and --status work too, without noise about * this). */ - r = find_esp_and_warn(arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = find_esp_and_warn(arg_root, arg_esp_path, unprivileged_mode, &np, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r == -ENOKEY) { if (graceful) return log_full_errno(arg_quiet ? LOG_DEBUG : LOG_INFO, r, @@ -131,7 +143,7 @@ static int acquire_xbootldr( char *np; int r; - r = find_xbootldr_and_warn(arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); + r = find_xbootldr_and_warn(arg_root, arg_xbootldr_path, unprivileged_mode, &np, ret_uuid, ret_devid); if (r == -ENOKEY) { log_debug_errno(r, "Didn't find an XBOOTLDR partition, using the ESP as $BOOT."); arg_xbootldr_path = mfree(arg_xbootldr_path); @@ -596,7 +608,6 @@ static int boot_config_load_and_select( const char *xbootldr_path, dev_t xbootldr_devid) { - _cleanup_strv_free_ char **efi_entries = NULL; int r; /* If XBOOTLDR and ESP actually refer to the same block device, suppress XBOOTLDR, since it would @@ -607,15 +618,19 @@ static int boot_config_load_and_select( if (r < 0) return r; - r = efi_loader_get_entries(&efi_entries); - if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) - log_debug_errno(r, "Boot loader reported no entries."); - else if (r < 0) - log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); - else - (void) boot_config_augment_from_loader(config, efi_entries, /* only_auto= */ false); + if (!arg_root) { + _cleanup_strv_free_ char **efi_entries = NULL; - return boot_config_select_special_entries(config); + r = efi_loader_get_entries(&efi_entries); + if (r == -ENOENT || ERRNO_IS_NOT_SUPPORTED(r)) + log_debug_errno(r, "Boot loader reported no entries."); + else if (r < 0) + log_warning_errno(r, "Failed to determine entries reported by boot loader, ignoring: %m"); + else + (void) boot_config_augment_from_loader(config, efi_entries, /* only_auto= */ false); + } + + return boot_config_select_special_entries(config, /* skip_efivars= */ !!arg_root); } static int status_entries( @@ -833,48 +848,82 @@ static int create_subdirs(const char *root, const char * const *subdirs) { } static int copy_one_file(const char *esp_path, const char *name, bool force) { + char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; + _cleanup_free_ char *source_path = NULL, *dest_path = NULL, *p = NULL, *q = NULL; const char *e; - char *p, *q, *dest_name, *s; - int r; + char *dest_name, *s; + int r, ret; dest_name = strdupa_safe(name); s = endswith_no_case(dest_name, ".signed"); if (s) *s = 0; - p = strjoina(BOOTLIBDIR "/", name); - q = strjoina(esp_path, "/EFI/systemd/", dest_name); - r = copy_file_with_version_check(p, q, force); + p = path_join(BOOTLIBDIR, name); + if (!p) + return log_oom(); + + r = chase_symlinks(p, root, CHASE_PREFIX_ROOT, &source_path, NULL); + /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ + if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) + r = chase_symlinks(p, NULL, CHASE_PREFIX_ROOT, &source_path, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + p, + root ? " under directory " : "", + root ?: ""); + + q = path_join("/EFI/systemd/", dest_name); + if (!q) + return log_oom(); + + r = chase_symlinks(q, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &dest_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", q, esp_path); + + /* Note that if this fails we do the second copy anyway, but return this error code, + * so we stash it away in a separate variable. */ + ret = copy_file_with_version_check(source_path, dest_path, force); e = startswith(dest_name, "systemd-boot"); if (e) { - int k; + _cleanup_free_ char *default_dest_path = NULL; char *v; /* Create the EFI default boot loader name (specified for removable devices) */ - v = strjoina(esp_path, "/EFI/BOOT/BOOT", e); + v = strjoina("/EFI/BOOT/BOOT", e); ascii_strupper(strrchr(v, '/') + 1); - k = copy_file_with_version_check(p, v, force); - if (k < 0 && r == 0) - r = k; + r = chase_symlinks(v, esp_path, CHASE_PREFIX_ROOT | CHASE_NONEXISTENT, &default_dest_path, NULL); + if (r < 0) + return log_error_errno(r, "Failed to resolve path %s under directory %s: %m", v, esp_path); + + r = copy_file_with_version_check(source_path, default_dest_path, force); + if (r < 0 && ret == 0) + ret = r; } - return r; + return ret; } static int install_binaries(const char *esp_path, const char *arch, bool force) { + char *root = IN_SET(arg_install_source, ARG_INSTALL_SOURCE_AUTO, ARG_INSTALL_SOURCE_IMAGE) ? arg_root : NULL; _cleanup_closedir_ DIR *d = NULL; - int r = 0; + _cleanup_free_ char *path = NULL; + int r; - d = opendir(BOOTLIBDIR); - if (!d) - return log_error_errno(errno, "Failed to open \""BOOTLIBDIR"\": %m"); + r = chase_symlinks_and_opendir(BOOTLIBDIR, root, CHASE_PREFIX_ROOT, &path, &d); + /* If we had a root directory to try, we didn't find it and we are in auto mode, retry on the host */ + if (r == -ENOENT && root && arg_install_source == ARG_INSTALL_SOURCE_AUTO) + r = chase_symlinks_and_opendir(BOOTLIBDIR, NULL, CHASE_PREFIX_ROOT, &path, &d); + if (r < 0) + return log_error_errno(r, "Failed to open boot loader directory %s%s: %m", root ?: "", BOOTLIBDIR); const char *suffix = strjoina(arch, ".efi"); const char *suffix_signed = strjoina(arch, ".efi.signed"); - FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \""BOOTLIBDIR"\": %m")) { + FOREACH_DIRENT(de, d, return log_error_errno(errno, "Failed to read \"%s\": %m", path)) { int k; if (!endswith_no_case(de->d_name, suffix) && !endswith_no_case(de->d_name, suffix_signed)) @@ -1022,6 +1071,12 @@ static int install_variables(const char *esp_path, uint16_t slot; int r; + if (arg_root) { + log_info("Acting on %s, skipping EFI variable setup.", + arg_image ? "image" : "root directory"); + return 0; + } + if (!is_efi_boot()) { log_warning("Not booted with EFI, skipping EFI variable setup."); return 0; @@ -1178,7 +1233,7 @@ static int remove_variables(sd_id128_t uuid, const char *path, bool in_order) { uint16_t slot; int r; - if (!is_efi_boot()) + if (arg_root || !is_efi_boot()) return 0; r = find_slot(uuid, path, &slot); @@ -1350,6 +1405,10 @@ static int help(int argc, char *argv[], void *userdata) { " --version Print version\n" " --esp-path=PATH Path to the EFI System Partition (ESP)\n" " --boot-path=PATH Path to the $BOOT partition\n" + " --root=PATH Operate on an alternate filesystem root\n" + " --image=PATH Operate on disk image as filesystem root\n" + " --install-source=auto|image|host\n" + " Where to pick files when using --root=/--image=\n" " -p --print-esp-path Print path to the EFI System Partition\n" " -x --print-boot-path Print path to the $BOOT partition\n" " --no-variables Don't touch EFI variables\n" @@ -1380,6 +1439,9 @@ static int parse_argv(int argc, char *argv[]) { enum { ARG_ESP_PATH = 0x100, ARG_BOOT_PATH, + ARG_ROOT, + ARG_IMAGE, + ARG_INSTALL_SOURCE, ARG_VERSION, ARG_NO_VARIABLES, ARG_NO_PAGER, @@ -1396,6 +1458,9 @@ static int parse_argv(int argc, char *argv[]) { { "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 }, + { "root", required_argument, NULL, ARG_ROOT }, + { "image", required_argument, NULL, ARG_IMAGE }, + { "install-source", required_argument, NULL, ARG_INSTALL_SOURCE }, { "print-esp-path", no_argument, NULL, 'p' }, { "print-path", no_argument, NULL, 'p' }, /* Compatibility alias */ { "print-boot-path", no_argument, NULL, 'x' }, @@ -1439,6 +1504,31 @@ static int parse_argv(int argc, char *argv[]) { return log_oom(); break; + case ARG_ROOT: + r = parse_path_argument(optarg, /* suppress_root= */ true, &arg_root); + if (r < 0) + return r; + break; + + case ARG_IMAGE: + r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image); + if (r < 0) + return r; + break; + + case ARG_INSTALL_SOURCE: + if (streq(optarg, "auto")) + arg_install_source = ARG_INSTALL_SOURCE_AUTO; + else if (streq(optarg, "image")) + arg_install_source = ARG_INSTALL_SOURCE_IMAGE; + else if (streq(optarg, "host")) + arg_install_source = ARG_INSTALL_SOURCE_HOST; + else + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Unexpected parameter for --install-source=: %s", optarg); + + break; + case 'p': if (arg_print_dollar_boot_path) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), @@ -1523,6 +1613,18 @@ static int parse_argv(int argc, char *argv[]) { assert_not_reached(); } + if ((arg_root || arg_image) && argv[optind] && !STR_IN_SET(argv[optind], "status", "list", + "install", "update", "remove", "is-installed", "random-seed")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Options --root= and --image= are not supported with verb %s.", + argv[optind]); + + if (arg_root && arg_image) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported."); + + if (arg_install_source != ARG_INSTALL_SOURCE_AUTO && !arg_root && !arg_image) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--install-from-host is only supported with --root= or --image=."); + return 1; } @@ -1608,7 +1710,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { pager_open(arg_pager_flags); - if (is_efi_boot()) { + if (!arg_root && is_efi_boot()) { static const struct { uint64_t flag; const char *name; @@ -1722,7 +1824,7 @@ static int verb_status(int argc, char *argv[], void *userdata) { r = k; } - if (is_efi_boot()) { + if (!arg_root && is_efi_boot()) { k = status_variables(); if (k < 0) r = k; @@ -1846,6 +1948,12 @@ static int install_random_seed(const char *esp) { return 0; } + if (arg_root) { + log_warning("Acting on %s, skipping EFI variable setup.", + arg_image ? "image" : "root directory"); + return 0; + } + r = getenv_bool("SYSTEMD_WRITE_SYSTEM_TOKEN"); if (r < 0) { if (r != -ENXIO) @@ -2204,6 +2312,11 @@ static int parse_loader_entry_target_arg(const char *arg1, char16_t **ret_target static int verb_set_efivar(int argc, char *argv[], void *userdata) { int r; + if (arg_root) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Acting on %s, skipping EFI variable setup.", + arg_image ? "image" : "root directory"); + if (!is_efi_boot()) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Not booted with UEFI."); @@ -2267,7 +2380,7 @@ static int verb_set_efivar(int argc, char *argv[], void *userdata) { static int verb_random_seed(int argc, char *argv[], void *userdata) { int r; - r = find_esp_and_warn(arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); + r = find_esp_and_warn(arg_root, arg_esp_path, false, &arg_esp_path, NULL, NULL, NULL, NULL, NULL); if (r == -ENOKEY) { /* find_esp_and_warn() doesn't warn about ENOKEY, so let's do that on our own */ if (!arg_graceful) @@ -2375,6 +2488,9 @@ static int bootctl_main(int argc, char *argv[]) { } static int run(int argc, char *argv[]) { + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *unlink_dir = NULL; int r; log_parse_environment(); @@ -2388,6 +2504,25 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + /* Open up and mount the image */ + if (arg_image) { + assert(!arg_root); + + r = mount_image_privately_interactively( + arg_image, + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_RELAX_VAR_CHECK, + &unlink_dir, + &loop_device, + &decrypted_image); + if (r < 0) + return r; + + arg_root = strdup(unlink_dir); + if (!arg_root) + return log_oom(); + } + return bootctl_main(argc, argv); } diff --git a/src/fuzz/fuzz-bootspec.c b/src/fuzz/fuzz-bootspec.c index c26cc94db9..c08f76c14a 100644 --- a/src/fuzz/fuzz-bootspec.c +++ b/src/fuzz/fuzz-bootspec.c @@ -104,7 +104,7 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { assert_se(boot_config_finalize(&config) >= 0); - (void) boot_config_select_special_entries(&config); + (void) boot_config_select_special_entries(&config, /* skip_efivars= */ false); _cleanup_close_ int orig_stdout_fd = -1; if (getenv_bool("SYSTEMD_FUZZ_OUTPUT") <= 0) { diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c index 2c0ec6c272..1a47fff167 100644 --- a/src/shared/bootspec.c +++ b/src/shared/bootspec.c @@ -840,12 +840,12 @@ static int boot_entries_select_selected(const BootConfig *config) { return boot_config_find(config, config->entry_selected); } -static int boot_load_efi_entry_pointers(BootConfig *config) { +static int boot_load_efi_entry_pointers(BootConfig *config, bool skip_efivars) { int r; assert(config); - if (!is_efi_boot()) + if (skip_efivars || !is_efi_boot()) return 0; /* Loads the three "pointers" to boot loader entries from their EFI variables */ @@ -871,12 +871,12 @@ static int boot_load_efi_entry_pointers(BootConfig *config) { return 1; } -int boot_config_select_special_entries(BootConfig *config) { +int boot_config_select_special_entries(BootConfig *config, bool skip_efivars) { int r; assert(config); - r = boot_load_efi_entry_pointers(config); + r = boot_load_efi_entry_pointers(config, skip_efivars); if (r < 0) return r; @@ -967,11 +967,11 @@ int boot_config_load_auto( "Failed to determine whether /run/boot-loader-entries/ exists: %m"); } - r = find_esp_and_warn(override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); + r = find_esp_and_warn(NULL, override_esp_path, /* unprivileged_mode= */ false, &esp_where, NULL, NULL, NULL, NULL, &esp_devid); if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */ return r; - r = find_xbootldr_and_warn(override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); + r = find_xbootldr_and_warn(NULL, override_xbootldr_path, /* unprivileged_mode= */ false, &xbootldr_where, NULL, &xbootldr_devid); if (r < 0 && r != -ENOKEY) return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */ diff --git a/src/shared/bootspec.h b/src/shared/bootspec.h index ff54cc2e84..36a3489e87 100644 --- a/src/shared/bootspec.h +++ b/src/shared/bootspec.h @@ -100,7 +100,7 @@ int boot_config_load(BootConfig *config, const char *esp_path, const char *xboot int boot_config_load_auto(BootConfig *config, const char *override_esp_path, const char *override_xbootldr_path); int boot_config_augment_from_loader(BootConfig *config, char **list, bool only_auto); -int boot_config_select_special_entries(BootConfig *config); +int boot_config_select_special_entries(BootConfig *config, bool skip_efivars); static inline const char* boot_entry_title(const BootEntry *entry) { assert(entry); diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c index ea27d7911a..87ac9d167e 100644 --- a/src/shared/find-esp.c +++ b/src/shared/find-esp.c @@ -8,6 +8,7 @@ #include "alloc-util.h" #include "blkid-util.h" +#include "chase-symlinks.h" #include "device-util.h" #include "devnum-util.h" #include "env-util.h" @@ -20,6 +21,12 @@ #include "string-util.h" #include "virt.h" +typedef enum VerifyESPFlags { + VERIFY_ESP_SEARCHING = 1 << 0, /* Downgrade various "not found" logs to debug level */ + VERIFY_ESP_UNPRIVILEGED_MODE = 1 << 1, /* Call into udev rather than blkid */ + VERIFY_ESP_RELAX_CHECKS = 1 << 2, /* Do not validate ESP partition */ +} VerifyESPFlags; + static int verify_esp_blkid( dev_t devid, bool searching, @@ -298,15 +305,15 @@ static int verify_fsroot_dir( 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, - dev_t *ret_devid) { + dev_t *ret_devid, + VerifyESPFlags flags) { - bool relax_checks; + bool relax_checks, searching = FLAGS_SET(flags, VERIFY_ESP_SEARCHING), + unprivileged_mode = FLAGS_SET(flags, VERIFY_ESP_UNPRIVILEGED_MODE); dev_t devid; int r; @@ -319,7 +326,7 @@ static int verify_esp( * -EACESS → if 'unprivileged_mode' is set, and we have trouble accessing the thing */ - relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; + relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0 || FLAGS_SET(flags, VERIFY_ESP_RELAX_CHECKS); /* Non-root user can only check the status, so if an error occurred in the following, it does not cause any * issues. Let's also, silence the error messages. */ @@ -381,6 +388,7 @@ finish: } int find_esp_and_warn( + const char *root, const char *path, bool unprivileged_mode, char **ret_path, @@ -390,6 +398,9 @@ int find_esp_and_warn( sd_id128_t *ret_uuid, dev_t *ret_devid) { + VerifyESPFlags flags = (unprivileged_mode ? VERIFY_ESP_UNPRIVILEGED_MODE : 0) | + (root ? VERIFY_ESP_RELAX_CHECKS : 0); + _cleanup_free_ char *p = NULL; int r; /* This logs about all errors except: @@ -399,7 +410,15 @@ int find_esp_and_warn( */ if (path) { - r = verify_esp(path, /* searching= */ false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, flags); if (r < 0) return r; @@ -410,19 +429,27 @@ int find_esp_and_warn( if (path) { struct stat st; - if (!path_is_valid(path) || !path_is_absolute(path)) + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + if (!path_is_valid(p) || !path_is_absolute(p)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s", - path); + p); /* Note: when the user explicitly configured things with an env var we won't validate the * path beyond checking it refers to a directory. After all we want this to be useful for * testing. */ - if (stat(path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", path); + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", p); if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", p); if (ret_part) *ret_part = 0; @@ -438,29 +465,33 @@ int find_esp_and_warn( goto found; } - FOREACH_STRING(_path, "/efi", "/boot", "/boot/efi") { - path = _path; + FOREACH_STRING(dir, "/efi", "/boot", "/boot/efi") { + r = chase_symlinks(dir, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r == -ENOENT) + continue; + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + dir, + root ? " under directory " : "", + root ?: ""); - r = verify_esp(path, /* searching= */ true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); + r = verify_esp(p, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid, + flags | VERIFY_ESP_SEARCHING); if (r >= 0) goto found; if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ return r; + + p = mfree(p); } /* No logging here */ return -ENOKEY; found: - if (ret_path) { - char *c; - - c = strdup(path); - if (!c) - return log_oom(); - - *ret_path = c; - } + if (ret_path) + *ret_path = TAKE_PTR(p); return 0; } @@ -662,18 +693,28 @@ finish: } int find_xbootldr_and_warn( + const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid) { + _cleanup_free_ char *p = NULL; int r; /* Similar to find_esp_and_warn(), but finds the XBOOTLDR partition. Returns the same errors. */ if (path) { - r = verify_xbootldr(path, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid); + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + r = verify_xbootldr(p, /* searching= */ false, unprivileged_mode, ret_uuid, ret_devid); if (r < 0) return r; @@ -684,15 +725,23 @@ int find_xbootldr_and_warn( if (path) { struct stat st; - if (!path_is_valid(path) || !path_is_absolute(path)) + r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &p, NULL); + if (r < 0) + return log_error_errno(r, + "Failed to resolve path %s%s%s: %m", + path, + root ? " under directory " : "", + root ?: ""); + + if (!path_is_valid(p) || !path_is_absolute(p)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "$SYSTEMD_XBOOTLDR_PATH does not refer to absolute path, refusing to use it: %s", - path); + p); - if (stat(path, &st) < 0) - return log_error_errno(errno, "Failed to stat '%s': %m", path); + if (stat(p, &st) < 0) + return log_error_errno(errno, "Failed to stat '%s': %m", p); if (!S_ISDIR(st.st_mode)) - return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", path); + return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", p); if (ret_uuid) *ret_uuid = SD_ID128_NULL; @@ -702,26 +751,26 @@ int find_xbootldr_and_warn( goto found; } - r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid, ret_devid); - if (r >= 0) { - path = "/boot"; + r = chase_symlinks("/boot", root, CHASE_PREFIX_ROOT, &p, NULL); + if (r == -ENOENT) + return -ENOKEY; + if (r < 0) + return log_error_errno(r, + "Failed to resolve path /boot%s%s: %m", + root ? " under directory " : "", + root ?: ""); + + r = verify_xbootldr(p, true, unprivileged_mode, ret_uuid, ret_devid); + if (r >= 0) 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; - } + if (ret_path) + *ret_path = TAKE_PTR(p); return 0; } diff --git a/src/shared/find-esp.h b/src/shared/find-esp.h index e4e65ac3e2..78d7f4551e 100644 --- a/src/shared/find-esp.h +++ b/src/shared/find-esp.h @@ -8,5 +8,5 @@ #include "sd-id128.h" -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, dev_t *ret_devid); -int find_xbootldr_and_warn(const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); +int find_esp_and_warn(const char *root, 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, dev_t *ret_devid); +int find_xbootldr_and_warn(const char *root, const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid); diff --git a/src/systemctl/systemctl-start-special.c b/src/systemctl/systemctl-start-special.c index cfd0078964..9363764cd7 100644 --- a/src/systemctl/systemctl-start-special.c +++ b/src/systemctl/systemctl-start-special.c @@ -43,7 +43,7 @@ static int load_kexec_kernel(void) { if (r < 0) return r; - r = boot_config_select_special_entries(&config); + r = boot_config_select_special_entries(&config, /* skip_efivars= */ false); if (r < 0) return r;