nuttx/fs/partition/fs_gpt.c
Jukka Laitinen dd2ffbc768 Fixes for GPT partition parsing
1. Don't handle invalid or empty pte entries

num_partition_entries field in GPT typically means number of maximum entries
and not the number of used entries. Empty entries are indentified with
"0" partition type guid. Loop through all the entries

2. Fix the GPT partition size calculation

"ending_lba" is included in the partition, it is not the start of the next one.
Thus the correct size of the partition is end-start+1

Signed-off-by: Jukka Laitinen <jukkax@ssrc.tii.ae>
2022-09-27 09:42:39 +08:00

493 lines
15 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/****************************************************************************
* fs/partition/fs_gpt.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <ctype.h>
#include <debug.h>
#include <endian.h>
#include <inttypes.h>
#include <nuttx/crc32.h>
#include <nuttx/kmalloc.h>
#include "partition.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define GPT_BLOCK_SIZE 512
#define GPT_HEADER_SIGNATURE 0x5452415020494645ull
#define GPT_PARTNAME_MAX_SIZE (72 / sizeof(uint16_t))
#define GPT_LBA_TO_BLOCK(lba, blk) ((le64toh(lba) * 512 + (blk) -1) / (blk))
#define GPT_MIN(x, y) (((x) < (y)) ? (x) : (y))
/****************************************************************************
* Private Types
****************************************************************************/
struct gpt_guid_s
{
uint8_t b[16];
};
/* For limited backward compatibility, the space of the legacy MBR is still
* reserved in the GPT specification, but it is now used in a way that
* prevents MBR-based disk utilities from misrecognizing and possibly
* overwriting GPT disks. This is referred to as a protective MBR.
*/
begin_packed_struct struct legacy_partition_s
{
uint8_t boot_ind; /* 0x80 - active */
uint8_t head; /* Starting head */
uint8_t sector; /* Starting sector */
uint8_t cyl; /* Starting cylinder */
uint8_t sys_ind; /* What partition type */
uint8_t end_head; /* End head */
uint8_t end_sector; /* End sector */
uint8_t end_cyl; /* End cylinder */
uint32_t start_sect; /* Starting sector counting from 0 */
uint32_t nr_sects; /* Nr of sectors in partition */
} end_packed_struct;
/* The partition table header defines the usable blocks on the disk.
* It also defines the number and size of the partition entries that
* make up the partition table (offsets 80 and 84 in the table).
*/
begin_packed_struct struct gpt_header_s
{
uint64_t signature; /* EFI PART */
uint32_t revision; /* Revision info */
uint32_t header_size; /* Header size in little endian */
uint32_t header_crc32; /* CRC32 of header (offset +0 up to header size) */
uint32_t reserved1; /* Must be zero */
uint64_t my_lba; /* Current LBA (location of this header copy) */
uint64_t alternate_lba; /* Backup LBA (location of the other header copy) */
uint64_t first_usable_lba; /* First usable LBA for partitions primary partition table last LBA + 1 */
uint64_t last_usable_lba; /* Last usable LBA secondary partition table first LBA 1 */
struct gpt_guid_s disk_guid; /* Disk GUID in mixed endian */
uint64_t partition_entry_lba; /* Starting LBA of array of partition entries (always 2 in primary copy) */
uint32_t num_partition_entries; /* Number of partition entries in array */
uint32_t sizeof_partition_entry; /* Size of a single partition entry */
uint32_t partition_entry_array_crc32; /* CRC32 of partition entries array in little endian */
/* The rest of the logical block is reserved by UEFI and must be zero.
* EFI standard handles this by:
*
* uint8_t reserved2[ BlockSize - 92 ];
*/
} end_packed_struct;
/* After the header, the Partition Entry Array describes partitions,
* using a minimum size of 128 bytes for each entry block.
*/
/* The 64-bit partition table attributes are shared between 48-bit
* common attributes for all partition types, and 16-bit
* type-specific attributes
*/
begin_packed_struct struct gpt_entry_attributes_s
{
uint64_t required_to_function:1;
uint64_t reserved:47;
uint64_t type_guid_specific:16;
} end_packed_struct;
begin_packed_struct struct gpt_entry_s
{
struct gpt_guid_s partition_type_guid; /* Partition type GUID */
struct gpt_guid_s unique_partition_guid; /* Unique partition GUID */
uint64_t starting_lba; /* First LBA */
uint64_t ending_lba; /* Last LBA */
struct gpt_entry_attributes_s attributes; /* Attribute flags */
uint16_t partition_name[GPT_PARTNAME_MAX_SIZE]; /* Partition name */
} end_packed_struct;
begin_packed_struct struct gpt_ptable_s
{
uint8_t mbr[512];
union
{
struct gpt_header_s gpt_header;
uint8_t gpt[512];
} u;
} end_packed_struct;
/****************************************************************************
* Private Data
****************************************************************************/
static const struct gpt_guid_s g_null_guid;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: gpt_last_lba
*
* Description:
* Return number of last logical block of device, 0 on error.
*
* Input Parameters:
* state - The partition table state
*
* Returned Value:
* Returns last LBA value on success, 0 on error.
* This is stored (by sd and ide-geometry) in
* the part[0] entry for this disk, and is the number of
* physical sectors available on the disk.
*
****************************************************************************/
static inline blkcnt_t gpt_last_lba(FAR struct partition_state_s *state)
{
return (((uint64_t)state->nblocks) * state->blocksize + GPT_BLOCK_SIZE - 1)
/ GPT_BLOCK_SIZE - 1;
}
/****************************************************************************
* Name: gpt_alloc_verify_entries()
*
* Description:
* reads and verifies partition entries from disk
*
* Input Parameters:
* state - the handle of partition state
* gpt - a GPT header ptr.
*
* Returned Value:
* Returns ptes on success, NULL on error.
* Allocates space for PTEs based on information found in @gpt.
* Notes: remember to free pte when you're done!
*
****************************************************************************/
static FAR struct gpt_entry_s *
gpt_alloc_verify_entries(FAR struct partition_state_s *state,
FAR struct gpt_header_s *gpt)
{
FAR struct gpt_entry_s *pte;
unsigned long from;
unsigned long size;
unsigned long blk;
uint32_t crc;
int ret;
size = le32toh(gpt->num_partition_entries) *
le32toh(gpt->sizeof_partition_entry);
if (!size)
{
return NULL;
}
blk = (size + (state->blocksize -1)) / state->blocksize;
pte = kmm_zalloc(blk * state->blocksize);
if (!pte)
{
return NULL;
}
from = GPT_LBA_TO_BLOCK(gpt->partition_entry_lba,
state->blocksize);
ret = read_partition_block(state, pte, from, blk);
if (ret < 0)
{
kmm_free(pte);
ferr("Read ptr from block failed:%d.\n", ret);
return NULL;
}
/* Check the GUID Partition Table Entry Array CRC */
crc = crc32part((FAR const uint8_t *)pte, size, ~0l) ^ ~0l;
if (crc != le32toh(gpt->partition_entry_array_crc32))
{
ferr("GUID Partitition Entry Array CRC check failed.\n");
kmm_free(pte);
return NULL;
}
return pte;
}
/****************************************************************************
* Name: gpt_header_is_valid
*
* Description:
* tests one GPT header for validity
*
* Input Parameters:
* state - The partition table state
* gpt - is a GPT header ptr.
* lba - is the logical block address of the GPT header to test
*
* Returned Value:
* Returns 0 if valid, a negative errno returned on error.
*
****************************************************************************/
static int gpt_header_is_valid(FAR struct partition_state_s *state,
FAR struct gpt_header_s *gpt, blkcnt_t lba)
{
uint32_t crc;
uint32_t origcrc;
blkcnt_t lastlba;
/* Check the GPT header signature */
if (le64toh(gpt->signature) != GPT_HEADER_SIGNATURE)
{
ferr("GUID Partition Table Header signature is wrong:"
"0x%" PRIx64 " != 0x%llx\n",
le64toh(gpt->signature), GPT_HEADER_SIGNATURE);
return -EINVAL;
}
/* Check the GUID Partition Table CRC */
origcrc = gpt->header_crc32;
gpt->header_crc32 = 0;
crc = crc32part((FAR const uint8_t *)gpt,
le32toh(gpt->header_size), ~0l) ^ ~0l;
if (crc != le32toh(origcrc))
{
ferr("GUID Partition Table Header CRC is wrong: %" PRIx32
" != %" PRIx32 "\n", crc, le32toh(origcrc));
return -EINVAL;
}
gpt->header_crc32 = origcrc;
/* Check that the my_lba entry points to the LBA that contains
* the GUID Partition Table
*/
if (le64toh(gpt->my_lba) != lba)
{
ferr("GPT: my_lba incorrect: %" PRIx64 " != %" PRIxOFF "\n",
le64toh(gpt->my_lba), lba);
return -EINVAL;
}
/* Check the first_usable_lba and last_usable_lba are within the disk. */
lastlba = gpt_last_lba(state);
if (le64toh(gpt->first_usable_lba) > lastlba)
{
ferr("GPT: first_usable_lba incorrect: %" PRId64 " > %" PRIdOFF "\n",
le64toh(gpt->first_usable_lba), lastlba);
return -EINVAL;
}
if (le64toh(gpt->last_usable_lba) > lastlba)
{
ferr("GPT: last_usable_lba incorrect: %" PRId64 " > %" PRIdOFF "\n",
le64toh(gpt->last_usable_lba), lastlba);
return -EINVAL;
}
return OK;
}
/****************************************************************************
* Name: gpt_pte_is_valid()
*
* Description:
* tests one PTE for validity
*
* Input Parameters:
* pte is the pte to check
* lastlba is last lba of the disk
*
* Returned Value:
* Returns 1 if valid, 0 on error.
*
****************************************************************************/
static inline int gpt_pte_is_valid(FAR const struct gpt_entry_s *pte,
blkcnt_t lastlba)
{
if (!memcmp(&pte->partition_type_guid, &g_null_guid,
sizeof(g_null_guid)) ||
le64toh(pte->starting_lba) > lastlba ||
le64toh(pte->ending_lba) > lastlba)
{
return 0;
}
return 1;
}
static void gpt_part_set_name(FAR struct gpt_entry_s *pte,
FAR char *dest, size_t len)
{
int i;
if (--len > GPT_PARTNAME_MAX_SIZE)
{
len = GPT_PARTNAME_MAX_SIZE;
}
for (i = 0; i < len; i++)
{
uint8_t c = pte->partition_name[i];
dest[i] = (c && !isprint(c)) ? '.' : c;
}
dest[i] = 0;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: parse_gpt_partition
*
* Description:
* parse the gpt(EFI GUID Partition Table) partition.
*
* Input Parameters:
* state - The partition table state
* handler - The function to be called for each found partition
* arg - A caller provided value to return with the handler
*
* Returned Value:
* Zero on success; A negated errno value is returned on a failure
*
****************************************************************************/
int parse_gpt_partition(FAR struct partition_state_s *state,
partition_handler_t handler,
FAR void *arg)
{
FAR struct legacy_partition_s *pmbr;
FAR struct gpt_ptable_s *ptbl;
FAR struct gpt_header_s *gpt;
FAR struct gpt_entry_s *ptes;
struct partition_s pentry;
blkcnt_t lastlba;
int nb_part;
int count;
int ret;
/* Read GPT Ptable (LBA0 + LBA1) */
count = (sizeof(struct gpt_ptable_s) + (state->blocksize - 1)) /
state->blocksize;
ptbl = kmm_malloc(count * state->blocksize);
if (!ptbl)
{
return -ENOMEM;
}
ret = read_partition_block(state, ptbl, 0, count);
if (ret < 0)
{
goto err;
}
/* Verify mbr is valid */
pmbr = (FAR struct legacy_partition_s *)&ptbl->mbr[0x1be];
if (pmbr->sys_ind != 0xee)
{
ret = -EINVAL;
goto err;
}
/* Verify gpt header is valid */
gpt = &(ptbl->u.gpt_header);
ret = gpt_header_is_valid(state, gpt, 1);
if (ret >= 0)
{
/* Verify gpt header is valid */
ptes = gpt_alloc_verify_entries(state, gpt);
}
if (ret < 0 || !ptes)
{
/* Read and Verify backup gpt header is valid */
finfo("Primary GPT is invalid, using alternate GPT.\n");
count = (GPT_BLOCK_SIZE + state->blocksize - 1) / state->blocksize;
ret = read_partition_block(state, ptbl,
GPT_LBA_TO_BLOCK(gpt->alternate_lba,
state->blocksize), count);
if (ret < 0)
{
goto err;
}
gpt = (FAR struct gpt_header_s *)ptbl;
ret = gpt_header_is_valid(state, gpt,
le64toh(gpt->alternate_lba));
if (ret >= 0)
{
/* Verify gpt header is valid */
ptes = gpt_alloc_verify_entries(state, gpt);
}
}
if (ret < 0 || !ptes)
{
finfo("Alternate GPT is also invalid!!\n");
goto err;
}
lastlba = gpt_last_lba(state);
nb_part = le32toh(gpt->num_partition_entries);
for (pentry.index = 0; pentry.index < nb_part; pentry.index++)
{
/* Skip the empty or invalid entries */
if (!gpt_pte_is_valid(&ptes[pentry.index], lastlba))
{
continue;
}
pentry.firstblock = GPT_LBA_TO_BLOCK(ptes[pentry.index].starting_lba,
state->blocksize);
pentry.nblocks = GPT_LBA_TO_BLOCK(ptes[pentry.index].ending_lba + 1,
state->blocksize) -
pentry.firstblock;
pentry.blocksize = state->blocksize;
gpt_part_set_name(&ptes[pentry.index], pentry.name,
sizeof(pentry.name));
handler(&pentry, arg);
}
kmm_free(ptes);
err:
kmm_free(ptbl);
return ret;
}