gpt: start new GPT module
This module is a new implementation for reading GUID Partition Tables which is much stricter than the existing part_gpt module and exports GPT data directly instead of the generic grub_partition structure. It will be the basis for modules that need to read/write/update GPT data. The current code does nothing more than read and verify the table.
This commit is contained in:
parent
28b0af948e
commit
f82f65f338
6 changed files with 837 additions and 0 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -42,6 +42,7 @@ gensymlist.sh
|
|||
gentrigtables
|
||||
gentrigtables.exe
|
||||
gettext_strings_test
|
||||
gpt_unit_test
|
||||
grub-bin2h
|
||||
/grub-bios-setup
|
||||
/grub-bios-setup.exe
|
||||
|
|
|
@ -1227,6 +1227,22 @@ program = {
|
|||
ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
|
||||
};
|
||||
|
||||
program = {
|
||||
testcase;
|
||||
name = gpt_unit_test;
|
||||
common = tests/gpt_unit_test.c;
|
||||
common = tests/lib/unit_test.c;
|
||||
common = grub-core/disk/host.c;
|
||||
common = grub-core/kern/emu/hostfs.c;
|
||||
common = grub-core/lib/gpt.c;
|
||||
common = grub-core/tests/lib/test.c;
|
||||
ldadd = libgrubmods.a;
|
||||
ldadd = libgrubgcry.a;
|
||||
ldadd = libgrubkern.a;
|
||||
ldadd = grub-core/gnulib/libgnu.a;
|
||||
ldadd = '$(LIBDEVMAPPER) $(LIBZFS) $(LIBNVPAIR) $(LIBGEOM)';
|
||||
};
|
||||
|
||||
program = {
|
||||
name = grub-menulst2cfg;
|
||||
mansection = 1;
|
||||
|
|
|
@ -826,6 +826,11 @@ module = {
|
|||
common = commands/gptsync.c;
|
||||
};
|
||||
|
||||
module = {
|
||||
name = gpt;
|
||||
common = lib/gpt.c;
|
||||
};
|
||||
|
||||
module = {
|
||||
name = halt;
|
||||
nopc = commands/halt.c;
|
||||
|
|
288
grub-core/lib/gpt.c
Normal file
288
grub-core/lib/gpt.c
Normal file
|
@ -0,0 +1,288 @@
|
|||
/* gpt.c - Read/Verify/Write GUID Partition Tables (GPT). */
|
||||
/*
|
||||
* GRUB -- GRand Unified Bootloader
|
||||
* Copyright (C) 2002,2005,2006,2007,2008 Free Software Foundation, Inc.
|
||||
* Copyright (C) 2014 CoreOS, Inc.
|
||||
*
|
||||
* GRUB is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GRUB is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <grub/crypto.h>
|
||||
#include <grub/disk.h>
|
||||
#include <grub/misc.h>
|
||||
#include <grub/mm.h>
|
||||
#include <grub/dl.h>
|
||||
#include <grub/msdos_partition.h>
|
||||
#include <grub/gpt_partition.h>
|
||||
|
||||
GRUB_MOD_LICENSE ("GPLv3+");
|
||||
|
||||
static grub_uint8_t grub_gpt_magic[] = GRUB_GPT_HEADER_MAGIC;
|
||||
|
||||
|
||||
static grub_err_t
|
||||
grub_gpt_header_crc32 (struct grub_gpt_header *gpt, grub_uint32_t *crc)
|
||||
{
|
||||
grub_uint8_t *crc32_context;
|
||||
grub_uint32_t old;
|
||||
|
||||
crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize);
|
||||
if (!crc32_context)
|
||||
return grub_errno;
|
||||
|
||||
/* crc32 must be computed with the field cleared. */
|
||||
old = gpt->crc32;
|
||||
gpt->crc32 = 0;
|
||||
GRUB_MD_CRC32->init (crc32_context);
|
||||
GRUB_MD_CRC32->write (crc32_context, gpt, sizeof (*gpt));
|
||||
GRUB_MD_CRC32->final (crc32_context);
|
||||
gpt->crc32 = old;
|
||||
|
||||
/* GRUB_MD_CRC32 always uses big endian, gpt is always little. */
|
||||
*crc = grub_swap_bytes32 (*(grub_uint32_t *)
|
||||
GRUB_MD_CRC32->read (crc32_context));
|
||||
|
||||
grub_free (crc32_context);
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
/* Make sure the MBR is a protective MBR and not a normal MBR. */
|
||||
grub_err_t
|
||||
grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
if (mbr->signature !=
|
||||
grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE))
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid MBR signature");
|
||||
|
||||
for (i = 0; i < sizeof (mbr->entries); i++)
|
||||
if (mbr->entries[i].type == GRUB_PC_PARTITION_TYPE_GPT_DISK)
|
||||
return GRUB_ERR_NONE;
|
||||
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid protective MBR");
|
||||
}
|
||||
|
||||
grub_err_t
|
||||
grub_gpt_header_check (struct grub_gpt_header *gpt,
|
||||
unsigned int log_sector_size)
|
||||
{
|
||||
grub_uint32_t crc = 0, size;
|
||||
|
||||
if (grub_memcmp (gpt->magic, grub_gpt_magic, sizeof (grub_gpt_magic)) != 0)
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT signature");
|
||||
|
||||
if (gpt->version != GRUB_GPT_HEADER_VERSION)
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "unknown GPT version");
|
||||
|
||||
if (grub_gpt_header_crc32 (gpt, &crc))
|
||||
return grub_errno;
|
||||
|
||||
if (gpt->crc32 != crc)
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header crc32");
|
||||
|
||||
/* The header size must be between 92 and the sector size. */
|
||||
size = grub_le_to_cpu32 (gpt->headersize);
|
||||
if (size < 92U || size > (1U << log_sector_size))
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT header size");
|
||||
|
||||
/* The partition entry size must be a multiple of 128. */
|
||||
size = grub_le_to_cpu32 (gpt->partentry_size);
|
||||
if (size < 128 || size % 128)
|
||||
return grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry size");
|
||||
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_gpt_read_primary (grub_disk_t disk, grub_gpt_t gpt)
|
||||
{
|
||||
grub_disk_addr_t addr;
|
||||
|
||||
/* TODO: The gpt partmap module searches for the primary header instead
|
||||
* of relying on the disk's sector size. For now trust the disk driver
|
||||
* but eventually this code should match the existing behavior. */
|
||||
gpt->log_sector_size = disk->log_sector_size;
|
||||
|
||||
addr = grub_gpt_sector_to_addr (gpt, 1);
|
||||
if (grub_disk_read (disk, addr, 0, sizeof (gpt->primary), &gpt->primary))
|
||||
return grub_errno;
|
||||
|
||||
if (grub_gpt_header_check (&gpt->primary, gpt->log_sector_size))
|
||||
return grub_errno;
|
||||
|
||||
gpt->status |= GRUB_GPT_PRIMARY_HEADER_VALID;
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
grub_gpt_read_backup (grub_disk_t disk, grub_gpt_t gpt)
|
||||
{
|
||||
grub_uint64_t sector;
|
||||
grub_disk_addr_t addr;
|
||||
|
||||
/* Assumes gpt->log_sector_size == disk->log_sector_size */
|
||||
if (disk->total_sectors != GRUB_DISK_SIZE_UNKNOWN)
|
||||
sector = disk->total_sectors - 1;
|
||||
else if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID)
|
||||
sector = grub_le_to_cpu64 (gpt->primary.backup);
|
||||
else
|
||||
return grub_error (GRUB_ERR_NOT_IMPLEMENTED_YET,
|
||||
"Unable to locate backup GPT");
|
||||
|
||||
addr = grub_gpt_sector_to_addr (gpt, sector);
|
||||
if (grub_disk_read (disk, addr, 0, sizeof (gpt->backup), &gpt->backup))
|
||||
return grub_errno;
|
||||
|
||||
if (grub_gpt_header_check (&gpt->backup, gpt->log_sector_size))
|
||||
return grub_errno;
|
||||
|
||||
gpt->status |= GRUB_GPT_BACKUP_HEADER_VALID;
|
||||
return GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static struct grub_gpt_partentry *
|
||||
grub_gpt_read_entries (grub_disk_t disk, grub_gpt_t gpt,
|
||||
struct grub_gpt_header *header)
|
||||
{
|
||||
struct grub_gpt_partentry *entries = NULL;
|
||||
grub_uint8_t *crc32_context = NULL;
|
||||
grub_uint32_t count, size, crc;
|
||||
grub_disk_addr_t addr;
|
||||
grub_size_t entries_size;
|
||||
|
||||
/* Grub doesn't include calloc, hence the manual overflow check. */
|
||||
count = grub_le_to_cpu32 (header->maxpart);
|
||||
size = grub_le_to_cpu32 (header->partentry_size);
|
||||
entries_size = count *size;
|
||||
if (size && entries_size / size != count)
|
||||
{
|
||||
grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("out of memory"));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
entries = grub_malloc (entries_size);
|
||||
if (!entries)
|
||||
goto fail;
|
||||
|
||||
addr = grub_gpt_sector_to_addr (gpt, grub_le_to_cpu64 (header->partitions));
|
||||
if (grub_disk_read (disk, addr, 0, entries_size, entries))
|
||||
goto fail;
|
||||
|
||||
crc32_context = grub_zalloc (GRUB_MD_CRC32->contextsize);
|
||||
if (!crc32_context)
|
||||
goto fail;
|
||||
|
||||
GRUB_MD_CRC32->init (crc32_context);
|
||||
GRUB_MD_CRC32->write (crc32_context, entries, entries_size);
|
||||
GRUB_MD_CRC32->final (crc32_context);
|
||||
|
||||
crc = *(grub_uint32_t *) GRUB_MD_CRC32->read (crc32_context);
|
||||
if (grub_swap_bytes32 (crc) != header->partentry_crc32)
|
||||
{
|
||||
grub_error (GRUB_ERR_BAD_PART_TABLE, "invalid GPT entry crc32");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
grub_free (crc32_context);
|
||||
return entries;
|
||||
|
||||
fail:
|
||||
grub_free (entries);
|
||||
grub_free (crc32_context);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
grub_gpt_t
|
||||
grub_gpt_read (grub_disk_t disk)
|
||||
{
|
||||
grub_gpt_t gpt;
|
||||
struct grub_gpt_partentry *backup_entries;
|
||||
|
||||
gpt = grub_zalloc (sizeof (*gpt));
|
||||
if (!gpt)
|
||||
goto fail;
|
||||
|
||||
if (grub_disk_read (disk, 0, 0, sizeof (gpt->mbr), &gpt->mbr))
|
||||
goto fail;
|
||||
|
||||
/* Check the MBR but errors aren't reported beyond the status bit. */
|
||||
if (grub_gpt_pmbr_check (&gpt->mbr))
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
else
|
||||
gpt->status |= GRUB_GPT_PROTECTIVE_MBR;
|
||||
|
||||
/* If both the primary and backup fail report the primary's error. */
|
||||
if (grub_gpt_read_primary (disk, gpt))
|
||||
{
|
||||
grub_error_push ();
|
||||
grub_gpt_read_backup (disk, gpt);
|
||||
grub_error_pop ();
|
||||
}
|
||||
else
|
||||
grub_gpt_read_backup (disk, gpt);
|
||||
|
||||
/* If either succeeded clear any possible error from the other. */
|
||||
if (gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID ||
|
||||
gpt->status & GRUB_GPT_BACKUP_HEADER_VALID)
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
else
|
||||
goto fail;
|
||||
|
||||
/* Same error handling scheme for the entry tables. */
|
||||
gpt->entries = grub_gpt_read_entries (disk, gpt, &gpt->primary);
|
||||
if (!gpt->entries)
|
||||
{
|
||||
grub_error_push ();
|
||||
backup_entries = grub_gpt_read_entries (disk, gpt, &gpt->backup);
|
||||
grub_error_pop ();
|
||||
}
|
||||
else
|
||||
{
|
||||
gpt->status |= GRUB_GPT_PRIMARY_ENTRIES_VALID;
|
||||
backup_entries = grub_gpt_read_entries (disk, gpt, &gpt->backup);
|
||||
}
|
||||
|
||||
if (backup_entries)
|
||||
{
|
||||
gpt->status |= GRUB_GPT_BACKUP_ENTRIES_VALID;
|
||||
|
||||
if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID)
|
||||
grub_free (backup_entries);
|
||||
else
|
||||
gpt->entries = backup_entries;
|
||||
}
|
||||
|
||||
if (gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID ||
|
||||
gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID)
|
||||
{
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
return gpt;
|
||||
}
|
||||
|
||||
fail:
|
||||
grub_gpt_free (gpt);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void
|
||||
grub_gpt_free (grub_gpt_t gpt)
|
||||
{
|
||||
if (!gpt)
|
||||
return;
|
||||
|
||||
grub_free (gpt->entries);
|
||||
grub_free (gpt);
|
||||
}
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
#include <grub/types.h>
|
||||
#include <grub/partition.h>
|
||||
#include <grub/msdos_partition.h>
|
||||
|
||||
struct grub_gpt_part_type
|
||||
{
|
||||
|
@ -50,6 +51,12 @@ typedef struct grub_gpt_part_type grub_gpt_part_type_t;
|
|||
{ 0x85, 0xD2, 0xE1, 0xE9, 0x04, 0x34, 0xCF, 0xB3 } \
|
||||
}
|
||||
|
||||
#define GRUB_GPT_HEADER_MAGIC \
|
||||
{ 0x45, 0x46, 0x49, 0x20, 0x50, 0x41, 0x52, 0x54 }
|
||||
|
||||
#define GRUB_GPT_HEADER_VERSION \
|
||||
grub_cpu_to_le32_compile_time (0x00010000U)
|
||||
|
||||
struct grub_gpt_header
|
||||
{
|
||||
grub_uint8_t magic[8];
|
||||
|
@ -78,10 +85,63 @@ struct grub_gpt_partentry
|
|||
char name[72];
|
||||
} GRUB_PACKED;
|
||||
|
||||
/* Basic GPT partmap module. */
|
||||
grub_err_t
|
||||
grub_gpt_partition_map_iterate (grub_disk_t disk,
|
||||
grub_partition_iterate_hook_t hook,
|
||||
void *hook_data);
|
||||
|
||||
/* Advanced GPT library. */
|
||||
typedef enum grub_gpt_status
|
||||
{
|
||||
GRUB_GPT_PROTECTIVE_MBR = 0x01,
|
||||
GRUB_GPT_HYBRID_MBR = 0x02,
|
||||
GRUB_GPT_PRIMARY_HEADER_VALID = 0x04,
|
||||
GRUB_GPT_PRIMARY_ENTRIES_VALID = 0x08,
|
||||
GRUB_GPT_BACKUP_HEADER_VALID = 0x10,
|
||||
GRUB_GPT_BACKUP_ENTRIES_VALID = 0x20,
|
||||
} grub_gpt_status_t;
|
||||
|
||||
#define GRUB_GPT_MBR_VALID (GRUB_GPT_PROTECTIVE_MBR|GRUB_GPT_HYBRID_MBR)
|
||||
|
||||
/* UEFI requires the entries table to be at least 16384 bytes for a
|
||||
* total of 128 entries given the standard 128 byte entry size. */
|
||||
#define GRUB_GPT_DEFAULT_ENTRIES_LENGTH 128
|
||||
|
||||
struct grub_gpt
|
||||
{
|
||||
/* Bit field indicating which structures on disk are valid. */
|
||||
grub_gpt_status_t status;
|
||||
|
||||
/* Protective or hybrid MBR. */
|
||||
struct grub_msdos_partition_mbr mbr;
|
||||
|
||||
/* Each of the two GPT headers. */
|
||||
struct grub_gpt_header primary;
|
||||
struct grub_gpt_header backup;
|
||||
|
||||
/* Only need one entries table, on disk both copies are identical. */
|
||||
struct grub_gpt_partentry *entries;
|
||||
|
||||
/* Logarithm of sector size, in case GPT and disk driver disagree. */
|
||||
unsigned int log_sector_size;
|
||||
};
|
||||
typedef struct grub_gpt *grub_gpt_t;
|
||||
|
||||
/* Translate GPT sectors to GRUB's 512 byte block addresses. */
|
||||
static inline grub_disk_addr_t
|
||||
grub_gpt_sector_to_addr (grub_gpt_t gpt, grub_uint64_t sector)
|
||||
{
|
||||
return (sector << (gpt->log_sector_size - GRUB_DISK_SECTOR_BITS));
|
||||
}
|
||||
|
||||
/* Allocates and fills new grub_gpt structure, free with grub_gpt_free. */
|
||||
grub_gpt_t grub_gpt_read (grub_disk_t disk);
|
||||
|
||||
void grub_gpt_free (grub_gpt_t gpt);
|
||||
|
||||
grub_err_t grub_gpt_pmbr_check (struct grub_msdos_partition_mbr *mbr);
|
||||
grub_err_t grub_gpt_header_check (struct grub_gpt_header *gpt,
|
||||
unsigned int log_sector_size);
|
||||
|
||||
#endif /* ! GRUB_GPT_PARTITION_HEADER */
|
||||
|
|
467
tests/gpt_unit_test.c
Normal file
467
tests/gpt_unit_test.c
Normal file
|
@ -0,0 +1,467 @@
|
|||
/*
|
||||
* GRUB -- GRand Unified Bootloader
|
||||
* Copyright (C) 2014 CoreOS, Inc.
|
||||
*
|
||||
* GRUB is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* GRUB is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with GRUB. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <grub/command.h>
|
||||
#include <grub/device.h>
|
||||
#include <grub/disk.h>
|
||||
#include <grub/emu/hostdisk.h>
|
||||
#include <grub/emu/misc.h>
|
||||
#include <grub/err.h>
|
||||
#include <grub/gpt_partition.h>
|
||||
#include <grub/msdos_partition.h>
|
||||
#include <grub/test.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
/* from gnulib */
|
||||
#include <verify.h>
|
||||
|
||||
|
||||
/* GPT section sizes. */
|
||||
#define HEADER_SIZE (sizeof (struct grub_gpt_header))
|
||||
#define HEADER_PAD (GRUB_DISK_SECTOR_SIZE - HEADER_SIZE)
|
||||
#define ENTRY_SIZE (sizeof (struct grub_gpt_partentry))
|
||||
#define TABLE_ENTRIES 0x80
|
||||
#define TABLE_SIZE (TABLE_ENTRIES * ENTRY_SIZE)
|
||||
#define TABLE_SECTORS (TABLE_SIZE / GRUB_DISK_SECTOR_SIZE)
|
||||
|
||||
/* Double check that the table size calculation was valid. */
|
||||
verify (TABLE_SECTORS * GRUB_DISK_SECTOR_SIZE == TABLE_SIZE);
|
||||
|
||||
/* GPT section locations for a 1MiB disk. */
|
||||
#define DISK_SECTORS 0x800
|
||||
#define DISK_SIZE (GRUB_DISK_SECTOR_SIZE * DISK_SECTORS)
|
||||
#define PRIMARY_HEADER_SECTOR 0x1
|
||||
#define PRIMARY_TABLE_SECTOR 0x2
|
||||
#define BACKUP_HEADER_SECTOR (DISK_SECTORS - 0x1)
|
||||
#define BACKUP_TABLE_SECTOR (BACKUP_HEADER_SECTOR - TABLE_SECTORS)
|
||||
|
||||
#define DATA_START_SECTOR (PRIMARY_TABLE_SECTOR + TABLE_SECTORS)
|
||||
#define DATA_END_SECTOR (BACKUP_TABLE_SECTOR - 0x1)
|
||||
#define DATA_SECTORS (BACKUP_TABLE_SECTOR - DATA_START_SECTOR)
|
||||
#define DATA_SIZE (GRUB_DISK_SECTOR_SIZE * DATA_SECTORS)
|
||||
|
||||
struct test_disk
|
||||
{
|
||||
struct grub_msdos_partition_mbr mbr;
|
||||
|
||||
struct grub_gpt_header primary_header;
|
||||
grub_uint8_t primary_header_pad[HEADER_PAD];
|
||||
struct grub_gpt_partentry primary_entries[TABLE_ENTRIES];
|
||||
|
||||
grub_uint8_t data[DATA_SIZE];
|
||||
|
||||
struct grub_gpt_partentry backup_entries[TABLE_ENTRIES];
|
||||
struct grub_gpt_header backup_header;
|
||||
grub_uint8_t backup_header_pad[HEADER_PAD];
|
||||
} GRUB_PACKED;
|
||||
|
||||
/* Sanity check that all the above ugly math was correct. */
|
||||
verify (sizeof (struct test_disk) == DISK_SIZE);
|
||||
|
||||
struct test_data
|
||||
{
|
||||
int fd;
|
||||
grub_device_t dev;
|
||||
struct test_disk *raw;
|
||||
};
|
||||
|
||||
|
||||
/* Sample primary GPT header for an empty 1MB disk. */
|
||||
static const struct grub_gpt_header example_primary = {
|
||||
.magic = GRUB_GPT_HEADER_MAGIC,
|
||||
.version = GRUB_GPT_HEADER_VERSION,
|
||||
.headersize = sizeof (struct grub_gpt_header),
|
||||
.crc32 = grub_cpu_to_le32_compile_time (0x7cd8642c),
|
||||
.primary = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR),
|
||||
.backup = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR),
|
||||
.start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR),
|
||||
.end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR),
|
||||
.guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46,
|
||||
0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac},
|
||||
.partitions = grub_cpu_to_le64_compile_time (PRIMARY_TABLE_SECTOR),
|
||||
.maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES),
|
||||
.partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE),
|
||||
.partentry_crc32 = grub_cpu_to_le32_compile_time (0xab54d286),
|
||||
};
|
||||
|
||||
/* And the backup header. */
|
||||
static const struct grub_gpt_header example_backup = {
|
||||
.magic = GRUB_GPT_HEADER_MAGIC,
|
||||
.version = GRUB_GPT_HEADER_VERSION,
|
||||
.headersize = sizeof (struct grub_gpt_header),
|
||||
.crc32 = grub_cpu_to_le32_compile_time (0xcfaa4a27),
|
||||
.primary = grub_cpu_to_le64_compile_time (BACKUP_HEADER_SECTOR),
|
||||
.backup = grub_cpu_to_le64_compile_time (PRIMARY_HEADER_SECTOR),
|
||||
.start = grub_cpu_to_le64_compile_time (DATA_START_SECTOR),
|
||||
.end = grub_cpu_to_le64_compile_time (DATA_END_SECTOR),
|
||||
.guid = {0xad, 0x31, 0xc1, 0x69, 0xd6, 0x67, 0xc6, 0x46,
|
||||
0x93, 0xc4, 0x12, 0x4c, 0x75, 0x52, 0x56, 0xac},
|
||||
.partitions = grub_cpu_to_le64_compile_time (BACKUP_TABLE_SECTOR),
|
||||
.maxpart = grub_cpu_to_le32_compile_time (TABLE_ENTRIES),
|
||||
.partentry_size = grub_cpu_to_le32_compile_time (ENTRY_SIZE),
|
||||
.partentry_crc32 = grub_cpu_to_le32_compile_time (0xab54d286),
|
||||
};
|
||||
|
||||
/* Sample protective MBR for the same 1MB disk. Note, this matches
|
||||
* parted and fdisk behavior. The UEFI spec uses different values. */
|
||||
static const struct grub_msdos_partition_mbr example_pmbr = {
|
||||
.entries = {{.flag = 0x00,
|
||||
.start_head = 0x00,
|
||||
.start_sector = 0x01,
|
||||
.start_cylinder = 0x00,
|
||||
.type = 0xee,
|
||||
.end_head = 0xfe,
|
||||
.end_sector = 0xff,
|
||||
.end_cylinder = 0xff,
|
||||
.start = grub_cpu_to_le32_compile_time (0x1),
|
||||
.length = grub_cpu_to_le32_compile_time (DISK_SECTORS - 0x1),
|
||||
}},
|
||||
.signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE),
|
||||
};
|
||||
|
||||
/* If errors are left in grub's error stack things can get confused. */
|
||||
static void
|
||||
assert_error_stack_empty (void)
|
||||
{
|
||||
do
|
||||
{
|
||||
grub_test_assert (grub_errno == GRUB_ERR_NONE,
|
||||
"error on stack: %s", grub_errmsg);
|
||||
}
|
||||
while (grub_error_pop ());
|
||||
}
|
||||
|
||||
static grub_err_t
|
||||
execute_command2 (const char *name, const char *arg1, const char *arg2)
|
||||
{
|
||||
grub_command_t cmd;
|
||||
grub_err_t err;
|
||||
char *argv[2];
|
||||
|
||||
cmd = grub_command_find (name);
|
||||
if (!cmd)
|
||||
grub_fatal ("can't find command %s", name);
|
||||
|
||||
argv[0] = strdup (arg1);
|
||||
argv[1] = strdup (arg2);
|
||||
err = (cmd->func) (cmd, 2, argv);
|
||||
free (argv[0]);
|
||||
free (argv[1]);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void
|
||||
sync_disk (struct test_data *data)
|
||||
{
|
||||
if (msync (data->raw, DISK_SIZE, MS_SYNC | MS_INVALIDATE) < 0)
|
||||
grub_fatal ("Syncing disk failed: %s", strerror (errno));
|
||||
|
||||
grub_disk_cache_invalidate_all ();
|
||||
}
|
||||
|
||||
static void
|
||||
reset_disk (struct test_data *data)
|
||||
{
|
||||
memset (data->raw, 0, DISK_SIZE);
|
||||
|
||||
/* Initialize image with valid example tables. */
|
||||
memcpy (&data->raw->mbr, &example_pmbr, sizeof (data->raw->mbr));
|
||||
memcpy (&data->raw->primary_header, &example_primary,
|
||||
sizeof (data->raw->primary_header));
|
||||
memcpy (&data->raw->backup_header, &example_backup,
|
||||
sizeof (data->raw->backup_header));
|
||||
|
||||
sync_disk (data);
|
||||
}
|
||||
|
||||
static void
|
||||
open_disk (struct test_data *data)
|
||||
{
|
||||
const char *loop = "loop0";
|
||||
char template[] = "/tmp/grub_gpt_test.XXXXXX";
|
||||
char host[sizeof ("(host)") + sizeof (template)];
|
||||
|
||||
data->fd = mkstemp (template);
|
||||
if (data->fd < 0)
|
||||
grub_fatal ("Creating %s failed: %s", template, strerror (errno));
|
||||
|
||||
if (ftruncate (data->fd, DISK_SIZE) < 0)
|
||||
{
|
||||
int err = errno;
|
||||
unlink (template);
|
||||
grub_fatal ("Resizing %s failed: %s", template, strerror (err));
|
||||
}
|
||||
|
||||
data->raw = mmap (NULL, DISK_SIZE, PROT_READ | PROT_WRITE,
|
||||
MAP_SHARED, data->fd, 0);
|
||||
if (data->raw == MAP_FAILED)
|
||||
{
|
||||
int err = errno;
|
||||
unlink (template);
|
||||
grub_fatal ("Maping %s failed: %s", template, strerror (err));
|
||||
}
|
||||
|
||||
snprintf (host, sizeof (host), "(host)%s", template);
|
||||
if (execute_command2 ("loopback", loop, host) != GRUB_ERR_NONE)
|
||||
{
|
||||
unlink (template);
|
||||
grub_fatal ("loopback %s %s failed: %s", loop, host, grub_errmsg);
|
||||
}
|
||||
|
||||
if (unlink (template) < 0)
|
||||
grub_fatal ("Unlinking %s failed: %s", template, strerror (errno));
|
||||
|
||||
reset_disk (data);
|
||||
|
||||
data->dev = grub_device_open (loop);
|
||||
if (!data->dev)
|
||||
grub_fatal ("Opening %s failed: %s", loop, grub_errmsg);
|
||||
}
|
||||
|
||||
static void
|
||||
close_disk (struct test_data *data)
|
||||
{
|
||||
char *loop;
|
||||
|
||||
assert_error_stack_empty ();
|
||||
|
||||
if (munmap (data->raw, DISK_SIZE) || close (data->fd))
|
||||
grub_fatal ("Closing disk image failed: %s", strerror (errno));
|
||||
|
||||
loop = strdup (data->dev->disk->name);
|
||||
grub_test_assert (grub_device_close (data->dev) == GRUB_ERR_NONE,
|
||||
"Closing disk device failed: %s", grub_errmsg);
|
||||
|
||||
grub_test_assert (execute_command2 ("loopback", "-d", loop) ==
|
||||
GRUB_ERR_NONE, "loopback -d %s failed: %s", loop,
|
||||
grub_errmsg);
|
||||
|
||||
free (loop);
|
||||
}
|
||||
|
||||
static grub_gpt_t
|
||||
read_disk (struct test_data *data)
|
||||
{
|
||||
grub_gpt_t gpt;
|
||||
|
||||
gpt = grub_gpt_read (data->dev->disk);
|
||||
if (gpt == NULL)
|
||||
{
|
||||
grub_print_error ();
|
||||
grub_fatal ("grub_gpt_read failed");
|
||||
}
|
||||
|
||||
|
||||
return gpt;
|
||||
}
|
||||
|
||||
static void
|
||||
pmbr_test (void)
|
||||
{
|
||||
struct grub_msdos_partition_mbr mbr;
|
||||
|
||||
memset (&mbr, 0, sizeof (mbr));
|
||||
|
||||
/* Empty is invalid. */
|
||||
grub_gpt_pmbr_check (&mbr);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
/* A table without a protective partition is invalid. */
|
||||
mbr.signature = grub_cpu_to_le16_compile_time (GRUB_PC_PARTITION_SIGNATURE);
|
||||
grub_gpt_pmbr_check (&mbr);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
/* A table with a protective type is ok. */
|
||||
memcpy (&mbr, &example_pmbr, sizeof (mbr));
|
||||
grub_gpt_pmbr_check (&mbr);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_NONE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
header_test (void)
|
||||
{
|
||||
struct grub_gpt_header primary, backup;
|
||||
|
||||
/* Example headers should be valid. */
|
||||
memcpy (&primary, &example_primary, sizeof (primary));
|
||||
grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_NONE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
memcpy (&backup, &example_backup, sizeof (backup));
|
||||
grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_NONE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
/* Twiddle the GUID to invalidate the CRC. */
|
||||
primary.guid[0] = 0;
|
||||
grub_gpt_header_check (&primary, GRUB_DISK_SECTOR_BITS);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
backup.guid[0] = 0;
|
||||
grub_gpt_header_check (&backup, GRUB_DISK_SECTOR_BITS);
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
}
|
||||
|
||||
static void
|
||||
read_valid_test (void)
|
||||
{
|
||||
struct test_data data;
|
||||
grub_gpt_t gpt;
|
||||
|
||||
open_disk (&data);
|
||||
gpt = read_disk (&data);
|
||||
grub_test_assert (gpt->status == (GRUB_GPT_PROTECTIVE_MBR |
|
||||
GRUB_GPT_PRIMARY_HEADER_VALID |
|
||||
GRUB_GPT_PRIMARY_ENTRIES_VALID |
|
||||
GRUB_GPT_BACKUP_HEADER_VALID |
|
||||
GRUB_GPT_BACKUP_ENTRIES_VALID),
|
||||
"unexpected status: 0x%02x", gpt->status);
|
||||
grub_gpt_free (gpt);
|
||||
close_disk (&data);
|
||||
}
|
||||
|
||||
static void
|
||||
read_invalid_entries_test (void)
|
||||
{
|
||||
struct test_data data;
|
||||
grub_gpt_t gpt;
|
||||
|
||||
open_disk (&data);
|
||||
|
||||
/* Corrupt the first entry in both tables. */
|
||||
memset (&data.raw->primary_entries[0], 0x55,
|
||||
sizeof (data.raw->primary_entries[0]));
|
||||
memset (&data.raw->backup_entries[0], 0x55,
|
||||
sizeof (data.raw->backup_entries[0]));
|
||||
sync_disk (&data);
|
||||
|
||||
gpt = grub_gpt_read (data.dev->disk);
|
||||
grub_test_assert (gpt == NULL, "no error reported for corrupt entries");
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
close_disk (&data);
|
||||
}
|
||||
|
||||
static void
|
||||
read_fallback_test (void)
|
||||
{
|
||||
struct test_data data;
|
||||
grub_gpt_t gpt;
|
||||
|
||||
open_disk (&data);
|
||||
|
||||
/* Corrupt the primary header. */
|
||||
memset (&data.raw->primary_header.guid, 0x55,
|
||||
sizeof (data.raw->primary_header.guid));
|
||||
sync_disk (&data);
|
||||
gpt = read_disk (&data);
|
||||
grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_HEADER_VALID) == 0,
|
||||
"unreported corrupt primary header");
|
||||
grub_gpt_free (gpt);
|
||||
reset_disk (&data);
|
||||
|
||||
/* Corrupt the backup header. */
|
||||
memset (&data.raw->backup_header.guid, 0x55,
|
||||
sizeof (data.raw->backup_header.guid));
|
||||
sync_disk (&data);
|
||||
gpt = read_disk (&data);
|
||||
grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_HEADER_VALID) == 0,
|
||||
"unreported corrupt backup header");
|
||||
grub_gpt_free (gpt);
|
||||
reset_disk (&data);
|
||||
|
||||
/* Corrupt the primary entry table. */
|
||||
memset (&data.raw->primary_entries[0], 0x55,
|
||||
sizeof (data.raw->primary_entries[0]));
|
||||
sync_disk (&data);
|
||||
gpt = read_disk (&data);
|
||||
grub_test_assert ((gpt->status & GRUB_GPT_PRIMARY_ENTRIES_VALID) == 0,
|
||||
"unreported corrupt primary entries table");
|
||||
grub_gpt_free (gpt);
|
||||
reset_disk (&data);
|
||||
|
||||
/* Corrupt the backup entry table. */
|
||||
memset (&data.raw->backup_entries[0], 0x55,
|
||||
sizeof (data.raw->backup_entries[0]));
|
||||
sync_disk (&data);
|
||||
gpt = read_disk (&data);
|
||||
grub_test_assert ((gpt->status & GRUB_GPT_BACKUP_ENTRIES_VALID) == 0,
|
||||
"unreported corrupt backup entries table");
|
||||
grub_gpt_free (gpt);
|
||||
reset_disk (&data);
|
||||
|
||||
/* If primary is corrupt and disk size is unknown fallback fails. */
|
||||
memset (&data.raw->primary_header.guid, 0x55,
|
||||
sizeof (data.raw->primary_header.guid));
|
||||
sync_disk (&data);
|
||||
data.dev->disk->total_sectors = GRUB_DISK_SIZE_UNKNOWN;
|
||||
gpt = grub_gpt_read (data.dev->disk);
|
||||
grub_test_assert (gpt == NULL, "no error reported");
|
||||
grub_test_assert (grub_errno == GRUB_ERR_BAD_PART_TABLE,
|
||||
"unexpected error: %s", grub_errmsg);
|
||||
grub_errno = GRUB_ERR_NONE;
|
||||
|
||||
close_disk (&data);
|
||||
}
|
||||
|
||||
void
|
||||
grub_unit_test_init (void)
|
||||
{
|
||||
grub_init_all ();
|
||||
grub_hostfs_init ();
|
||||
grub_host_init ();
|
||||
grub_test_register ("gpt_pmbr_test", pmbr_test);
|
||||
grub_test_register ("gpt_header_test", header_test);
|
||||
grub_test_register ("gpt_read_valid_test", read_valid_test);
|
||||
grub_test_register ("gpt_read_invalid_test", read_invalid_entries_test);
|
||||
grub_test_register ("gpt_read_fallback_test", read_fallback_test);
|
||||
}
|
||||
|
||||
void
|
||||
grub_unit_test_fini (void)
|
||||
{
|
||||
grub_test_unregister ("gpt_pmbr_test");
|
||||
grub_test_unregister ("gpt_header_test");
|
||||
grub_test_unregister ("gpt_read_valid_test");
|
||||
grub_test_unregister ("gpt_read_invalid_test");
|
||||
grub_test_unregister ("gpt_read_fallback_test");
|
||||
grub_fini_all ();
|
||||
}
|
Loading…
Reference in a new issue