Blame SOURCES/0021-gpt-try-to-avoid-trusting-unverified-partition-table.patch

ac385c
From 29a53c93ce063c8272e44f4981201a4752fc6ba1 Mon Sep 17 00:00:00 2001
ac385c
From: Peter Jones <pjones@redhat.com>
ac385c
Date: Tue, 9 May 2017 14:20:13 -0400
ac385c
Subject: [PATCH 21/24] gpt: try to avoid trusting unverified partition table
ac385c
 data.
ac385c
ac385c
Covscan complains thusly:
ac385c
 4. efivar-31/src/gpt.c:338: tainted_data_return: Function "alloc_read_gpt_header" returns tainted data.
ac385c
 7. efivar-31/src/gpt.c:311:2: tainted_data_argument: Function "read_lba" taints argument "gpt".
ac385c
12. efivar-31/src/gpt.c:245:2: tainted_data_argument: Calling function "read" taints parameter "*iobuf". [Note: The source code implementation of the function has been overridden by a builtin model.]
ac385c
13. efivar-31/src/gpt.c:246:2: tainted_data_transitive: "memcpy" taints argument "buffer" because argument "iobuf" is tainted. [Note: The source code implementation of the function has been overridden by a builtin model.]
ac385c
16. efivar-31/src/gpt.c:316:2: return_tainted_data: Returning tainted variable "gpt".
ac385c
17. efivar-31/src/gpt.c:338: var_assign: Assigning: "*gpt" = "alloc_read_gpt_header", which taints "*gpt".
ac385c
26. efivar-31/src/gpt.c:382: tainted_data: Passing tainted variable "(*gpt)->num_partition_entries" to a tainted sink.
ac385c
27. efivar-31/src/gpt.c:272:15: var_assign_alias: Assigning: "count" = "(__u32)(__le32)gpt->num_partition_entries * (__u32)(__le32)gpt->sizeof_partition_entry". Both are now tainted.
ac385c
30. efivar-31/src/gpt.c:278:2: tainted_data_sink_lv_call: Passing tainted variable "count" to tainted data sink "malloc".
ac385c
ac385c
Hopefully this patch validates num_partition_entries and
ac385c
sizeof_partition_entry well enough...
ac385c
ac385c
Signed-off-by: Peter Jones <pjones@redhat.com>
ac385c
---
ac385c
 src/disk.c |   3 +-
ac385c
 src/gpt.c  | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
ac385c
 src/gpt.h  |   3 +-
ac385c
 3 files changed, 173 insertions(+), 23 deletions(-)
