/* SPDX-License-Identifier: LGPL-2.1-or-later */ #include #include #include "sd-device.h" #include "alloc-util.h" #include "blkid-util.h" #include "devnum-util.h" #include "env-util.h" #include "errno-util.h" #include "find-esp.h" #include "gpt.h" #include "id128-util.h" #include "parse-util.h" #include "path-util.h" #include "stat-util.h" #include "string-util.h" #include "virt.h" static int verify_esp_blkid( dev_t devid, bool searching, 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; 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_superblocks(b, 1); blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); 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); r = blkid_probe_lookup_value(b, "TYPE", &v, NULL); if (r != 0) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), "No filesystem found on \"%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); r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &v, NULL); if (r != 0) return log_full_errno(searching ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV), "File system \"%s\" is not located on a partitioned block device.", 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 of \"%s\": %m", node); if (id128_equal_string(v, GPT_ESP) <= 0) 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 ?: 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); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_NUMBER", &v, NULL); if (r != 0) 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."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_OFFSET", &v, NULL); if (r != 0) 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."); errno = 0; r = blkid_probe_lookup_value(b, "PART_ENTRY_SIZE", &v, NULL); if (r != 0) 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 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_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 (id128_equal_string(v, GPT_ESP) <= 0) 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); if (path_equal(path, "/")) { /* Let's assume that the root directory of the OS is always the root of its file system * (which technically doesn't have to be the case, but it's close enough, and it's not easy * to be fully correct for it, since we can't look further up than the root dir easily.) */ if (ret_dev) *ret_dev = st.st_dev; return 0; } 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(); r = RET_NERRNO(stat(parent, &st2)); } 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, dev_t *ret_devid) { 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 accessing the thing */ relax_checks = getenv_bool("SYSTEMD_RELAX_ESP_CHECKS") > 0; /* 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. */ 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) r = verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid); else r = verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid); if (r < 0) return r; if (ret_devid) *ret_devid = devid; return 0; 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; if (ret_devid) *ret_devid = 0; return 0; } 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 r; /* This logs about all errors except: * * -ENOKEY → when we can't find the partition * -EACCESS → when unprivileged_mode is true, and we can't access something */ if (path) { r = verify_esp(path, /* searching= */ false, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r < 0) return r; goto found; } path = getenv("SYSTEMD_ESP_PATH"); if (path) { struct stat st; if (!path_is_valid(path) || !path_is_absolute(path)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "$SYSTEMD_ESP_PATH does not refer to absolute path, refusing to use it: %s", path); /* 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 (!S_ISDIR(st.st_mode)) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "ESP path '%s' is not a directory.", path); 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; if (ret_devid) *ret_devid = st.st_dev; goto found; } FOREACH_STRING(_path, "/efi", "/boot", "/boot/efi") { path = _path; r = verify_esp(path, /* searching= */ true, unprivileged_mode, ret_part, ret_pstart, ret_psize, ret_uuid, ret_devid); if (r >= 0) goto found; if (!IN_SET(r, -ENOENT, -EADDRNOTAVAIL)) /* This one is not it */ return r; } /* No logging here */ return -ENOKEY; found: if (ret_path) { char *c; c = strdup(path); if (!c) return log_oom(); *ret_path = c; } 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 (id128_equal_string(v, GPT_XBOOTLDR) <= 0) 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 (id128_equal_string(v, GPT_XBOOTLDR)) 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, dev_t *ret_devid) { 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) r = verify_xbootldr_udev(devid, searching, ret_uuid); else r = verify_xbootldr_blkid(devid, searching, ret_uuid); if (r < 0) return r; if (ret_devid) *ret_devid = devid; return 0; finish: if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) *ret_devid = 0; return 0; } int find_xbootldr_and_warn( const char *path, bool unprivileged_mode, char **ret_path, sd_id128_t *ret_uuid, dev_t *ret_devid) { 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); if (r < 0) return r; goto found; } path = getenv("SYSTEMD_XBOOTLDR_PATH"); if (path) { struct stat st; 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); if (stat(path, &st) < 0) return log_error_errno(errno, "Failed to stat '%s': %m", path); if (!S_ISDIR(st.st_mode)) return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "XBOOTLDR path '%s' is not a directory.", path); if (ret_uuid) *ret_uuid = SD_ID128_NULL; if (ret_devid) *ret_devid = st.st_dev; goto found; } r = verify_xbootldr("/boot", true, unprivileged_mode, ret_uuid, ret_devid); 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; }