The current GPT parser implemented in the block layer mixes blockdev-specific code with GPT concepts. Separate out device-agnostic GPT functions from the rest of the parser, in preparation for the creation of a new generic purpose GPT parser library. This mostly implies renaming functions and changing argument types. The only significant change is the new gpt_validate_header function, which has been separated out from the is_gpt_valid function. Signed-off-by: Romain Gantois <romain.gantois@xxxxxxxxxxx> --- block/partitions/efi.c | 199 ++++++++++++++++++++++------------------- include/linux/gpt.h | 37 +++++++- 2 files changed, 143 insertions(+), 93 deletions(-) diff --git a/block/partitions/efi.c b/block/partitions/efi.c index bac514a62d61..3630ebf4b997 100644 --- a/block/partitions/efi.c +++ b/block/partitions/efi.c @@ -151,7 +151,7 @@ static inline int pmbr_part_valid(gpt_mbr_record *part) } /** - * is_pmbr_valid(): test Protective MBR for validity + * gpt_is_pmbr_valid(): test Protective MBR for validity * @mbr: pointer to a legacy mbr structure * @total_sectors: amount of sectors in the device * @@ -168,7 +168,7 @@ static inline int pmbr_part_valid(gpt_mbr_record *part) * Returns 0 upon invalid MBR, or GPT_MBR_PROTECTIVE or * GPT_MBR_HYBRID depending on the device layout. */ -static int is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors) +int gpt_is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors) { uint32_t sz = 0; int i, part = 0, ret = 0; /* invalid by default */ @@ -324,166 +324,183 @@ static gpt_header *alloc_read_gpt_header(struct parsed_partitions *state, } /** - * is_gpt_valid() - tests one GPT header and PTEs for validity - * @state: disk parsed partitions - * @lba: logical block address of the GPT header to test - * @gpt: GPT header ptr, filled on return. - * @ptes: PTEs ptr, filled on return. + * gpt_validate_header() - tests one GPT header for validity + * @gpt: header to check + * @lba: logical block address of the GPT header to test + * @lba_size: logical block size of the partitioned device + * @lastlba: last logical block on the partitioned device * - * Description: returns 1 if valid, 0 on error. - * If valid, returns pointers to newly allocated GPT header and PTEs. + * Returns 0 if validation was successful. */ -static int is_gpt_valid(struct parsed_partitions *state, u64 lba, - gpt_header **gpt, gpt_entry **ptes) +int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size, + u64 lastlba) { u32 crc, origcrc; - u64 lastlba, pt_size; - - if (!ptes) - return 0; - if (!(*gpt = alloc_read_gpt_header(state, lba))) - return 0; + u64 pt_size; /* Check the GUID Partition Table signature */ - if (le64_to_cpu((*gpt)->signature) != GPT_HEADER_SIGNATURE) { - pr_debug("GUID Partition Table Header signature is wrong:" - "%lld != %lld\n", - (unsigned long long)le64_to_cpu((*gpt)->signature), + if (le64_to_cpu(gpt->signature) != GPT_HEADER_SIGNATURE) { + pr_debug("GUID Partition Table Header signature is wrong: %lld != %lld\n", + (unsigned long long)le64_to_cpu(gpt->signature), (unsigned long long)GPT_HEADER_SIGNATURE); - goto fail; + return -EINVAL; } /* Check the GUID Partition Table header size is too big */ - if (le32_to_cpu((*gpt)->header_size) > - queue_logical_block_size(state->disk->queue)) { + if (le32_to_cpu(gpt->header_size) > lba_size) { pr_debug("GUID Partition Table Header size is too large: %u > %u\n", - le32_to_cpu((*gpt)->header_size), - queue_logical_block_size(state->disk->queue)); - goto fail; + le32_to_cpu(gpt->header_size), lba_size); + return -EINVAL; } /* Check the GUID Partition Table header size is too small */ - if (le32_to_cpu((*gpt)->header_size) < sizeof(gpt_header)) { + if (le32_to_cpu(gpt->header_size) < sizeof(gpt_header)) { pr_debug("GUID Partition Table Header size is too small: %u < %zu\n", - le32_to_cpu((*gpt)->header_size), - sizeof(gpt_header)); - goto fail; + le32_to_cpu(gpt->header_size), + sizeof(gpt_header)); + return -EINVAL; } /* Check the GUID Partition Table CRC */ - origcrc = le32_to_cpu((*gpt)->header_crc32); - (*gpt)->header_crc32 = 0; - crc = efi_crc32((const unsigned char *) (*gpt), le32_to_cpu((*gpt)->header_size)); + origcrc = le32_to_cpu(gpt->header_crc32); + gpt->header_crc32 = 0; + crc = efi_crc32((const unsigned char *)gpt, le32_to_cpu(gpt->header_size)); if (crc != origcrc) { pr_debug("GUID Partition Table Header CRC is wrong: %x != %x\n", crc, origcrc); - goto fail; + return -EINVAL; } - (*gpt)->header_crc32 = cpu_to_le32(origcrc); + gpt->header_crc32 = cpu_to_le32(origcrc); /* Check that the my_lba entry points to the LBA that contains - * the GUID Partition Table */ - if (le64_to_cpu((*gpt)->my_lba) != lba) { + * the GUID Partition Table + */ + if (le64_to_cpu(gpt->my_lba) != lba) { pr_debug("GPT my_lba incorrect: %lld != %lld\n", - (unsigned long long)le64_to_cpu((*gpt)->my_lba), + (unsigned long long)le64_to_cpu(gpt->my_lba), (unsigned long long)lba); - goto fail; + return -EINVAL; } /* Check the first_usable_lba and last_usable_lba are * within the disk. */ - lastlba = last_lba(state->disk); - if (le64_to_cpu((*gpt)->first_usable_lba) > lastlba) { + if (le64_to_cpu(gpt->first_usable_lba) > lastlba) { pr_debug("GPT: first_usable_lba incorrect: %lld > %lld\n", - (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba), + (unsigned long long)le64_to_cpu(gpt->first_usable_lba), (unsigned long long)lastlba); - goto fail; + return -EINVAL; } - if (le64_to_cpu((*gpt)->last_usable_lba) > lastlba) { + if (le64_to_cpu(gpt->last_usable_lba) > lastlba) { pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n", - (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba), + (unsigned long long)le64_to_cpu(gpt->last_usable_lba), (unsigned long long)lastlba); - goto fail; + return -EINVAL; } - if (le64_to_cpu((*gpt)->last_usable_lba) < le64_to_cpu((*gpt)->first_usable_lba)) { + if (le64_to_cpu(gpt->last_usable_lba) < le64_to_cpu(gpt->first_usable_lba)) { pr_debug("GPT: last_usable_lba incorrect: %lld > %lld\n", - (unsigned long long)le64_to_cpu((*gpt)->last_usable_lba), - (unsigned long long)le64_to_cpu((*gpt)->first_usable_lba)); - goto fail; + (unsigned long long)le64_to_cpu(gpt->last_usable_lba), + (unsigned long long)le64_to_cpu(gpt->first_usable_lba)); + return -EINVAL; } + /* Check that sizeof_partition_entry has the correct value */ - if (le32_to_cpu((*gpt)->sizeof_partition_entry) != sizeof(gpt_entry)) { + if (le32_to_cpu(gpt->sizeof_partition_entry) != sizeof(gpt_entry)) { pr_debug("GUID Partition Entry Size check failed.\n"); - goto fail; + return -EINVAL; } /* Sanity check partition table size */ - pt_size = (u64)le32_to_cpu((*gpt)->num_partition_entries) * - le32_to_cpu((*gpt)->sizeof_partition_entry); + pt_size = (u64)get_pt_size(gpt); if (pt_size > KMALLOC_MAX_SIZE) { pr_debug("GUID Partition Table is too large: %llu > %lu bytes\n", (unsigned long long)pt_size, KMALLOC_MAX_SIZE); - goto fail; + return -EINVAL; } - if (!(*ptes = alloc_read_gpt_entries(state, *gpt))) - goto fail; + return 0; +} - /* Check the GUID Partition Entry Array CRC */ - crc = efi_crc32((const unsigned char *) (*ptes), pt_size); +/* Check the GUID Partition Entry Array CRC */ +int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes) +{ + u32 crc; - if (crc != le32_to_cpu((*gpt)->partition_entry_array_crc32)) { + crc = efi_crc32((const unsigned char *)ptes, get_pt_size(gpt)); + if (crc != le32_to_cpu(gpt->partition_entry_array_crc32)) { pr_debug("GUID Partition Entry Array CRC check failed.\n"); - goto fail_ptes; + return -EINVAL; } - /* We're done, all's well */ - return 1; - - fail_ptes: - kfree(*ptes); - *ptes = NULL; - fail: - kfree(*gpt); - *gpt = NULL; return 0; } /** - * is_pte_valid() - tests one PTE for validity - * @pte:pte to check - * @lastlba: last lba of the disk + * is_gpt_valid() - tests one GPT header and PTEs for validity + * @state: disk parsed partitions + * @lba: logical block address of the GPT header to test + * @gpt: GPT header ptr, filled on return. + * @ptes: PTEs ptr, filled on return. * * Description: returns 1 if valid, 0 on error. + * If valid, returns pointers to newly allocated GPT header and PTEs. */ -static inline int -is_pte_valid(const gpt_entry *pte, const u64 lastlba) +static int is_gpt_valid(struct parsed_partitions *state, u64 lba, + gpt_header **gpt, gpt_entry **ptes) { - if ((!efi_guidcmp(pte->partition_type_guid, NULL_GUID)) || - le64_to_cpu(pte->starting_lba) > lastlba || - le64_to_cpu(pte->ending_lba) > lastlba) + u64 lastlba; + + if (!ptes) return 0; + + *gpt = alloc_read_gpt_header(state, lba); + if (!(*gpt)) + return 0; + + lastlba = last_lba(state->disk); + if (gpt_validate_header(*gpt, lba, + queue_logical_block_size(state->disk->queue), + lastlba)) + goto fail; + + *ptes = alloc_read_gpt_entries(state, *gpt); + if (!(*ptes)) + goto fail; + + if (gpt_check_pte_array_crc(*gpt, *ptes)) + goto fail_ptes; + + /* We're done, all's well */ return 1; + +fail_ptes: + kfree(*ptes); + *ptes = NULL; +fail: + kfree(*gpt); + *gpt = NULL; + return 0; } /** - * compare_gpts() - Search disk for valid GPT headers and PTEs + * gpt_compare_alt() - Compares the Primary and Alternate GPT headers * @pgpt: primary GPT header * @agpt: alternate GPT header * @lastlba: last LBA number * - * Description: Returns nothing. Sanity checks pgpt and agpt fields - * and prints warnings on discrepancies. - * + * Description: Sanity checks pgpt and agpt fields and prints warnings + * on discrepancies. Returns error count. GPT parsers can choose to + * ignore this or not. + * */ -static void -compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba) +int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba) { int error_found = 0; + if (!pgpt || !agpt) - return; + return -EINVAL; + if (le64_to_cpu(pgpt->my_lba) != le64_to_cpu(agpt->alternate_lba)) { pr_warn("GPT:Primary header LBA != Alt. header alternate_lba\n"); pr_warn("GPT:%lld != %lld\n", @@ -557,7 +574,7 @@ compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba) if (error_found) pr_warn("GPT: Use GNU Parted to correct GPT errors.\n"); - return; + return error_found; } /** @@ -601,7 +618,7 @@ static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt, goto fail; read_lba(state, 0, (u8 *)legacymbr, sizeof(*legacymbr)); - good_pmbr = is_pmbr_valid(legacymbr, total_sectors); + good_pmbr = gpt_is_pmbr_valid(legacymbr, total_sectors); kfree(legacymbr); if (!good_pmbr) @@ -635,7 +652,7 @@ static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt, if (!good_pgpt && !good_agpt) goto fail; - compare_gpts(pgpt, agpt, lastlba); + gpt_compare_alt(pgpt, agpt, lastlba); /* The good cases */ if (good_pgpt) { @@ -674,7 +691,7 @@ static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt, * Description: Converts @size UTF16-LE symbols from @in string to 7-bit * ASCII characters and stores them to @out. Adds trailing zero to @out array. */ -static void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out) +void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out) { unsigned int i = 0; @@ -731,7 +748,7 @@ int efi_partition(struct parsed_partitions *state) u64 size = le64_to_cpu(ptes[i].ending_lba) - le64_to_cpu(ptes[i].starting_lba) + 1ULL; - if (!is_pte_valid(&ptes[i], last_lba(state->disk))) + if (!gpt_is_pte_valid(&ptes[i], last_lba(state->disk))) continue; put_partition(state, i + 1, start * ssz, size * ssz); diff --git a/include/linux/gpt.h b/include/linux/gpt.h index 633be6bc826c..f7f5892fe256 100644 --- a/include/linux/gpt.h +++ b/include/linux/gpt.h @@ -8,8 +8,8 @@ * Copyright 2000,2001 Dell Inc. ************************************************************/ -#ifndef FS_PART_EFI_H_INCLUDED -#define FS_PART_EFI_H_INCLUDED +#ifndef _GPT_H +#define _GPT_H #include <linux/types.h> #include <linux/fs.h> @@ -111,4 +111,37 @@ typedef struct _legacy_mbr { __le16 signature; } __packed legacy_mbr; +// Helpers for validating GPT metadata +int gpt_is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors); +int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size, + u64 lastlba); +int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes); +int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba); + +/** + * is_pte_valid() - tests one PTE for validity + * @pte:pte to check + * @lastlba: last lba of the disk + * + * returns 1 if valid, 0 on error. + */ + static inline bool +gpt_is_pte_valid(const gpt_entry *pte, const u64 lastlba) +{ + if ((!efi_guidcmp(pte->partition_type_guid, NULL_GUID)) || + le64_to_cpu(pte->starting_lba) > lastlba || + le64_to_cpu(pte->ending_lba) > lastlba) + return 0; + return 1; +} + +// Returns size in bytes of PTE array +static inline int get_pt_size(gpt_header *gpt) +{ + return le32_to_cpu(gpt->num_partition_entries) + * le32_to_cpu(gpt->sizeof_partition_entry); +} + +void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out); + #endif -- 2.43.0