ac385c
ac385c
diff --git a/src/disk.c b/src/disk.c
ac385c
index 91d636d..0a7a769 100644
ac385c
--- a/src/disk.c
ac385c
+++ b/src/disk.c
ac385c
@@ -225,7 +225,8 @@ get_partition_info(int fd, uint32_t options,
ac385c
 						  signature,
ac385c
 						  mbr_type,
ac385c
 						  signature_type,
ac385c
-			(options & EFIBOOT_OPTIONS_IGNORE_PMBR_ERR)?1:0);
ac385c
+			(options & EFIBOOT_OPTIONS_IGNORE_PMBR_ERR)?1:0,
ac385c
+			sector_size);
ac385c
 	if (gpt_invalid) {
ac385c
 		mbr_invalid = msdos_disk_get_partition_info(fd,
ac385c
 			(options & EFIBOOT_OPTIONS_WRITE_SIGNATURE)?1:0,
ac385c
diff --git a/src/gpt.c b/src/gpt.c
ac385c
index e9c713b..7baa992 100644
ac385c
--- a/src/gpt.c
ac385c
+++ b/src/gpt.c
ac385c
@@ -29,6 +29,7 @@
ac385c
 #include <stdio.h>
ac385c
 #include <stdlib.h>
ac385c
 #include <string.h>
ac385c
+#include <sys/param.h>
ac385c
 #include <sys/stat.h>
ac385c
 #include <sys/utsname.h>
ac385c
 #include <unistd.h>
ac385c
@@ -266,11 +267,10 @@ read_lba(int fd, uint64_t lba, void *buffer, size_t bytes)
ac385c
  * Notes: remember to free pte when you're done!
ac385c
  */
ac385c
 static gpt_entry *
ac385c
-alloc_read_gpt_entries(int fd, gpt_header * gpt)
ac385c
+alloc_read_gpt_entries(int fd, uint32_t nptes, uint32_t ptesz, uint64_t ptelba)
ac385c
 {
ac385c
 	gpt_entry *pte;
ac385c
-	size_t count = __le32_to_cpu(gpt->num_partition_entries) *
ac385c
-		__le32_to_cpu(gpt->sizeof_partition_entry);
ac385c
+	size_t count = nptes * ptesz;
ac385c
 
ac385c
 	if (!count)
ac385c
 		return NULL;
ac385c
@@ -280,8 +280,7 @@ alloc_read_gpt_entries(int fd, gpt_header * gpt)
ac385c
 		return NULL;
ac385c
 
ac385c
 	memset(pte, 0, count);
ac385c
-	if (!read_lba(fd, __le64_to_cpu(gpt->partition_entry_lba), pte,
ac385c
-		      count)) {
ac385c
+	if (!read_lba(fd, ptelba, pte, count)) {
ac385c
 		free(pte);
ac385c
 		return NULL;
ac385c
 	}
ac385c
@@ -317,6 +316,65 @@ alloc_read_gpt_header(int fd, uint64_t lba)
ac385c
 }
ac385c
 
ac385c
 /**
ac385c
+ * validate_nptes(): Tries to ensure that nptes is a reasonable value
ac385c
+ * @first_block is the beginning LBA to bound the table
ac385c
+ * @pte_start is the starting LBA of the partition table
ac385c
+ * @last_block is the end LBA of to bound the table
ac385c
+ * @ptesz is the size of a partition table entry
ac385c
+ * @nptes is the number of entries we have.
ac385c
+ * @blksz is the block size of the device.
ac385c
+ *
ac385c
+ * Description: returns 0 if the partition table doesn't fit, 1 if it does
ac385c
+ */
ac385c
+static int
ac385c
+validate_nptes(uint64_t first_block, uint64_t pte_start, uint64_t last_block,
ac385c
+	       uint32_t ptesz, uint32_t nptes, uint32_t blksz)
ac385c
+{
ac385c
+	uint32_t min_entry_size = sizeof(gpt_entry);
ac385c
+	uint32_t min_entry_size_mod = 128 - sizeof(gpt_entry) % 128;
ac385c
+	uint64_t max_blocks, max_bytes;
ac385c
+
ac385c
+	if (min_entry_size_mod == 128)
ac385c
+		min_entry_size_mod = 0;
ac385c
+	min_entry_size += min_entry_size_mod;
ac385c
+
ac385c
+	if (ptesz < min_entry_size)
ac385c
+		return 0;
ac385c
+
ac385c
+	if (pte_start < first_block || pte_start > last_block)
ac385c
+		return 0;
ac385c
+
ac385c
+	max_blocks = last_block - pte_start;
ac385c
+	if (UINT64_MAX / blksz < max_blocks)
ac385c
+		return 0;
ac385c
+
ac385c
+	max_bytes = max_blocks * blksz;
ac385c
+	if (UINT64_MAX / ptesz < max_bytes)
ac385c
+		return 0;
ac385c
+
ac385c
+	if (ptesz > max_bytes / nptes)
ac385c
+		return 0;
ac385c
+
ac385c
+	if (max_bytes / ptesz < nptes)
ac385c
+		return 0;
ac385c
+
ac385c
+	return 1;
ac385c
+}
ac385c
+
ac385c
+static int
ac385c
+check_lba(uint64_t lba, uint64_t lastlba, char *name)
ac385c
+{
ac385c
+	if (lba > lastlba) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"Invalid %s LBA %"PRIx64" max:%"PRIx64"\n",
ac385c
+				name, lba, lastlba);
ac385c
+		return 0;
ac385c
+	}
ac385c
+	return 1;
ac385c
+}
ac385c
+
ac385c
+/**
ac385c
  * is_gpt_valid() - tests one GPT header and PTEs for validity
ac385c
  * @fd  is an open file descriptor to the whole disk
ac385c
  * @lba is the logical block address of the GPT header to test
ac385c
@@ -328,10 +386,12 @@ alloc_read_gpt_header(int fd, uint64_t lba)
ac385c
  */
ac385c
 static int
ac385c
 is_gpt_valid(int fd, uint64_t lba,
ac385c
-	     gpt_header ** gpt, gpt_entry ** ptes)
ac385c
+	     gpt_header ** gpt, gpt_entry ** ptes,
ac385c
+	     uint32_t logical_block_size)
ac385c
 {
ac385c
 	int rc = 0;		/* default to not valid */
ac385c
 	uint32_t crc, origcrc;
ac385c
+	uint64_t max_device_lba = last_lba(fd);
ac385c
 
ac385c
 	if (!gpt || !ptes)
ac385c
 		return 0;
ac385c
@@ -343,7 +403,7 @@ is_gpt_valid(int fd, uint64_t lba,
ac385c
 		if (report_errors)
ac385c
 			fprintf(stderr,
ac385c
 				"GUID Partition Table Header signature is wrong"
ac385c
-			       ": %" PRIx64" != %" PRIx64 "\n",
ac385c
+			       ": %"PRIx64" != %"PRIx64"\n",
ac385c
 			       (uint64_t)__le64_to_cpu((*gpt)->signature),
ac385c
 			       GPT_HEADER_SIGNATURE);
ac385c
 		free(*gpt);
ac385c
@@ -351,6 +411,20 @@ is_gpt_valid(int fd, uint64_t lba,
ac385c
 		return rc;
ac385c
 	}
ac385c
 
ac385c
+	uint32_t hdrsz = __le32_to_cpu((*gpt)->header_size);
ac385c
+	uint32_t hdrmin = MAX(92,
ac385c
+			      sizeof(gpt_header) - sizeof((*gpt)->reserved2));
ac385c
+	if (hdrsz < hdrmin || hdrsz > logical_block_size) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"GUID Partition Table Header size is invalid (%d < %d < %d)\n",
ac385c
+				hdrmin, hdrsz,
ac385c
+				logical_block_size);
ac385c
+		free (*gpt);
ac385c
+		*gpt = NULL;
ac385c
+		return rc;
ac385c
+	}
ac385c
+
ac385c
 	/* Check the GUID Partition Table Header CRC */
ac385c
 	origcrc = __le32_to_cpu((*gpt)->header_crc32);
ac385c
 	(*gpt)->header_crc32 = 0;
ac385c
@@ -369,26 +443,97 @@ is_gpt_valid(int fd, uint64_t lba,
ac385c
 
ac385c
 	/* Check that the my_lba entry points to the LBA
ac385c
 	 * that contains the GPT we read */
ac385c
-	if (__le64_to_cpu((*gpt)->my_lba) != lba) {
ac385c
+	uint64_t mylba = __le64_to_cpu((*gpt)->my_lba);
ac385c
+	uint64_t altlba = __le64_to_cpu((*gpt)->alternate_lba);
ac385c
+	if (mylba != lba && altlba != lba) {
ac385c
 		if (report_errors)
ac385c
 			fprintf(stderr,
ac385c
-				"my_lba %"PRIx64 "x != lba %"PRIx64 "x.\n",
ac385c
-				(uint64_t)__le64_to_cpu((*gpt)->my_lba), lba);
ac385c
+				"lba %"PRIx64" != lba %"PRIx64".\n",
ac385c
+				mylba, lba);
ac385c
+err:
ac385c
 		free(*gpt);
ac385c
 		*gpt = NULL;
ac385c
 		return 0;
ac385c
 	}
ac385c
 
ac385c
-	if (!(*ptes = alloc_read_gpt_entries(fd, *gpt))) {
ac385c
+	if (!check_lba(mylba, max_device_lba, "GPT"))
ac385c
+		goto err;
ac385c
+
ac385c
+	if (!check_lba(altlba, max_device_lba, "GPT Alt"))
ac385c
+		goto err;
ac385c
+
ac385c
+	uint64_t ptelba = __le64_to_cpu((*gpt)->partition_entry_lba);
ac385c
+	uint64_t fulba = __le64_to_cpu((*gpt)->first_usable_lba);
ac385c
+	uint64_t lulba = __le64_to_cpu((*gpt)->last_usable_lba);
ac385c
+	uint32_t nptes = __le32_to_cpu((*gpt)->num_partition_entries);
ac385c
+	uint32_t ptesz = __le32_to_cpu((*gpt)->sizeof_partition_entry);
ac385c
+
ac385c
+	if (!check_lba(ptelba, max_device_lba, "PTE"))
ac385c
+		goto err;
ac385c
+	if (!check_lba(fulba, max_device_lba, "First Usable"))
ac385c
+		goto err;
ac385c
+	if (!check_lba(lulba, max_device_lba, "Last Usable"))
ac385c
+		goto err;
ac385c
+
ac385c
+	if (ptesz < sizeof(gpt_entry) || ptesz % 128 != 0) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"Invalid GPT entry size is %d.\n",
ac385c
+				ptesz);
ac385c
+		goto err;
ac385c
+	}
ac385c
+
ac385c
+	/* There's really no good answer to maximum bounds, but this large
ac385c
+	 * would be completely absurd, so... */
ac385c
+	if (nptes > 1024) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"Not honoring insane number of Partition Table Entries 0x%"PRIx32".\n",
ac385c
+				nptes);
ac385c
+
ac385c
+		goto err;
ac385c
+	}
ac385c
+
ac385c
+	if (ptesz > 4096) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"Not honoring insane Partition Table Entry size 0x%"PRIx32".\n",
ac385c
+				ptesz);
ac385c
+		goto err;
ac385c
+	}
ac385c
+
ac385c
+	uint64_t pte_blocks;
ac385c
+	uint64_t firstlba, lastlba;
ac385c
+
ac385c
+	if (altlba > mylba) {
ac385c
+		firstlba = mylba + 1;
ac385c
+		lastlba = fulba;
ac385c
+		pte_blocks = fulba - ptelba;
ac385c
+		rc = validate_nptes(firstlba, ptelba, fulba,
ac385c
+				    ptesz, nptes, logical_block_size);
ac385c
+	} else {
ac385c
+		firstlba = lulba;
ac385c
+		lastlba = mylba;
ac385c
+		pte_blocks = mylba - ptelba;
ac385c
+		rc = validate_nptes(lulba, ptelba, mylba,
ac385c
+				    ptesz, nptes, logical_block_size);
ac385c
+	}
ac385c
+	if (!rc) {
ac385c
+		if (report_errors)
ac385c
+			fprintf(stderr,
ac385c
+				"%"PRIu32" partition table entries with size 0x%"PRIx32" doesn't fit in 0x%"PRIx64" blocks between 0x%"PRIx64" and 0x%"PRIx64".\n",
ac385c
+				nptes, ptesz, pte_blocks, firstlba, lastlba);
ac385c
+		goto err;
ac385c
+	}
ac385c
+
ac385c
+	if (!(*ptes = alloc_read_gpt_entries(fd, nptes, ptesz, ptelba))) {
ac385c
 		free(*gpt);
ac385c
 		*gpt = NULL;
ac385c
 		return 0;
ac385c
 	}
