/* * EFI GPT partition parsing code * * Copyright (C) 2009 Karel Zak * * This file may be redistributed under the terms of the * GNU Lesser General Public License. * * This code is not copy & past from any other implementation. * * For more information about GPT start your study at: * http://en.wikipedia.org/wiki/GUID_Partition_Table * http://technet.microsoft.com/en-us/library/cc739412(WS.10).aspx */ #include #include #include #include #include #include #include #include "partitions.h" #include "crc32.h" #define GPT_PRIMARY_LBA 1 /* Signature - “EFI PART” */ #define GPT_HEADER_SIGNATURE 0x5452415020494645ULL #define GPT_HEADER_SIGNATURE_STR "EFI PART" /* basic types */ typedef uint16_t efi_char16_t; /* UUID */ typedef struct { uint32_t time_low; uint16_t time_mid; uint16_t time_hi_and_version; uint8_t clock_seq_hi; uint8_t clock_seq_low; uint8_t node[6]; } efi_guid_t; #define GPT_UNUSED_ENTRY_GUID \ ((efi_guid_t) { 0x00000000, 0x0000, 0x0000, 0x00, 0x00, \ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }}) struct gpt_header { uint64_t signature; /* "EFI PART" */ uint32_t revision; uint32_t header_size; /* usually 92 bytes */ uint32_t header_crc32; /* checksum of header with this * field zeroed during calculation */ uint32_t reserved1; uint64_t my_lba; /* location of this header copy */ uint64_t alternate_lba; /* location of the other header copy */ uint64_t first_usable_lba; /* first usable LBA for partitions */ uint64_t last_usable_lba; /* last usable LBA for partitions */ efi_guid_t disk_guid; /* disk UUID */ uint64_t partition_entries_lba; /* always 2 in primary header copy */ uint32_t num_partition_entries; uint32_t sizeof_partition_entry; uint32_t partition_entry_array_crc32; /* * The rest of the block is reserved by UEFI and must be zero. EFI * standard handles this by: * * uint8_t reserved2[ BLKSSZGET - 92 ]; * * This definition is useless in practice. It is necessary to read * whole block from the device rather than sizeof(struct gpt_header) * only. */ } __attribute__ ((packed)); /*** not used struct gpt_entry_attributes { uint64_t required_to_function:1; uint64_t reserved:47; uint64_t type_guid_specific:16; } __attribute__ ((packed)); ***/ struct gpt_entry { efi_guid_t partition_type_guid; /* type UUID */ efi_guid_t unique_partition_guid; /* partition UUID */ uint64_t starting_lba; uint64_t ending_lba; /*struct gpt_entry_attributes attributes;*/ uint64_t attributes; efi_char16_t partition_name[72 / sizeof(efi_char16_t)]; /* UTF-16LE string*/ } __attribute__ ((packed)); /* * EFI uses crc32 with ~0 seed and xor's with ~0 at the end. */ static inline uint32_t count_crc32(const unsigned char *buf, size_t len, size_t exclude_off, size_t exclude_len) { return (ul_crc32_exclude_offset(~0L, buf, len, exclude_off, exclude_len) ^ ~0L); } static inline unsigned char *get_lba_buffer(blkid_probe pr, uint64_t lba, size_t bytes) { return blkid_probe_get_buffer(pr, blkid_probe_get_sectorsize(pr) * lba, bytes); } static inline int guidcmp(efi_guid_t left, efi_guid_t right) { return memcmp(&left, &right, sizeof (efi_guid_t)); } /* * UUID is traditionally 16 byte big-endian array, except Intel EFI * specification where the UUID is a structure of little-endian fields. */ static void swap_efi_guid(efi_guid_t *uid) { uid->time_low = swab32(uid->time_low); uid->time_mid = swab16(uid->time_mid); uid->time_hi_and_version = swab16(uid->time_hi_and_version); } static int last_lba(blkid_probe pr, uint64_t *lba) { uint64_t sz = blkid_probe_get_size(pr); unsigned int ssz = blkid_probe_get_sectorsize(pr); if (sz < ssz) return -1; *lba = (sz / ssz) - 1ULL; return 0; } /* * Protective (legacy) MBR. * * This MBR contains standard DOS partition table with a single partition, type * of 0xEE. The partition usually encompassing the entire GPT drive - or 2TiB * for large disks. * * Note that Apple uses GPT/MBR hybrid disks, where the DOS partition table is * synchronized with GPT. This synchronization has many restriction of course * (due DOS PT limitations). * * Note that the PMBR detection is optional (enabled by default) and could be * disabled by BLKID_PARTS_FOPCE_GPT flag (see also blkid_partitions_set_flags()). */ static int is_pmbr_valid(blkid_probe pr, int *has) { int flags = blkid_partitions_get_flags(pr); unsigned char *data; struct dos_partition *p; int i; if (has) *has = 0; else if (flags & BLKID_PARTS_FORCE_GPT) return 1; /* skip PMBR check */ data = blkid_probe_get_sector(pr, 0); if (!data) { if (errno) return -errno; goto failed; } if (!mbr_is_valid_magic(data)) goto failed; for (i = 0, p = mbr_get_partition(data, 0); i < 4; i++, p++) { if (p->sys_ind == MBR_GPT_PARTITION) { DBG(LOWPROBE, ul_debug(" #%d valid PMBR partition", i + 1)); goto ok; } } failed: return 0; ok: if (has) *has = 1; return 1; } /* * Reads GPT header to @hdr and returns a pointer to @hdr or NULL in case of * error. The function also returns GPT entries in @ents. * * Note, this function does not allocate any memory. The GPT header has fixed * size so we use stack, and @ents returns memory from libblkid buffer (so the * next blkid_probe_get_buffer() will overwrite this buffer). * * This function checks validity of header and entries array. A corrupted * header is not returned. */ static struct gpt_header *get_gpt_header( blkid_probe pr, struct gpt_header *hdr, struct gpt_entry **ents, uint64_t lba, uint64_t lastlba) { struct gpt_header *h; uint32_t crc; uint64_t lu, fu; uint64_t esz; uint32_t hsz, ssz; ssz = blkid_probe_get_sectorsize(pr); DBG(LOWPROBE, ul_debug(" checking for GPT header at %"PRIu64, lba)); /* whole sector is allocated for GPT header */ h = (struct gpt_header *) get_lba_buffer(pr, lba, ssz); if (!h) return NULL; if (le64_to_cpu(h->signature) != GPT_HEADER_SIGNATURE) return NULL; hsz = le32_to_cpu(h->header_size); /* EFI: The HeaderSize must be greater than 92 and must be less * than or equal to the logical block size. */ if (hsz > ssz || hsz < sizeof(*h)) return NULL; /* Header has to be verified when header_crc32 is zero */ crc = count_crc32((unsigned char *) h, hsz, offsetof(struct gpt_header, header_crc32), sizeof(h->header_crc32)); if (crc != le32_to_cpu(h->header_crc32)) { DBG(LOWPROBE, ul_debug("GPT header corrupted")); return NULL; } /* Valid header has to be at MyLBA */ if (le64_to_cpu(h->my_lba) != lba) { DBG(LOWPROBE, ul_debug( "GPT->MyLBA mismatch with real position")); return NULL; } fu = le64_to_cpu(h->first_usable_lba); lu = le64_to_cpu(h->last_usable_lba); /* Check if First and Last usable LBA makes sense */ if (lu < fu || fu > lastlba || lu > lastlba) { DBG(LOWPROBE, ul_debug( "GPT->{First,Last}UsableLBA out of range")); return NULL; } /* The header has to be outside usable range */ if (fu < lba && lba < lu) { DBG(LOWPROBE, ul_debug("GPT header is inside usable area")); return NULL; } /* Size of blocks with GPT entries */ esz = (uint64_t)le32_to_cpu(h->num_partition_entries) * le32_to_cpu(h->sizeof_partition_entry); if (esz == 0 || esz >= UINT32_MAX || le32_to_cpu(h->sizeof_partition_entry) != sizeof(struct gpt_entry)) { DBG(LOWPROBE, ul_debug("GPT entries undefined")); return NULL; } /* The header seems valid, save it * (we don't care about zeros in hdr->reserved2 area) */ memcpy(hdr, h, sizeof(*h)); h = hdr; /* Read GPT entries */ *ents = (struct gpt_entry *) get_lba_buffer(pr, le64_to_cpu(h->partition_entries_lba), esz); if (!*ents) { DBG(LOWPROBE, ul_debug("GPT entries unreadable")); return NULL; } /* Validate entries */ crc = count_crc32((unsigned char *) *ents, esz, 0, 0); if (crc != le32_to_cpu(h->partition_entry_array_crc32)) { DBG(LOWPROBE, ul_debug("GPT entries corrupted")); return NULL; } return h; } static int probe_gpt_pt(blkid_probe pr, const struct blkid_idmag *mag __attribute__((__unused__))) { uint64_t lastlba = 0, lba; struct gpt_header hdr, *h; struct gpt_entry *e; blkid_parttable tab = NULL; blkid_partlist ls; uint64_t fu, lu; uint32_t ssf, i; efi_guid_t guid; int ret; if (last_lba(pr, &lastlba)) goto nothing; ret = is_pmbr_valid(pr, NULL); if (ret < 0) return ret; else if (ret == 0) goto nothing; errno = 0; h = get_gpt_header(pr, &hdr, &e, (lba = GPT_PRIMARY_LBA), lastlba); if (!h && !errno) h = get_gpt_header(pr, &hdr, &e, (lba = lastlba), lastlba); if (!h) { if (errno) return -errno; goto nothing; } blkid_probe_use_wiper(pr, lba * blkid_probe_get_size(pr), 8); if (blkid_probe_set_magic(pr, blkid_probe_get_sectorsize(pr) * lba, sizeof(GPT_HEADER_SIGNATURE_STR) - 1, (unsigned char *) GPT_HEADER_SIGNATURE_STR)) goto err; guid = h->disk_guid; swap_efi_guid(&guid); if (blkid_partitions_need_typeonly(pr)) { /* Non-binary interface -- caller does not ask for details * about partitions, just set generic variables only. */ blkid_partitions_set_ptuuid(pr, (unsigned char *) &guid); return BLKID_PROBE_OK; } ls = blkid_probe_get_partlist(pr); if (!ls) goto nothing; tab = blkid_partlist_new_parttable(ls, "gpt", blkid_probe_get_sectorsize(pr) * lba); if (!tab) goto err; blkid_parttable_set_uuid(tab, (const unsigned char *) &guid); ssf = blkid_probe_get_sectorsize(pr) / 512; fu = le64_to_cpu(h->first_usable_lba); lu = le64_to_cpu(h->last_usable_lba); for (i = 0; i < le32_to_cpu(h->num_partition_entries); i++, e++) { blkid_partition par; uint64_t start = le64_to_cpu(e->starting_lba); uint64_t size = le64_to_cpu(e->ending_lba) - le64_to_cpu(e->starting_lba) + 1ULL; /* 00000000-0000-0000-0000-000000000000 entry */ if (!guidcmp(e->partition_type_guid, GPT_UNUSED_ENTRY_GUID)) { blkid_partlist_increment_partno(ls); continue; } /* the partition has to inside usable range */ if (start < fu || start + size - 1 > lu) { DBG(LOWPROBE, ul_debug( "GPT entry[%d] overflows usable area - ignore", i)); blkid_partlist_increment_partno(ls); continue; } par = blkid_partlist_add_partition(ls, tab, start * ssf, size * ssf); if (!par) goto err; blkid_partition_set_utf8name(par, (unsigned char *) e->partition_name, sizeof(e->partition_name), BLKID_ENC_UTF16LE); guid = e->unique_partition_guid; swap_efi_guid(&guid); blkid_partition_set_uuid(par, (const unsigned char *) &guid); guid = e->partition_type_guid; swap_efi_guid(&guid); blkid_partition_set_type_uuid(par, (const unsigned char *) &guid); blkid_partition_set_flags(par, le64_to_cpu(e->attributes)); } return BLKID_PROBE_OK; nothing: return BLKID_PROBE_NONE; err: return -ENOMEM; } const struct blkid_idinfo gpt_pt_idinfo = { .name = "gpt", .probefunc = probe_gpt_pt, .minsz = 1024 * 1440 + 1, /* ignore floppies */ /* * It would be possible to check for DOS signature (0xAA55), but * unfortunately almost all EFI GPT implementations allow to optionally * skip the legacy MBR. We follows this behavior and MBR is optional. * See is_valid_pmbr(). * * It means we have to always call probe_gpt_pt(). */ .magics = BLKID_NONE_MAGIC }; /* probe for *alone* protective MBR */ static int probe_pmbr_pt(blkid_probe pr, const struct blkid_idmag *mag __attribute__((__unused__))) { int has = 0; struct gpt_entry *e; uint64_t lastlba = 0; struct gpt_header hdr; if (last_lba(pr, &lastlba)) goto nothing; is_pmbr_valid(pr, &has); if (!has) goto nothing; if (!get_gpt_header(pr, &hdr, &e, GPT_PRIMARY_LBA, lastlba) && !get_gpt_header(pr, &hdr, &e, lastlba, lastlba)) return 0; nothing: return 1; } const struct blkid_idinfo pmbr_pt_idinfo = { .name = "PMBR", .probefunc = probe_pmbr_pt, .magics = { { .magic = "\x55\xAA", .len = 2, .sboff = 510 }, { NULL } } };