/* Copyright (c) 2010 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "cgptlib.h" #include "cgptlib_internal.h" #include "crc32.h" #include "gpt.h" #include "utility.h" int CheckParameters(GptData *gpt) { /* Currently, we only support 512-byte sector. In the future, we may support * larger sector. */ if (gpt->sector_bytes != 512) return GPT_ERROR_INVALID_SECTOR_SIZE; /* The sector number of a drive should be reasonable. If the given value is * too small to contain basic GPT structure (PMBR + Headers + Entries), * the value is wrong. */ if (gpt->drive_sectors < (1 + 2 * (1 + GPT_ENTRIES_SECTORS))) return GPT_ERROR_INVALID_SECTOR_NUMBER; return GPT_SUCCESS; } uint32_t HeaderCrc(GptHeader* h) { uint32_t crc32, original_crc32; /* Original CRC is calculated with the CRC field 0. */ original_crc32 = h->header_crc32; h->header_crc32 = 0; crc32 = Crc32((const uint8_t *)h, h->size); h->header_crc32 = original_crc32; return crc32; } int CheckHeader(GptHeader *h, int is_secondary, uint64_t drive_sectors) { if (!h) return 1; /* Make sure we're looking at a header of reasonable size before * attempting to calculate CRC. */ if (Memcmp(h->signature, GPT_HEADER_SIGNATURE, GPT_HEADER_SIGNATURE_SIZE) && Memcmp(h->signature, GPT_HEADER_SIGNATURE2, GPT_HEADER_SIGNATURE_SIZE)) return 1; if (h->revision != GPT_HEADER_REVISION) return 1; if (h->size < MIN_SIZE_OF_HEADER || h->size > MAX_SIZE_OF_HEADER) return 1; /* Check CRC before looking at remaining fields */ if (HeaderCrc(h) != h->header_crc32) return 1; /* Reserved fields must be zero. */ if (h->reserved_zero) return 1; /* Could check that padding is zero, but that doesn't matter to us. */ /* If entry size is different than our struct, we won't be able to * parse it. Technically, any size 2^N where N>=7 is valid. */ if (h->size_of_entry != sizeof(GptEntry)) return 1; if ((h->number_of_entries < MIN_NUMBER_OF_ENTRIES) || (h->number_of_entries > MAX_NUMBER_OF_ENTRIES) || (h->number_of_entries * h->size_of_entry != TOTAL_ENTRIES_SIZE)) return 1; /* Check locations for the header and its entries. The primary * immediately follows the PMBR, and is followed by its entries. * The secondary is at the end of the drive, preceded by its * entries. */ if (is_secondary) { if (h->my_lba != drive_sectors - 1) return 1; if (h->entries_lba != h->my_lba - GPT_ENTRIES_SECTORS) return 1; } else { if (h->my_lba != 1) return 1; if (h->entries_lba != h->my_lba + 1) return 1; } /* FirstUsableLBA must be after the end of the primary GPT table * array. LastUsableLBA must be before the start of the secondary * GPT table array. FirstUsableLBA <= LastUsableLBA. */ if (h->first_usable_lba < 2 + GPT_ENTRIES_SECTORS) return 1; if (h->last_usable_lba >= drive_sectors - 1 - GPT_ENTRIES_SECTORS) return 1; if (h->first_usable_lba > h->last_usable_lba) return 1; /* Success */ return 0; } /* Return non-zero if the entry is unused, 0 if it is used. */ int IsUnusedEntry(const GptEntry* e) { static Guid zero = {{{0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0}}}}; return !Memcmp(&zero, (const uint8_t*)(&e->type), sizeof(zero)); } /* Returns non-zero if the entry is a Chrome OS kernel partition, else 0. */ int IsKernelEntry(const GptEntry* e) { static Guid chromeos_kernel = GPT_ENT_TYPE_CHROMEOS_KERNEL; return !Memcmp(&e->type, &chromeos_kernel, sizeof(Guid)); } int CheckEntries(GptEntry* entries, GptHeader* h) { GptEntry* entry; uint32_t crc32; uint32_t i; /* Check CRC before examining entries. */ crc32 = Crc32((const uint8_t *)entries, h->size_of_entry * h->number_of_entries); if (crc32 != h->entries_crc32) return GPT_ERROR_CRC_CORRUPTED; /* Check all entries. */ for (i = 0, entry = entries; i < h->number_of_entries; i++, entry++) { GptEntry* e2; uint32_t i2; if (IsUnusedEntry(entry)) continue; /* Entry must be in valid region. */ if ((entry->starting_lba < h->first_usable_lba) || (entry->ending_lba > h->last_usable_lba) || (entry->ending_lba < entry->starting_lba)) return GPT_ERROR_OUT_OF_REGION; /* Entry must not overlap other entries. */ for (i2 = 0, e2 = entries; i2 < h->number_of_entries; i2++, e2++) { if (i2 == i || IsUnusedEntry(e2)) continue; if ((entry->starting_lba >= e2->starting_lba) && (entry->starting_lba <= e2->ending_lba)) return GPT_ERROR_START_LBA_OVERLAP; if ((entry->ending_lba >= e2->starting_lba) && (entry->ending_lba <= e2->ending_lba)) return GPT_ERROR_END_LBA_OVERLAP; /* UniqueGuid field must be unique. */ if (0 == Memcmp(&entry->unique, &e2->unique, sizeof(Guid))) return GPT_ERROR_DUP_GUID; } } /* Success */ return 0; } /* Returns 0 if the GptHeaders are the same for all fields which don't * differ between the primary and secondary headers - that is, all * fields other than: * * my_lba * alternate_lba * entries_lba */ int HeaderFieldsSame(GptHeader *h1, GptHeader *h2) { if (Memcmp(h1->signature, h2->signature, sizeof(h1->signature))) return 1; if (h1->revision != h2->revision) return 1; if (h1->size != h2->size) return 1; if (h1->reserved_zero != h2->reserved_zero) return 1; if (h1->first_usable_lba != h2->first_usable_lba) return 1; if (h1->last_usable_lba != h2->last_usable_lba) return 1; if (Memcmp(&h1->disk_uuid, &h2->disk_uuid, sizeof(Guid))) return 1; if (h1->number_of_entries != h2->number_of_entries) return 1; if (h1->size_of_entry != h2->size_of_entry) return 1; if (h1->entries_crc32 != h2->entries_crc32) return 1; return 0; } int GptSanityCheck(GptData *gpt) { int retval; GptHeader* header1 = (GptHeader*)(gpt->primary_header); GptHeader* header2 = (GptHeader*)(gpt->secondary_header); GptEntry* entries1 = (GptEntry*)(gpt->primary_entries); GptEntry* entries2 = (GptEntry*)(gpt->secondary_entries); GptHeader* goodhdr = NULL; gpt->valid_headers = 0; gpt->valid_entries = 0; retval = CheckParameters(gpt); if (retval != GPT_SUCCESS) return retval; /* Check both headers; we need at least one valid header. */ if (0 == CheckHeader(header1, 0, gpt->drive_sectors)) { gpt->valid_headers |= MASK_PRIMARY; goodhdr = header1; } if (0 == CheckHeader(header2, 1, gpt->drive_sectors)) { gpt->valid_headers |= MASK_SECONDARY; if (!goodhdr) goodhdr = header2; } if (!gpt->valid_headers) return GPT_ERROR_INVALID_HEADERS; /* Checks if entries are valid. * * Note that we use the same header in both checks. This way we'll * catch the case where (header1,entries1) and (header2,entries2) * are both valid, but (entries1 != entries2). */ if (0 == CheckEntries(entries1, goodhdr)) gpt->valid_entries |= MASK_PRIMARY; if (0 == CheckEntries(entries2, goodhdr)) gpt->valid_entries |= MASK_SECONDARY; /* If both headers are good but neither entries were good, check the * entries with the secondary header. */ if (MASK_BOTH == gpt->valid_headers && !gpt->valid_entries) { if (0 == CheckEntries(entries1, header2)) gpt->valid_entries |= MASK_PRIMARY; if (0 == CheckEntries(entries2, header2)) gpt->valid_entries |= MASK_SECONDARY; if (gpt->valid_entries) { /* Sure enough, header2 had a good CRC for one of the entries. Mark * header1 invalid, so we'll update its entries CRC. */ gpt->valid_headers &= ~MASK_PRIMARY; goodhdr = header2; } } if (!gpt->valid_entries) return GPT_ERROR_INVALID_ENTRIES; /* Now that we've determined which header contains a good CRC for * the entries, make sure the headers are otherwise identical. */ if (MASK_BOTH == gpt->valid_headers && 0 != HeaderFieldsSame(header1, header2)) gpt->valid_headers &= ~MASK_SECONDARY; return GPT_SUCCESS; } void GptRepair(GptData *gpt) { GptHeader* header1 = (GptHeader*)(gpt->primary_header); GptHeader* header2 = (GptHeader*)(gpt->secondary_header); GptEntry* entries1 = (GptEntry*)(gpt->primary_entries); GptEntry* entries2 = (GptEntry*)(gpt->secondary_entries); int entries_size; /* Need at least one good header and one good set of entries. */ if (MASK_NONE == gpt->valid_headers || MASK_NONE == gpt->valid_entries) return; /* Repair headers if necessary */ if (MASK_PRIMARY == gpt->valid_headers) { /* Primary is good, secondary is bad */ Memcpy(header2, header1, sizeof(GptHeader)); header2->my_lba = gpt->drive_sectors - 1; header2->alternate_lba = 1; header2->entries_lba = header2->my_lba - GPT_ENTRIES_SECTORS; header2->header_crc32 = HeaderCrc(header2); gpt->modified |= GPT_MODIFIED_HEADER2; } else if (MASK_SECONDARY == gpt->valid_headers) { /* Secondary is good, primary is bad */ Memcpy(header1, header2, sizeof(GptHeader)); header1->my_lba = 1; header1->alternate_lba = gpt->drive_sectors - 1; header1->entries_lba = header1->my_lba + 1; header1->header_crc32 = HeaderCrc(header1); gpt->modified |= GPT_MODIFIED_HEADER1; } gpt->valid_headers = MASK_BOTH; /* Repair entries if necessary */ entries_size = header1->size_of_entry * header1->number_of_entries; if (MASK_PRIMARY == gpt->valid_entries) { /* Primary is good, secondary is bad */ Memcpy(entries2, entries1, entries_size); gpt->modified |= GPT_MODIFIED_ENTRIES2; } else if (MASK_SECONDARY == gpt->valid_entries) { /* Secondary is good, primary is bad */ Memcpy(entries1, entries2, entries_size); gpt->modified |= GPT_MODIFIED_ENTRIES1; } gpt->valid_entries = MASK_BOTH; } int GetEntrySuccessful(const GptEntry* e) { return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_SUCCESSFUL_MASK) >> CGPT_ATTRIBUTE_SUCCESSFUL_OFFSET; } int GetEntryPriority(const GptEntry* e) { return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_PRIORITY_MASK) >> CGPT_ATTRIBUTE_PRIORITY_OFFSET; } int GetEntryTries(const GptEntry* e) { return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_TRIES_MASK) >> CGPT_ATTRIBUTE_TRIES_OFFSET; } void SetEntrySuccessful(GptEntry* e, int successful) { if (successful) e->attrs.fields.gpt_att |= CGPT_ATTRIBUTE_SUCCESSFUL_MASK; else e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_SUCCESSFUL_MASK; } void SetEntryPriority(GptEntry* e, int priority) { e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_PRIORITY_MASK; e->attrs.fields.gpt_att |= (priority << CGPT_ATTRIBUTE_PRIORITY_OFFSET) & CGPT_ATTRIBUTE_PRIORITY_MASK; } void SetEntryTries(GptEntry* e, int tries) { e->attrs.fields.gpt_att &= ~CGPT_ATTRIBUTE_TRIES_MASK; e->attrs.fields.gpt_att |= (tries << CGPT_ATTRIBUTE_TRIES_OFFSET) & CGPT_ATTRIBUTE_TRIES_MASK; } void GetCurrentKernelUniqueGuid(GptData *gpt, void *dest) { GptEntry* entries = (GptEntry*)gpt->primary_entries; GptEntry* e = entries + gpt->current_kernel; Memcpy(dest, &e->unique, sizeof(Guid)); } const char* GptErrorText(int error_code) { switch(error_code) { case GPT_SUCCESS: return "none"; case GPT_ERROR_NO_VALID_KERNEL: return "Invalid kernel"; case GPT_ERROR_INVALID_HEADERS: return "Invalid headers"; case GPT_ERROR_INVALID_ENTRIES: return "Invalid entries"; case GPT_ERROR_INVALID_SECTOR_SIZE: return "Invalid sector size"; case GPT_ERROR_INVALID_SECTOR_NUMBER: return "Invalid sector number"; case GPT_ERROR_INVALID_UPDATE_TYPE: return "Invalid update type"; case GPT_ERROR_CRC_CORRUPTED: return "Entries' crc corrupted"; case GPT_ERROR_OUT_OF_REGION: return "Entry outside of valid region"; case GPT_ERROR_START_LBA_OVERLAP: return "Starting LBA overlaps"; case GPT_ERROR_END_LBA_OVERLAP: return "Ending LBA overlaps"; case GPT_ERROR_DUP_GUID: return "Duplicated GUID"; default: break; }; return "Unknown"; }