[RFC PATCH 4/6] block: partitions: efi: Move GPT-specific code to a new library

Romain Gantois romain.gantois at bootlin.com
Mon Dec 11 07:12:40 PST 2023


The GPT parser in the block layer contains much logic that is not specific
to block devices. Move all of this logic to a new generic GPT library so
that future GPT parsers can make use of it.

lib/gpt.c is designed as a stateless library. It mainly contains helpers
that validate GPT metadata.

The efi_crc32 function is moved out of the block layer GPT parser and into
the generic efi header.

There is no functional change.

Signed-off-by: Romain Gantois <romain.gantois at bootlin.com>
---
 MAINTAINERS              |   1 +
 block/partitions/Kconfig |   2 +-
 block/partitions/efi.c   | 336 --------------------------------------
 include/linux/efi.h      |  18 +++
 lib/Kconfig              |   3 +
 lib/Makefile             |   3 +
 lib/gpt.c                | 342 +++++++++++++++++++++++++++++++++++++++
 7 files changed, 368 insertions(+), 337 deletions(-)
 create mode 100644 lib/gpt.c

diff --git a/MAINTAINERS b/MAINTAINERS
index 22e37e2ea1ae..c01abee48b75 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -9235,6 +9235,7 @@ L:	linux-efi at vger.kernel.org
 S:	Maintained
 F:	block/partitions/efi.c
 F:	include/linux/gpt.h
+F:	lib/gpt.c
 
 HABANALABS PCI DRIVER
 M:	Oded Gabbay <ogabbay at kernel.org>
diff --git a/block/partitions/Kconfig b/block/partitions/Kconfig
index 7aff4eb81c60..c2b2618213ba 100644
--- a/block/partitions/Kconfig
+++ b/block/partitions/Kconfig
@@ -250,7 +250,7 @@ config KARMA_PARTITION
 config EFI_PARTITION
 	bool "EFI GUID Partition support" if PARTITION_ADVANCED
 	default y
-	select CRC32
+	select GENERIC_LIB_GPT
 	help
 	  Say Y here if you would like to use hard disks under Linux which
 	  were partitioned using EFI GPT.
diff --git a/block/partitions/efi.c b/block/partitions/efi.c
index 3630ebf4b997..58bcd2cbcdf8 100644
--- a/block/partitions/efi.c
+++ b/block/partitions/efi.c
@@ -83,7 +83,6 @@
  *
  ************************************************************/
 #include <linux/kernel.h>
-#include <linux/crc32.h>
 #include <linux/ctype.h>
 #include <linux/math64.h>
 #include <linux/slab.h>
@@ -103,24 +102,6 @@ force_gpt_fn(char *str)
 }
 __setup("gpt", force_gpt_fn);
 
