aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichal Soltys <soltys@ziu.info>2014-06-29 21:41:38 +0200
committerH. Peter Anvin <hpa@zytor.com>2014-08-26 18:07:16 -0700
commitf964a9772fb580d373b5b3da88f649f50d575a5a (patch)
tree85b2bd3783e8dc015510f5308d2b2a180fda67b8
parent1ada8a37ae30c2e4283c3e65444ccd72cf8a2e85 (diff)
downloadsyslinux-f964a9772fb580d373b5b3da88f649f50d575a5a.tar.gz
syslinux-f964a9772fb580d373b5b3da88f649f50d575a5a.tar.xz
syslinux-f964a9772fb580d373b5b3da88f649f50d575a5a.zip
chain/partiter: fix and improve gpt handling in buggy cases
Previous version had some issues in case of error situations, among those: - backup gpt header was not read if reading of primary failed - alternating nature of lba_cur and lba_alt was ignored This patch fixes this and extends the gpt handling. The current behavior is: - try primary header; if unreadable or checksum fails (or sanity checks fail unless relax flag is set) - try secondary header - try main partition table referenced in the header that was read; if it's unreadable or if its crc check fails - try alternative (so if we managed to read primary header, table at the end would be considered as alternative - if we managed to read secondary header, table at the beginning would be treated as such) Signed-off-by: H. Peter Anvin <hpa@zytor.com>
-rw-r--r--com32/chain/partiter.c204
1 files changed, 128 insertions, 76 deletions
diff --git a/com32/chain/partiter.c b/com32/chain/partiter.c
index 1eb5350d..ee19e8be 100644
--- a/com32/chain/partiter.c
+++ b/com32/chain/partiter.c
@@ -412,34 +412,16 @@ static inline int valid_crc(uint32_t crc, const uint8_t *buf, unsigned int siz)
return crc == crc32(crc32(0, NULL, 0), buf, siz);
}
-static int gpt_check_hdr_crc(const struct disk_info * const diskinfo, struct disk_gpt_header **_gh)
+static int valid_crc_hdr(void *buf)
{
- struct disk_gpt_header *gh = *_gh;
- uint64_t lba_alt;
- uint32_t hold_crc32;
+ struct disk_gpt_header *gh = buf;
+ uint32_t crc = gh->chksum;
+ int valid;
- hold_crc32 = gh->chksum;
gh->chksum = 0;
- if (!valid_crc(hold_crc32, (const uint8_t *)gh, gh->hdr_size)) {
- warn("Primary GPT header checksum invalid.");
- /* retry with backup */
- lba_alt = gh->lba_alt;
- free(gh);
- if (!(gh = *_gh = disk_read_sectors(diskinfo, lba_alt, 1))) {
- error("Couldn't read backup GPT header.");
- return -1;
- }
- hold_crc32 = gh->chksum;
- gh->chksum = 0;
- if (!valid_crc(hold_crc32, (const uint8_t *)gh, gh->hdr_size)) {
- error("Secondary GPT header checksum invalid.");
- return -1;
- }
- }
- /* restore old checksum */
- gh->chksum = hold_crc32;
-
- return 0;
+ valid = crc == crc32(crc32(0, NULL, 0), buf, gh->hdr_size);
+ gh->chksum = crc;
+ return valid;
}
static int pi_next_(struct part_iter *iter)
@@ -546,10 +528,108 @@ void pi_del(struct part_iter **_iter)
*_iter = NULL;
}
+static void try_gpt_we(const char *str, int sec)
+{
+ if (sec)
+ error(str);
+ else
+ warn(str);
+}
+
+static struct disk_gpt_header *try_gpt_hdr(const struct disk_info *di, int sec)
+{
+ const char *desc = sec ? "backup" : "primary";
+ uint64_t gpt_cur = sec ? di->lbacnt - 1 : 1;
+ struct disk_gpt_header *gpth;
+ char errbuf[64];
+
+ gpth = disk_read_sectors(di, gpt_cur, 1);
+ if (!gpth) {
+ sprintf(errbuf, "Unable to read %s GPT header.", desc);
+ try_gpt_we(errbuf, sec);
+ return NULL;
+ }
+ if(!valid_crc_hdr(gpth)) {
+ sprintf(errbuf, "Invalid checksum of %s GPT header.", desc);
+ try_gpt_we(errbuf, sec);
+ free(gpth);
+ return NULL;
+ }
+ return gpth;
+}
+
+static struct disk_gpt_part_entry *try_gpt_list(const struct disk_info *di, const struct disk_gpt_header *gpth, int alt)
+{
+ int pri = gpth->lba_cur < gpth->lba_alt;
+ const char *desc = alt ? "alternative" : "main";
+ struct disk_gpt_part_entry *gptl;
+ char errbuf[64];
+ uint64_t gpt_lsiz; /* size of GPT partition list in bytes */
+ uint64_t gpt_lcnt; /* size of GPT partition in sectors */
+ uint64_t gpt_loff; /* offset to GPT partition list in sectors */
+
+ gpt_lsiz = (uint64_t)gpth->part_size * gpth->part_count;
+ gpt_lcnt = (gpt_lsiz + di->bps - 1) / di->bps;
+ if (!alt) {
+ /* prefer header value for partition table if not asking for alternative */
+ gpt_loff = gpth->lba_table;
+ } else {
+ /* try to read alternative, we have to calculate its position */
+ if (!pri)
+ gpt_loff = gpth->lba_alt + 1;
+ else
+ gpt_loff = gpth->lba_alt - gpt_lcnt;
+ }
+
+ gptl = disk_read_sectors(di, gpt_loff, gpt_lcnt);
+ if (!gptl) {
+ sprintf(errbuf, "Unable to read %s GPT partition list.", desc);
+ try_gpt_we(errbuf, alt);
+ return NULL;
+ }
+ if (!valid_crc(gpth->table_chksum, (const uint8_t *)gptl, gpt_lsiz)) {
+ sprintf(errbuf, "Invalid checksum of %s GPT partition list.", desc);
+ try_gpt_we(errbuf, alt);
+ free(gptl);
+ return NULL;
+ }
+ return gptl;
+}
+
+static int notsane_gpt_hdr(const struct disk_info *di, const struct disk_gpt_header *gpth, int flags)
+{
+ uint64_t gpt_loff; /* offset to GPT partition list in sectors */
+ uint64_t gpt_lsiz; /* size of GPT partition list in bytes */
+ uint64_t gpt_lcnt; /* size of GPT partition in sectors */
+
+ if (flags & PIF_RELAX)
+ return 0;
+
+ gpt_loff = gpth->lba_table;
+ gpt_lsiz = (uint64_t)gpth->part_size * gpth->part_count;
+ gpt_lcnt = (gpt_lsiz + di->bps - 1) / di->bps;
+
+ /*
+ * disk_read_sectors allows reading of max 255 sectors, so we use
+ * it as a sanity check base. EFI doesn't specify max (AFAIK).
+ */
+ if (gpt_loff < 2 || !gpt_lsiz || gpt_lcnt > 255u ||
+ gpth->lba_first_usable > gpth->lba_last_usable ||
+ !sane(gpt_loff, gpt_lcnt) ||
+ gpt_loff + gpt_lcnt > gpth->lba_first_usable ||
+ !sane(gpth->lba_last_usable, gpt_lcnt) ||
+ gpth->lba_last_usable + gpt_lcnt >= gpth->lba_alt ||
+ gpth->lba_alt >= di->lbacnt ||
+ gpth->part_size < sizeof(struct disk_gpt_part_entry))
+ return -1;
+
+ return 0;
+}
+
/* pi_begin() - validate and and get proper iterator for a disk described by di */
struct part_iter *pi_begin(const struct disk_info *di, int flags)
{
- int gptprot, ret = -1;
+ int isgpt = 0, ret = -1;
struct part_iter *iter;
struct disk_dos_mbr *mbr = NULL;
struct disk_gpt_header *gpth = NULL;
@@ -561,7 +641,7 @@ struct part_iter *pi_begin(const struct disk_info *di, int flags)
/* Read MBR */
if (!(mbr = disk_read_sectors(di, 0, 1))) {
- error("Couldn't read the first disk sector.");
+ error("Unable to read the first disk sector.");
goto bail;
}
@@ -574,69 +654,41 @@ struct part_iter *pi_begin(const struct disk_info *di, int flags)
}
/* Check for GPT protective MBR */
- gptprot = 0;
for (size_t i = 0; i < 4; i++)
- gptprot |= (mbr->table[i].ostype == 0xEE);
- if (gptprot && !(flags & PIF_PREFMBR)) {
- if (!(gpth = disk_read_sectors(di, 1, 1))) {
- error("Couldn't read potential GPT header.");
+ isgpt |= (mbr->table[i].ostype == 0xEE);
+ isgpt = isgpt && !(flags & PIF_PREFMBR);
+
+ /* Try to read GPT header */
+ if (isgpt) {
+ gpth = try_gpt_hdr(di, 0);
+ if (!gpth)
+ /*
+ * this read might fail if bios reports different disk size (different vm/pc)
+ * not much we can do here to avoid it
+ */
+ gpth = try_gpt_hdr(di, 1);
+ if (!gpth)
goto bail;
- }
}
if (gpth && gpth->rev.uint32 == 0x00010000 &&
!memcmp(gpth->sig, disk_gpt_sig_magic, sizeof gpth->sig)) {
/* looks like GPT v1.0 */
- uint64_t gpt_loff; /* offset to GPT partition list in sectors */
- uint64_t gpt_lsiz; /* size of GPT partition list in bytes */
- uint64_t gpt_lcnt; /* size of GPT partition in sectors */
#ifdef DEBUG
dprintf("Looks like a GPT v1.0 disk.\n");
disk_gpt_header_dump(gpth);
#endif
- /* Verify checksum, fallback to backup, then bail if invalid */
- if (gpt_check_hdr_crc(di, &gpth))
- goto bail;
-
- gpt_loff = gpth->lba_table;
- gpt_lsiz = (uint64_t)gpth->part_size * gpth->part_count;
- gpt_lcnt = (gpt_lsiz + di->bps - 1) / di->bps;
-
- /*
- * disk_read_sectors allows reading of max 255 sectors, so we use
- * it as a sanity check base. EFI doesn't specify max (AFAIK).
- * Apart from that, some extensive sanity checks.
- */
- if (!(flags & PIF_RELAX) && (
- !gpt_loff || !gpt_lsiz || gpt_lcnt > 255u ||
- gpth->lba_first_usable > gpth->lba_last_usable ||
- !sane(gpt_loff, gpt_lcnt) ||
- gpt_loff + gpt_lcnt > gpth->lba_first_usable ||
- !sane(gpth->lba_last_usable, gpt_lcnt) ||
- gpth->lba_last_usable + gpt_lcnt >= gpth->lba_alt ||
- gpth->lba_alt >= di->lbacnt ||
- gpth->part_size < sizeof *gptl)) {
- error("Invalid GPT header's values.");
+ if (notsane_gpt_hdr(di, gpth, flags)) {
+ error("GPT header values are corrupted.");
goto bail;
}
- if (!(gptl = disk_read_sectors(di, gpt_loff, gpt_lcnt))) {
- error("Couldn't read GPT partition list.");
+
+ gptl = try_gpt_list(di, gpth, 0);
+ if (!gptl)
+ gptl = try_gpt_list(di, gpth, 1);
+ if (!gptl)
goto bail;
- }
- /* Check array checksum(s). */
- if (!valid_crc(gpth->table_chksum, (const uint8_t *)gptl, (unsigned int)gpt_lsiz)) {
- warn("Checksum of the main GPT partition list is invalid, trying backup.");
- free(gptl);
- /* secondary array directly precedes secondary header */
- if (!(gptl = disk_read_sectors(di, gpth->lba_alt - gpt_lcnt, gpt_lcnt))) {
- error("Couldn't read backup GPT partition list.");
- goto bail;
- }
- if (!valid_crc(gpth->table_chksum, (const uint8_t *)gptl, gpt_lsiz)) {
- error("Checksum of the backup GPT partition list is invalid, giving up.");
- goto bail;
- }
- }
+
/* looks like GPT */
ret = pi_gpt_ctor(iter, di, flags, gpth, gptl);
} else {