Commit 4e11ea7a authored by Matt Domsch's avatar Matt Domsch Committed by Linus Torvalds

[PATCH] EFI GPT: reduce alternate header probing

EFI partitioning scheme was reading the last reported sector of the
block device to look for the alternate GPT header, before it had
confirmed that it should.  This causes problems for devices with the
following problems:  a) those who misreport their size (typically
off-by-one), and b) those who fail when asked to read a block
outside their range.

This patch moves the test for the Protective Master Boot Record (PMBR)
ahead of the tests for the Primary and Alternate GPT headers.  If the
PMBR is not valid, the disk is assumed to not be a GPT disk.  This can
be overridden with the 'gpt' kernel command line option.  If the
Primary GPT header is not valid, the Alternate GPT header is not
probed automatically unless the 'gpt' kernel command line option is
passed.  If the both the PMBR and Primary GPT header are valid, then
the Alternate GPT header at the end of the disk is probed.

Also re-enables CONFIG_EFI_PARTITION for all architectures.
Signed-off-by: default avatarMatt Domsch <Matt_Domsch@dell.com>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 2b6ba494
...@@ -219,7 +219,7 @@ config SUN_PARTITION ...@@ -219,7 +219,7 @@ config SUN_PARTITION
config EFI_PARTITION config EFI_PARTITION
bool "EFI GUID Partition support" bool "EFI GUID Partition support"
depends on PARTITION_ADVANCED && IA64 depends on PARTITION_ADVANCED
select CRC32 select CRC32
help help
Say Y here if you would like to use hard disks under Linux which Say Y here if you would like to use hard disks under Linux which
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
* Per Intel EFI Specification v1.02 * Per Intel EFI Specification v1.02
* http://developer.intel.com/technology/efi/efi.htm * http://developer.intel.com/technology/efi/efi.htm
* efi.[ch] by Matt Domsch <Matt_Domsch@dell.com> * efi.[ch] by Matt Domsch <Matt_Domsch@dell.com>
* Copyright 2000,2001,2002 Dell Inc. * Copyright 2000,2001,2002,2004 Dell Inc.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
...@@ -23,6 +23,11 @@ ...@@ -23,6 +23,11 @@
* TODO: * TODO:
* *
* Changelog: * Changelog:
* Mon Nov 09 2004 Matt Domsch <Matt_Domsch@dell.com>
* - test for valid PMBR and valid PGPT before ever reading
* AGPT, allow override with 'gpt' kernel command line option.
* - check for first/last_usable_lba outside of size of disk
*
* Tue Mar 26 2002 Matt Domsch <Matt_Domsch@dell.com> * Tue Mar 26 2002 Matt Domsch <Matt_Domsch@dell.com>
* - Ported to 2.5.7-pre1 and 2.5.7-dj2 * - Ported to 2.5.7-pre1 and 2.5.7-dj2
* - Applied patch to avoid fault in alternate header handling * - Applied patch to avoid fault in alternate header handling
...@@ -130,32 +135,6 @@ efi_crc32(const void *buf, unsigned long len) ...@@ -130,32 +135,6 @@ efi_crc32(const void *buf, unsigned long len)
return (crc32(~0L, buf, len) ^ ~0L); return (crc32(~0L, buf, len) ^ ~0L);
} }
/**
* is_pmbr_valid(): test Protective MBR for validity
* @mbr: pointer to a legacy mbr structure
*
* Description: Returns 1 if PMBR is valid, 0 otherwise.
* Validity depends on two things:
* 1) MSDOS signature is in the last two bytes of the MBR
* 2) One partition of type 0xEE is found
*/
static int
is_pmbr_valid(legacy_mbr *mbr)
{
int i, found = 0, signature = 0;
if (!mbr)
return 0;
signature = (le16_to_cpu(mbr->signature) == MSDOS_MBR_SIGNATURE);
for (i = 0; signature && i < 4; i++) {
if (mbr->partition_record[i].sys_ind ==
EFI_PMBR_OSTYPE_EFI_GPT) {
found = 1;
break;
}
}
return (signature && found);
}
/** /**
* last_lba(): return number of last logical block of device * last_lba(): return number of last logical block of device
* @bdev: block device * @bdev: block device
...@@ -168,7 +147,40 @@ is_pmbr_valid(legacy_mbr *mbr) ...@@ -168,7 +147,40 @@ is_pmbr_valid(legacy_mbr *mbr)
static u64 static u64
last_lba(struct block_device *bdev) last_lba(struct block_device *bdev)
{ {
return (bdev->bd_inode->i_size >> 9) - 1; if (!bdev || !bdev->bd_inode)
return 0;
return (bdev->bd_inode->i_size >> 9) - 1ULL;
}
static inline int
pmbr_part_valid(struct partition *part, u64 lastlba)
{
if (part->sys_ind == EFI_PMBR_OSTYPE_EFI_GPT &&
le32_to_cpu(part->start_sect) == 1UL)
return 1;
return 0;
}
/**
* is_pmbr_valid(): test Protective MBR for validity
* @mbr: pointer to a legacy mbr structure
* @lastlba: last_lba for the whole device
*
* Description: Returns 1 if PMBR is valid, 0 otherwise.
* Validity depends on two things:
* 1) MSDOS signature is in the last two bytes of the MBR
* 2) One partition of type 0xEE is found
*/
static int
is_pmbr_valid(legacy_mbr *mbr, u64 lastlba)
{
int i;
if (!mbr || le16_to_cpu(mbr->signature) != MSDOS_MBR_SIGNATURE)
return 0;
for (i = 0; i < 4; i++)
if (pmbr_part_valid(&mbr->partition_record[i], lastlba))
return 1;
return 0;
} }
/** /**
...@@ -186,8 +198,8 @@ read_lba(struct block_device *bdev, u64 lba, u8 * buffer, size_t count) ...@@ -186,8 +198,8 @@ read_lba(struct block_device *bdev, u64 lba, u8 * buffer, size_t count)
{ {
size_t totalreadcount = 0; size_t totalreadcount = 0;
if (!bdev || !buffer) if (!bdev || !buffer || lba > last_lba(bdev))
return 0; return 0;
while (count) { while (count) {
int copied = 512; int copied = 512;
...@@ -206,7 +218,6 @@ read_lba(struct block_device *bdev, u64 lba, u8 * buffer, size_t count) ...@@ -206,7 +218,6 @@ read_lba(struct block_device *bdev, u64 lba, u8 * buffer, size_t count)
return totalreadcount; return totalreadcount;
} }
/** /**
* alloc_read_gpt_entries(): reads partition entries from disk * alloc_read_gpt_entries(): reads partition entries from disk
* @bdev * @bdev
...@@ -289,6 +300,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba, ...@@ -289,6 +300,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba,
gpt_header **gpt, gpt_entry **ptes) gpt_header **gpt, gpt_entry **ptes)
{ {
u32 crc, origcrc; u32 crc, origcrc;
u64 lastlba;
if (!bdev || !gpt || !ptes) if (!bdev || !gpt || !ptes)
return 0; return 0;
...@@ -301,9 +313,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba, ...@@ -301,9 +313,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba,
"%lld != %lld\n", "%lld != %lld\n",
(unsigned long long)le64_to_cpu((*gpt)->signature), (unsigned long long)le64_to_cpu((*gpt)->signature),
(unsigned long long)GPT_HEADER_SIGNATURE); (unsigned long long)GPT_HEADER_SIGNATURE);
kfree(*gpt); goto fail;
*gpt = NULL;
return 0;
} }
/* Check the GUID Partition Table CRC */ /* Check the GUID Partition Table CRC */
...@@ -315,9 +325,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba, ...@@ -315,9 +325,7 @@ is_gpt_valid(struct block_device *bdev, u64 lba,
Dprintk Dprintk
("GUID Partition Table Header CRC is wrong: %x != %x\n", ("GUID Partition Table Header CRC is wrong: %x != %x\n",
crc, origcrc); crc, origcrc);
kfree(*gpt); goto fail;
*gpt = NULL;
return 0;
} }
(*gpt)->header_crc32 = cpu_to_le32(origcrc); (*gpt)->header_crc32 = cpu_to_le32(origcrc);
...@@ -327,16 +335,28 @@ is_gpt_valid(struct block_device *bdev, u64 lba, ...@@ -327,16 +335,28 @@ is_gpt_valid(struct block_device *bdev, u64 lba,
Dprintk("GPT my_lba incorrect: %lld != %lld\n", Dprintk("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); (unsigned long long)lba);
kfree(*gpt); goto fail;
*gpt = NULL;
return 0;
} }
if (!(*ptes = alloc_read_gpt_entries(bdev, *gpt))) { /* Check the first_usable_lba and last_usable_lba are
kfree(*gpt); * within the disk.
*gpt = NULL; */
return 0; lastlba = last_lba(bdev);
if (le64_to_cpu((*gpt)->first_usable_lba) > lastlba) {
Dprintk("GPT: first_usable_lba incorrect: %lld > %lld\n",
(unsigned long long)le64_to_cpu((*gpt)->first_usable_lba),
(unsigned long long)lastlba);
goto fail;
} }
if (le64_to_cpu((*gpt)->last_usable_lba) > lastlba) {
Dprintk("GPT: last_usable_lba incorrect: %lld > %lld\n",
(unsigned long long)le64_to_cpu((*gpt)->last_usable_lba),
(unsigned long long)lastlba);
goto fail;
}
if (!(*ptes = alloc_read_gpt_entries(bdev, *gpt)))
goto fail;
/* Check the GUID Partition Entry Array CRC */ /* Check the GUID Partition Entry Array CRC */
crc = efi_crc32((const unsigned char *) (*ptes), crc = efi_crc32((const unsigned char *) (*ptes),
...@@ -345,15 +365,36 @@ is_gpt_valid(struct block_device *bdev, u64 lba, ...@@ -345,15 +365,36 @@ is_gpt_valid(struct block_device *bdev, u64 lba,
if (crc != le32_to_cpu((*gpt)->partition_entry_array_crc32)) { if (crc != le32_to_cpu((*gpt)->partition_entry_array_crc32)) {
Dprintk("GUID Partitition Entry Array CRC check failed.\n"); Dprintk("GUID Partitition Entry Array CRC check failed.\n");
kfree(*gpt); goto fail_ptes;
*gpt = NULL;
kfree(*ptes);
*ptes = NULL;
return 0;
} }
/* We're done, all's well */ /* We're done, all's well */
return 1; return 1;
fail_ptes:
kfree(*ptes);
*ptes = NULL;
fail:
kfree(*gpt);
*gpt = NULL;
return 0;
}
/**
* is_pte_valid() - tests one PTE for validity
* @pte is the pte to check
* @lastlba is last lba of the disk
*
* Description: returns 1 if valid, 0 on error.
*/
static inline int
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;
} }
/** /**
...@@ -464,8 +505,13 @@ compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba) ...@@ -464,8 +505,13 @@ compare_gpts(gpt_header *pgpt, gpt_header *agpt, u64 lastlba)
* @ptes is a PTEs ptr, filled on return. * @ptes is a PTEs ptr, filled on return.
* Description: Returns 1 if valid, 0 on error. * Description: Returns 1 if valid, 0 on error.
* If valid, returns pointers to newly allocated GPT header and PTEs. * If valid, returns pointers to newly allocated GPT header and PTEs.
* Validity depends on finding either the Primary GPT header and PTEs valid, * Validity depends on PMBR being valid (or being overridden by the
* or the Alternate GPT header and PTEs valid, and the PMBR valid. * 'gpt' kernel command line option) and finding either the Primary
* GPT header and PTEs valid, or the Alternate GPT header and PTEs
* valid. If the Primary GPT header is not valid, the Alternate GPT header
* is not checked unless the 'gpt' kernel command line option is passed.
* This protects against devices which misreport their size, and forces
* the user to decide to use the Alternate GPT.
*/ */
static int static int
find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes) find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes)
...@@ -479,70 +525,43 @@ find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes) ...@@ -479,70 +525,43 @@ find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes)
return 0; return 0;
lastlba = last_lba(bdev); lastlba = last_lba(bdev);
if (!force_gpt) {
/* This will be added to the EFI Spec. per Intel after v1.02. */
legacymbr = kmalloc(sizeof (*legacymbr), GFP_KERNEL);
if (legacymbr) {
memset(legacymbr, 0, sizeof (*legacymbr));
read_lba(bdev, 0, (u8 *) legacymbr,
sizeof (*legacymbr));
good_pmbr = is_pmbr_valid(legacymbr, lastlba);
kfree(legacymbr);
legacymbr=NULL;
}
if (!good_pmbr)
goto fail;
}
good_pgpt = is_gpt_valid(bdev, GPT_PRIMARY_PARTITION_TABLE_LBA, good_pgpt = is_gpt_valid(bdev, GPT_PRIMARY_PARTITION_TABLE_LBA,
&pgpt, &pptes); &pgpt, &pptes);
if (good_pgpt) { if (good_pgpt)
good_agpt = is_gpt_valid(bdev, good_agpt = is_gpt_valid(bdev,
le64_to_cpu(pgpt->alternate_lba), le64_to_cpu(pgpt->alternate_lba),
&agpt, &aptes); &agpt, &aptes);
if (!good_agpt) { if (!good_agpt && force_gpt)
good_agpt = is_gpt_valid(bdev, lastlba,
&agpt, &aptes);
}
}
else {
good_agpt = is_gpt_valid(bdev, lastlba, good_agpt = is_gpt_valid(bdev, lastlba,
&agpt, &aptes); &agpt, &aptes);
}
/* The obviously unsuccessful case */ /* The obviously unsuccessful case */
if (!good_pgpt && !good_agpt) { if (!good_pgpt && !good_agpt)
goto fail;
}
/* This will be added to the EFI Spec. per Intel after v1.02. */
legacymbr = kmalloc(sizeof (*legacymbr), GFP_KERNEL);
if (legacymbr) {
memset(legacymbr, 0, sizeof (*legacymbr));
read_lba(bdev, 0, (u8 *) legacymbr,
sizeof (*legacymbr));
good_pmbr = is_pmbr_valid(legacymbr);
kfree(legacymbr);
legacymbr=NULL;
}
/* Failure due to bad PMBR */
if ((good_pgpt || good_agpt) && !good_pmbr && !force_gpt) {
printk(KERN_WARNING
" Warning: Disk has a valid GPT signature "
"but invalid PMBR.\n");
printk(KERN_WARNING
" Assuming this disk is *not* a GPT disk anymore.\n");
printk(KERN_WARNING
" Use gpt kernel option to override. "
"Use GNU Parted to correct disk.\n");
goto fail; goto fail;
}
/* Would fail due to bad PMBR, but force GPT anyhow */
if ((good_pgpt || good_agpt) && !good_pmbr && force_gpt) {
printk(KERN_WARNING
" Warning: Disk has a valid GPT signature but "
"invalid PMBR.\n");
printk(KERN_WARNING
" Use GNU Parted to correct disk.\n");
printk(KERN_WARNING
" gpt option taken, disk treated as GPT.\n");
}
compare_gpts(pgpt, agpt, lastlba); compare_gpts(pgpt, agpt, lastlba);
/* The good cases */ /* The good cases */
if (good_pgpt && (good_pmbr || force_gpt)) { if (good_pgpt) {
*gpt = pgpt; *gpt = pgpt;
*ptes = pptes; *ptes = pptes;
if (agpt) { kfree(agpt); agpt = NULL; } kfree(agpt);
if (aptes) { kfree(aptes); aptes = NULL; } kfree(aptes);
if (!good_agpt) { if (!good_agpt) {
printk(KERN_WARNING printk(KERN_WARNING
"Alternate GPT is invalid, " "Alternate GPT is invalid, "
...@@ -550,21 +569,21 @@ find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes) ...@@ -550,21 +569,21 @@ find_valid_gpt(struct block_device *bdev, gpt_header **gpt, gpt_entry **ptes)
} }
return 1; return 1;
} }
else if (good_agpt && (good_pmbr || force_gpt)) { else if (good_agpt) {
*gpt = agpt; *gpt = agpt;
*ptes = aptes; *ptes = aptes;
if (pgpt) { kfree(pgpt); pgpt = NULL; } kfree(pgpt);
if (pptes) { kfree(pptes); pptes = NULL; } kfree(pptes);
printk(KERN_WARNING printk(KERN_WARNING
"Primary GPT is invalid, using alternate GPT.\n"); "Primary GPT is invalid, using alternate GPT.\n");
return 1; return 1;
} }
fail: fail:
if (pgpt) { kfree(pgpt); pgpt=NULL; } kfree(pgpt);
if (agpt) { kfree(agpt); agpt=NULL; } kfree(agpt);
if (pptes) { kfree(pptes); pptes=NULL; } kfree(pptes);
if (aptes) { kfree(aptes); aptes=NULL; } kfree(aptes);
*gpt = NULL; *gpt = NULL;
*ptes = NULL; *ptes = NULL;
return 0; return 0;
...@@ -606,15 +625,15 @@ efi_partition(struct parsed_partitions *state, struct block_device *bdev) ...@@ -606,15 +625,15 @@ efi_partition(struct parsed_partitions *state, struct block_device *bdev)
Dprintk("GUID Partition Table is valid! Yea!\n"); Dprintk("GUID Partition Table is valid! Yea!\n");
for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1; i++) { for (i = 0; i < le32_to_cpu(gpt->num_partition_entries) && i < state->limit-1; i++) {
if (!efi_guidcmp(ptes[i].partition_type_guid, NULL_GUID)) if (!is_pte_valid(&ptes[i], last_lba(bdev)))
continue; continue;
put_partition(state, i+1, le64_to_cpu(ptes[i].starting_lba), put_partition(state, i+1, le64_to_cpu(ptes[i].starting_lba),
(le64_to_cpu(ptes[i].ending_lba) - (le64_to_cpu(ptes[i].ending_lba) -
le64_to_cpu(ptes[i].starting_lba) + le64_to_cpu(ptes[i].starting_lba) +
1)); 1ULL));
/* If there's this is a RAID volume, tell md */ /* If this is a RAID volume, tell md */
if (!efi_guidcmp(ptes[i].partition_type_guid, if (!efi_guidcmp(ptes[i].partition_type_guid,
PARTITION_LINUX_RAID_GUID)) PARTITION_LINUX_RAID_GUID))
state->parts[i+1].flags = 1; state->parts[i+1].flags = 1;
...@@ -624,22 +643,3 @@ efi_partition(struct parsed_partitions *state, struct block_device *bdev) ...@@ -624,22 +643,3 @@ efi_partition(struct parsed_partitions *state, struct block_device *bdev)
printk("\n"); printk("\n");
return 1; return 1;
} }
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: nil
* tab-width: 8
* End:
*/
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment