From f0c98fdf9a6002a16305dce374e59490af4f86fe Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 16 Nov 2024 15:35:54 -0500 Subject: [PATCH 1/3] Add tool to adjust partition tables when sector size changes This includes a C program that adjusts partition tables when the device sector size changes. It is intended to be secure against malicious inputs, although it will not be used with untrusted inputs in Qubes OS. In particular, all calculations are done with sufficiently wide integer types to prevent overflow or wraparound, partition tables read from disk are strictly validated before use, and the total size of the partition table entries is limited to 1MiB. The partition table adjustment does not change the start or end of any partition. If the old sector size is 512 and the new size is 4096, the tool will return an error if any partition does not start or end on a 4096-byte boundary. Furthermore, this tool will refuse to write a partition table if either the primary or backup GPT would overwrite data that is part of any partition. This ensures that user data is not corrupted. All used entries in the original GPT are preserved. However, unused entries will be truncated if necessary. The FirstUsableLBA and LastUsableLBA fields are converted if necessary, and are rounded in the direction of _less_ usable space. This means that FirstUsableLBA is rounded up, while LastUsableLBA is rounded down. If the new primary GPT uses space after FirstUsableLBA, or the new backup GPT uses space before LastUsableLBA, the values of these fields are adjusted accordingly. The new partition tables are written in a crash-safe way. This ensures that in the event of a system crash or power loss, there is always a valid GPT that can be used. The specific algorithm used is as follows: 1. If the GPT being written is the primary GPT, start by writing the pMBR, followed by (new sector size - 512) bytes of zeroes. 2. If the new sector size is 512, overwrite the old 512-byte-sector GPT header with zeroes, then overwrite the old 4096-byte-sector GPT header with zeroes. 3. Write the partition entries. 4. Write the new partition header. All writes are made with O_SYNC, so they cannot be reordered on-disk. Furthermore, overwriting a single sector is atomic. If the primary GPT was valid, the backup GPT is written first; otherwise, the primary GPT is written first. The default CFLAGS enable sanitizers and are intended for local development only. Production builds should set CFLAGS via the 'make' command line or an environment variable, which will override the CFLAGS in the Makefile. The tool will exit with a failure status if file descriptor 0, 1, or 2 is closed when it is invoked. Using these FDs could potentially cause the device to get clobbered or read from accidentally, which would be bad. The block device is opened exclusively to prevent racing with other code that will use the device. It is fsync()d before being used to ensure that the code operates on the most recent on-disk state, not on stale state cached in memory. The whole block device is locked exclusively to avoid racing with udev. If the user passes a partition, the tool exits with an error. A BIOS Parameter Block (BPB) is checked for, to avoid accidentally corrupting a FAT or NTFS file system that happens to have a GPT with valid checksums at the wrong location. --- .gitlab-ci.yml | 6 - Makefile | 11 + archlinux/PKGBUILD.in | 1 + debian/control | 3 +- debian/qubes-kernel-vm-support.install | 1 + debian/rules | 1 + gptfixer/gpt.c | 1085 ++++++++++++++++++++++ initramfs-tools/qubes_vm | 1 + rpm_spec/qubes-kernel-vm-support.spec.in | 6 +- 9 files changed, 1107 insertions(+), 8 deletions(-) create mode 100644 gptfixer/gpt.c diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2cf12c20..e3f4fadd 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -15,12 +15,6 @@ checks:tests: tags: - docker include: -- file: /r4.1/gitlab-base.yml - project: QubesOS/qubes-continuous-integration -- file: /r4.1/gitlab-dom0.yml - project: QubesOS/qubes-continuous-integration -- file: /r4.1/gitlab-vm.yml - project: QubesOS/qubes-continuous-integration - file: /r4.2/gitlab-base.yml project: QubesOS/qubes-continuous-integration - file: /r4.2/gitlab-host.yml diff --git a/Makefile b/Makefile index 83af3b3e..9892ca21 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,10 @@ +SBINDIR ?= /usr/sbin LIBDIR ?= /usr/lib64 SCRIPTSDIR ?= /usr/lib/qubes SYSLIBDIR ?= /usr/lib INCLUDEDIR ?= /usr/include +CFLAGS ?= -Wall -Wextra -Werror -O3 -g3 -Werror=format=2 +CC ?= gcc export LIBDIR SCRIPTSDIR SYSLIBDIR INCLUDEDIR .PHONY: all selinux install install-selinux install-fedora-kernel-support install-debian-kernel-support clean @@ -35,6 +38,14 @@ install-debian-kernel-support: $(MAKE) -C dracut install $(MAKE) -C grub install-debian +install-gptfix: gptfixer/gpt + install -D gptfixer/gpt $(DESTDIR)$(SBINDIR)/gptfix +gptfixer/gpt_LDLIBS := -lz +gptfixer/gpt_CFLAGS := -D_GNU_SOURCE -fno-strict-aliasing -fno-delete-null-pointer-checks -fno-strict-overflow +%: %.c Makefile + $(CC) $($(@)_CFLAGS) -o $@ $< $(CFLAGS) -MD -MP -MF $@.dep $($(@)_LDLIBS) +-include gptfixer/*.dep + clean: $(MAKE) -C qrexec-lib clean $(MAKE) -C qmemman clean diff --git a/archlinux/PKGBUILD.in b/archlinux/PKGBUILD.in index cb3a7d88..00d66332 100644 --- a/archlinux/PKGBUILD.in +++ b/archlinux/PKGBUILD.in @@ -62,4 +62,5 @@ package_qubes-vm-kernel-support() { install -m 611 "${srcdir}/${_pkgnvr}/archlinux/PKGBUILD-initcpio-hook.sh" "${pkgdir}/usr/lib/initcpio/hooks/qubes" install -m 755 "${srcdir}/${_pkgnvr}/dracut/full-dmroot/qubes_cow_setup.sh" "${pkgdir}/usr/lib/qubes/qubes_cow_setup.sh" install -m 0644 "${srcdir}/${_pkgnvr}/grub/grub.qubes-kernel-vm-support" "${pkgdir}/etc/default/grub.qubes-kernel-vm-support" + make install-gptfix SBINDIR=/usr/bin "DESTDIR=$pkgdir" } diff --git a/debian/control b/debian/control index 0bd0fd18..9af81a7c 100644 --- a/debian/control +++ b/debian/control @@ -7,7 +7,8 @@ Build-Depends: libxen-dev, pkg-config, python3-setuptools, - libicu-dev + libicu-dev, + libz-dev, Standards-Version: 4.4.0.1 Homepage: https://www.qubes-os.org Vcs-Git: https://github.com/QubesOS/qubes-linux-utils.git diff --git a/debian/qubes-kernel-vm-support.install b/debian/qubes-kernel-vm-support.install index 120e3ea5..b4bbfc7e 100644 --- a/debian/qubes-kernel-vm-support.install +++ b/debian/qubes-kernel-vm-support.install @@ -7,3 +7,4 @@ usr/lib/dracut/modules.d/90qubes-vm-modules/* usr/lib/dracut/modules.d/90qubes-vm-simple/* usr/lib/dracut/modules.d/80xen-scrub-pages/* etc/default/grub.d/30-qubes-kernel-vm-support.cfg +usr/sbin/gptfix diff --git a/debian/rules b/debian/rules index a84875be..416a9dda 100755 --- a/debian/rules +++ b/debian/rules @@ -15,6 +15,7 @@ override_dh_auto_build: override_dh_auto_install: make install LIBDIR=/usr/lib DEBIANBUILD=1 PYTHON_PREFIX_ARG=--install-layout=deb make install-debian-kernel-support LIBDIR=/usr/lib DEBIANBUILD=1 + make install-gptfix SBINDIR=/usr/sbin override_dh_install: dh_install --fail-missing diff --git a/gptfixer/gpt.c b/gptfixer/gpt.c new file mode 100644 index 00000000..124fe461 --- /dev/null +++ b/gptfixer/gpt.c @@ -0,0 +1,1085 @@ +#ifndef _GNU_SOURCE +#define _GNU_SOURCE 1 +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +// Disable some warnings that are not helpful and will not be fixed. + +#ifdef __clang__ +// C89/C90/C94 are not supported. +#pragma clang diagnostic ignored "-Wdeclaration-after-statement" +// This warning is good for security, but it requires massive +// changes to how C code is written, and this code is still written +// in the unsafe "traditional" style. +#pragma clang diagnostic ignored "-Wunsafe-buffer-usage" +#endif + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ +#error Only little-endian machines are supported +#endif + +// Exit codes: +// 0: everything OK +// 1: unspecified error +// 2: I/O error +// 3: GPT might be valid but uses unsupported feature +// 4: No partition table found +// 5: GPT invalid, missing, or corrupt +// 6: Cannot create new GPT because of some constraint +// 7: Device needs fixing but "check" is passed +// 8: Disk appears truncated +#define EXIT_IO 2 +#define EXIT_UNSUPPORTED 3 +#define EXIT_NOT_PARTITIONED 4 +#define EXIT_INVALID_GPT 5 +#define EXIT_CONSTRAINT 6 +#define EXIT_NEEDS_FIX 7 +#define EXIT_TRUNCATED 8 + +struct ChsSpec { + uint8_t spec[3]; +}; + +struct Guid { + uint32_t first; + uint16_t second, third; + uint8_t rest[8]; +} __attribute__((packed, aligned(1))); + +struct GPTHeader { + uint8_t Signature[8]; /* 0 */ + uint32_t Revision; /* 8 */ + uint32_t HeaderSize; /* 12 */ + uint32_t HeaderCRC32; /* 16 */ + uint32_t Reserved; /* 20 */ + uint64_t MyLBA; /* 24 */ + uint64_t AlternateLBA; /* 32 */ + uint64_t FirstUsableLBA; /* 40 */ + uint64_t LastUsableLBA; /* 48 */ + struct Guid DiskGUID; /* 56 */ + uint64_t PartitionEntryLBA; /* 72 */ + uint32_t NumberOfPartitionEntries; /* 80 */ + uint32_t SizeOfPartitionEntry; /* 84 */ + uint32_t PartitionEntryArrayCRC32; /* 88 */ +} __attribute__((packed, aligned(1))); +static_assert(sizeof(struct GPTHeader) == 92, "bug"); + +struct GPTPartitionEntry { + struct Guid PartitionTypeGUID; /* 0 */ + struct Guid UniquePartitionGUID; /* 16 */ + uint64_t StartingLBA; /* 32 */ + uint64_t EndingLBA; /* 40 */ + uint64_t Attributes; /* 48 */ + uint8_t PartitionName[72]; /* 56 */ +} __attribute__((packed, aligned(1))); +static_assert(sizeof(struct GPTPartitionEntry) == 128, "bug"); + +struct __attribute__((aligned(1))) MbrPartitionRecord { + uint8_t boot_indicator; + struct ChsSpec starting_chs; + uint8_t os_type; + struct ChsSpec ending_chs; + uint8_t starting_lba[4]; + uint8_t size_in_lba[4]; +}; +#define ASSERT_OFFSET(a, b, offset, size) \ + static_assert(offsetof(a, b) == offset, "wrong offset"); \ + static_assert(sizeof(((a){0}).b) == size, "wrong size"); +ASSERT_OFFSET(struct MbrPartitionRecord, boot_indicator, 0, 1) +ASSERT_OFFSET(struct MbrPartitionRecord, starting_chs, 1, 3) +ASSERT_OFFSET(struct MbrPartitionRecord, os_type, 4, 1) +ASSERT_OFFSET(struct MbrPartitionRecord, ending_chs, 5, 3) +ASSERT_OFFSET(struct MbrPartitionRecord, starting_lba, 8, 4) +ASSERT_OFFSET(struct MbrPartitionRecord, size_in_lba, 12, 4) + +struct Mbr { + uint8_t jmp[3]; + uint8_t name[8]; + uint8_t bytes_per_sector[2]; + uint8_t sectors_per_cluster; + uint8_t boot_code[426]; + uint8_t unique_mbr_disk_signature[4]; + uint8_t unknown[2]; + struct MbrPartitionRecord partition_record[4]; + uint8_t signature[2]; +}; +ASSERT_OFFSET(struct Mbr, boot_code, 14, 426) +ASSERT_OFFSET(struct Mbr, unique_mbr_disk_signature, 440, 4) +ASSERT_OFFSET(struct Mbr, unknown, 444, 2) +ASSERT_OFFSET(struct Mbr, partition_record, 446, 64) +ASSERT_OFFSET(struct Mbr, signature, 510, 2) + +struct UUID { + uint8_t data[16]; +}; + +#define max(a, b) \ + (__extension__({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _a : _b; \ + })) + +#define min(a, b) \ + (__extension__({ \ + typeof(a) _a = (a); \ + typeof(b) _b = (b); \ + _a > _b ? _b : _a; \ + })) +#pragma GCC poison _a _b + +// 512B -> 4096B: +// +// - Bytes 512 through 4096 will NOT be zero. +// +// 4096B -> 512B: +// +// - Bytes 512 through 4096 WILL be zero. + +// Step 1: Validate GPT +// Step 2: + +static bool bool_ioctl(int fd, long unsigned int op, void *arg) { + switch (ioctl(fd, op, arg)) { + case 0: + return true; + case -1: + return false; + default: + warnx("invalid return value from ioctl()"); + abort(); + } +} + +static FILE *log_file; +__attribute__((format(printf, 1, 2))) +static void vlog(const char *fmt, ...) { + va_list args; + va_start(args, fmt); + if (fprintf(log_file, "%s: ", program_invocation_name) != (int)strlen(program_invocation_name) + 2 || + vfprintf(log_file, fmt, args) < 0 || + fputc((int)(unsigned char)'\n', log_file) != (int)(unsigned char)'\n') + err(EXIT_IO, "gptfix: log write error"); + va_end(args); +} + +static uint64_t gpt_partition_table_bytes(const struct GPTHeader *gpt) { + /* Arguments are 32-bit so wraparound is not possible. */ + return (uint64_t)gpt->NumberOfPartitionEntries * (uint64_t)gpt->SizeOfPartitionEntry; +} + +static bool valid_sector_size(uint32_t sector_size) { + return (sector_size >= 512) && ((sector_size & (sector_size - 1)) == 0); +} + +struct GPT { + struct GPTHeader header; + uint32_t used_entries; + struct GPTPartitionEntry *entries; + uint32_t sector_size; + uint32_t _padding; +}; + +static uint64_t round_to_next_sector(uint64_t value, uint32_t sector_size) { + uint64_t sum; + assert(valid_sector_size(sector_size)); + if (__builtin_add_overflow(value, sector_size - 1, &sum)) + assert(NULL == "Arithmetic overflow calculating rounded value"); + return sum & ~(uint64_t)(sector_size - 1); +} + +static uint64_t gpt_partition_table_required_bytes(const struct GPT *gpt, uint32_t sector_size, uint32_t entries) { + assert(valid_sector_size(sector_size)); + /* Compute bytes required for partition entries, rounded to the next + * multiple of the sector size. Wraparound is impossible because + * (uint64_t)UINT32_MAX * (uint64_t)UINT32_MAX + (uint64_t)UINT32_MAX < + * UINT64_MAX. + */ + return round_to_next_sector((uint64_t)gpt->header.SizeOfPartitionEntry * (uint64_t)entries, + sector_size); +} + +static void check_mbr(struct Mbr *mbr, uint64_t size_in_sectors, bool skip_bpb_check, bool verbose) { + if (mbr->signature[0] != 0x55 || mbr->signature[1] != 0xAA) { + errx(EXIT_NOT_PARTITIONED, "Invalid MBR signature: expected 0x55 0xAA, got 0x%02" PRIX8 " 0x%02" PRIX8, + mbr->signature[0], mbr->signature[1]); + } + if (mbr->partition_record[0].os_type != 0xEE) { + errx(EXIT_UNSUPPORTED, "Bad protective MBR: OS type is not GPT Protective"); + } + struct MbrPartitionRecord first_partition = {0}; + for (int i = 1; i <= 3; ++i) { + if (memcmp(&first_partition, mbr->partition_record + i, sizeof(first_partition)) != 0) { + errx(EXIT_UNSUPPORTED, + "Partition record %d is not zeroed - this device does not " + "have a protective MBR", + i + 1); + } + } + + first_partition = mbr->partition_record[0]; + uint32_t starting_lba, size_in_lba; + memcpy(&starting_lba, first_partition.starting_lba, 4); + memcpy(&size_in_lba, first_partition.size_in_lba, 4); + if (starting_lba != 0x1) { + errx(EXIT_INVALID_GPT, "Bad protective MBR: does not start at first block"); + } + + if (!skip_bpb_check) { + for (size_t i = 3; i < 14; ++i) { + if (((char *)mbr)[i] != 0) { + errx(EXIT_UNSUPPORTED, "Refusing to change partition table that might overlap a FAT or NTFS volume"); + } + } + } + + uint32_t const expected_size_in_lba = + (uint32_t)(size_in_sectors - 1 > UINT32_MAX ? UINT32_MAX : size_in_sectors - 1); + if (size_in_lba != expected_size_in_lba) { + if (verbose) { + warnx("Protective MBR does not cover whole disk: size in LBA is " + "%" PRIu32 ", expected %" PRIu32, + size_in_lba, expected_size_in_lba); + } + memcpy(mbr->partition_record[0].size_in_lba, &expected_size_in_lba, 4); + } +} + +static bool unused_entry(const struct GPTPartitionEntry *const entry) { + const char zero[sizeof(entry->PartitionTypeGUID)] = {0}; + return memcmp(zero, &entry->PartitionTypeGUID, sizeof(zero)) == 0; +} + +static const struct GPT *foreach_loop_helper_check(const struct GPT *gpt) +{ + assert(gpt != NULL); + assert(gpt->header.SizeOfPartitionEntry >= sizeof(struct GPTPartitionEntry)); + assert(gpt->header.SizeOfPartitionEntry % _Alignof(struct GPTPartitionEntry) == 0); + assert(gpt->entries != NULL); + return gpt; +} + +#define for_each_used_gpt_entry(i, gpt, entry) \ + for (i = UINT32_C(0), entry = foreach_loop_helper_check(gpt)->entries; (i < gpt->used_entries); \ + ((void)++i, (void)(entry = (const struct GPTPartitionEntry *)((const char *)entry + gpt->header.SizeOfPartitionEntry)))) \ + if (unused_entry(entry)) \ + ; \ + else /* user code */ + +// Gets the GPT entry at the corresponding 0-based index. Asserts if the index is out of bounds. +static struct GPTPartitionEntry *gpt_entry_mut(struct GPT *gpt, uint32_t i) +{ + assert(i < gpt->header.NumberOfPartitionEntries); + return (struct GPTPartitionEntry *)(((char *)gpt->entries) + (uint64_t)i * (uint64_t)gpt->header.SizeOfPartitionEntry); +} + +// Gets the GPT entry at the corresponding 0-based index. Asserts if the index is out of bounds. +static const struct GPTPartitionEntry *gpt_entry(const struct GPT *gpt, uint32_t i) +{ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-qual" + // The returned pointer is (implicitly) casted back to const, and gpt_entry_mut() doesn't + // make any writes of its own. + return gpt_entry_mut((struct GPT *)gpt, i); +#pragma GCC diagnostic pop +} + +static void fixup_gpt(struct GPT *gpt, uint64_t bytes_required) { + uint64_t partition_entry_bytes = gpt_partition_table_bytes(&gpt->header); + assert(partition_entry_bytes <= UINT_MAX); + gpt->header.PartitionEntryArrayCRC32 = + (uint32_t)crc32(0UL, (const unsigned char *)gpt->entries, (unsigned int)partition_entry_bytes); + gpt->header.HeaderCRC32 = (uint32_t)crc32(0UL, (const unsigned char *)&gpt->header, sizeof(gpt->header)); + memset((char *)gpt->entries + partition_entry_bytes, 0, bytes_required - partition_entry_bytes); +} + +static struct GPT *gpt_adjust_sectors(uint64_t sector_offset, uint32_t sector_size, + uint64_t sectors, const struct GPT *gpt, + uint64_t *allocated, bool verbose) { + assert(sectors >= 6); + assert(valid_sector_size(gpt->sector_size)); + assert(valid_sector_size(sector_size)); + assert(sector_offset == 1 || sector_offset == sectors - 1); + + uint64_t const size = sector_size * sectors; + // This is the largest value FirstUsableLBA can be set to. + uint64_t first_start_byte_offset = UINT64_MAX; + uint64_t last_end_byte_offset = 0; + uint32_t partition_that_starts_first = UINT32_MAX; + uint32_t partition_that_ends_last = UINT32_MAX; + uint32_t i; + const struct GPTPartitionEntry *entry; + for_each_used_gpt_entry (i, gpt, entry) { + uint64_t start_byte_offset = entry->StartingLBA * gpt->sector_size; + uint64_t end_byte_offset = (entry->EndingLBA + 1) * gpt->sector_size; + if (verbose) { + vlog("Partition consumes bytes [0x%" PRIX64 ", 0x%" PRIX64 ")", start_byte_offset, end_byte_offset); + } + if (start_byte_offset % sector_size != 0) { + errx(EXIT_CONSTRAINT, + "Cannot preserve starting LBA for partition %" PRIu32 ": starting byte offset 0x%" PRIX64 + " is not a multiple of %" PRIu32, + i + 1, start_byte_offset, sector_size); + } + if (end_byte_offset % sector_size != 0) { + // TODO better error message + errx(EXIT_CONSTRAINT, + "Cannot preserve ending LBA for partition %" PRIu32 ": ending byte offset 0x%" PRIX64 + " is not a multiple of %" PRIu32, + i + 1, end_byte_offset, sector_size); + } + // If there is a tie, use the first partition for start and last partition for end. + if (start_byte_offset < first_start_byte_offset) { + first_start_byte_offset = start_byte_offset; + partition_that_starts_first = i; + } + if (last_end_byte_offset <= end_byte_offset) { + last_end_byte_offset = end_byte_offset; + partition_that_ends_last = i; + } + } + assert(first_start_byte_offset < last_end_byte_offset); + uint64_t partition_table_bytes = gpt_partition_table_required_bytes(gpt, sector_size, gpt->used_entries); + // Add sector_size once for backup GPT header. + uint64_t backup_gpt_required_bytes = partition_table_bytes + sector_size; + // Add sector_size twice: once for protective MBR, and once for + // primary GPT header. Use 64-bit multiplication to prevent wraparound + // in the absurd case of 2**31-byte sectors. + uint64_t primary_gpt_required_bytes = partition_table_bytes + UINT64_C(2) * sector_size; + + // If the disk is 1000 bytes and the sector size is 10 bytes, the + // backup GPT would be at offset 990 bytes. If the first unused + // byte is offset 980, there are 10 bytes for the partition table. + assert(size >= last_end_byte_offset); + if (size - last_end_byte_offset < backup_gpt_required_bytes) { + vlog("The first byte after partition %" PRIu32 " is at offset 0x%" PRIX64 ", leaving 0x%" PRIX64 " bytes remaining.", + partition_that_ends_last + 1, + last_end_byte_offset, + size - last_end_byte_offset); + vlog("However, at least %" PRIu64 " bytes are needed at the end to hold the backup GPT header and backup partition table entries.", + backup_gpt_required_bytes); + exit(EXIT_CONSTRAINT); + } + + // If the disk is 1000 bytes, the sector size is 10 bytes, and the + // partition table used 10 bytes, the first sector must not start + // before byte 30. + if (primary_gpt_required_bytes > first_start_byte_offset) { + errx(EXIT_CONSTRAINT, + "The protective MBR, primary GPT header, and primary partition table entries need the first %" PRIu64 + " bytes, but partition %" PRIu32 " starts at offset %" PRIu64 ".", + primary_gpt_required_bytes, + partition_that_starts_first + 1, + first_start_byte_offset); + } + + // The number of bytes available for the partition table. + // Equal to the number of bytes between the primary GPT header + // and the first partition or the last partition and the + // backup GPT header, whichever is smaller. + uint64_t const partition_table_available_bytes = + min((size - sector_size) - last_end_byte_offset, + first_start_byte_offset - UINT64_C(2) * sector_size); + + // The number of partition table entries that can be held. + // Equal to the number of available bytes divided by the size + // of an entry, rounded down. + uint64_t const max_partition_entries = + partition_table_available_bytes / gpt->header.SizeOfPartitionEntry; + assert(max_partition_entries >= gpt->used_entries); + + // The number of entries to be written is equal to the number there is space for + // or the number in the source GPT, whichever is smaller. + uint32_t const new_entries = (uint32_t)min(max_partition_entries, + (uint64_t)gpt->header.NumberOfPartitionEntries); + // Update sizes for the new number of entries. + partition_table_bytes = gpt_partition_table_required_bytes(gpt, sector_size, new_entries); + primary_gpt_required_bytes = partition_table_bytes + UINT64_C(2) * sector_size; + backup_gpt_required_bytes = partition_table_bytes + sector_size; + + struct GPT *new_gpt = malloc(sector_size); + if (new_gpt == NULL) { + err(EXIT_FAILURE, "malloc()"); + } + uint64_t sectors_required = partition_table_bytes >> __builtin_ctz(sector_size); + + // Preserve the first usable byte if possible, but increase it + // if fitting the pMBR and primary partition table requires it. + uint64_t const first_usable_byte = + max(gpt->header.FirstUsableLBA * gpt->sector_size, + primary_gpt_required_bytes); + + // Preserve the last usable byte if possible, but decrease it + // if fitting the backup partition table requires it. Add 1 + // because LastUsableLBA is an inclusive bound. + uint64_t const first_unusable_byte = + min((gpt->header.LastUsableLBA + 1) * (uint64_t)gpt->sector_size, + size - backup_gpt_required_bytes); + + memset(new_gpt, 0, sector_size); + bool primary = sector_offset == 1; + new_gpt->header = (struct GPTHeader){ + .Signature = {'E', 'F', 'I', ' ', 'P', 'A', 'R', 'T'}, + .Revision = gpt->header.Revision, + .HeaderSize = gpt->header.HeaderSize, + .HeaderCRC32 = 0, + .Reserved = 0, + .MyLBA = sector_offset, + .AlternateLBA = primary ? sectors - 1 : 1, + // Round in the direction of less usable space (up). + .FirstUsableLBA = round_to_next_sector(first_usable_byte, sector_size) / sector_size, + // Round in the direction of less usable space (down). + // Subtract 1 because LastUsableLBA is an inclusive bound. + .LastUsableLBA = first_unusable_byte / sector_size - 1, + .DiskGUID = gpt->header.DiskGUID, + .PartitionEntryLBA = primary ? 2 : sectors - 1 - sectors_required, + .NumberOfPartitionEntries = new_entries, + .SizeOfPartitionEntry = gpt->header.SizeOfPartitionEntry, + .PartitionEntryArrayCRC32 = 0, + }; + new_gpt->used_entries = new_entries; + + if (verbose) { + vlog("LBA is 0x%" PRIX64 ", Alternate LBA is 0x%" PRIX64 ", allocating 0x%" PRIX64 " bytes", + sector_offset, new_gpt->header.AlternateLBA, partition_table_bytes); + } + + new_gpt->entries = malloc(partition_table_bytes); + if (new_gpt->entries == NULL) { + err(EXIT_FAILURE, "malloc(%" PRIu64 ")", partition_table_bytes); + } + for (i = 0; i < new_entries; ++i) { + const struct GPTPartitionEntry *old_entry = gpt_entry(gpt, i); + struct GPTPartitionEntry *new_entry = gpt_entry_mut(new_gpt, i); + memcpy(new_entry, old_entry, gpt->header.SizeOfPartitionEntry); + if (!unused_entry(old_entry)) { + uint64_t start_byte_offset = old_entry->StartingLBA * gpt->sector_size; + uint64_t end_byte_offset = (old_entry->EndingLBA + 1) * gpt->sector_size; + new_entry->StartingLBA = start_byte_offset / sector_size; + new_entry->EndingLBA = end_byte_offset / sector_size - 1; + } + } + *allocated = partition_table_bytes; + fixup_gpt(new_gpt, partition_table_bytes); + assert(new_gpt->entries); + return new_gpt; +} + +/* Next up: Process GUID partition table (GPT) */ +static uint64_t check_gpt_header(const struct GPTHeader *header, uint64_t sector_offset, uint32_t sector_size, + uint64_t sectors, bool verbose) { + assert(sectors >= 6); + assert(sector_offset < sectors); + if (memcmp(header->Signature, "EFI PART", 8) != 0) { + if (verbose) { + warnx("Wrong GPT signature"); + } + return 0; + } + struct GPTHeader h = *header; + h.HeaderCRC32 = 0; + if (header->Revision != 0x00010000) { + warnx("Partition table revision 0x%08" PRIX32 " != 0x00010000", + header->Revision); + return 0; + } + if (header->HeaderCRC32 != crc32(0UL, (const unsigned char *)&h, sizeof(h))) { + warnx("Partition table at sector offset 0x%" PRIX64 " has bad CRC", header->MyLBA); + return 0; + } + if (header->MyLBA != sector_offset) { + warnx("Header at wrong offset: %" PRIu64 " != %" PRIu64, header->MyLBA, sector_offset); + } + + if (header->HeaderSize < sizeof(*header)) { + warnx("Header size %" PRIu32 " is less than %zu", header->HeaderSize, sizeof(*header)); + return 0; + } + + if (header->HeaderSize > sector_size) { + warnx("Header size %" PRIu32 " exceeds block size %" PRIu32, header->HeaderSize, sector_size); + return 0; + } + + if (header->SizeOfPartitionEntry < sizeof(struct GPTPartitionEntry)) { + warnx("Partition entry size %" PRIu32 " is less than %zu", header->SizeOfPartitionEntry, + sizeof(struct GPTPartitionEntry)); + return 0; + } + + if (header->SizeOfPartitionEntry > sector_size) { + warnx("Partition entry size %" PRIu32 " exceeds block size %" PRIu32, header->SizeOfPartitionEntry, + sector_size); + return 0; + } + + if (header->SizeOfPartitionEntry > sizeof(struct GPTPartitionEntry)) { + // This could be handled in the future if there is ever a need for + // it, but it would make anything involving modification and + // writing significantly more complex. + errx(EXIT_UNSUPPORTED, "Partition entry size %" PRIu32 " is greater than %zu, which is not supported", + header->SizeOfPartitionEntry, sizeof(struct GPTPartitionEntry)); + } + + if (header->FirstUsableLBA < 3) { + warnx("First usable LBA %" PRIu64 " is less than 3", header->FirstUsableLBA); + return 0; + } + + if (header->LastUsableLBA < header->FirstUsableLBA) { + warnx("No usable space: %" PRIu64 " < %" PRIu64, header->LastUsableLBA, header->FirstUsableLBA); + return 0; + } + + uint64_t partition_table_usable_space; + + if (sector_offset != 1) { + if (header->AlternateLBA != 1) { + warnx("Alternate LBA for backup GPT is at 0x%" PRIx64 ", not 1", header->AlternateLBA); + return 0; + } + + if (header->LastUsableLBA >= header->PartitionEntryLBA) { + warnx("Last usable LBA 0x%" PRIX64 " not before backup partition table at 0x%" PRIX64, + header->LastUsableLBA, header->PartitionEntryLBA); + return 0; + } + + if (header->PartitionEntryLBA >= header->MyLBA) { + warnx("Backup partition table LBA 0x%" PRIX64 " not before backup partition header at 0x%" PRIX64, + header->PartitionEntryLBA, header->MyLBA); + return 0; + } + + partition_table_usable_space = header->MyLBA - header->PartitionEntryLBA; + } else { + if (header->PartitionEntryLBA != 2) { + warnx("Primary partition table at offset %" PRIu64 ", not 2", header->PartitionEntryLBA); + return 0; + } + + if (header->LastUsableLBA >= header->AlternateLBA) { + warnx("Alternate partition header %" PRIu64 " overlaps usable space ending at %" PRIu64, + header->AlternateLBA, header->LastUsableLBA); + return 0; + } + + if (header->AlternateLBA >= sectors) { + errx(EXIT_TRUNCATED, + "Alternate partition header %" PRIu64 " after end of device at %" PRIu64, header->AlternateLBA, + sectors); + } + + partition_table_usable_space = header->FirstUsableLBA - header->PartitionEntryLBA; + } + + if (header->NumberOfPartitionEntries < 1) { + warnx("No partitions"); + return 0; + } + + const uint64_t partition_table_bytes = gpt_partition_table_bytes(header); + + /* Sector counts bounds checked so wraparound is not possible */ + const uint64_t partition_table_usable_bytes = partition_table_usable_space * sector_size; + if (partition_table_usable_bytes < partition_table_bytes) { + warnx("Partition table does not fit in available space"); + return 0; + } + + if (header->MyLBA != 1 && partition_table_usable_bytes - partition_table_bytes >= sector_size) { + warnx("Backup partition table has too much padding: 0x%" PRIx64 " bytes padding, sector size 0x%" PRIx32, + partition_table_usable_bytes - partition_table_bytes, sector_size); + // Allow this case as it isn't technically going to prevent reading + // the table and doesn't violate any invariants other code depends on. + // At the very least it is better to try to read the backup GPT than + // to give up and cause loss of user data. + } + + if (partition_table_bytes > (1UL << 20)) { + errx(EXIT_UNSUPPORTED, "Partition table size %" PRIu64 " exceeds 1MiB limit", partition_table_bytes); + } + + if (verbose) { + vlog("Partition table is %" PRIu64 " bytes long", partition_table_bytes); + } + return partition_table_bytes; +} + +static struct GPT *read_and_check_gpt(int fd, uint64_t sector_offset, uint32_t sector_size, uint64_t sectors, + bool verbose) { + char *header_buffer = NULL, *entries_buffer = NULL; + + if (sector_offset >= sectors) { + warnx("Request for GPT header at offset (in sectors) past end of " + "disk: " + "%" PRIu64 " >= %" PRIu64, + sector_offset, sectors); + return NULL; + } + + // 1 for pMBR, 1 for primary header, 1 for primary table, + // 1 for usable space, 1 for backup table, 1 for backup header + if (sector_offset != 1 && sector_offset < 5) { + warnx("Request for GPT at invalid offset %" PRIu64 ": offset must be 1 or >= 5", sector_offset); + return NULL; + } + + header_buffer = malloc(sector_size); + if (header_buffer == NULL) { + err(EXIT_FAILURE, "malloc(%" PRIu32 ")", sector_size * 2); + } + + if (pread(fd, header_buffer, sector_size, (off_t)(sector_offset * sector_size)) != sector_size) { + err(EXIT_IO, "Failed reading GPT header from block device or short read"); + } + /* If we made it here, the MBR is okay. We think! */ + struct GPTHeader *header = (struct GPTHeader *)header_buffer; + + const uint64_t partition_table_bytes = check_gpt_header(header, sector_offset, sector_size, sectors, verbose); + if (partition_table_bytes == 0) { + goto fail; + } + if (verbose) { + vlog("Partition table entries consume %" PRIu64 " bytes", partition_table_bytes); + } + + static_assert(offsetof(struct GPT, used_entries) == sizeof(struct GPTHeader), "struct def bug"); + memset(header_buffer + offsetof(struct GPT, used_entries), 0, sector_size - offsetof(struct GPT, used_entries)); + + /* Cannot overflow because sectors == size / sector_size + * and header->PartitionEntryLBA < sectors. Will always fit in + * "size" for the same reason. */ + const uint64_t partition_table_offset = sector_size * header->PartitionEntryLBA; + const uint64_t needed = round_to_next_sector(partition_table_bytes, sector_size); + entries_buffer = malloc(needed); + if (entries_buffer == NULL) { + err(EXIT_FAILURE, "allocating partition table"); + } + ssize_t read = pread64(fd, entries_buffer, needed, (off_t)partition_table_offset); + if (read == -1) { + err(EXIT_SUCCESS, "Error reading partition table"); + } + + if (read != (ssize_t)needed) { + errx(EXIT_IO, "Expected to read %" PRIu64 " bytes, but only got %zd bytes", partition_table_bytes, read); + } + + if (header->PartitionEntryArrayCRC32 != + crc32(0UL, (const unsigned char *)entries_buffer, (uint32_t)partition_table_bytes)) { + warnx("Partition table array at sector offset 0x%" PRIX64 " has bad CRC", header->PartitionEntryLBA); + goto fail; + } +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wcast-align" + struct GPT *gpt = (struct GPT *)header_buffer; +#pragma GCC diagnostic pop + + gpt->entries = (struct GPTPartitionEntry *)entries_buffer; + gpt->sector_size = sector_size; + uint32_t used_entries = 0, i; + gpt->used_entries = header->NumberOfPartitionEntries; + const struct GPTPartitionEntry *entry; + for_each_used_gpt_entry (i, gpt, entry) { + if (entry->StartingLBA < header->FirstUsableLBA) { + warnx("Starting LBA %" PRIu64 " less than first usable LBA %" PRIu64, entry->StartingLBA, + header->FirstUsableLBA); + goto fail; + } + if (entry->EndingLBA > header->LastUsableLBA) { + warnx("Ending LBA %" PRIu64 " greater than last usable LBA %" PRIu64, entry->EndingLBA, + header->LastUsableLBA); + goto fail; + } + used_entries = i + 1; + } + if (used_entries == 0) { + // This corresponds to a disk with no partitions at all, which + // makes no real sense. + errx(EXIT_UNSUPPORTED, "No used partitions"); + } + gpt->used_entries = used_entries; + return gpt; +fail: + free(entries_buffer); + free(header_buffer); + return NULL; +} + +static void print_gpts(struct GPT *gpt) { + struct GPTHeader *header = &gpt->header; + vlog("GPT Partition Table Header:"); + vlog(" Revision: 0x%08" PRIx32, header->Revision); + vlog(" HeaderSize: %" PRIu32, header->HeaderSize); + vlog(" HeaderCRC32: %" PRIu32, header->HeaderCRC32); + vlog(" Reserved: %" PRIu32, header->Reserved); + vlog(" MyLBA: %" PRIu64, header->MyLBA); + vlog(" AlternateLBA: %" PRIu64, header->AlternateLBA); + vlog(" FirstUsableLBA: %" PRIu64, header->FirstUsableLBA); + vlog(" LastUsableLBA: %" PRIu64, header->LastUsableLBA); + vlog(" DiskGUID[16]: " + "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + header->DiskGUID.first, header->DiskGUID.second, header->DiskGUID.third, + header->DiskGUID.rest[0], header->DiskGUID.rest[1], header->DiskGUID.rest[2], header->DiskGUID.rest[3], + header->DiskGUID.rest[4], header->DiskGUID.rest[5], header->DiskGUID.rest[6], header->DiskGUID.rest[7]); + vlog(" PartitionEntryLBA: %" PRIu64, header->PartitionEntryLBA); + vlog(" NumberOfPartitionEntries: %" PRIu32, header->NumberOfPartitionEntries); + vlog(" SizeOfPartitionEntry: %" PRIu32, header->SizeOfPartitionEntry); + vlog(" PartitionEntryArrayCRC32: %" PRIu32, header->PartitionEntryArrayCRC32); + uint32_t i; + const struct GPTPartitionEntry *entry; + for_each_used_gpt_entry (i, gpt, entry) { + vlog("GPT Partition Table Entry:"); + vlog(" PartitionTypeGUID: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + entry->PartitionTypeGUID.first, entry->PartitionTypeGUID.second, entry->PartitionTypeGUID.third, + entry->PartitionTypeGUID.rest[0], entry->PartitionTypeGUID.rest[1], entry->PartitionTypeGUID.rest[2], entry->PartitionTypeGUID.rest[3], + entry->PartitionTypeGUID.rest[4], entry->PartitionTypeGUID.rest[5], entry->PartitionTypeGUID.rest[6], entry->PartitionTypeGUID.rest[7]); + vlog(" UniquePartitionGUID: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", + entry->UniquePartitionGUID.first, entry->UniquePartitionGUID.second, entry->UniquePartitionGUID.third, + entry->UniquePartitionGUID.rest[0], entry->UniquePartitionGUID.rest[1], entry->UniquePartitionGUID.rest[2], entry->UniquePartitionGUID.rest[3], + entry->UniquePartitionGUID.rest[4], entry->UniquePartitionGUID.rest[5], entry->UniquePartitionGUID.rest[6], entry->UniquePartitionGUID.rest[7]); + vlog(" StartingLBA: 0x%" PRIx64, entry->StartingLBA); + vlog(" EndingLBA: 0x%" PRIx64, entry->EndingLBA); + vlog(" Attributes: 0x%016" PRIx64, entry->Attributes); + } +} + +static void sync_pwrite(int fd, const void *data, uint64_t length, uint64_t offset) { + assert(fd >= 0); + assert(data != NULL); + assert(length <= SSIZE_MAX); + assert((off_t)offset >= 0); + assert((uint64_t)(off_t)offset == offset); + + ssize_t res = pwrite64(fd, data, (size_t)length, (off_t)offset); + if (res == -1) + err(EXIT_IO, "write error"); + if ((size_t)res != length) + errx(EXIT_IO, "short write: expected to write %" PRIu64 " bytes, but only wrote %zd", length, res); +} + +static void create_new_gpt(int fd, uint64_t sector_to_write, uint64_t sectors, uint32_t sector_size, + struct GPT *gpt, struct Mbr *mbr, bool verbose) { + struct GPT *new_gpt; + uint64_t to_write = 0; + new_gpt = gpt_adjust_sectors(sector_to_write, sector_size, sectors, gpt, &to_write, verbose); + assert(new_gpt->entries); + assert(check_gpt_header(&new_gpt->header, sector_to_write, sector_size, sectors, false) != 0); + assert(new_gpt->entries); + if (verbose) { + print_gpts(new_gpt); + } + assert(new_gpt->entries); + assert(to_write); + void *entries = new_gpt->entries; + assert(entries != NULL); + memset((char *)new_gpt + offsetof(struct GPT, used_entries), 0, sector_size - offsetof(struct GPT, used_entries)); + const uint64_t big_size = 4096; + char *buf = malloc(big_size); + if (buf == NULL) + err(EXIT_FAILURE, "malloc(%" PRIu64 ")", big_size); + memset(buf, 0, big_size); + if (sector_to_write == sectors - 1) { + // Writing backup GPT. gpt_adjust_sectors() checked that there is no user + // data in the area going to be overwritten. + sync_pwrite(fd, buf, big_size, sector_size * sectors - big_size); + } else if (sector_to_write == 1) { + // Writing primary GPT. + if (sector_size == big_size) { + assert(gpt->sector_size == 512); + // Write the pMBR followed by (4096 - 512) bytes of zeroes in a single (atomic) operation. + // This clobbers the 512-byte-sector GPT header, and there is no GPT header for 4096-byte sectors + // or otherwise this code would not be running. Note that mbr points to sector_size valid bytes here. + sync_pwrite(fd, mbr, sector_size, 0); + } else { + assert(sector_size == 512); + assert(gpt->sector_size == big_size); + // Write the pMBR. + sync_pwrite(fd, mbr, sector_size, 0); + // Overwrite the 512-byte-sector GPT header, which definitely did not exist. + sync_pwrite(fd, buf, 512, 512); + // Overwrite the 4096-byte-sector GPT header. If this existed, a backup GPT (for 512-byte-sectors) has been + // written. This is not atomic, but overwriting the first sector of the header (its own header) _is_ + // atomic. + sync_pwrite(fd, buf, big_size, big_size); + } + } else { + assert(0); + } + sync_pwrite(fd, entries, to_write, new_gpt->header.PartitionEntryLBA * sector_size); + sync_pwrite(fd, new_gpt, sector_size, new_gpt->header.MyLBA * sector_size); + free(buf); + free(new_gpt); + free(entries); +} + +// Refuse to operate on a partition. There is no convienient ioctl() interface +// to check if a device is a partition, so sysfs must be used instead. The +// Linux block device API really is terrible :(. +static void check_whole_block_device(struct stat *buf) +{ + char *buffer; + struct stat new_statbuf; + int buflen = asprintf(&buffer, "/sys/dev/block/%ju:%ju/partition", + (uintmax_t)major(buf->st_rdev), + (uintmax_t)minor(buf->st_rdev)); + if (buflen < (int)sizeof("/sys/dev/block/1:1/partition") - 1) { + errx(1, "asprintf() problem"); + } + switch (lstat(buffer, &new_statbuf)) { + case 0: + if ((new_statbuf.st_mode & S_IFMT) != S_IFREG) { + errx(1, "bad sysfs entry (\"partition\" attribute is not regular file)"); + } else { + errx(1, "Refusing to operate on partition: pass the whole block device instead"); + } + case -1: + if (errno == ENOENT) { + free(buffer); + return; + } + err(1, "lstat(\"%s\") failed: cannot check if device is a partition", buffer); + default: + abort(); + } +} + +static const struct option options[] = { + { "unsafe-skip-bpb-check", no_argument, NULL, 'b' }, + { "verbose", no_argument, NULL, 'v' }, + { NULL, 0, NULL, 0 }, +}; + +int main(int argc, char **argv) { + const char *dev = NULL; + bool verbose = false; + bool skip_bpb_check = false; + enum { + MODE_UNKNOWN, + MODE_CHECK, + MODE_FIX, + } mode; + + log_file = stderr; + for (;;) { + int longindex; + int lastind = optind; + switch (getopt_long(argc, argv, "+", options, &longindex)) { + case '?': + case ':': + goto usage; + case 'b': + skip_bpb_check = true; + break; + case 'v': + verbose = true; + break; + case 'h': + log_file = stdout; + goto usage; + case -1: + /* Check if "--" was provided */ + if (argc - optind != 2) { + goto usage; + } + if (optind - lastind != 1 || lastind < 1 || strcmp(argv[lastind], "--") != 0) { + for (int j = optind; j < argc; ++j) { + if (argv[j][0] == '-') { + errx(1, "Use '--' to end options if a non-option argument starts with '-'"); + } + } + } + if (strcmp(argv[optind], "check") == 0) { + mode = MODE_CHECK; + } else if (strcmp(argv[optind], "fix") == 0) { + mode = MODE_FIX; + } else { + goto usage; + } + dev = argv[optind + 1]; + // blame C for not having nested break + goto okay; + default: + abort(); + } + } +okay:; + + int const fd = open(dev, O_CLOEXEC | O_NOCTTY |O_RDONLY); + if (fd < 0) { + err(EXIT_FAILURE, "Cannot open %s", dev); + } + + if (fd < 3) { + // Caller messed up! We do _not_ want to accidentally overwrite the block device + // with a message, so exit immediately before printing anything. + return EXIT_IO; + } + + struct stat statbuf; + if (fstat(fd, &statbuf)) { + err(EXIT_IO, "fstat()"); + } + + if ((statbuf.st_mode & S_IFMT) != S_IFBLK) { + errx(EXIT_FAILURE, "%s is not a block device", dev); + } + + check_whole_block_device(&statbuf); + // Avoid racing with systemd-udevd + if (flock(fd, LOCK_EX)) { + err(EXIT_FAILURE, "%s cannot be locked", dev); + } + + int signed_sector_size; + if (!bool_ioctl(fd, BLKSSZGET, &signed_sector_size)) { + err(EXIT_FAILURE, "ioctl(%d, BLKSSZGET, %p)", fd, (void *)&signed_sector_size); + } + + const uint32_t sector_size = (uint32_t)signed_sector_size; // size in bytes of a sector + + if (sector_size != 512 && sector_size != 4096) { + errx(EXIT_UNSUPPORTED, "block size is 0x%" PRIX32 " is neither 512 or 4096 and we don't support that", + sector_size); + } + + uint64_t size; // size in bytes + if (!bool_ioctl(fd, BLKGETSIZE64, &size)) { + err(EXIT_IO, "ioctl(%d, BLKGETSIZE64, %p)", fd, (void *)&size); + } + + if ((off_t)size < 0 || (uint64_t)(off_t)size != size) { + errx(EXIT_UNSUPPORTED, "size 0x%" PRIx64 " does not fit in off_t", size); + } + + if (size % sector_size != 0) { + errx(EXIT_UNSUPPORTED, "size 0x%" PRIx64 " is not multiple of sector size 0x%" PRIx32, size, sector_size); + } + const uint64_t sectors = size / sector_size; // number of sectors + if (sectors < 6) { + errx(EXIT_UNSUPPORTED, "Device has too few sectors for GPT: need 6, found %" PRIu64, sectors); + } + + // Ensure the kernel buffer cache is in sync with the underlying device. + if (fsync(fd)) { + err(EXIT_IO, "fsync(\"%s\")", dev); + } + + char *buffer = malloc(sector_size); + if (pread(fd, buffer, sector_size, 0) != sector_size) { + err(EXIT_IO, "Failed reading MBR from block device or short read"); + } + memset(buffer + sizeof(struct Mbr), 0, sector_size - sizeof(struct Mbr)); + static_assert(sizeof(struct Mbr) <= 512, "Bad MBR size"); + check_mbr((struct Mbr *)buffer, sectors, skip_bpb_check, verbose); + if (verbose) { + vlog("Sector size is %" PRIu32 ", device size is 0x%" PRIx64 ", 0x%" PRIx64 " sectors", sector_size, size, sectors); + } + + struct GPT *gpt = read_and_check_gpt(fd, 1, sector_size, sectors, verbose); + if (gpt == NULL) { + gpt = read_and_check_gpt(fd, sectors - 1, sector_size, sectors, verbose); + } + bool try_changing = gpt == NULL; + if (try_changing) { + if (size % 4096) { + errx(EXIT_UNSUPPORTED, "Disk size is not a multiple of 4096"); + } + uint32_t old_sector_size = (sector_size == 4096) ? 512 : 4096; + uint64_t old_sectors = (sector_size == 4096) ? (sectors << 3) : (sectors >> 3); + bool used_primary = true; + gpt = read_and_check_gpt(fd, 1, old_sector_size, old_sectors, verbose); + if (gpt == NULL) { + used_primary = false; + gpt = read_and_check_gpt(fd, old_sectors - 1, old_sector_size, old_sectors, verbose); + } + if (gpt == NULL) { + errx(EXIT_INVALID_GPT, "Cannot find valid GPT"); + } + if (mode == MODE_FIX) { + // Reopen device for writing + { + char *buf; + int r = asprintf(&buf, "/proc/self/fd/%d", fd); + if (r < (int)sizeof("/proc/self/fd/")) { + err(1, "asprintf()"); + } + int const new_fd = open(buf, O_CLOEXEC | O_NOCTTY | O_RDWR | O_SYNC | O_EXCL); + if (new_fd == -1) { + err(1, "cannot reopen block device %s for writing", dev); + } + if (dup3(new_fd, fd, O_CLOEXEC) == -1) { + err(1, "Cannot duplicate %d over %d", new_fd, fd); + } + if (close(new_fd) == -1) { + err(1, "Cannot close %d", new_fd); + } + free(buf); + } + warnx("Found GPT with different sector size, altering"); + // "First, do no harm": always start by overwriting the GPT we + // did *not* use, so there is always at least one good GPT! + create_new_gpt(fd, used_primary ? sectors - 1 : 1, sectors, sector_size, gpt, + (struct Mbr *)buffer, verbose); + create_new_gpt(fd, used_primary ? 1 : sectors - 1, sectors, sector_size, gpt, + (struct Mbr *)buffer, verbose); + } else { + assert(mode == MODE_CHECK); + errx(EXIT_NEEDS_FIX, "GPT needs to be updated for new sector size"); + } + } else { + if (mode == MODE_CHECK && verbose) { + print_gpts(gpt); + } + } + + free(buffer); + free(gpt->entries); + free(gpt); + + if (mode == MODE_FIX && try_changing) { + if (!bool_ioctl(fd, BLKRRPART, NULL) && errno != EINVAL) { + err(EXIT_IO, "Cannot reload kernel partition table"); + } + } + + close(fd); + fflush(NULL); + if (ferror(stdout)) { + err(EXIT_IO, "Write error on stdout"); + } + return 0; + +usage: + vlog("Usage:"); + vlog(" gptfix [OPTIONS] [--] [check|fix] block-device-path\n%s:", program_invocation_name); + vlog("Options:"); + vlog("--unsafe-skip-bpb-check Do not check for a BIOS Parameter Block (BPB) before making changes."); + vlog(" WARNING: in some (EXTREMELY rare) cases, this can corrupt a"); + vlog(" FAT or NTFS file system that happens to have a valid GPT."); + vlog("--verbose Log a bunch of messages to stderr."); + vlog("--help Print this message."); + return log_file == stderr ? EXIT_FAILURE : 0; +} diff --git a/initramfs-tools/qubes_vm b/initramfs-tools/qubes_vm index 3a0b7d4c..9aa099e3 100755 --- a/initramfs-tools/qubes_vm +++ b/initramfs-tools/qubes_vm @@ -17,5 +17,6 @@ esac copy_exec /sbin/sfdisk copy_exec /sbin/mkswap +copy_exec /usr/sbin/gptfix force_load xen-blkfront force_load dm-snapshot diff --git a/rpm_spec/qubes-kernel-vm-support.spec.in b/rpm_spec/qubes-kernel-vm-support.spec.in index fecedb9a..55d67bf5 100644 --- a/rpm_spec/qubes-kernel-vm-support.spec.in +++ b/rpm_spec/qubes-kernel-vm-support.spec.in @@ -38,6 +38,7 @@ Requires: e2fsprogs BuildRequires: make BuildRequires: gcc BuildRequires: e2fsprogs-devel +BuildRequires: pkgconfig(zlib) %if 0%{?is_opensuse} # for directory ownership BuildRequires: dracut @@ -54,9 +55,11 @@ initrd is provided by dom0. %build make -C kernel-modules all +make gptfixer/gpt %install make install-fedora-kernel-support DESTDIR=%{buildroot} +make install-gptfix SBINDIR=%_sbindir DESTDIR=%{buildroot} %files %dir %{_prefix}/lib/qubes @@ -65,7 +68,8 @@ make install-fedora-kernel-support DESTDIR=%{buildroot} /usr/lib/dracut/modules.d/90qubes-vm-simple /usr/lib/dracut/modules.d/80xen-scrub-pages /usr/lib/qubes/vm-modules-genfs -/usr/sbin/qubes-prepare-vm-kernel +%_sbindir/qubes-prepare-vm-kernel +%_sbindir/gptfix %config(noreplace) /etc/default/grub.qubes-kernel-vm-support %triggerin -- grub2-tools From 3fbb2148452711cc644be02c204acd8648d3bfc3 Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Mon, 25 Nov 2024 18:11:14 -0500 Subject: [PATCH 2/3] Fix up the GPT in the initramfs This adjusts the GPT in the initramfs, and (obviously) requires the previous commit. Fixes: QubesOS/qubes-issues#4974 --- dracut/full-dmroot/module-setup.sh | 3 ++- dracut/full-dmroot/qubes_cow_setup.sh | 19 ++++++++++++++++--- dracut/simple/init.sh | 21 ++++++++++++++++----- dracut/simple/module-setup.sh | 3 ++- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/dracut/full-dmroot/module-setup.sh b/dracut/full-dmroot/module-setup.sh index e4e90631..05e368c5 100644 --- a/dracut/full-dmroot/module-setup.sh +++ b/dracut/full-dmroot/module-setup.sh @@ -18,5 +18,6 @@ install() { inst_multiple \ sfdisk \ swapon \ - mkswap + mkswap \ + gptfix } diff --git a/dracut/full-dmroot/qubes_cow_setup.sh b/dracut/full-dmroot/qubes_cow_setup.sh index 8bc214be..e8815598 100644 --- a/dracut/full-dmroot/qubes_cow_setup.sh +++ b/dracut/full-dmroot/qubes_cow_setup.sh @@ -54,19 +54,32 @@ modprobe xenblk || modprobe xen-blkfront || warn "Qubes: Cannot load Xen Block F log_begin "Waiting for /dev/xvda* devices..." udevadm settle --exit-if-exists=/dev/xvda -log_end # prefer partition if exists -if [ -b /dev/xvda1 ]; then +if /usr/sbin/gptfix fix /dev/xvda; then + udevadm settle --exit-if-exists=/dev/xvda1 if [ -e "/dev/disk/by-partlabel/Root\\x20filesystem" ]; then ROOT_DEV=$(readlink "/dev/disk/by-partlabel/Root\\x20filesystem") ROOT_DEV=${ROOT_DEV##*/} else ROOT_DEV=xvda3 fi + if ! [ -b "/dev/$ROOT_DEV" ]; then udevadm settle "--exit-if-exists=/dev/$ROOT_DEV"; fi else - ROOT_DEV=xvda + status=$? + case $status in + (1|2) # EIO, ENOMEM, or bug. Fatal. + die 'Fatal error reading partition table';; + (4|5|8) # Bad or no partition table + ROOT_DEV=xvda;; + (*) + # TODO: what should be done? + # This is things like: + # - "Partition table not supported" + die 'GPT cannot be fixed';; + esac fi +log_end SWAP_SIZE_GiB=1 SWAP_SIZE_512B=$(( SWAP_SIZE_GiB * 1024 * 1024 * 2 )) diff --git a/dracut/simple/init.sh b/dracut/simple/init.sh index e5299fb1..a8e0c1ec 100644 --- a/dracut/simple/init.sh +++ b/dracut/simple/init.sh @@ -28,22 +28,33 @@ die() { echo "Waiting for /dev/xvda* devices..." while ! [ -e /dev/xvda ]; do sleep 0.1; done - -# prefer partition if exists -if [ -b /dev/xvda1 ]; then +# Fix up partition tables +if /usr/sbin/gptfix fix /dev/xvda; then + while ! [ -e /dev/xvda1 ]; do sleep 0.01; done if [ -d /dev/disk/by-partlabel ]; then ROOT_DEV=$(readlink "/dev/disk/by-partlabel/Root\\x20filesystem") ROOT_DEV=${ROOT_DEV##*/} else - ROOT_DEV=$(grep -l "PARTNAME=Root filesystem" /sys/block/xvda/xvda*/uevent |\ + ROOT_DEV=$(grep -l "PARTNAME=Root filesystem" /sys/block/xvda/xvda*/uevent | grep -o "xvda[0-9]") fi if [ -z "$ROOT_DEV" ]; then # fallback to third partition ROOT_DEV=xvda3 fi + while ! [ -b "/dev/$ROOT_DEV" ]; do sleep 0.01; done else - ROOT_DEV=xvda + case $? in + (1|2) # EIO, ENOMEM, or bug. Fatal. + die 'Fatal error reading partition table';; + (4|5|8) # Bad or no partition table + ROOT_DEV=xvda;; + (*) + # TODO: what should be done? + # - "Partition table not supported" + # - "Disk truncated" + die 'GPT cannot be fixed or disk truncated';; + esac fi SWAP_SIZE_GiB=1 diff --git a/dracut/simple/module-setup.sh b/dracut/simple/module-setup.sh index 2dd6d1f9..1dd2ee75 100644 --- a/dracut/simple/module-setup.sh +++ b/dracut/simple/module-setup.sh @@ -29,5 +29,6 @@ install() { rmdir \ sleep \ sfdisk \ - mkswap + mkswap \ + gptfix } From f3a860de86a251eb7ce463a9a84d81eb97fa965d Mon Sep 17 00:00:00 2001 From: Demi Marie Obenour Date: Sat, 30 Nov 2024 00:07:38 -0500 Subject: [PATCH 3/3] Add test case No functional change. The large number of partitions in the test is to ensure that the code correctly handles such partition tables without a buffer overflow. Previous versions of the code did have a buffer overflow due to incorrectly computing the buffer size. --- .gitlab-ci.yml | 2 + gptfixer/layout | 42 +++++++++ gptfixer/test.sh | 38 ++++++++ gptfixer/test.sh.stderr | 27 ++++++ gptfixer/test.sh.stdout | 195 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 304 insertions(+) create mode 100644 gptfixer/layout create mode 100755 gptfixer/test.sh create mode 100644 gptfixer/test.sh.stderr create mode 100644 gptfixer/test.sh.stdout diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e3f4fadd..d7a3e608 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,7 @@ checks:tests: after_script: - (cd qrexec-lib; gcov *.c || :) + - (cd gptfixer; gcov *.c || :) - ./ci/codecov-wrapper --gcov before_script: - sudo dnf install -y python3-pip 'pkgconfig(icu-uc)' sequoia-sqv @@ -11,6 +12,7 @@ checks:tests: - python3 -m coverage run -m unittest discover -v imgconverter -p test.py - export CFLAGS="--coverage -DCOVERAGE" LDFLAGS=--coverage - make -C qrexec-lib check NO_REBUILD_TABLE=1 + - gptfixer/test.sh stage: checks tags: - docker diff --git a/gptfixer/layout b/gptfixer/layout new file mode 100644 index 00000000..2f618261 --- /dev/null +++ b/gptfixer/layout @@ -0,0 +1,42 @@ +label: gpt +label-id: F4796A2A-E377-45BD-B539-D6D49E569055 +device: /dev/loop0 +unit: sectors +first-lba: 24 +sector-size: 512 +table-length: 33 +last-lba: 41943029 + +/dev/loop0p1 : start=32, size=409592, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=FA4D6529-56DA-47C7-AE88-E2DFECB72621, name="EFI System" +/dev/loop0p2 : start=411648, size= 4096, type=21686148-6449-6E6F-744E-656564454649, uuid=1E6C9DB4-1E91-46C4-846A-2030DCB13B8C, name="BIOS boot partition" +/dev/loop0p3 : start=415744, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=5DCECC9F-71B8-4357-8039-1BEBB7A48653, name="/home" +/dev/loop0p4 : start=419840, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=3EA4A03F-ACCF-4EDF-99EC-8E5F2B382459, name="/usr" +/dev/loop0p5 : start=423944,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p6 : start=423952,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p7 : start=423960,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p8 : start=423968,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p9 : start=423976,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p10 : start=423984,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p11 : start=423992,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p12 : start=424000,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p13 : start=424008,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p14 : start=424016,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p15 : start=424024,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p16 : start=424032,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p17 : start=424040,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p18 : start=424048,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p19 : start=424056,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p20 : start=424064,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p21 : start=424072,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p22 : start=424080,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p23 : start=424088,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p24 : start=424096,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p25 : start=424104,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p26 : start=424112,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p27 : start=424120,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p28 : start=424128,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p29 : start=424136,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p30 : start=424144,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p31 : start=424152,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p32 : start=424160,size=8, type=00000000-0000-0000-0000-000000000000,uuid=00000000-0000-0000-0000-000000000000,name="" +/dev/loop0p33 : start=424168,size=41518848,type=0FC63DAF-8483-4772-8E79-3D69D8477DE4,uuid=693244E6-3E07-47BF-AD79-ACADE4293FE7,name="Root filesystem" diff --git a/gptfixer/test.sh b/gptfixer/test.sh new file mode 100755 index 00000000..5204e6bc --- /dev/null +++ b/gptfixer/test.sh @@ -0,0 +1,38 @@ +#!/bin/bash -- +set -euo pipefail + +case $0 in (/*) cd "${0%/*}/";; (*/*) cd "./${0%/*}";; (*) :;; esac +make -C .. gptfixer/gpt +chk () { + loopdev=$(sudo losetup --nooverlap --find --sector-size "$1" --show -- dummy.img) + if [[ "$loopdev" != '/dev/loop0' ]]; then + printf 'Loop device is not /dev/loop0 (got %q), expect test failure\n' "$loopdev" + fi >&3 + echo Dumping broken partition table + sudo sfdisk --label=gpt --dump -- "$loopdev" + sudo ./gpt fix "$loopdev" + echo Dumping fixed partition table + sudo sfdisk --label=gpt --dump -- "$loopdev" + sudo losetup -d "$loopdev" +} + +go () ( + set -x + truncate -s 0 dummy.img + truncate -s 20GiB dummy.img + sfdisk --force dummy.img < layout + chk 4096 + chk 512 +) + +case "$#,${1-}" in +('1,update') go 3>&2 > test.sh.stdout 2> test.sh.stderr;; +(0,) + tmpdir=$(mktemp -d) + go 3>&2 > "$tmpdir/stdout" 2> "$tmpdir/stderr" + diff -u -- "$tmpdir/stdout" test.sh.stdout + diff -u -- "$tmpdir/stderr" test.sh.stderr + rm -rf -- "$tmpdir" +;; +(*) echo "Usage: test.sh [update]" >&2; exit 1;; +esac diff --git a/gptfixer/test.sh.stderr b/gptfixer/test.sh.stderr new file mode 100644 index 00000000..e52d9a2b --- /dev/null +++ b/gptfixer/test.sh.stderr @@ -0,0 +1,27 @@ ++ truncate -s 0 dummy.img ++ truncate -s 20GiB dummy.img ++ sfdisk --force dummy.img ++ chk 4096 +++ sudo losetup --nooverlap --find --sector-size 4096 --show -- dummy.img ++ loopdev=/dev/loop0 ++ [[ /dev/loop0 != \/\d\e\v\/\l\o\o\p\0 ]] ++ echo Dumping broken partition table ++ sudo sfdisk --label=gpt --dump -- /dev/loop0 +GPT PMBR size mismatch (41943039 != 5242879) will be corrected by write. ++ sudo ./gpt fix /dev/loop0 +gpt: Found GPT with different sector size, altering ++ echo Dumping fixed partition table ++ sudo sfdisk --label=gpt --dump -- /dev/loop0 ++ sudo losetup -d /dev/loop0 ++ chk 512 +++ sudo losetup --nooverlap --find --sector-size 512 --show -- dummy.img ++ loopdev=/dev/loop0 ++ [[ /dev/loop0 != \/\d\e\v\/\l\o\o\p\0 ]] ++ echo Dumping broken partition table ++ sudo sfdisk --label=gpt --dump -- /dev/loop0 +GPT PMBR size mismatch (5242879 != 41943039) will be corrected by write. ++ sudo ./gpt fix /dev/loop0 +gpt: Found GPT with different sector size, altering ++ echo Dumping fixed partition table ++ sudo sfdisk --label=gpt --dump -- /dev/loop0 ++ sudo losetup -d /dev/loop0 diff --git a/gptfixer/test.sh.stdout b/gptfixer/test.sh.stdout new file mode 100644 index 00000000..48179d21 --- /dev/null +++ b/gptfixer/test.sh.stdout @@ -0,0 +1,195 @@ +Checking that no-one is using this disk right now ... OK + +Disk dummy.img: 20 GiB, 21474836480 bytes, 41943040 sectors +Units: sectors of 1 * 512 = 512 bytes +Sector size (logical/physical): 512 bytes / 512 bytes +I/O size (minimum/optimal): 512 bytes / 512 bytes + +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Script header accepted. +>>> Created a new GPT disklabel (GUID: F4796A2A-E377-45BD-B539-D6D49E569055). +The maximal number of partitions is 33 (default is 128). +dummy.img1: Created a new partition 1 of type 'EFI System' and of size 200 MiB. +dummy.img2: Created a new partition 2 of type 'BIOS boot' and of size 2 MiB. +dummy.img3: Created a new partition 3 of type 'Linux filesystem' and of size 2 MiB. +dummy.img4: Created a new partition 4 of type 'Linux filesystem' and of size 2 MiB. +dummy.img5: Created a new partition 5 of type 'unknown' and of size 4 KiB. +dummy.img6: Created a new partition 6 of type 'unknown' and of size 4 KiB. +dummy.img7: Created a new partition 7 of type 'unknown' and of size 4 KiB. +dummy.img8: Created a new partition 8 of type 'unknown' and of size 4 KiB. +dummy.img9: Created a new partition 9 of type 'unknown' and of size 4 KiB. +dummy.img10: Created a new partition 10 of type 'unknown' and of size 4 KiB. +dummy.img11: Created a new partition 11 of type 'unknown' and of size 4 KiB. +dummy.img12: Created a new partition 12 of type 'unknown' and of size 4 KiB. +dummy.img13: Created a new partition 13 of type 'unknown' and of size 4 KiB. +dummy.img14: Created a new partition 14 of type 'unknown' and of size 4 KiB. +dummy.img15: Created a new partition 15 of type 'unknown' and of size 4 KiB. +dummy.img16: Created a new partition 16 of type 'unknown' and of size 4 KiB. +dummy.img17: Created a new partition 17 of type 'unknown' and of size 4 KiB. +dummy.img18: Created a new partition 18 of type 'unknown' and of size 4 KiB. +dummy.img19: Created a new partition 19 of type 'unknown' and of size 4 KiB. +dummy.img20: Created a new partition 20 of type 'unknown' and of size 4 KiB. +dummy.img21: Created a new partition 21 of type 'unknown' and of size 4 KiB. +dummy.img22: Created a new partition 22 of type 'unknown' and of size 4 KiB. +dummy.img23: Created a new partition 23 of type 'unknown' and of size 4 KiB. +dummy.img24: Created a new partition 24 of type 'unknown' and of size 4 KiB. +dummy.img25: Created a new partition 25 of type 'unknown' and of size 4 KiB. +dummy.img26: Created a new partition 26 of type 'unknown' and of size 4 KiB. +dummy.img27: Created a new partition 27 of type 'unknown' and of size 4 KiB. +dummy.img28: Created a new partition 28 of type 'unknown' and of size 4 KiB. +dummy.img29: Created a new partition 29 of type 'unknown' and of size 4 KiB. +dummy.img30: Created a new partition 30 of type 'unknown' and of size 4 KiB. +dummy.img31: Created a new partition 31 of type 'unknown' and of size 4 KiB. +dummy.img32: Created a new partition 32 of type 'unknown' and of size 4 KiB. +dummy.img33: Created a new partition 33 of type 'Linux filesystem' and of size 19.8 GiB. +All partitions used. + +New situation: +Disklabel type: gpt +Disk identifier: F4796A2A-E377-45BD-B539-D6D49E569055 + +Device Start End Sectors Size Type +dummy.img1 32 409623 409592 200M EFI System +dummy.img2 411648 415743 4096 2M BIOS boot +dummy.img3 415744 419839 4096 2M Linux filesystem +dummy.img4 419840 423935 4096 2M Linux filesystem +dummy.img5 423944 423951 8 4K unknown +dummy.img6 423952 423959 8 4K unknown +dummy.img7 423960 423967 8 4K unknown +dummy.img8 423968 423975 8 4K unknown +dummy.img9 423976 423983 8 4K unknown +dummy.img10 423984 423991 8 4K unknown +dummy.img11 423992 423999 8 4K unknown +dummy.img12 424000 424007 8 4K unknown +dummy.img13 424008 424015 8 4K unknown +dummy.img14 424016 424023 8 4K unknown +dummy.img15 424024 424031 8 4K unknown +dummy.img16 424032 424039 8 4K unknown +dummy.img17 424040 424047 8 4K unknown +dummy.img18 424048 424055 8 4K unknown +dummy.img19 424056 424063 8 4K unknown +dummy.img20 424064 424071 8 4K unknown +dummy.img21 424072 424079 8 4K unknown +dummy.img22 424080 424087 8 4K unknown +dummy.img23 424088 424095 8 4K unknown +dummy.img24 424096 424103 8 4K unknown +dummy.img25 424104 424111 8 4K unknown +dummy.img26 424112 424119 8 4K unknown +dummy.img27 424120 424127 8 4K unknown +dummy.img28 424128 424135 8 4K unknown +dummy.img29 424136 424143 8 4K unknown +dummy.img30 424144 424151 8 4K unknown +dummy.img31 424152 424159 8 4K unknown +dummy.img32 424160 424167 8 4K unknown +dummy.img33 424168 41943015 41518848 19.8G Linux filesystem + +The partition table has been altered. +Syncing disks. +Dumping broken partition table +label: dos +label-id: 0x00000000 +device: /dev/loop0 +unit: sectors +sector-size: 4096 + +/dev/loop0p1 : start= 1, size= 5242879, type=ee +Dumping fixed partition table +label: gpt +label-id: F4796A2A-E377-45BD-B539-D6D49E569055 +device: /dev/loop0 +unit: sectors +first-lba: 4 +last-lba: 5242876 +table-length: 33 +sector-size: 4096 + +/dev/loop0p1 : start= 4, size= 51199, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=FA4D6529-56DA-47C7-AE88-E2DFECB72621, name="EFI System" +/dev/loop0p2 : start= 51456, size= 512, type=21686148-6449-6E6F-744E-656564454649, uuid=1E6C9DB4-1E91-46C4-846A-2030DCB13B8C, name="BIOS boot partition" +/dev/loop0p3 : start= 51968, size= 512, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=5DCECC9F-71B8-4357-8039-1BEBB7A48653, name="/home" +/dev/loop0p4 : start= 52480, size= 512, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=3EA4A03F-ACCF-4EDF-99EC-8E5F2B382459, name="/usr" +/dev/loop0p5 : start= 423944, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p6 : start= 423952, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p7 : start= 423960, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p8 : start= 423968, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p9 : start= 423976, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p10 : start= 423984, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p11 : start= 423992, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p12 : start= 424000, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p13 : start= 424008, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p14 : start= 424016, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p15 : start= 424024, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p16 : start= 424032, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p17 : start= 424040, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p18 : start= 424048, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p19 : start= 424056, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p20 : start= 424064, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p21 : start= 424072, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p22 : start= 424080, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p23 : start= 424088, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p24 : start= 424096, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p25 : start= 424104, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p26 : start= 424112, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p27 : start= 424120, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p28 : start= 424128, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p29 : start= 424136, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p30 : start= 424144, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p31 : start= 424152, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p32 : start= 424160, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p33 : start= 53021, size= 5189856, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=693244E6-3E07-47BF-AD79-ACADE4293FE7, name="Root filesystem" +Dumping broken partition table +label: dos +label-id: 0x00000000 +device: /dev/loop0 +unit: sectors +sector-size: 512 + +/dev/loop0p1 : start= 1, size= 41943039, type=ee +Dumping fixed partition table +label: gpt +label-id: F4796A2A-E377-45BD-B539-D6D49E569055 +device: /dev/loop0 +unit: sectors +first-lba: 32 +last-lba: 41943015 +table-length: 33 +sector-size: 512 + +/dev/loop0p1 : start= 32, size= 409592, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=FA4D6529-56DA-47C7-AE88-E2DFECB72621, name="EFI System" +/dev/loop0p2 : start= 411648, size= 4096, type=21686148-6449-6E6F-744E-656564454649, uuid=1E6C9DB4-1E91-46C4-846A-2030DCB13B8C, name="BIOS boot partition" +/dev/loop0p3 : start= 415744, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=5DCECC9F-71B8-4357-8039-1BEBB7A48653, name="/home" +/dev/loop0p4 : start= 419840, size= 4096, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=3EA4A03F-ACCF-4EDF-99EC-8E5F2B382459, name="/usr" +/dev/loop0p5 : start= 423944, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p6 : start= 423952, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p7 : start= 423960, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p8 : start= 423968, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p9 : start= 423976, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p10 : start= 423984, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p11 : start= 423992, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p12 : start= 424000, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p13 : start= 424008, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p14 : start= 424016, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p15 : start= 424024, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p16 : start= 424032, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p17 : start= 424040, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p18 : start= 424048, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p19 : start= 424056, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p20 : start= 424064, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p21 : start= 424072, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p22 : start= 424080, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p23 : start= 424088, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p24 : start= 424096, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p25 : start= 424104, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p26 : start= 424112, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p27 : start= 424120, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p28 : start= 424128, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p29 : start= 424136, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p30 : start= 424144, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p31 : start= 424152, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p32 : start= 424160, size= 8, type=00000000-0000-0000-0000-000000000000, uuid=00000000-0000-0000-0000-000000000000 +/dev/loop0p33 : start= 424168, size= 41518848, type=0FC63DAF-8483-4772-8E79-3D69D8477DE4, uuid=693244E6-3E07-47BF-AD79-ACADE4293FE7, name="Root filesystem"