-/**
- * efi_crc32() - EFI version of crc32 function
- * @buf: buffer to calculate crc32 of
- * @len: length of buf
- *
- * Description: Returns EFI-style CRC32 value for @buf
- *
- * This function uses the little endian Ethernet polynomial
- * but seeds the function with ~0, and xor's with ~0 at the end.
- * Note, the EFI Specification, v1.02, has a reference to
- * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
- */
-static inline u32
-efi_crc32(const void *buf, unsigned long len)
-{
-	return (crc32(~0L, buf, len) ^ ~0L);
-}
-
 /**
  * last_lba(): return number of last logical block of device
  * @disk: block device
@@ -136,91 +117,6 @@ static u64 last_lba(struct gendisk *disk)
 		       queue_logical_block_size(disk->queue)) - 1ULL;
 }
 
-static inline int pmbr_part_valid(gpt_mbr_record *part)
-{
-	if (part->os_type != EFI_PMBR_OSTYPE_EFI_GPT)
-		goto invalid;
-
-	/* set to 0x00000001 (i.e., the LBA of the GPT Partition Header) */
-	if (le32_to_cpu(part->starting_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA)
-		goto invalid;
-
-	return GPT_MBR_PROTECTIVE;
-invalid:
-	return 0;
-}
-
-/**
- * gpt_is_pmbr_valid(): test Protective MBR for validity
- * @mbr: pointer to a legacy mbr structure
- * @total_sectors: amount of sectors in the device
- *
- * Description: Checks for a valid protective or hybrid
- * master boot record (MBR). The validity of a pMBR depends
- * on all of the following properties:
- *  1) MSDOS signature is in the last two bytes of the MBR
- *  2) One partition of type 0xEE is found
- *
- * In addition, a hybrid MBR will have up to three additional
- * primary partitions, which point to the same space that's
- * marked out by up to three GPT partitions.
- *
- * Returns 0 upon invalid MBR, or GPT_MBR_PROTECTIVE or
- * GPT_MBR_HYBRID depending on the device layout.
- */
-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 */
-
-	if (!mbr || le16_to_cpu(mbr->signature) != MSDOS_MBR_SIGNATURE)
-		goto done;
-
-	for (i = 0; i < 4; i++) {
-		ret = pmbr_part_valid(&mbr->partition_record[i]);
-		if (ret == GPT_MBR_PROTECTIVE) {
-			part = i;
-			/*
-			 * Ok, we at least know that there's a protective MBR,
-			 * now check if there are other partition types for
-			 * hybrid MBR.
-			 */
-			goto check_hybrid;
-		}
-	}
-
-	if (ret != GPT_MBR_PROTECTIVE)
-		goto done;
-check_hybrid:
-	for (i = 0; i < 4; i++)
-		if (mbr->partition_record[i].os_type !=
-		     EFI_PMBR_OSTYPE_EFI_GPT &&
-		    mbr->partition_record[i].os_type != 0x00)
-			ret = GPT_MBR_HYBRID;
-
-	/*
-	 * Protective MBRs take up the lesser of the whole disk
-	 * or 2 TiB (32bit LBA), ignoring the rest of the disk.
-	 * Some partitioning programs, nonetheless, choose to set
-	 * the size to the maximum 32-bit limitation, disregarding
-	 * the disk size.
-	 *
-	 * Hybrid MBRs do not necessarily comply with this.
-	 *
-	 * Consider a bad value here to be a warning to support dd'ing
-	 * an image from a smaller disk to a larger disk.
-	 */
-	if (ret == GPT_MBR_PROTECTIVE) {
-		sz = le32_to_cpu(mbr->partition_record[part].size_in_lba);
-		if (sz != (uint32_t)total_sectors - 1 && sz != 0xFFFFFFFF)
-			pr_debug("GPT: mbr size in lba (%u) different than whole disk (%u).\n",
-				 sz, min_t(uint32_t,
-					   total_sectors - 1, 0xFFFFFFFF));
-	}
-done:
-	return ret;
-}
-
 /**
  * read_lba(): Read bytes from disk, starting at given LBA
  * @state: disk parsed partitions
@@ -323,119 +219,6 @@ static gpt_header *alloc_read_gpt_header(struct parsed_partitions *state,
 	return gpt;
 }
 
-/**
- * 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
- *
- * Returns 0 if validation was successful.
- */
-int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size,
-			u64 lastlba)
-{
-	u32 crc, origcrc;
-	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),
-			 (unsigned long long)GPT_HEADER_SIGNATURE);
-		return -EINVAL;
-	}
-
-	/* Check the GUID Partition Table header size is too big */
-	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), lba_size);
-		return -EINVAL;
-	}
-
-	/* Check the GUID Partition Table header size is too small */
-	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));
-		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));
-
-	if (crc != origcrc) {
-		pr_debug("GUID Partition Table Header CRC is wrong: %x != %x\n",
-			 crc, origcrc);
-		return -EINVAL;
-	}
-	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) {
-		pr_debug("GPT my_lba incorrect: %lld != %lld\n",
-			 (unsigned long long)le64_to_cpu(gpt->my_lba),
-			 (unsigned long long)lba);
-		return -EINVAL;
-	}
-
-	/* Check the first_usable_lba and last_usable_lba are
-	 * within the disk.
-	 */
-	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)lastlba);
-		return -EINVAL;
-	}
-	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)lastlba);
-		return -EINVAL;
-	}
-	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));
-		return -EINVAL;
-	}
-
-	/* Check that sizeof_partition_entry has the correct value */
-	if (le32_to_cpu(gpt->sizeof_partition_entry) != sizeof(gpt_entry)) {
-		pr_debug("GUID Partition Entry Size check failed.\n");
-		return -EINVAL;
-	}
-
-	/* Sanity check partition table size */
-	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);
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
-/* Check the GUID Partition Entry Array CRC */
-int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes)
-{
-	u32 crc;
-
-	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");
-		return -EINVAL;
-	}
-
-	return 0;
-}
-
 /**
  * is_gpt_valid() - tests one GPT header and PTEs for validity
  * @state: disk parsed partitions
@@ -483,100 +266,6 @@ static int is_gpt_valid(struct parsed_partitions *state, u64 lba,
 	return 0;
 }
 
-/**
- * gpt_compare_alt() - Compares the Primary and Alternate GPT headers
- * @pgpt: primary GPT header
- * @agpt: alternate GPT header
- * @lastlba: last LBA number
- *
- * Description: Sanity checks pgpt and agpt fields and prints warnings
- * on discrepancies. Returns error count. GPT parsers can choose to
- * ignore this or not.
- *
- */
-int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
-{
-	int error_found = 0;
-
-	if (!pgpt || !agpt)
-		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",
-			(unsigned long long)le64_to_cpu(pgpt->my_lba),
-			(unsigned long long)le64_to_cpu(agpt->alternate_lba));
-		error_found++;
-	}
-	if (le64_to_cpu(pgpt->alternate_lba) != le64_to_cpu(agpt->my_lba)) {
-		pr_warn("GPT:Primary header alternate_lba != Alt. header my_lba\n");
-		pr_warn("GPT:%lld != %lld\n",
-			(unsigned long long)le64_to_cpu(pgpt->alternate_lba),
-			(unsigned long long)le64_to_cpu(agpt->my_lba));
-		error_found++;
-	}
-	if (le64_to_cpu(pgpt->first_usable_lba) !=
-	    le64_to_cpu(agpt->first_usable_lba)) {
-		pr_warn("GPT:first_usable_lbas don't match.\n");
-		pr_warn("GPT:%lld != %lld\n",
-			(unsigned long long)le64_to_cpu(pgpt->first_usable_lba),
-			(unsigned long long)le64_to_cpu(agpt->first_usable_lba));
-		error_found++;
-	}
-	if (le64_to_cpu(pgpt->last_usable_lba) !=
-	    le64_to_cpu(agpt->last_usable_lba)) {
-		pr_warn("GPT:last_usable_lbas don't match.\n");
-		pr_warn("GPT:%lld != %lld\n",
-			(unsigned long long)le64_to_cpu(pgpt->last_usable_lba),
-			(unsigned long long)le64_to_cpu(agpt->last_usable_lba));
-		error_found++;
-	}
-	if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) {
-		pr_warn("GPT:disk_guids don't match.\n");
-		error_found++;
-	}
-	if (le32_to_cpu(pgpt->num_partition_entries) !=
-	    le32_to_cpu(agpt->num_partition_entries)) {
-		pr_warn("GPT:num_partition_entries don't match: 0x%x != 0x%x\n",
-			le32_to_cpu(pgpt->num_partition_entries),
-			le32_to_cpu(agpt->num_partition_entries));
-		error_found++;
-	}
-	if (le32_to_cpu(pgpt->sizeof_partition_entry) !=
-	    le32_to_cpu(agpt->sizeof_partition_entry)) {
-		pr_warn("GPT:sizeof_partition_entry values don't match: 0x%x != 0x%x\n",
-			le32_to_cpu(pgpt->sizeof_partition_entry),
-			le32_to_cpu(agpt->sizeof_partition_entry));
-		error_found++;
-	}
-	if (le32_to_cpu(pgpt->partition_entry_array_crc32) !=
-	    le32_to_cpu(agpt->partition_entry_array_crc32)) {
-		pr_warn("GPT:partition_entry_array_crc32 values don't match: 0x%x != 0x%x\n",
-			le32_to_cpu(pgpt->partition_entry_array_crc32),
-			le32_to_cpu(agpt->partition_entry_array_crc32));
-		error_found++;
-	}
-	if (le64_to_cpu(pgpt->alternate_lba) != lastlba) {
-		pr_warn("GPT:Primary header thinks Alt. header is not at the end of the disk.\n");
-		pr_warn("GPT:%lld != %lld\n",
-			(unsigned long long)le64_to_cpu(pgpt->alternate_lba),
-			(unsigned long long)lastlba);
-		error_found++;
-	}
-
-	if (le64_to_cpu(agpt->my_lba) != lastlba) {
-		pr_warn("GPT:Alternate GPT header not at the end of the disk.\n");
-		pr_warn("GPT:%lld != %lld\n",
-			(unsigned long long)le64_to_cpu(agpt->my_lba),
-			(unsigned long long)lastlba);
-		error_found++;
-	}
-
-	if (error_found)
-		pr_warn("GPT: Use GNU Parted to correct GPT errors.\n");
-	return error_found;
-}
-
 /**
  * find_valid_gpt() - Search disk for valid GPT headers and PTEs
  * @state: disk parsed partitions
@@ -682,31 +371,6 @@ static int find_valid_gpt(struct parsed_partitions *state, gpt_header **gpt,
 	return 0;
 }
 
-/**
- * utf16_le_to_7bit(): Naively converts a UTF-16LE string to 7-bit ASCII characters
- * @in: input UTF-16LE string
- * @size: size of the input string
- * @out: output string ptr, should be capable to store @size+1 characters
- *
- * 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.
- */
-void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out)
-{
-	unsigned int i = 0;
-
-	out[size] = 0;
-
-	while (i < size) {
-		u8 c = le16_to_cpu(in[i]) & 0xff;
-
-		if (c && !isprint(c))
-			c = '!';
-		out[i] = c;
-		i++;
-	}
-}
-
 /**
  * efi_partition - scan for GPT partitions
  * @state: disk parsed partitions
diff --git a/include/linux/efi.h b/include/linux/efi.h
index 9cc5bf32f6f2..c4b818f77de7 100644
--- a/include/linux/efi.h
+++ b/include/linux/efi.h
@@ -24,6 +24,7 @@
 #include <linux/range.h>
 #include <linux/reboot.h>
 #include <linux/uuid.h>
+#include <linux/crc32.h>
 
 #include <asm/page.h>
 
@@ -1348,4 +1349,21 @@ bool efi_config_table_is_usable(const efi_guid_t *guid, unsigned long table)
 
 umode_t efi_attr_is_visible(struct kobject *kobj, struct attribute *attr, int n);
 
+/**
+ * efi_crc32() - EFI version of crc32 function
+ * @buf: buffer to calculate crc32 of
+ * @len: length of buf
+ *
+ * Description: Returns EFI-style CRC32 value for @buf
+ *
+ * This function uses the little endian Ethernet polynomial
+ * but seeds the function with ~0, and xor's with ~0 at the end.
+ * Note, the EFI Specification, v1.02, has a reference to
+ * Dr. Dobbs Journal, May 1994 (actually it's in May 1992).
+ */
+static inline u32 efi_crc32(const void *buf, unsigned long len)
+{
+	return (crc32(~0L, buf, len) ^ ~0L);
+}
+
 #endif /* _LINUX_EFI_H */