ac385c
 
ac385c
 	/* Check the GUID Partition Entry Array CRC */
ac385c
-	crc = efi_crc32(*ptes,
ac385c
-			__le32_to_cpu((*gpt)->num_partition_entries) *
ac385c
-			__le32_to_cpu((*gpt)->sizeof_partition_entry));
ac385c
+	crc = efi_crc32(*ptes, nptes * ptesz);
ac385c
 	if (crc != __le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
ac385c
 		if (report_errors)
ac385c
 		     fprintf(stderr,
ac385c
@@ -519,7 +664,7 @@ compare_gpts(gpt_header *pgpt, gpt_header *agpt, uint64_t lastlba)
ac385c
  */
ac385c
 static int
ac385c
 find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes,
ac385c
-	       int ignore_pmbr_err)
ac385c
+	       int ignore_pmbr_err, int logical_block_size)
ac385c
 {
ac385c
 	int good_pgpt = 0, good_agpt = 0, good_pmbr = 0;
ac385c
 	gpt_header *pgpt = NULL, *agpt = NULL;
ac385c
@@ -535,16 +680,18 @@ find_valid_gpt(int fd, gpt_header ** gpt, gpt_entry ** ptes,
ac385c
 
ac385c
 	lastlba = last_lba(fd);
ac385c
 	good_pgpt = is_gpt_valid(fd, GPT_PRIMARY_PARTITION_TABLE_LBA,
ac385c
-				 &pgpt, &pptes);
ac385c
+				 &pgpt, &pptes, logical_block_size);
ac385c
 	if (good_pgpt) {
ac385c
 		good_agpt = is_gpt_valid(fd,
ac385c
 					 __le64_to_cpu(pgpt->alternate_lba),
ac385c
-					 &agpt, &aptes);
ac385c
+					 &agpt, &aptes, logical_block_size);
ac385c
 		if (!good_agpt) {
ac385c
-			good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes);
ac385c
+			good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes,
ac385c
+						 logical_block_size);
ac385c
 		}
