From f86ffe711102f53b793c6529c82017b31389cbab Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 17 Jan 2023 18:06:05 +0100 Subject: [PATCH] dissect-image: add probe_sector_size() helper for detecting sector size of a GPT disk image When we operate with DDIs with sector sizes != 512 we need to configure the loopback device to match it, otherwise the image and the kernel block device will disagree what things are. Let's add a prober that tries to determine the sector size of a GPT DDI. It does this by looking for the GPT partition table header at the various byte offsets they must be located on, given a specific sector size. It will try sector size 512, 1024, 2048 and 4096. Of these only the 512 and 4096 really make sense IRL I guess, but let's be thorough. (cherry picked from commit 05c4c59ff127668ddaa85f0a9fd67cee3c41ce00) Related: #2170883 --- src/shared/dissect-image.c | 82 ++++++++++++++++++++++++++++++++++++++ src/shared/dissect-image.h | 2 + 2 files changed, 84 insertions(+) diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 462ee4b3e8..5c7d26d6cf 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -61,6 +61,7 @@ #include "raw-clone.h" #include "resize-fs.h" #include "signal-util.h" +#include "sparse-endian.h" #include "stat-util.h" #include "stdio-util.h" #include "string-table.h" @@ -74,6 +75,87 @@ /* how many times to wait for the device nodes to appear */ #define N_DEVICE_NODE_LIST_ATTEMPTS 10 +int probe_sector_size(int fd, uint32_t *ret) { + + struct gpt_header { + char signature[8]; + le32_t revision; + le32_t header_size; + le32_t crc32; + le32_t reserved; + le64_t my_lba; + le64_t alternate_lba; + le64_t first_usable_lba; + le64_t last_usable_lba; + sd_id128_t disk_guid; + le64_t partition_entry_lba; + le32_t number_of_partition_entries; + le32_t size_of_partition_entry; + le32_t partition_entry_array_crc32; + } _packed_; + + /* Disk images might be for 512B or for 4096 sector sizes, let's try to auto-detect that by searching + * for the GPT headers at the relevant byte offsets */ + + assert_cc(sizeof(struct gpt_header) == 92); + + /* We expect a sector size in the range 512…4096. The GPT header is located in the second + * sector. Hence it could be at byte 512 at the earliest, and at byte 4096 at the latest. And we must + * read with granularity of the largest sector size we care about. Which means 8K. */ + uint8_t sectors[2 * 4096]; + uint32_t found = 0; + ssize_t n; + + assert(fd >= 0); + assert(ret); + + n = pread(fd, sectors, sizeof(sectors), 0); + if (n < 0) + return -errno; + if (n != sizeof(sectors)) /* too short? */ + goto not_found; + + /* Let's see if we find the GPT partition header with various expected sector sizes */ + for (uint32_t sz = 512; sz <= 4096; sz <<= 1) { + struct gpt_header *p; + + assert(sizeof(sectors) >= sz * 2); + p = (struct gpt_header*) (sectors + sz); + + if (memcmp(p->signature, (const char[8]) { 'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T' }, 8) != 0) + continue; + + if (le32toh(p->revision) != UINT32_C(0x00010000)) /* the only known revision of the spec: 1.0 */ + continue; + + if (le32toh(p->header_size) < sizeof(struct gpt_header)) + continue; + + if (le32toh(p->header_size) > 4096) /* larger than a sector? something is off… */ + continue; + + if (le64toh(p->my_lba) != 1) /* this sector must claim to be at sector offset 1 */ + continue; + + if (found != 0) + return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "Detected valid partition table at offsets matching multiple sector sizes, refusing."); + + found = sz; + } + + if (found != 0) { + log_debug("Determined sector size %" PRIu32 " based on discovered partition table.", found); + *ret = found; + return 1; /* indicate we *did* find it */ + } + +not_found: + log_debug("Couldn't find any partition table to derive sector size of."); + *ret = 512; /* pick the traditional default */ + return 0; /* indicate we didn't find it */ +} + int probe_filesystem_full(int fd, const char *path, char **ret_fstype) { /* Try to find device content type and return it in *ret_fstype. If nothing is found, * 0/NULL will be returned. -EUCLEAN will be returned for ambiguous results, and an diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 631d4c7a04..2c7b84f15b 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -315,3 +315,5 @@ bool dissected_image_verity_sig_ready(const DissectedImage *image, PartitionDesi int mount_image_privately_interactively(const char *path, DissectImageFlags flags, char **ret_directory, LoopDevice **ret_loop_device); int verity_dissect_and_mount(int src_fd, const char *src, const char *dest, const MountOptions *options, const char *required_host_os_release_id, const char *required_host_os_release_version_id, const char *required_host_os_release_sysext_level, const char *required_sysext_scope); + +int probe_sector_size(int fd, uint32_t *ret);