diff --git a/lib/Kconfig b/lib/Kconfig
index 3ea1c830efab..de776911944e 100644
--- a/lib/Kconfig
+++ b/lib/Kconfig
@@ -748,6 +748,9 @@ config GENERIC_LIB_ASHLDI3
 config GENERIC_LIB_ASHRDI3
 	bool
 
+config GENERIC_LIB_GPT
+	bool
+
 config GENERIC_LIB_LSHRDI3
 	bool
 
diff --git a/lib/Makefile b/lib/Makefile
index 6b09731d8e61..fba8fae70efa 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -444,3 +444,6 @@ $(obj)/$(TEST_FORTIFY_LOG): $(addprefix $(obj)/, $(TEST_FORTIFY_LOGS)) FORCE
 ifeq ($(CONFIG_FORTIFY_SOURCE),y)
 $(obj)/string.o: $(obj)/$(TEST_FORTIFY_LOG)
 endif
+
+# GPT parsing routines
+obj-$(CONFIG_GENERIC_LIB_GPT) += gpt.o
diff --git a/lib/gpt.c b/lib/gpt.c
new file mode 100644
index 000000000000..16303634f4fb
--- /dev/null
+++ b/lib/gpt.c
@@ -0,0 +1,342 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/* EFI GUID Partition Table handling
+ *
+ * http://www.uefi.org/specs/
+ * http://www.intel.com/technology/efi/
+ *
+ * efi.[ch] by Matt Domsch <Matt_Domsch at dell.com>
+ *   Copyright 2000,2001,2002,2004 Dell Inc.
+ *
+ * This code was previously in block/partitions/efi.c
+ * and was moved in /lib so that other kernel subsystems
+ * could use it as a common GPT parsing library.
+ *
+ * This library should be stateless and not make any
+ * assumptions about the type of device the GPT data
+ * came from.
+ *
+ */
+
+#include <linux/gpt.h>
+#include <linux/efi.h>
+
+static inline int pmbr_part_valid(gpt_mbr_record *part)
+{
+	if (part->os_type != EFI_PMBR_OSTYPE_EFI_GPT)
+		goto invalid;
+
+	/* set to 0x00000001 (i.e., the LBA of the GPT Partition Header) */
+	if (le32_to_cpu(part->starting_lba) != GPT_PRIMARY_PARTITION_TABLE_LBA)
+		goto invalid;
+
+	return GPT_MBR_PROTECTIVE;
+invalid:
+	return 0;
+}
+
+/**
+ * gpt_is_pmbr_valid(): test Protective MBR for validity
+ * @mbr: pointer to a legacy mbr structure
+ * @total_sectors: amount of sectors in the device
+ *
+ * Description: Checks for a valid protective or hybrid
+ * master boot record (MBR). The validity of a pMBR depends
+ * on all of the following properties:
+ *  1) MSDOS signature is in the last two bytes of the MBR
+ *  2) One partition of type 0xEE is found
+ *
+ * In addition, a hybrid MBR will have up to three additional
+ * primary partitions, which point to the same space that's
+ * marked out by up to three GPT partitions.
+ *
+ * Returns 0 upon invalid MBR, or GPT_MBR_PROTECTIVE or
+ * GPT_MBR_HYBRID depending on the device layout.
+ */
+int gpt_is_pmbr_valid(legacy_mbr *mbr, sector_t total_sectors)
+{
+	int i, part = 0, ret = 0; /* invalid by default */
+	uint32_t sz = 0;
+
+	if (!mbr || le16_to_cpu(mbr->signature) != MSDOS_MBR_SIGNATURE)
+		goto done;
+
+	for (i = 0; i < 4; i++) {
+		ret = pmbr_part_valid(&mbr->partition_record[i]);
+		if (ret == GPT_MBR_PROTECTIVE) {
+			part = i;
+			/*
+			 * Ok, we at least know that there's a protective MBR,
+			 * now check if there are other partition types for
+			 * hybrid MBR.
+			 */
+			goto check_hybrid;
+		}
+	}
+
+	if (ret != GPT_MBR_PROTECTIVE)
+		goto done;
+check_hybrid:
+	for (i = 0; i < 4; i++)
+		if (mbr->partition_record[i].os_type != EFI_PMBR_OSTYPE_EFI_GPT &&
+		    mbr->partition_record[i].os_type != 0x00)
+			ret = GPT_MBR_HYBRID;
+
+	/*
+	 * Protective MBRs take up the lesser of the whole disk
+	 * or 2 TiB (32bit LBA), ignoring the rest of the disk.
+	 * Some partitioning programs, nonetheless, choose to set
+	 * the size to the maximum 32-bit limitation, disregarding
+	 * the disk size.
+	 *
+	 * Hybrid MBRs do not necessarily comply with this.
+	 *
+	 * Consider a bad value here to be a warning to support dd'ing
+	 * an image from a smaller disk to a larger disk.
+	 */
+	if (ret == GPT_MBR_PROTECTIVE) {
+		sz = le32_to_cpu(mbr->partition_record[part].size_in_lba);
+		if (sz != (uint32_t)total_sectors - 1 && sz != 0xFFFFFFFF)
+			pr_debug("GPT: mbr size in lba (%u) different than whole disk (%u).\n",
+				 sz, min_t(uint32_t,
+					   total_sectors - 1, 0xFFFFFFFF));
+	}
+done:
+	return ret;
+}
+EXPORT_SYMBOL_GPL(gpt_is_pmbr_valid);
+
+/**
+ * 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
+ *
+ * Returns 0 if validation was successful.
+ */
+int gpt_validate_header(gpt_header *gpt, u64 lba, unsigned int lba_size,
+			u64 lastlba)
+{
+	u32 crc, origcrc;
+	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),
+			 (unsigned long long)GPT_HEADER_SIGNATURE);
+		return -EINVAL;
+	}
+
+	/* Check the GUID Partition Table header size is too big */
+	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), lba_size);
+		return -EINVAL;
+	}
+
+	/* Check the GUID Partition Table header size is too small */
+	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));
+		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));
+
+	if (crc != origcrc) {
+		pr_debug("GUID Partition Table Header CRC is wrong: %x != %x\n",
+			 crc, origcrc);
+		return -EINVAL;
+	}
+	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) {
+		pr_debug("GPT my_lba incorrect: %lld != %lld\n",
+			 (unsigned long long)le64_to_cpu(gpt->my_lba),
+			 (unsigned long long)lba);
+		return -EINVAL;
+	}
+
+	/* Check the first_usable_lba and last_usable_lba are
+	 * within the disk.
+	 */
+	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)lastlba);
+		return -EINVAL;
+	}
+	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)lastlba);
+		return -EINVAL;
+	}
+	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));
+		return -EINVAL;
+	}
+
+	/* Check that sizeof_partition_entry has the correct value */
+	if (le32_to_cpu(gpt->sizeof_partition_entry) != sizeof(gpt_entry)) {
+		pr_debug("GUID Partition Entry Size check failed.\n");
+		return -EINVAL;
+	}
+
+	/* Sanity check partition table size */
+	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);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gpt_validate_header);
+
+/* Check the GUID Partition Entry Array CRC */
+int gpt_check_pte_array_crc(gpt_header *gpt, gpt_entry *ptes)
+{
+	u32 crc;
+
+	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");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_GPL(gpt_check_pte_array_crc);
+
+/**
+ * gpt_compare_alt() - Compares the Primary and Alternate GPT headers
+ * @pgpt: primary GPT header
+ * @agpt: alternate GPT header
+ * @lastlba: last LBA number
+ *
+ * Description: Sanity checks pgpt and agpt fields and prints warnings
+ * on discrepancies. Returns error count. GPT parsers can choose to
+ * ignore this or not.
+ *
+ */
+int gpt_compare_alt(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
+{
+	int error_found = 0;
+
+	if (!pgpt || !agpt)
+		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",
+			(unsigned long long)le64_to_cpu(pgpt->my_lba),
+			(unsigned long long)le64_to_cpu(agpt->alternate_lba));
+		error_found++;
+	}
+	if (le64_to_cpu(pgpt->alternate_lba) != le64_to_cpu(agpt->my_lba)) {
+		pr_warn("GPT:Primary header alternate_lba != Alt. header my_lba\n");
+		pr_warn("GPT:%lld != %lld\n",
+			(unsigned long long)le64_to_cpu(pgpt->alternate_lba),
+			(unsigned long long)le64_to_cpu(agpt->my_lba));
+		error_found++;
+	}
+	if (le64_to_cpu(pgpt->first_usable_lba) !=
+	    le64_to_cpu(agpt->first_usable_lba)) {
+		pr_warn("GPT:first_usable_lbas don't match.\n");
+		pr_warn("GPT:%lld != %lld\n",
+			(unsigned long long)le64_to_cpu(pgpt->first_usable_lba),
+			(unsigned long long)le64_to_cpu(agpt->first_usable_lba));
+		error_found++;
+	}
+	if (le64_to_cpu(pgpt->last_usable_lba) !=
+	    le64_to_cpu(agpt->last_usable_lba)) {
+		pr_warn("GPT:last_usable_lbas don't match.\n");
+		pr_warn("GPT:%lld != %lld\n",
+			(unsigned long long)le64_to_cpu(pgpt->last_usable_lba),
+			(unsigned long long)le64_to_cpu(agpt->last_usable_lba));
+		error_found++;
+	}
+	if (efi_guidcmp(pgpt->disk_guid, agpt->disk_guid)) {
+		pr_warn("GPT:disk_guids don't match.\n");
+		error_found++;
+	}
+	if (le32_to_cpu(pgpt->num_partition_entries) !=
+	    le32_to_cpu(agpt->num_partition_entries)) {
+		pr_warn("GPT:num_partition_entries don't match: 0x%x != 0x%x\n",
+			le32_to_cpu(pgpt->num_partition_entries),
+			le32_to_cpu(agpt->num_partition_entries));
+		error_found++;
+	}
+	if (le32_to_cpu(pgpt->sizeof_partition_entry) !=
+	    le32_to_cpu(agpt->sizeof_partition_entry)) {
+		pr_warn("GPT:sizeof_partition_entry values don't match: 0x%x != 0x%x\n",
+			le32_to_cpu(pgpt->sizeof_partition_entry),
+			le32_to_cpu(agpt->sizeof_partition_entry));
+		error_found++;
+	}
+	if (le32_to_cpu(pgpt->partition_entry_array_crc32) !=
+	    le32_to_cpu(agpt->partition_entry_array_crc32)) {
+		pr_warn("GPT:partition_entry_array_crc32 values don't match: 0x%x != 0x%x\n",
+			le32_to_cpu(pgpt->partition_entry_array_crc32),
+			le32_to_cpu(agpt->partition_entry_array_crc32));
+		error_found++;
+	}
+	if (le64_to_cpu(pgpt->alternate_lba) != lastlba) {
+		pr_warn("GPT:Primary header thinks Alt. header is not at the end of the disk.\n");
+		pr_warn("GPT:%lld != %lld\n",
+			(unsigned long long)le64_to_cpu(pgpt->alternate_lba),
+			(unsigned long long)lastlba);
+		error_found++;
+	}
+
+	if (le64_to_cpu(agpt->my_lba) != lastlba) {
+		pr_warn("GPT:Alternate GPT header not at the end of the disk.\n");
+		pr_warn("GPT:%lld != %lld\n",
+			(unsigned long long)le64_to_cpu(agpt->my_lba),
+			(unsigned long long)lastlba);
+		error_found++;
+	}
+
+	if (error_found)
+		pr_warn("GPT: Use GNU Parted to correct GPT errors.\n");
+	return error_found;
+}
+EXPORT_SYMBOL_GPL(gpt_compare_alt);
+
+/**
+ * utf16_le_to_7bit(): Naively converts a UTF-16LE string to 7-bit ASCII characters
+ * @in: input UTF-16LE string
+ * @size: size of the input string
+ * @out: output string ptr, should be capable to store @size+1 characters
+ *
+ * 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.
+ */
+void utf16_le_to_7bit(const __le16 *in, unsigned int size, u8 *out)
+{
+	unsigned int i = 0;
+
+	out[size] = 0;
+
+	while (i < size) {
+		u8 c = le16_to_cpu(in[i]) & 0xff;
+
+		if (c && !isprint(c))
+			c = '!';
+		out[i] = c;
+		i++;
+	}
+}
+EXPORT_SYMBOL_GPL(utf16_le_to_7bit);
-- 
2.43.0




More information about the linux-mtd mailing list