ac385c
 	} else {
ac385c
-		good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes);
ac385c
+		good_agpt = is_gpt_valid(fd, lastlba, &agpt, &aptes,
ac385c
+					 logical_block_size);
ac385c
 	}
ac385c
 
ac385c
 	/* The obviously unsuccessful case */
ac385c
@@ -634,7 +781,7 @@ __attribute__((__visibility__ ("hidden")))
ac385c
 gpt_disk_get_partition_info(int fd, uint32_t num, uint64_t * start,
ac385c
 			    uint64_t * size, uint8_t *signature,
ac385c
 			    uint8_t * mbr_type, uint8_t * signature_type,
ac385c
-			    int ignore_pmbr_error)
ac385c
+			    int ignore_pmbr_error, int logical_block_size)
ac385c
 {
ac385c
 	gpt_header *gpt = NULL;
ac385c
 	gpt_entry *ptes = NULL, *p;
ac385c
@@ -644,7 +791,8 @@ gpt_disk_get_partition_info(int fd, uint32_t num, uint64_t * start,
ac385c
 	if (report)
ac385c
 		report_errors = 1;
ac385c
 
ac385c
-	rc = find_valid_gpt(fd, &gpt, &ptes, ignore_pmbr_error);
ac385c
+	rc = find_valid_gpt(fd, &gpt, &ptes, ignore_pmbr_error,
ac385c
+			    logical_block_size);
ac385c
 	if (rc < 0)
ac385c
 		return rc;
ac385c
 
ac385c
diff --git a/src/gpt.h b/src/gpt.h
ac385c
index 2249b59..678ee37 100644
ac385c
--- a/src/gpt.h
ac385c
+++ b/src/gpt.h
ac385c
@@ -147,7 +147,8 @@ extern int gpt_disk_get_partition_info (int fd, uint32_t num, uint64_t *start,
ac385c
 					uint64_t *size, uint8_t *signature,
ac385c
 					uint8_t *mbr_type,
ac385c
 					uint8_t *signature_type,
ac385c
-					int ignore_pmbr_error)
ac385c
+					int ignore_pmbr_error,
ac385c
+					int logical_sector_size)
ac385c
 	__attribute__((__nonnull__ (3, 4, 5, 6, 7)))
ac385c
 	__attribute__((__visibility__ ("hidden")));
ac385c
 
ac385c
-- 
ac385c
2.12.2
ac385c