mirror of https://github.com/wolfSSL/wolfBoot.git
1194 lines
37 KiB
C
1194 lines
37 KiB
C
/* unit-disk.c
|
|
*
|
|
* Unit tests for disk.c and gpt.c
|
|
*
|
|
* Copyright (C) 2026 wolfSSL Inc.
|
|
* This file is part of wolfBoot.
|
|
*
|
|
* wolfBoot 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.
|
|
*
|
|
* wolfBoot 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 this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
|
|
*/
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <check.h>
|
|
|
|
#include "gpt.c"
|
|
#include "disk.c"
|
|
#include "gpt-sfdisk-test.h"
|
|
|
|
/* Fake disk backing store for mock disk_read/disk_write */
|
|
#define FAKE_DISK_SIZE (128 * 1024) /* 128 KB */
|
|
static uint8_t fake_disk[FAKE_DISK_SIZE];
|
|
|
|
/* Set to a byte offset to make disk_read fail at that address. -1 = no fail */
|
|
static int64_t mock_disk_read_fail_at = -1;
|
|
|
|
/* Number of disk_read calls since last reset (used to detect scan blow-ups) */
|
|
static unsigned int disk_read_count = 0;
|
|
|
|
/* Mock disk I/O — copies to/from fake_disk buffer */
|
|
int disk_read(int drv, uint64_t start, uint32_t count, uint8_t *buf)
|
|
{
|
|
(void)drv;
|
|
disk_read_count++;
|
|
if (mock_disk_read_fail_at >= 0 && (int64_t)start == mock_disk_read_fail_at)
|
|
return -1;
|
|
if (start + count > FAKE_DISK_SIZE)
|
|
return -1;
|
|
memcpy(buf, fake_disk + start, count);
|
|
return 0;
|
|
}
|
|
|
|
int disk_write(int drv, uint64_t start, uint32_t count, const uint8_t *buf)
|
|
{
|
|
(void)drv;
|
|
if (start + count > FAKE_DISK_SIZE)
|
|
return -1;
|
|
memcpy(fake_disk + start, buf, count);
|
|
return 0;
|
|
}
|
|
|
|
int disk_init(int drv) { (void)drv; return 0; }
|
|
void disk_close(int drv) { (void)drv; }
|
|
|
|
/* GPT partition layout (LBA offsets) */
|
|
static const int PART0_OFF = 40;
|
|
static const int PART0_END = 100;
|
|
static const int PART1_OFF = 101;
|
|
static const int PART1_END = 200;
|
|
|
|
static uint32_t test_crc32(const uint8_t *data, uint32_t len)
|
|
{
|
|
uint32_t crc = 0xFFFFFFFFU;
|
|
uint32_t i;
|
|
uint32_t j;
|
|
|
|
for (i = 0; i < len; i++) {
|
|
crc ^= data[i];
|
|
for (j = 0; j < 8; j++) {
|
|
uint32_t mask = -(crc & 1U);
|
|
crc = (crc >> 1) ^ (0xEDB88320U & mask);
|
|
}
|
|
}
|
|
|
|
return ~crc;
|
|
}
|
|
|
|
/* Every on-disk field is little-endian regardless of host byte order, so the
|
|
* fixture writes bytes explicitly instead of assigning packed struct members.
|
|
* Assigning members only produces correct on-disk bytes on a little-endian
|
|
* host, which would make these tests unable to run big-endian. */
|
|
static void d_put16(uint8_t *p, uint16_t v)
|
|
{
|
|
p[0] = (uint8_t)(v & 0xFFU);
|
|
p[1] = (uint8_t)((v >> 8) & 0xFFU);
|
|
}
|
|
|
|
static void d_put32(uint8_t *p, uint32_t v)
|
|
{
|
|
p[0] = (uint8_t)(v & 0xFFU);
|
|
p[1] = (uint8_t)((v >> 8) & 0xFFU);
|
|
p[2] = (uint8_t)((v >> 16) & 0xFFU);
|
|
p[3] = (uint8_t)((v >> 24) & 0xFFU);
|
|
}
|
|
|
|
static void d_put64(uint8_t *p, uint64_t v)
|
|
{
|
|
d_put32(p, (uint32_t)(v & 0xFFFFFFFFU));
|
|
d_put32(p + 4, (uint32_t)((v >> 32) & 0xFFFFFFFFU));
|
|
}
|
|
|
|
/* GPT on-disk offsets (see src/gpt.c) */
|
|
#define D_HDR_SIGNATURE 0x00
|
|
#define D_HDR_REVISION 0x08
|
|
#define D_HDR_SIZE 0x0C
|
|
#define D_HDR_CRC32 0x10
|
|
#define D_HDR_START_ARRAY 0x48
|
|
#define D_HDR_N_PART 0x50
|
|
#define D_HDR_ARRAY_SZ 0x54
|
|
#define D_HDR_PART_CRC 0x58
|
|
#define D_PE_TYPE 0x00
|
|
#define D_PE_UUID 0x10
|
|
#define D_PE_FIRST 0x20
|
|
#define D_PE_LAST 0x28
|
|
#define D_PE_NAME 0x38
|
|
|
|
static uint32_t d_get32(const uint8_t *p)
|
|
{
|
|
return ((uint32_t)p[0]) | ((uint32_t)p[1] << 8) |
|
|
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
|
|
}
|
|
|
|
static uint64_t d_get64(const uint8_t *p)
|
|
{
|
|
return ((uint64_t)d_get32(p)) | ((uint64_t)d_get32(p + 4) << 32);
|
|
}
|
|
|
|
static void finalize_gpt_header_crc(uint8_t *hdr)
|
|
{
|
|
uint32_t hdr_size = d_get32(hdr + D_HDR_SIZE);
|
|
|
|
d_put32(hdr + D_HDR_CRC32, 0);
|
|
d_put32(hdr + D_HDR_CRC32, test_crc32(hdr, hdr_size));
|
|
}
|
|
|
|
static void finalize_gpt_part_array_crc(uint8_t *hdr)
|
|
{
|
|
uint64_t start_array = d_get64(hdr + D_HDR_START_ARRAY);
|
|
uint32_t n_part = d_get32(hdr + D_HDR_N_PART);
|
|
uint32_t array_sz = d_get32(hdr + D_HDR_ARRAY_SZ);
|
|
uint8_t *array = fake_disk + (start_array * GPT_SECTOR_SIZE);
|
|
|
|
d_put32(hdr + D_HDR_PART_CRC, test_crc32(array, n_part * array_sz));
|
|
}
|
|
|
|
/* --- Helpers to build fake disk layouts --- */
|
|
|
|
/* Write a UTF-16LE string into a buffer (no BOM).
|
|
* Uses memcpy to avoid unaligned-pointer warnings on packed structs. */
|
|
static void write_utf16(void *dst, const char *ascii, unsigned int max)
|
|
{
|
|
uint8_t *p = (uint8_t *)dst;
|
|
unsigned int i;
|
|
|
|
memset(dst, 0, max * sizeof(uint16_t));
|
|
for (i = 0; i < max && ascii[i]; i++) {
|
|
d_put16(p + (i * 2U), (uint16_t)ascii[i]);
|
|
}
|
|
}
|
|
|
|
/* Populate fake_disk with a valid protective-MBR + GPT header + N entries. */
|
|
static void build_gpt_disk(void)
|
|
{
|
|
uint8_t *mbr_entry;
|
|
uint8_t *gpt_hdr;
|
|
uint8_t *pe;
|
|
|
|
memset(fake_disk, 0, FAKE_DISK_SIZE);
|
|
|
|
/* --- Sector 0: MBR --- */
|
|
mbr_entry = fake_disk + GPT_MBR_ENTRY_START;
|
|
mbr_entry[0x04] = GPT_PTYPE_PROTECTIVE; /* 0xEE */
|
|
d_put32(mbr_entry + 0x08, 1); /* lba_first */
|
|
d_put32(mbr_entry + 0x0C, 0xFFFFFFFF); /* lba_size */
|
|
|
|
d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE);
|
|
|
|
/* --- Sector 1: GPT header --- */
|
|
gpt_hdr = fake_disk + GPT_SECTOR_SIZE;
|
|
d_put64(gpt_hdr + D_HDR_SIGNATURE, GPT_SIGNATURE);
|
|
d_put32(gpt_hdr + D_HDR_REVISION, 0x00010000);
|
|
d_put32(gpt_hdr + D_HDR_SIZE, 92);
|
|
d_put64(gpt_hdr + D_HDR_START_ARRAY, 2); /* entries start at LBA 2 */
|
|
d_put32(gpt_hdr + D_HDR_N_PART, 2);
|
|
d_put32(gpt_hdr + D_HDR_ARRAY_SZ, 128); /* bytes per entry */
|
|
|
|
/* --- Sector 2: Partition entries --- */
|
|
/* Entry 0: name "boot" */
|
|
pe = fake_disk + (2 * GPT_SECTOR_SIZE);
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL); /* non-zero type GUID */
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, PART0_OFF);
|
|
d_put64(pe + D_PE_LAST, PART0_END);
|
|
write_utf16(pe + D_PE_NAME, "boot", GPT_PART_NAME_SIZE);
|
|
|
|
/* Entry 1: name "rootfs" */
|
|
pe = fake_disk + (2 * GPT_SECTOR_SIZE) + 128;
|
|
d_put64(pe + D_PE_TYPE, 0x1011121314151617ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x18191A1B1C1D1E1FULL);
|
|
d_put64(pe + D_PE_FIRST, PART1_OFF);
|
|
d_put64(pe + D_PE_LAST, PART1_END);
|
|
write_utf16(pe + D_PE_NAME, "rootfs", GPT_PART_NAME_SIZE);
|
|
|
|
/* Fill partition data areas with known patterns for read tests.
|
|
* GPT last LBA is inclusive, so partition spans
|
|
* (PART_END - PART_OFF + 1) sectors. */
|
|
memset(fake_disk + PART0_OFF * GPT_SECTOR_SIZE, 0xAA,
|
|
(PART0_END - PART0_OFF + 1) * GPT_SECTOR_SIZE);
|
|
memset(fake_disk + PART1_OFF * GPT_SECTOR_SIZE, 0xBB,
|
|
(PART1_END - PART1_OFF + 1) * GPT_SECTOR_SIZE);
|
|
|
|
finalize_gpt_part_array_crc(gpt_hdr);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
}
|
|
|
|
/* Populate fake_disk with MBR-only layout (no GPT protective entry).
|
|
* Two partitions: part0 type=0x0C at LBA 2048 size 4096,
|
|
* part1 type=0x83 at LBA 8192 size 8192
|
|
*/
|
|
static void build_mbr_disk(void)
|
|
{
|
|
uint8_t *pte;
|
|
|
|
memset(fake_disk, 0, FAKE_DISK_SIZE);
|
|
|
|
/* MBR entry 0: FAT32 LBA */
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START);
|
|
pte[0x04] = (uint8_t)(0x0C); /* FAT32 LBA */
|
|
d_put32(pte + 0x08, 16);
|
|
d_put32(pte + 0x0C, 32);
|
|
|
|
/* MBR entry 1: Linux */
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START +
|
|
sizeof(struct gpt_mbr_part_entry));
|
|
pte[0x04] = (uint8_t)(0x83); /* Linux */
|
|
d_put32(pte + 0x08, 48);
|
|
d_put32(pte + 0x0C, 64);
|
|
|
|
d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE);
|
|
}
|
|
|
|
/* ============================================================
|
|
* GPT test cases
|
|
* ============================================================ */
|
|
|
|
START_TEST(test_gpt_check_mbr_protective)
|
|
{
|
|
uint32_t lba = 0;
|
|
|
|
build_gpt_disk();
|
|
|
|
/* Valid protective MBR */
|
|
ck_assert_int_eq(gpt_check_mbr_protective(fake_disk, &lba), 0);
|
|
ck_assert_uint_eq(lba, 1);
|
|
|
|
/* MBR without 0xEE entry should fail */
|
|
build_mbr_disk();
|
|
ck_assert_int_eq(gpt_check_mbr_protective(fake_disk, &lba), -1);
|
|
|
|
/* NULL input */
|
|
ck_assert_int_eq(gpt_check_mbr_protective(NULL, &lba), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_header)
|
|
{
|
|
struct guid_ptable hdr;
|
|
|
|
build_gpt_disk();
|
|
|
|
/* Valid GPT header at sector 1 */
|
|
ck_assert_int_eq(
|
|
gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, &hdr), 0);
|
|
ck_assert_uint_eq(hdr.signature, GPT_SIGNATURE);
|
|
ck_assert_uint_eq(hdr.n_part, 2);
|
|
ck_assert_uint_eq(hdr.start_array, 2);
|
|
ck_assert_uint_eq(hdr.array_sz, 128);
|
|
|
|
/* Corrupt a header field without updating CRC */
|
|
{
|
|
uint8_t *gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put32(gpt_hdr + D_HDR_N_PART, 3);
|
|
ck_assert_int_eq(
|
|
gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, &hdr), -1);
|
|
d_put32(gpt_hdr + D_HDR_N_PART, 2);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
}
|
|
|
|
/* Corrupt signature in the real header */
|
|
{
|
|
uint8_t *gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put64(gpt_hdr + D_HDR_SIGNATURE, 0);
|
|
ck_assert_int_eq(
|
|
gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, &hdr), -1);
|
|
d_put64(gpt_hdr + D_HDR_SIGNATURE, GPT_SIGNATURE);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
}
|
|
|
|
/* NULL inputs */
|
|
ck_assert_int_eq(gpt_parse_header(NULL, &hdr), -1);
|
|
ck_assert_int_eq(
|
|
gpt_parse_header(fake_disk + GPT_SECTOR_SIZE, NULL), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_partition)
|
|
{
|
|
struct gpt_part_info info;
|
|
|
|
build_gpt_disk();
|
|
|
|
/* Valid entry 0 */
|
|
ck_assert_int_eq(gpt_parse_partition(
|
|
fake_disk + 2 * GPT_SECTOR_SIZE, 128, &info), 0);
|
|
ck_assert_uint_eq(info.start, PART0_OFF * GPT_SECTOR_SIZE);
|
|
ck_assert_uint_eq(info.end, (PART0_END + 1) * GPT_SECTOR_SIZE - 1);
|
|
|
|
/* Valid entry 1 */
|
|
ck_assert_int_eq(gpt_parse_partition(
|
|
fake_disk + 2 * GPT_SECTOR_SIZE + 128, 128, &info), 0);
|
|
ck_assert_uint_eq(info.start, PART1_OFF * GPT_SECTOR_SIZE);
|
|
ck_assert_uint_eq(info.end, (PART1_END + 1) * GPT_SECTOR_SIZE - 1);
|
|
|
|
/* All-zero type GUID → invalid */
|
|
{
|
|
uint8_t empty_entry[128];
|
|
memset(empty_entry, 0, sizeof(empty_entry));
|
|
ck_assert_int_eq(gpt_parse_partition(empty_entry, 128, &info), -1);
|
|
}
|
|
|
|
/* Entry size too small */
|
|
ck_assert_int_eq(gpt_parse_partition(
|
|
fake_disk + 2 * GPT_SECTOR_SIZE, 10, &info), -1);
|
|
|
|
/* NULL inputs */
|
|
ck_assert_int_eq(gpt_parse_partition(NULL, 128, &info), -1);
|
|
ck_assert_int_eq(gpt_parse_partition(
|
|
fake_disk + 2 * GPT_SECTOR_SIZE, 128, NULL), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_part_name_eq)
|
|
{
|
|
uint16_t name[GPT_PART_NAME_SIZE];
|
|
|
|
/* Simple match */
|
|
write_utf16(name, "boot", GPT_PART_NAME_SIZE);
|
|
ck_assert_int_eq(gpt_part_name_eq(name, "boot"), 1);
|
|
|
|
/* Mismatch */
|
|
ck_assert_int_eq(gpt_part_name_eq(name, "rootfs"), 0);
|
|
|
|
/* BOM prefix */
|
|
name[0] = 0xFEFF;
|
|
write_utf16(name + 1, "efi", GPT_PART_NAME_SIZE - 1);
|
|
ck_assert_int_eq(gpt_part_name_eq(name, "efi"), 1);
|
|
|
|
/* NULL inputs */
|
|
ck_assert_int_eq(gpt_part_name_eq(NULL, "boot"), 0);
|
|
ck_assert_int_eq(gpt_part_name_eq(name, NULL), 0);
|
|
}
|
|
END_TEST
|
|
|
|
/* ============================================================
|
|
* Disk test cases
|
|
* ============================================================ */
|
|
|
|
START_TEST(test_disk_open_gpt)
|
|
{
|
|
int n;
|
|
|
|
build_gpt_disk();
|
|
|
|
n = disk_open(0);
|
|
ck_assert_int_eq(n, 2);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_rejects_part_array_crc_mismatch)
|
|
{
|
|
uint8_t *pe;
|
|
|
|
build_gpt_disk();
|
|
|
|
pe = (uint8_t *)(fake_disk + 2 * GPT_SECTOR_SIZE);
|
|
d_put64(pe + D_PE_FIRST, PART1_OFF);
|
|
d_put64(pe + D_PE_LAST, PART1_END);
|
|
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_mbr)
|
|
{
|
|
int n;
|
|
|
|
build_mbr_disk();
|
|
|
|
n = disk_open(0);
|
|
ck_assert_int_eq(n, 2);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_part_read)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
int ret;
|
|
unsigned int i;
|
|
|
|
build_gpt_disk();
|
|
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
/* Read first sector of partition 0 — should be 0xAA pattern */
|
|
ret = disk_part_read(0, 0, 0, GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_gt(ret, 0);
|
|
for (i = 0; i < GPT_SECTOR_SIZE; i++)
|
|
ck_assert_uint_eq(buf[i], 0xAA);
|
|
|
|
/* Read first sector of partition 1 — should be 0xBB pattern */
|
|
ret = disk_part_read(0, 1, 0, GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_gt(ret, 0);
|
|
for (i = 0; i < GPT_SECTOR_SIZE; i++)
|
|
ck_assert_uint_eq(buf[i], 0xBB);
|
|
|
|
/* Invalid partition */
|
|
ck_assert_int_eq(disk_part_read(0, 99, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
}
|
|
END_TEST
|
|
|
|
/* ============================================================
|
|
* Regression tests: disk.c unsigned underflow in bounds check
|
|
* ============================================================ */
|
|
|
|
START_TEST(test_disk_part_rw_offset_past_end)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
int ret;
|
|
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
/* Partition 0 spans PART0_OFF to PART0_END LBAs.
|
|
* Offset 40000 exceeds partition size, both read and write
|
|
* should return -1. */
|
|
ret = disk_part_read(0, 0, 40000, GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_eq(ret, -1);
|
|
|
|
memset(buf, 0x55, sizeof(buf));
|
|
ret = disk_part_write(0, 0, 40000, GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_eq(ret, -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_part_rw_size_past_end)
|
|
{
|
|
uint8_t buf[2 * GPT_SECTOR_SIZE];
|
|
int ret;
|
|
uint64_t off = 60 * GPT_SECTOR_SIZE; /* within partition (last sector) */
|
|
uint64_t sz = 2 * GPT_SECTOR_SIZE; /* 1024: extends past end */
|
|
|
|
/* Partition 0: start = PART0_OFF * 512 = 20480,
|
|
* end = (PART0_END + 1) * 512 - 1 = 51711
|
|
* p->start + off = 51200, which is < p->end (51711)
|
|
* Remaining = 51711 - 51200 + 1 = 512, so len is clamped to 512 */
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
ret = disk_part_read(0, 0, off, sz, buf);
|
|
ck_assert_int_eq(ret, 512);
|
|
|
|
memset(buf, 0x55, sizeof(buf));
|
|
ret = disk_part_write(0, 0, off, sz, buf);
|
|
ck_assert_int_eq(ret, 512);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_mbr_max_partitions)
|
|
{
|
|
/* If n_parts is already at MAX_PARTITIONS, disk_open_mbr must not
|
|
* add more entries (would be an OOB write). */
|
|
build_mbr_disk();
|
|
|
|
/* Pre-fill drive 0 as if it already has MAX_PARTITIONS */
|
|
Drives[0].n_parts = MAX_PARTITIONS;
|
|
Drives[0].is_open = 1;
|
|
Drives[0].drv = 0;
|
|
|
|
/* Call disk_open_mbr directly — it should not add any entries */
|
|
disk_open_mbr(&Drives[0], fake_disk);
|
|
ck_assert_int_eq(Drives[0].n_parts, MAX_PARTITIONS);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_part_name_eq_bom_boundary)
|
|
{
|
|
uint16_t name[GPT_PART_NAME_SIZE];
|
|
char label35[36]; /* 35 chars + NUL */
|
|
char label36[37]; /* 36 chars + NUL */
|
|
unsigned int i;
|
|
|
|
/* Build a 35-char label: fills indices 1-35 after BOM at index 0 */
|
|
for (i = 0; i < 35; i++)
|
|
label35[i] = 'A' + (i % 26);
|
|
label35[35] = '\0';
|
|
|
|
memset(name, 0, sizeof(name));
|
|
name[0] = 0xFEFF; /* BOM */
|
|
for (i = 0; i < 35; i++)
|
|
name[i + 1] = (uint16_t)label35[i];
|
|
|
|
/* Should match — BOM + 35 chars exactly fills 36 slots */
|
|
ck_assert_int_eq(gpt_part_name_eq(name, label35), 1);
|
|
|
|
/* Build a 36-char label: BOM + 36 would read index 37 = OOB */
|
|
for (i = 0; i < 36; i++)
|
|
label36[i] = 'A' + (i % 26);
|
|
label36[36] = '\0';
|
|
|
|
/* Should return 0 — label too long for name array with BOM */
|
|
ck_assert_int_eq(gpt_part_name_eq(name, label36), 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_find_partition_by_label)
|
|
{
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
/* Find existing partitions */
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "boot"), 0);
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "rootfs"), 1);
|
|
|
|
/* Non-existent label */
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "nonexistent"), -1);
|
|
|
|
/* Invalid drive */
|
|
ck_assert_int_eq(disk_find_partition_by_label(99, "boot"), -1);
|
|
|
|
/* Unopened drive */
|
|
ck_assert_int_eq(disk_find_partition_by_label(1, "boot"), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_partition_end_inclusive)
|
|
{
|
|
/* GPT spec: last LBA is inclusive. A partition with first=10, last=20
|
|
* spans 11 sectors. End byte = (20+1)*512 - 1. */
|
|
uint8_t entry[128];
|
|
uint8_t *pe = (uint8_t *)entry;
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, 10);
|
|
d_put64(pe + D_PE_LAST, 20);
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), 0);
|
|
ck_assert_uint_eq(info.start, 10 * 512);
|
|
ck_assert_uint_eq(info.end, (21 * 512) - 1);
|
|
/* Partition size = end - start + 1 = 11 * 512 */
|
|
ck_assert_uint_eq(info.end - info.start + 1, 11 * 512);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_failure_clears_is_open)
|
|
{
|
|
/* If GPT header parse fails, is_open must be reset to 0. */
|
|
uint8_t *gpt_hdr;
|
|
|
|
build_gpt_disk();
|
|
|
|
/* Corrupt the GPT header signature */
|
|
gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put64(gpt_hdr + D_HDR_SIGNATURE, 0xDEADBEEF);
|
|
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_partition_last_zero)
|
|
{
|
|
/* If first=0, last=0 with non-zero type GUID, (last+1)*512 - 1 would
|
|
* be 511 but LBA 0 is the protective MBR — must reject. */
|
|
uint8_t entry[128];
|
|
uint8_t *pe = (uint8_t *)entry;
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, 0);
|
|
d_put64(pe + D_PE_LAST, 0);
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_partition_last_overflow)
|
|
{
|
|
/* last = UINT64_MAX passes the first>last and last==0 guards, but
|
|
* (last + 1) * GPT_SECTOR_SIZE - 1 would wrap to UINT64_MAX, defeating
|
|
* the bounds check in disk_part_read. Must be rejected. */
|
|
uint8_t entry[128];
|
|
uint8_t *pe = (uint8_t *)entry;
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, 1);
|
|
d_put64(pe + D_PE_LAST, 0xFFFFFFFFFFFFFFFFULL);
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1);
|
|
}
|
|
END_TEST
|
|
|
|
/* ============================================================
|
|
* Coverage tests: disk.c error paths and boundary conditions
|
|
* ============================================================ */
|
|
|
|
START_TEST(test_disk_open_invalid_drive)
|
|
{
|
|
ck_assert_int_eq(disk_open(-1), -1);
|
|
ck_assert_int_eq(disk_open(MAX_DISKS), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_mbr_bad_bootsig)
|
|
{
|
|
/* MBR disk without valid 0xAA55 boot signature and no protective 0xEE.
|
|
* Falls through GPT check, then fails on boot_sig validation. */
|
|
uint8_t *pte;
|
|
|
|
memset(fake_disk, 0, FAKE_DISK_SIZE);
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START);
|
|
pte[0x04] = (uint8_t)(0x0C);
|
|
d_put32(pte + 0x08, 16);
|
|
d_put32(pte + 0x0C, 32);
|
|
/* No boot signature set — 0x0000 instead of 0xAA55 */
|
|
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_excess_partitions)
|
|
{
|
|
/* GPT header claims more partitions than MAX_PARTITIONS. disk_open
|
|
* must cap n_parts to MAX_PARTITIONS. */
|
|
uint8_t *gpt_hdr;
|
|
|
|
build_gpt_disk();
|
|
|
|
gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put32(gpt_hdr + D_HDR_N_PART, MAX_PARTITIONS + 10);
|
|
finalize_gpt_part_array_crc(gpt_hdr);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
|
|
/* Only 2 actual entries on disk so loop will break after parsing them,
|
|
* but the capping branch is exercised. */
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
ck_assert_int_le(Drives[0].n_parts, MAX_PARTITIONS);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_large_array_sz)
|
|
{
|
|
/* GPT header with array_sz larger than GPT_PART_ENTRY_SIZE (256).
|
|
* Loop must break immediately without reading entries. */
|
|
uint8_t *gpt_hdr;
|
|
|
|
build_gpt_disk();
|
|
|
|
gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put32(gpt_hdr + D_HDR_ARRAY_SZ, GPT_PART_ENTRY_SIZE + 1); /* 257 > 256 */
|
|
finalize_gpt_part_array_crc(gpt_hdr);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
|
|
ck_assert_int_eq(disk_open(0), 0); /* 0 partitions found */
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_rejects_huge_part_array)
|
|
{
|
|
/* A crafted GPT header with a valid (recomputed) header CRC but an
|
|
* enormous n_part * array_sz must be rejected before the partition-entry
|
|
* CRC scan loop runs, otherwise it forces a pre-auth DoS via one disk
|
|
* read per 512-byte chunk of the (here 8 MB) declared array. */
|
|
uint8_t *gpt_hdr;
|
|
|
|
build_gpt_disk();
|
|
|
|
gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put32(gpt_hdr + D_HDR_N_PART, 0x10000); /* 65536 entries ... */
|
|
d_put32(gpt_hdr + D_HDR_ARRAY_SZ, 128); /* ... * 128 bytes = 8 MB */
|
|
finalize_gpt_header_crc(gpt_hdr); /* attacker can always fix header CRC */
|
|
|
|
disk_read_count = 0;
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
/* Only the MBR sector and the GPT header sector may be read; the
|
|
* partition-array scan must not run at all. */
|
|
ck_assert_uint_le(disk_read_count, 2);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_lba_no_overflow)
|
|
{
|
|
/* The protective-MBR lba_first is an attacker-controlled uint32_t. The
|
|
* GPT-header byte offset must be computed in 64 bits. If it is computed
|
|
* as the 32-bit product GPT_SECTOR_SIZE * gpt_lba, it wraps for
|
|
* gpt_lba >= 0x800000: 512 * 0x800001 wraps to 0x200, silently
|
|
* redirecting the read back to LBA 1 (the real GPT header) instead of
|
|
* the out-of-range LBA the field actually names. */
|
|
uint8_t *mbr_entry;
|
|
|
|
build_gpt_disk();
|
|
|
|
/* Point the protective entry at an LBA whose 512* product overflows a
|
|
* 32-bit unsigned back to 0x200 (LBA 1). */
|
|
mbr_entry = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START);
|
|
d_put32(mbr_entry + 0x08, 0x800001);
|
|
|
|
/* With correct 64-bit arithmetic the header read targets byte
|
|
* 0x100000200, far past the fake disk, so disk_open must fail rather
|
|
* than wrap to LBA 1 and accept the table. */
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_empty_entry_mid_table)
|
|
{
|
|
/* GPT header says 3 partitions but entry[1] has zeroed type GUID.
|
|
* gpt_parse_partition returns -1 → loop breaks, only 1 partition found. */
|
|
uint8_t *gpt_hdr;
|
|
uint8_t *pe;
|
|
|
|
build_gpt_disk();
|
|
|
|
gpt_hdr = (uint8_t *)(fake_disk + GPT_SECTOR_SIZE);
|
|
d_put32(gpt_hdr + D_HDR_N_PART, 3);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
|
|
/* Zero out entry 1's type GUID */
|
|
pe = (uint8_t *)(fake_disk + 2 * GPT_SECTOR_SIZE + 128);
|
|
d_put64(pe + D_PE_TYPE, 0);
|
|
d_put64(pe + D_PE_TYPE + 8, 0);
|
|
finalize_gpt_part_array_crc(gpt_hdr);
|
|
finalize_gpt_header_crc(gpt_hdr);
|
|
|
|
ck_assert_int_eq(disk_open(0), 1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_mbr_zero_lba_entry)
|
|
{
|
|
/* MBR entry with lba_first=0 must be skipped. */
|
|
uint8_t *pte;
|
|
|
|
memset(fake_disk, 0, FAKE_DISK_SIZE);
|
|
|
|
/* Entry 0: valid */
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START);
|
|
pte[0x04] = (uint8_t)(0x0C);
|
|
d_put32(pte + 0x08, 16);
|
|
d_put32(pte + 0x0C, 32);
|
|
|
|
/* Entry 1: lba_first=0, should be skipped */
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START +
|
|
sizeof(struct gpt_mbr_part_entry));
|
|
pte[0x04] = (uint8_t)(0x83);
|
|
d_put32(pte + 0x08, 0);
|
|
d_put32(pte + 0x0C, 64);
|
|
|
|
/* Entry 2: lba_size=0, should also be skipped */
|
|
pte = (uint8_t *)(fake_disk + GPT_MBR_ENTRY_START +
|
|
2 * sizeof(struct gpt_mbr_part_entry));
|
|
pte[0x04] = (uint8_t)(0x83);
|
|
d_put32(pte + 0x08, 48);
|
|
d_put32(pte + 0x0C, 0);
|
|
|
|
d_put16(fake_disk + GPT_MBR_BOOTSIG_OFFSET, GPT_MBR_BOOTSIG_VALUE);
|
|
|
|
ck_assert_int_eq(disk_open(0), 1); /* only entry 0 counted */
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_open_part_invalid_drive)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
|
|
/* open_part rejects drv < 0 and drv >= MAX_DISKS */
|
|
ck_assert_int_eq(disk_part_read(-1, 0, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
ck_assert_int_eq(disk_part_read(MAX_DISKS, 0, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_open_part_drive_not_open)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
|
|
/* Drive 2 was never opened */
|
|
memset(&Drives[2], 0, sizeof(Drives[2]));
|
|
ck_assert_int_eq(disk_part_read(2, 0, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_open_part_part_beyond_nparts)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
|
|
/* Open drive 0 with 2 partitions, then request partition 5.
|
|
* 5 < MAX_PARTITIONS so passes range check, but 5 >= n_parts. */
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
ck_assert_int_eq(disk_part_read(0, 5, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_part_write_invalid_partition)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
/* Part 5 < MAX_PARTITIONS but >= n_parts → open_part returns NULL */
|
|
memset(buf, 0x55, sizeof(buf));
|
|
ck_assert_int_eq(disk_part_write(0, 5, 0, GPT_SECTOR_SIZE, buf), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_part_rw_sz_clamped_to_max)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
uint64_t off = 60 * GPT_SECTOR_SIZE; /* near partition end */
|
|
|
|
build_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
/* Request with sz > DISK_IO_MAX_SIZE triggers the clamping branch.
|
|
* Use offset near end so remaining bytes (512) fit in buf. */
|
|
ck_assert_int_eq(disk_part_read(0, 0, off,
|
|
(uint64_t)DISK_IO_MAX_SIZE + 1, buf), 512);
|
|
|
|
memset(buf, 0x55, sizeof(buf));
|
|
ck_assert_int_eq(disk_part_write(0, 0, off,
|
|
(uint64_t)DISK_IO_MAX_SIZE + 1, buf), 512);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_mbr_read_failure)
|
|
{
|
|
build_mbr_disk();
|
|
|
|
/* Fail reading sector 0 (MBR) */
|
|
mock_disk_read_fail_at = 0;
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
mock_disk_read_fail_at = -1;
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_header_read_failure)
|
|
{
|
|
build_gpt_disk();
|
|
|
|
/* Fail reading sector 1 (GPT header) */
|
|
mock_disk_read_fail_at = GPT_SECTOR_SIZE;
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
mock_disk_read_fail_at = -1;
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_disk_open_gpt_entry_read_failure)
|
|
{
|
|
build_gpt_disk();
|
|
|
|
/* Fail reading first partition entry at sector 2 */
|
|
mock_disk_read_fail_at = 2 * GPT_SECTOR_SIZE;
|
|
ck_assert_int_eq(disk_open(0), -1);
|
|
ck_assert_int_eq(Drives[0].is_open, 0);
|
|
mock_disk_read_fail_at = -1;
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_check_mbr_bad_bootsig)
|
|
{
|
|
uint32_t lba = 0;
|
|
uint8_t sector[GPT_SECTOR_SIZE];
|
|
|
|
/* Valid MBR structure but corrupt boot signature */
|
|
memset(sector, 0, sizeof(sector));
|
|
{
|
|
uint8_t *pte = (uint8_t *)(sector + GPT_MBR_ENTRY_START);
|
|
pte[0x04] = (uint8_t)(GPT_PTYPE_PROTECTIVE);
|
|
d_put32(pte + 0x08, 1);
|
|
d_put32(pte + 0x0C, 0xFFFFFFFF);
|
|
}
|
|
/* boot sig left as 0x0000 — not 0xAA55 */
|
|
|
|
ck_assert_int_eq(gpt_check_mbr_protective(sector, &lba), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_partition_first_gt_last)
|
|
{
|
|
uint8_t entry[128];
|
|
uint8_t *pe = (uint8_t *)entry;
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, 100);
|
|
d_put64(pe + D_PE_LAST, 50); /* first > last → invalid */
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_parse_partition_first_eq_last)
|
|
{
|
|
uint8_t entry[128];
|
|
uint8_t *pe = (uint8_t *)entry;
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(pe + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(pe + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(pe + D_PE_FIRST, 5);
|
|
d_put64(pe + D_PE_LAST, 5); /* first == last is a valid single-sector partition */
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, 128, &info), 0);
|
|
ck_assert_uint_eq(info.start, 5 * GPT_SECTOR_SIZE);
|
|
ck_assert_uint_eq(info.end, (6 * GPT_SECTOR_SIZE) - 1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_part_name_eq_label_too_long)
|
|
{
|
|
uint16_t name[GPT_PART_NAME_SIZE];
|
|
char label37[38]; /* 37 chars + NUL — exceeds GPT_PART_NAME_SIZE (36) */
|
|
unsigned int i;
|
|
|
|
memset(name, 0, sizeof(name));
|
|
for (i = 0; i < 37; i++)
|
|
label37[i] = 'A' + (i % 26);
|
|
label37[37] = '\0';
|
|
|
|
ck_assert_int_eq(gpt_part_name_eq(name, label37), 0);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_part_name_eq_not_null_terminated)
|
|
{
|
|
uint16_t name[GPT_PART_NAME_SIZE];
|
|
|
|
/* "boot" followed by non-zero garbage — should not match "boot" */
|
|
write_utf16(name, "boot", GPT_PART_NAME_SIZE);
|
|
name[4] = 0x0041; /* 'A' after "boot" instead of 0x0000 */
|
|
|
|
ck_assert_int_eq(gpt_part_name_eq(name, "boot"), 0);
|
|
}
|
|
END_TEST
|
|
|
|
/* ============================================================
|
|
* Tests using real sfdisk-generated GPT image
|
|
* ============================================================ */
|
|
|
|
static void build_sfdisk_gpt_disk(void)
|
|
{
|
|
memset(fake_disk, 0, FAKE_DISK_SIZE);
|
|
memcpy(fake_disk, gpt_sfdisk_data, gpt_sfdisk_data_len);
|
|
}
|
|
|
|
START_TEST(test_sfdisk_gpt_labels)
|
|
{
|
|
build_sfdisk_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "boot"), 0);
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "rootfs"), 1);
|
|
ck_assert_int_eq(disk_find_partition_by_label(0, "nonexistent"), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_sfdisk_gpt_last_lba_access)
|
|
{
|
|
uint8_t buf[GPT_SECTOR_SIZE];
|
|
struct disk_partition *p;
|
|
uint64_t part_size;
|
|
int ret;
|
|
int i;
|
|
|
|
build_sfdisk_gpt_disk();
|
|
ck_assert_int_eq(disk_open(0), 2);
|
|
|
|
for (i = 0; i < 2; i++) {
|
|
uint8_t pattern = (i == 0) ? 0xAA : 0xBB;
|
|
p = &Drives[0].part[i];
|
|
part_size = p->end - p->start + 1;
|
|
|
|
/* Fill partition data area in fake_disk */
|
|
memset(fake_disk + p->start, pattern, part_size);
|
|
|
|
/* Read the last sector of the partition */
|
|
ret = disk_part_read(0, i, part_size - GPT_SECTOR_SIZE,
|
|
GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_eq(ret, GPT_SECTOR_SIZE);
|
|
ck_assert_uint_eq(buf[0], pattern);
|
|
ck_assert_uint_eq(buf[GPT_SECTOR_SIZE - 1], pattern);
|
|
|
|
/* Read 1 byte at the very last byte offset */
|
|
ret = disk_part_read(0, i, part_size - 1, 1, buf);
|
|
ck_assert_int_eq(ret, 1);
|
|
ck_assert_uint_eq(buf[0], pattern);
|
|
|
|
/* Read that would span past end — gets clamped to 1 byte */
|
|
ret = disk_part_read(0, i, part_size - 1, GPT_SECTOR_SIZE, buf);
|
|
ck_assert_int_eq(ret, 1);
|
|
}
|
|
}
|
|
END_TEST
|
|
|
|
/* Lock the on-disk field offsets. Everything below is written as explicit
|
|
* little-endian bytes and then read back through the parser, so a change to
|
|
* an offset, a width, or the byte order fails here rather than silently
|
|
* mis-parsing a real disk. This is what guards the byte-wise conversion in
|
|
* src/gpt.c against a future refactor. */
|
|
START_TEST(test_gpt_header_field_offsets)
|
|
{
|
|
uint8_t sector[GPT_SECTOR_SIZE];
|
|
struct guid_ptable hdr;
|
|
|
|
memset(sector, 0, sizeof(sector));
|
|
d_put64(sector + D_HDR_SIGNATURE, GPT_SIGNATURE);
|
|
d_put32(sector + D_HDR_REVISION, 0x00010000);
|
|
d_put32(sector + D_HDR_SIZE, 92);
|
|
d_put64(sector + D_HDR_START_ARRAY, 0x1122334455667788ULL);
|
|
d_put32(sector + D_HDR_N_PART, 0x11223344);
|
|
d_put32(sector + D_HDR_ARRAY_SZ, 128);
|
|
finalize_gpt_header_crc(sector);
|
|
|
|
ck_assert_int_eq(gpt_parse_header(sector, &hdr), 0);
|
|
/* Values come back in HOST order, whatever the host byte order is. */
|
|
ck_assert_uint_eq(hdr.hdr_size, 92);
|
|
ck_assert(hdr.signature == GPT_SIGNATURE);
|
|
ck_assert_uint_eq(hdr.revision, 0x00010000U);
|
|
ck_assert(hdr.start_array == 0x1122334455667788ULL);
|
|
ck_assert_uint_eq(hdr.n_part, 0x11223344U);
|
|
ck_assert_uint_eq(hdr.array_sz, 128U);
|
|
}
|
|
END_TEST
|
|
|
|
/* A single flipped byte in the header must fail the CRC. */
|
|
START_TEST(test_gpt_header_crc_rejects_tamper)
|
|
{
|
|
uint8_t sector[GPT_SECTOR_SIZE];
|
|
struct guid_ptable hdr;
|
|
|
|
memset(sector, 0, sizeof(sector));
|
|
d_put64(sector + D_HDR_SIGNATURE, GPT_SIGNATURE);
|
|
d_put32(sector + D_HDR_SIZE, 92);
|
|
d_put64(sector + D_HDR_START_ARRAY, 2);
|
|
d_put32(sector + D_HDR_N_PART, 2);
|
|
d_put32(sector + D_HDR_ARRAY_SZ, 128);
|
|
finalize_gpt_header_crc(sector);
|
|
ck_assert_int_eq(gpt_parse_header(sector, &hdr), 0);
|
|
|
|
/* Flip one bit in a field the CRC covers. */
|
|
sector[D_HDR_N_PART] ^= 0x01;
|
|
ck_assert_int_eq(gpt_parse_header(sector, &hdr), -1);
|
|
}
|
|
END_TEST
|
|
|
|
START_TEST(test_gpt_partition_field_offsets)
|
|
{
|
|
uint8_t entry[128];
|
|
struct gpt_part_info info;
|
|
|
|
memset(entry, 0, sizeof(entry));
|
|
d_put64(entry + D_PE_TYPE, 0x0001020304050607ULL);
|
|
d_put64(entry + D_PE_TYPE + 8, 0x08090A0B0C0D0E0FULL);
|
|
d_put64(entry + D_PE_FIRST, 0x0000000000000022ULL);
|
|
d_put64(entry + D_PE_LAST, 0x0000000000000041ULL);
|
|
write_utf16(entry + D_PE_NAME, "rootfs", GPT_PART_NAME_SIZE);
|
|
|
|
ck_assert_int_eq(gpt_parse_partition(entry, sizeof(entry), &info), 0);
|
|
ck_assert(info.start == 0x22ULL * GPT_SECTOR_SIZE);
|
|
ck_assert(info.end == ((0x41ULL + 1U) * GPT_SECTOR_SIZE) - 1U);
|
|
/* The name is UTF-16LE on disk and host-order after parsing. */
|
|
ck_assert_int_eq(gpt_part_name_eq(info.name, "rootfs"), 1);
|
|
ck_assert_int_eq(gpt_part_name_eq(info.name, "boot"), 0);
|
|
}
|
|
END_TEST
|
|
|
|
|
|
/* ============================================================
|
|
* Suite setup
|
|
* ============================================================ */
|
|
|
|
Suite *wolfboot_suite(void)
|
|
{
|
|
Suite *s = suite_create("wolfboot-disk");
|
|
TCase *tc_gpt = tcase_create("gpt");
|
|
TCase *tc_disk = tcase_create("disk");
|
|
TCase *tc_disk_bugs = tcase_create("disk-bugs");
|
|
|
|
tcase_add_test(tc_gpt, test_gpt_check_mbr_protective);
|
|
tcase_add_test(tc_gpt, test_gpt_parse_header);
|
|
tcase_add_test(tc_gpt, test_gpt_parse_partition);
|
|
tcase_add_test(tc_gpt, test_gpt_part_name_eq);
|
|
tcase_add_test(tc_gpt, test_gpt_part_name_eq_bom_boundary);
|
|
suite_add_tcase(s, tc_gpt);
|
|
|
|
tcase_add_test(tc_disk, test_disk_open_gpt);
|
|
tcase_add_test(tc_disk, test_disk_open_gpt_rejects_part_array_crc_mismatch);
|
|
tcase_add_test(tc_disk, test_disk_open_mbr);
|
|
tcase_add_test(tc_disk, test_disk_part_read);
|
|
tcase_add_test(tc_disk, test_disk_find_partition_by_label);
|
|
suite_add_tcase(s, tc_disk);
|
|
|
|
tcase_add_test(tc_disk_bugs, test_disk_part_rw_offset_past_end);
|
|
tcase_add_test(tc_disk_bugs, test_disk_part_rw_size_past_end);
|
|
tcase_add_test(tc_disk_bugs, test_disk_open_mbr_max_partitions);
|
|
tcase_add_test(tc_disk_bugs, test_gpt_partition_end_inclusive);
|
|
tcase_add_test(tc_disk_bugs, test_disk_open_failure_clears_is_open);
|
|
tcase_add_test(tc_disk_bugs, test_gpt_parse_partition_last_zero);
|
|
tcase_add_test(tc_disk_bugs, test_gpt_parse_partition_last_overflow);
|
|
suite_add_tcase(s, tc_disk_bugs);
|
|
|
|
TCase *tc_cov = tcase_create("disk-coverage");
|
|
tcase_add_test(tc_cov, test_disk_open_invalid_drive);
|
|
tcase_add_test(tc_cov, test_disk_open_mbr_bad_bootsig);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_excess_partitions);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_large_array_sz);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_rejects_huge_part_array);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_lba_no_overflow);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_empty_entry_mid_table);
|
|
tcase_add_test(tc_cov, test_disk_open_mbr_zero_lba_entry);
|
|
tcase_add_test(tc_cov, test_open_part_invalid_drive);
|
|
tcase_add_test(tc_cov, test_open_part_drive_not_open);
|
|
tcase_add_test(tc_cov, test_open_part_part_beyond_nparts);
|
|
tcase_add_test(tc_cov, test_disk_part_write_invalid_partition);
|
|
tcase_add_test(tc_cov, test_disk_part_rw_sz_clamped_to_max);
|
|
tcase_add_test(tc_cov, test_disk_open_mbr_read_failure);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_header_read_failure);
|
|
tcase_add_test(tc_cov, test_disk_open_gpt_entry_read_failure);
|
|
tcase_add_test(tc_cov, test_gpt_check_mbr_bad_bootsig);
|
|
tcase_add_test(tc_cov, test_gpt_parse_partition_first_gt_last);
|
|
tcase_add_test(tc_cov, test_gpt_parse_partition_first_eq_last);
|
|
tcase_add_test(tc_cov, test_gpt_part_name_eq_label_too_long);
|
|
tcase_add_test(tc_cov, test_gpt_header_field_offsets);
|
|
tcase_add_test(tc_cov, test_gpt_header_crc_rejects_tamper);
|
|
tcase_add_test(tc_cov, test_gpt_partition_field_offsets);
|
|
tcase_add_test(tc_cov, test_gpt_part_name_eq_not_null_terminated);
|
|
suite_add_tcase(s, tc_cov);
|
|
|
|
TCase *tc_sfdisk = tcase_create("sfdisk-gpt");
|
|
tcase_add_test(tc_sfdisk, test_sfdisk_gpt_labels);
|
|
tcase_add_test(tc_sfdisk, test_sfdisk_gpt_last_lba_access);
|
|
suite_add_tcase(s, tc_sfdisk);
|
|
|
|
return s;
|
|
}
|
|
|
|
int main(void)
|
|
{
|
|
int fails;
|
|
Suite *s = wolfboot_suite();
|
|
SRunner *sr = srunner_create(s);
|
|
srunner_run_all(sr, CK_NORMAL);
|
|
fails = srunner_ntests_failed(sr);
|
|
srunner_free(sr);
|
|
return fails;
|
|
}
|