]> git.ipfire.org Git - thirdparty/systemd.git/blobdiff - src/shared/bootspec.c
Add fmemopen_unlocked() and use unlocked ops in fuzzers and some other tests
[thirdparty/systemd.git] / src / shared / bootspec.c
index 47da478c3e5469471154c633501bb5575185ee26..b2f8936038bf529cc5564d376140ee25903812e3 100644 (file)
@@ -2,6 +2,7 @@
 
 #include <stdio.h>
 #include <linux/magic.h>
+#include <unistd.h>
 
 #include "sd-device.h"
 #include "sd-id128.h"
 #include "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 "sort-util.h"
 #include "stat-util.h"
+#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
+#include "unaligned.h"
+#include "util.h"
 #include "virt.h"
 
 static void boot_entry_free(BootEntry *entry) {
@@ -46,7 +54,10 @@ static int boot_entry_load(
                 const char *path,
                 BootEntry *entry) {
 
-        _cleanup_(boot_entry_free) BootEntry tmp = {};
+        _cleanup_(boot_entry_free) BootEntry tmp = {
+                .type = BOOT_ENTRY_CONF,
+        };
+
         _cleanup_fclose_ FILE *f = NULL;
         unsigned line = 1;
         char *b, *c;
@@ -58,13 +69,16 @@ static int boot_entry_load(
 
         c = endswith_no_case(path, ".conf");
         if (!c)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry file suffix: %s", path);
 
         b = basename(path);
         tmp.id = strndup(b, c - b);
         if (!tmp.id)
                 return log_oom();
 
+        if (!efi_loader_entry_name_valid(tmp.id))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry filename: %s", path);
+
         tmp.path = strdup(path);
         if (!tmp.path)
                 return log_oom();
@@ -146,6 +160,7 @@ void boot_config_free(BootConfig *config) {
         free(config->editor);
         free(config->auto_entries);
         free(config->auto_firmware);
+        free(config->console_mode);
 
         free(config->entry_oneshot);
         free(config->entry_default);
@@ -234,7 +249,6 @@ static int boot_entries_find(
 
         _cleanup_strv_free_ char **files = NULL;
         size_t n_allocated = *n_entries;
-        bool added = false;
         char **f;
         int r;
 
@@ -256,11 +270,246 @@ static int boot_entries_find(
                         continue;
 
                 (*n_entries) ++;
-                added = true;
         }
 
-        if (added)
-                typesafe_qsort(*entries, *n_entries, 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) {
+
+        _cleanup_free_ char *os_pretty_name = NULL, *os_id = NULL, *version_id = NULL, *build_id = NULL;
+        _cleanup_(boot_entry_free) BootEntry tmp = {
+                .type = BOOT_ENTRY_UNIFIED,
+        };
+        _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_unlocked((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();
+
+        if (!efi_loader_entry_name_valid(tmp.id))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid loader entry: %s", tmp.id);
+
+        tmp.path = strdup(path);
+        if (!tmp.path)
+                return log_oom();
+
+        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(&sections[i].PointerToRawData);
+                size = unaligned_read_le32(&sections[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 ((size_t) n != size)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short read while reading section payload, refusing:");
+
+                /* Allow one trailing NUL byte, but nothing more. */
+                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(r, "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;
 }
@@ -339,6 +588,12 @@ static int boot_entries_select_default(const BootConfig *config) {
         int i;
 
         assert(config);
+        assert(config->entries || config->n_entries == 0);
+
+        if (config->n_entries == 0) {
+                log_debug("Found no default boot entry :(");
+                return -1; /* -1 means "no default" */
+        }
 
         if (config->entry_oneshot)
                 for (i = config->n_entries - 1; i >= 0; i--)
@@ -364,12 +619,8 @@ static int boot_entries_select_default(const BootConfig *config) {
                                 return i;
                         }
 
-        if (config->n_entries > 0)
-                log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
-        else
-                log_debug("Found no default boot entry :(");
-
-        return config->n_entries - 1; /* -1 means "no default" */
+        log_debug("Found default: last entry \"%s\"", config->entries[config->n_entries - 1].id);
+        return config->n_entries - 1;
 }
 
 int boot_entries_load_config(
@@ -392,6 +643,11 @@ int boot_entries_load_config(
                 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) {
@@ -399,26 +655,145 @@ int boot_entries_load_config(
                 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)
                 return log_error_errno(r, "Failed to uniquify boot entries: %m");
 
         if (is_efi_boot()) {
                 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryOneShot", &config->entry_oneshot);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read EFI var \"LoaderEntryOneShot\": %m");
+                if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
+                        log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryOneShot\": %m");
+                        if (r == -ENOMEM)
+                                return r;
+                }
 
                 r = efi_get_variable_string(EFI_VENDOR_LOADER, "LoaderEntryDefault", &config->entry_default);
-                if (r < 0 && r != -ENOENT)
-                        return log_error_errno(r, "Failed to read EFI var \"LoaderEntryDefault\": %m");
+                if (r < 0 && !IN_SET(r, -ENOENT, -ENODATA)) {
+                        log_warning_errno(r, "Failed to read EFI variable \"LoaderEntryDefault\": %m");
+                        if (r == -ENOMEM)
+                                return r;
+                }
         }
 
         config->default_entry = boot_entries_select_default(config);
         return 0;
 }
 
+int boot_entries_load_config_auto(
+                const char *override_esp_path,
+                const char *override_xbootldr_path,
+                BootConfig *config) {
+
+        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
+        int r;
+
+        assert(config);
+
+        /* This function is similar to boot_entries_load_config(), however we automatically search for the
+         * ESP and the XBOOTLDR partition unless it is explicitly specified. Also, if the user did not pass
+         * an ESP or XBOOTLDR path directly, let's see if /run/boot-loader-entries/ exists. If so, let's
+         * read data from there, as if it was an ESP (i.e. loading both entries and loader.conf data from
+         * it). This allows other boot loaders to pass boot loader entry information to our tools if they
+         * want to. */
+
+        if (!override_esp_path && !override_xbootldr_path) {
+                if (access("/run/boot-loader-entries/", F_OK) >= 0)
+                        return boot_entries_load_config("/run/boot-loader-entries/", NULL, config);
+
+                if (errno != ENOENT)
+                        return log_error_errno(errno,
+                                               "Failed to determine whether /run/boot-loader-entries/ exists: %m");
+        }
+
+        r = find_esp_and_warn(override_esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
+        if (r < 0) /* we don't log about ENOKEY here, but propagate it, leaving it to the caller to log */
+                return r;
+
+        r = find_xbootldr_and_warn(override_xbootldr_path, false, &xbootldr_where, NULL);
+        if (r < 0 && r != -ENOKEY)
+                return r; /* It's fine if the XBOOTLDR partition doesn't exist, hence we ignore ENOKEY here */
+
+        return boot_entries_load_config(esp_where, xbootldr_where, config);
+}
+
+int boot_entries_augment_from_loader(BootConfig *config, bool only_auto) {
+
+        static const char * const title_table[] = {
+                /* Pretty names for a few well-known automatically discovered entries. */
+                "auto-osx",                      "macOS",
+                "auto-windows",                  "Windows Boot Manager",
+                "auto-efi-shell",                "EFI Shell",
+                "auto-efi-default",              "EFI Default Loader",
+                "auto-reboot-to-firmware-setup", "Reboot Into Firmware Interface",
+        };
+
+        _cleanup_strv_free_ char **found_by_loader = NULL;
+        size_t n_allocated;
+        char **i;
+        int r;
+
+        assert(config);
+
+        /* Let's add the entries discovered by the boot loader to the end of our list, unless they are
+         * already included there. */
+
+        r = efi_loader_get_entries(&found_by_loader);
+        if (IN_SET(r, -ENOENT, -EOPNOTSUPP))
+                return log_debug_errno(r, "Boot loader reported no entries.");
+        if (r < 0)
+                return log_error_errno(r, "Failed to determine entries reported by boot loader: %m");
+
+        n_allocated = config->n_entries;
+
+        STRV_FOREACH(i, found_by_loader) {
+                _cleanup_free_ char *c = NULL, *t = NULL, *p = NULL;
+                char **a, **b;
+
+                if (boot_config_has_entry(config, *i))
+                        continue;
+
+                if (only_auto && !startswith(*i, "auto-"))
+                        continue;
+
+                c = strdup(*i);
+                if (!c)
+                        return log_oom();
+
+                STRV_FOREACH_PAIR(a, b, (char**) title_table)
+                        if (streq(*a, *i)) {
+                                t = strdup(*b);
+                                if (!t)
+                                        return log_oom();
+                                break;
+                        }
+
+                p = efi_variable_path(EFI_VENDOR_LOADER, "LoaderEntries");
+                if (!p)
+                        return log_oom();
+
+                if (!GREEDY_REALLOC0(config->entries, n_allocated, config->n_entries + 1))
+                        return log_oom();
+
+                config->entries[config->n_entries++] = (BootEntry) {
+                        .type = BOOT_ENTRY_LOADER,
+                        .id = TAKE_PTR(c),
+                        .title = TAKE_PTR(t),
+                        .path = TAKE_PTR(p),
+                };
+        }
+
+        return 0;
+}
+
 /********************************************************************************/
 
 static int verify_esp_blkid(
@@ -622,6 +997,72 @@ static int verify_esp_udev(
         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,
@@ -631,10 +1072,8 @@ static int verify_esp(
                 uint64_t *ret_psize,
                 sd_id128_t *ret_uuid) {
 
-        struct stat st, st2;
-        struct statfs sfs;
         bool relax_checks;
-        const char *t2;
+        dev_t devid;
         int r;
 
         assert(p);
@@ -652,6 +1091,8 @@ static int verify_esp(
          * 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) ||
@@ -664,26 +1105,9 @@ static int verify_esp(
                                               "File system \"%s\" is not a FAT EFI System Partition (ESP) file system.", p);
         }
 
-        if (stat(p, &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", p);
-
-        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.", p);
-
-        t2 = strjoina(p, "/..");
-        r = stat(t2, &st2);
+        r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
         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)
-                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
-                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
-                                      "Directory \"%s\" is not the root of the EFI System Partition (ESP) file system.", p);
+                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. */
@@ -696,9 +1120,9 @@ static int verify_esp(
          * 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(st.st_dev, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
+                return verify_esp_udev(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
         else
-                return verify_esp_blkid(st.st_dev, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
+                return verify_esp_blkid(devid, searching, ret_part, ret_pstart, ret_psize, ret_uuid);
 
 finish:
         if (ret_part)
@@ -855,49 +1279,91 @@ static int verify_xbootldr_blkid(
         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) {
 
-        struct stat st, st2;
         bool relax_checks;
-        const char *t2;
+        dev_t devid;
         int r;
 
         assert(p);
 
         relax_checks = getenv_bool("SYSTEMD_RELAX_XBOOTLDR_CHECKS") > 0;
 
-        if (stat(p, &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", p);
-
-        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.", p);
-
-        t2 = strjoina(p, "/..");
-        r = stat(t2, &st2);
+        r = verify_fsroot_dir(p, searching, unprivileged_mode, &devid);
         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)
-                return log_full_errno(searching ? LOG_DEBUG : LOG_ERR,
-                                      SYNTHETIC_ERRNO(searching ? EADDRNOTAVAIL : ENODEV),
-                                      "Directory \"%s\" is not the root of the XBOOTLDR file system.", p);
+                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. Also skip the following verification
-         * for non-root user. */
-        if (detect_container() > 0 || unprivileged_mode || relax_checks)
+        if (detect_container() > 0 || relax_checks)
                 goto finish;
 
-        return verify_xbootldr_blkid(st.st_dev, searching, ret_uuid);
+        if (unprivileged_mode)
+                return verify_xbootldr_udev(devid, searching, ret_uuid);
+        else
+                return verify_xbootldr_blkid(devid, searching, ret_uuid);
 
 finish:
         if (ret_uuid)
@@ -957,37 +1423,3 @@ found:
 
         return 0;
 }
-
-int find_default_boot_entry(
-                const char *esp_path,
-                const char *xbootldr_path,
-                BootConfig *config,
-                const BootEntry **e) {
-
-        _cleanup_free_ char *esp_where = NULL, *xbootldr_where = NULL;
-        int r;
-
-        assert(config);
-        assert(e);
-
-        r = find_esp_and_warn(esp_path, false, &esp_where, NULL, NULL, NULL, NULL);
-        if (r < 0)
-                return r;
-
-        r = find_xbootldr_and_warn(xbootldr_path, false, &xbootldr_where, NULL);
-        if (r < 0 && r != -ENOKEY)
-                return r;
-
-        r = boot_entries_load_config(esp_where, xbootldr_where, config);
-        if (r < 0)
-                return log_error_errno(r, "Failed to load boot loader entries: %m");
-
-        if (config->default_entry < 0)
-                return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
-                                       "No boot loader entry suitable as default, refusing to guess.");
-
-        *e = &config->entries[config->default_entry];
-        log_debug("Found default boot loader entry in file \"%s\"", (*e)->path);
-
-        return 0;
-}