wolfBoot/tools/unit-tests/unit-multiboot.c

938 lines
29 KiB
C

/* unit-multiboot.c
*
* Unit test for multiboot2 functions
*
*
* 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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <check.h>
#ifndef WOLFBOOT_MULTIBOOT2
#define WOLFBOOT_MULTIBOOT2
#endif
#ifndef WOLFBOOT_FSP
#define WOLFBOOT_FSP 1
#endif
#ifndef DEBUG_MB2
#define DEBUG_MB2 1
#endif
#include <multiboot.c>
/* ---- Mock hob_iterate_memory_map ---- */
struct mock_mem_region {
uint64_t start;
uint64_t length;
uint32_t type;
};
static struct mock_mem_region *mock_regions = NULL;
static int mock_region_count = 0;
int hob_iterate_memory_map(struct efi_hob *hobList, hob_mem_map_cb cb,
void *ctx)
{
int i, r;
(void)hobList;
for (i = 0; i < mock_region_count; i++) {
r = cb(mock_regions[i].start, mock_regions[i].length,
mock_regions[i].type, ctx);
if (r != 0)
return r;
}
return 0;
}
static struct stage2_parameter make_stage2(void)
{
struct stage2_parameter p;
memset(&p, 0, sizeof(p));
return p;
}
#define MB2_MAGIC 0xe85250d6
/* ---- Group 1: mb2_find_header ---- */
START_TEST(test_find_header_at_offset_0)
{
uint32_t image[16];
memset(image, 0, sizeof(image));
image[0] = MB2_MAGIC;
ck_assert_ptr_eq(mb2_find_header((uint8_t *)image, sizeof(image)),
(uint8_t *)image);
}
END_TEST
START_TEST(test_find_header_at_8k)
{
/* magic at byte 8192 in 16 KB image */
int sz = 16384;
uint8_t *image = calloc(1, sz);
ck_assert_ptr_nonnull(image);
*(uint32_t *)(image + 8192) = MB2_MAGIC;
ck_assert_ptr_eq(mb2_find_header(image, sz), image + 8192);
free(image);
}
END_TEST
START_TEST(test_find_header_at_9k)
{
int sz = 10240;
uint8_t *image = calloc(1, sz);
ck_assert_ptr_nonnull(image);
*(uint32_t *)(image + 9000) = MB2_MAGIC;
ck_assert_ptr_eq(mb2_find_header(image, sz), image + 9000);
free(image);
}
END_TEST
START_TEST(test_find_header_at_max_offset)
{
/* Magic at byte 32760 (last valid scan position)*/
int sz = 32764; /* MB2_HEADER_MAX_OFF */
uint8_t *image = calloc(1, sz);
ck_assert_ptr_nonnull(image);
*(uint32_t *)(image + 32760) = MB2_MAGIC;
ck_assert_ptr_eq(mb2_find_header(image, sz), image + 32760);
free(image);
}
END_TEST
START_TEST(test_find_header_beyond_32k)
{
/* Magic at byte 32764 — just past the last scanned position. */
int sz = 32768;
uint8_t *image = calloc(1, sz);
ck_assert_ptr_nonnull(image);
*(uint32_t *)(image + 32764) = MB2_MAGIC;
ck_assert_ptr_null(mb2_find_header(image, sz));
free(image);
}
END_TEST
START_TEST(test_find_header_no_magic)
{
uint8_t image[256];
memset(image, 0, sizeof(image));
ck_assert_ptr_null(mb2_find_header(image, sizeof(image)));
}
END_TEST
START_TEST(test_find_header_size_zero)
{
uint8_t image[4];
ck_assert_ptr_null(mb2_find_header(image, 0));
}
END_TEST
START_TEST(test_find_header_negative_size)
{
uint8_t image[4];
ck_assert_ptr_null(mb2_find_header(image, -1));
}
END_TEST
START_TEST(test_find_header_small_size)
{
/* size = 3: too small for even one uint32_t (3/4 == 0). */
uint8_t image[4];
image[0] = 0xd6; image[1] = 0x50; image[2] = 0x52;
ck_assert_ptr_null(mb2_find_header(image, 3));
}
END_TEST
/* ---- Group 2: mb2_find_tag_by_type ---- */
START_TEST(test_find_tag_single)
{
uint8_t buf[16] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0, sizeof(buf));
/* Tag: type=5, size=8 */
t = (struct mb2_tag *)buf;
t->type = 5; t->flags = 0; t->size = 8;
/* Terminator */
t = (struct mb2_tag *)(buf + 8);
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_ptr_eq(mb2_find_tag_by_type(buf, sizeof(buf), 5), buf);
}
END_TEST
START_TEST(test_find_tag_multiple)
{
uint8_t buf[24] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0, sizeof(buf));
/* Tag 1: type=3 */
t = (struct mb2_tag *)buf;
t->type = 3; t->flags = 0; t->size = 8;
/* Tag 2: type=5 */
t = (struct mb2_tag *)(buf + 8);
t->type = 5; t->flags = 0; t->size = 8;
/* Terminator */
t = (struct mb2_tag *)(buf + 16);
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_ptr_eq(mb2_find_tag_by_type(buf, sizeof(buf), 5), buf + 8);
}
END_TEST
START_TEST(test_find_tag_not_found)
{
uint8_t buf[16] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0, sizeof(buf));
t = (struct mb2_tag *)buf;
t->type = 3; t->flags = 0; t->size = 8;
t = (struct mb2_tag *)(buf + 8);
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_ptr_null(mb2_find_tag_by_type(buf, sizeof(buf), 5));
}
END_TEST
START_TEST(test_find_tag_empty_list)
{
uint8_t buf[8] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0, sizeof(buf));
t = (struct mb2_tag *)buf;
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_ptr_null(mb2_find_tag_by_type(buf, sizeof(buf), 5));
}
END_TEST
START_TEST(test_find_tag_zero_size)
{
uint8_t buf[24] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0, sizeof(buf));
t = (struct mb2_tag *)buf;
t->type = 5; t->flags = 0; t->size = 0;
t = (struct mb2_tag *)(buf + 8);
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_ptr_null(mb2_find_tag_by_type(buf, sizeof(buf), 99));
}
END_TEST
/* Bug 3: missing terminator must not read past buffer */
START_TEST(test_find_tag_no_terminator)
{
uint8_t buf[16] __attribute__((aligned(8)));
struct mb2_tag *t;
memset(buf, 0xFF, sizeof(buf));
/* Tag 1: type=3 (not the one we search for) */
t = (struct mb2_tag *)buf;
t->type = 3; t->flags = 0; t->size = 8;
/* Tag 2: type=5 (the one we search for, but beyond the declared size) */
t = (struct mb2_tag *)(buf + 8);
t->type = 5; t->flags = 0; t->size = 8;
/* Pass size=8: only covers tag 1. Tag 2 must not be found. */
ck_assert_ptr_null(mb2_find_tag_by_type(buf, 8, 5));
}
END_TEST
/* ---- Group 3: mb2_align_address_up ---- */
START_TEST(test_align_already_aligned)
{
uint8_t *r = mb2_align_address_up((uint8_t *)0x1000, 8);
ck_assert_ptr_eq(r, (uint8_t *)0x1000);
}
END_TEST
START_TEST(test_align_needs_rounding)
{
uint8_t *r = mb2_align_address_up((uint8_t *)0x1001, 8);
ck_assert_ptr_eq(r, (uint8_t *)0x1008);
}
END_TEST
START_TEST(test_align_one_below)
{
uint8_t *r = mb2_align_address_up((uint8_t *)0x100F, 8);
ck_assert_ptr_eq(r, (uint8_t *)0x1010);
}
END_TEST
/* ---- Group 4: mb2_build_boot_info_header ---- */
/* Helper: build a mb2 header + info_req tag requesting one tag type,
* followed by a terminator. buf must be >= 48 bytes and 8-byte aligned. */
static void build_header_with_info_req(uint8_t *buf, uint32_t req_type)
{
struct mb2_header *h = (struct mb2_header *)buf;
struct mb2_tag_info_req *info;
struct mb2_tag *term;
memset(buf, 0, 48);
h->magic = MB2_MAGIC;
h->architecture = 0;
h->header_length = 40; /* header(16) + info_req(16 aligned) + term(8) */
h->checksum = 0;
/* Info request tag at offset 16 */
info = (struct mb2_tag_info_req *)(buf + 16);
info->type = 1; /* MB2_TAG_TYPE_INFO_REQ */
info->flags = 0;
info->size = 12; /* 8 + one uint32_t */
info->mbi_tag_types[0] = req_type;
/* Terminator at offset 32 (12 bytes rounded up to 8-byte alignment) */
term = (struct mb2_tag *)(buf + 32);
term->type = 0;
term->flags = 0;
term->size = 8;
}
START_TEST(test_build_info_max_size_too_small)
{
uint8_t boot_info[8];
uint8_t header[48] __attribute__((aligned(8)));
struct mb2_header *h = (struct mb2_header *)header;
memset(header, 0, sizeof(header));
h->header_length = sizeof(struct mb2_header) + 8;
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, NULL, 4), -1);
}
END_TEST
START_TEST(test_build_info_no_info_req_tag)
{
uint8_t header[32] __attribute__((aligned(8)));
uint8_t boot_info[64];
struct mb2_header *h = (struct mb2_header *)header;
struct mb2_tag *t;
memset(header, 0, sizeof(header));
h->magic = MB2_MAGIC;
h->header_length = sizeof(struct mb2_header) + 8; /* header + terminator */
/* Only a terminator tag — no info request */
t = (struct mb2_tag *)(header + sizeof(struct mb2_header));
t->type = 0; t->flags = 0; t->size = 8;
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, NULL,
sizeof(boot_info)), -1);
}
END_TEST
START_TEST(test_build_info_unsupported_tag)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[256];
build_header_with_info_req(header, 99); /* unknown type */
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, NULL,
sizeof(boot_info)), -1);
}
END_TEST
/* Bug 2: info_req tag with size < sizeof(struct mb2_tag) is rejected by the
* size guard in mb2_find_tag_by_type, so the tag is never found. */
START_TEST(test_build_info_malformed_size)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[256];
struct mb2_header *h = (struct mb2_header *)header;
struct mb2_tag_info_req *info;
struct mb2_tag *term;
memset(header, 0, sizeof(header));
h->magic = MB2_MAGIC;
h->architecture = 0;
h->header_length = 32;
h->checksum = 0;
/* Info request tag with impossibly small size (4 < 8) */
info = (struct mb2_tag_info_req *)(header + 16);
info->type = 1;
info->flags = 0;
info->size = 4; /* smaller than struct mb2_tag */
term = (struct mb2_tag *)(header + 24);
term->type = 0; term->flags = 0; term->size = 8;
/* Malformed tag is skipped, info_req not found → returns -1 */
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, NULL,
sizeof(boot_info)), -1);
}
END_TEST
/* check that header_length < sizeof(struct mb2_header) do not causes uint32_t
* underflow in the subtraction header_length - sizeof(struct mb2_header),
* wrapping to ~4GB and defeating the bounds check in mb2_find_tag_by_type.
*
* we place a fake info_req tag right after the header so
* that the overflowed bounds check lets mb2_find_tag_by_type find it.
* Without the fix the function processes the tag and returns 0 (success).
* With the fix it rejects header_length < 16 and returns -1. */
START_TEST(test_build_info_header_length_underflow)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[256];
struct stage2_parameter p = make_stage2();
struct mb2_header *h = (struct mb2_header *)header;
struct mb2_tag_info_req *info;
struct mb2_tag *term;
memset(header, 0, sizeof(header));
h->magic = MB2_MAGIC;
h->header_length = 8; /* less than sizeof(struct mb2_header) = 16 */
/* Place a reachable info_req tag after the header. With the underflow
* bug tags_len wraps to ~4GB so the tag is "in bounds". */
info = (struct mb2_tag_info_req *)(header + sizeof(struct mb2_header));
info->type = 1; /* MB2_TAG_TYPE_INFO_REQ */
info->flags = 0;
info->size = 12;
info->mbi_tag_types[0] = 4; /* MB2_REQ_TAG_BASIC_MEM_INFO */
term = (struct mb2_tag *)(header + 32);
term->type = 0; term->flags = 0; term->size = 8;
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, &p,
sizeof(boot_info)), -1);
}
END_TEST
/* check that an attacker-controlled header_length larger than the 32 KiB
* Multiboot2 header window cannot inflate the tag walker's bounds. With
* header_length = 0xFFFFFFFF the subtraction header_length - sizeof(struct
* mb2_header) yields ~4GB, so end = tags + ~4GB in mb2_find_tag_by_type and the
* size guard becomes ineffective, letting the walker read far past the image.
*
* We place a fake info_req tag right after the header. Without the upper-bound
* fix mb2_find_tag_by_type finds it and the function returns 0 (success). With
* the fix the oversized header_length is rejected and it returns -1. */
START_TEST(test_build_info_header_length_overflow)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[256];
struct stage2_parameter p = make_stage2();
struct mb2_header *h = (struct mb2_header *)header;
struct mb2_tag_info_req *info;
struct mb2_tag *term;
memset(header, 0, sizeof(header));
h->magic = MB2_MAGIC;
h->header_length = 0xFFFFFFFFu; /* far larger than the 32 KiB window */
info = (struct mb2_tag_info_req *)(header + sizeof(struct mb2_header));
info->type = 1; /* MB2_TAG_TYPE_INFO_REQ */
info->flags = 0;
info->size = 12;
info->mbi_tag_types[0] = 4; /* MB2_REQ_TAG_BASIC_MEM_INFO */
term = (struct mb2_tag *)(header + 32);
term->type = 0; term->flags = 0; term->size = 8;
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, &p,
sizeof(boot_info)), -1);
}
END_TEST
START_TEST(test_dump_header_length_underflow)
{
uint8_t header[48] __attribute__((aligned(8)));
struct mb2_header *h = (struct mb2_header *)header;
struct mb2_tag *tag;
FILE *capture;
int stderr_fd;
char output[512];
size_t output_len;
memset(header, 0, sizeof(header));
h->magic = MB2_MAGIC;
h->header_length = 8; /* less than sizeof(struct mb2_header) */
tag = (struct mb2_tag *)(header + sizeof(struct mb2_header));
tag->type = 1;
tag->flags = 0;
tag->size = sizeof(*tag);
capture = tmpfile();
ck_assert_ptr_nonnull(capture);
fflush(stderr);
stderr_fd = dup(fileno(stderr));
ck_assert_int_ge(stderr_fd, 0);
ck_assert_int_ge(dup2(fileno(capture), fileno(stderr)), 0);
mb2_dump_header(header);
fflush(stderr);
ck_assert_int_ge(dup2(stderr_fd, fileno(stderr)), 0);
close(stderr_fd);
rewind(capture);
output_len = fread(output, 1, sizeof(output) - 1, capture);
output[output_len] = '\0';
fclose(capture);
ck_assert_ptr_nonnull(strstr(output, "Invalid header length"));
ck_assert_ptr_null(strstr(output, "Tag Type: 1\r\n"));
}
END_TEST
/* ---- Group 5: mb2_add_basic_mem_info ---- */
START_TEST(test_basic_mem_info_normal)
{
uint8_t buf[64] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(buf);
struct mb2_basic_memory_info *info;
struct mock_mem_region regions[] = {
{0, 640 * 1024, EFI_RESOURCE_SYSTEM_MEMORY},
{1024 * 1024, 127ULL * 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 2;
ck_assert_int_eq(mb2_add_basic_mem_info(&idx, &p, &max_size), 0);
info = (struct mb2_basic_memory_info *)buf;
ck_assert_uint_eq(info->type, 4);
ck_assert_uint_eq(info->size, sizeof(*info));
ck_assert_uint_eq(info->mem_lower, 640);
ck_assert_uint_eq(info->mem_upper, 127 * 1024);
ck_assert_ptr_eq(idx, buf + sizeof(*info));
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_basic_mem_info_no_lower)
{
uint8_t buf[64] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(buf);
struct mb2_basic_memory_info *info;
struct mock_mem_region regions[] = {
{1024 * 1024, 127ULL * 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 1;
ck_assert_int_eq(mb2_add_basic_mem_info(&idx, &p, &max_size), 0);
info = (struct mb2_basic_memory_info *)buf;
ck_assert_uint_eq(info->mem_lower, 0);
ck_assert_uint_eq(info->mem_upper, 127 * 1024);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_basic_mem_info_no_upper)
{
uint8_t buf[64] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(buf);
struct mb2_basic_memory_info *info;
struct mock_mem_region regions[] = {
{0, 640 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 1;
ck_assert_int_eq(mb2_add_basic_mem_info(&idx, &p, &max_size), 0);
info = (struct mb2_basic_memory_info *)buf;
ck_assert_uint_eq(info->mem_lower, 640);
ck_assert_uint_eq(info->mem_upper, 0);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_basic_mem_info_too_small)
{
uint8_t buf[8];
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(struct mb2_basic_memory_info) - 1;
mock_regions = NULL;
mock_region_count = 0;
ck_assert_int_eq(mb2_add_basic_mem_info(&idx, &p, &max_size), -1);
}
END_TEST
/* ---- Group 6: mb2_add_mem_map ---- */
START_TEST(test_mem_map_single_ram)
{
uint8_t buf[128] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(buf);
struct mb2_mem_map_header *hdr;
struct mb2_mem_map_entry *entry;
struct mock_mem_region regions[] = {
{0x100000, 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 1;
ck_assert_int_eq(mb2_add_mem_map(&idx, &p, &max_size), 0);
hdr = (struct mb2_mem_map_header *)buf;
ck_assert_uint_eq(hdr->type, 6);
ck_assert_uint_eq(hdr->entry_size, sizeof(struct mb2_mem_map_entry));
ck_assert_uint_eq(hdr->entry_version, 0);
ck_assert_uint_eq(hdr->size,
sizeof(struct mb2_mem_map_header) +
sizeof(struct mb2_mem_map_entry));
entry = (struct mb2_mem_map_entry *)(buf + sizeof(*hdr));
ck_assert(entry->base_addr == 0x100000);
ck_assert(entry->length == 1024 * 1024);
ck_assert_uint_eq(entry->type, 1); /* MB2_MEM_INFO_MEM_RAM */
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_mem_map_mixed_types)
{
uint8_t buf[128] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(buf);
struct mb2_mem_map_header *hdr;
struct mb2_mem_map_entry *e;
struct mock_mem_region regions[] = {
{0, 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY},
{0xF0000000ULL, 16ULL * 1024 * 1024, EFI_RESOURCE_MEMORY_RESERVED}
};
mock_regions = regions;
mock_region_count = 2;
ck_assert_int_eq(mb2_add_mem_map(&idx, &p, &max_size), 0);
hdr = (struct mb2_mem_map_header *)buf;
ck_assert_uint_eq(hdr->size,
sizeof(struct mb2_mem_map_header) +
2 * sizeof(struct mb2_mem_map_entry));
e = (struct mb2_mem_map_entry *)(buf + sizeof(*hdr));
ck_assert_uint_eq(e->type, 1); /* RAM */
e++;
ck_assert_uint_eq(e->type, 2); /* RESERVED */
ck_assert(e->base_addr == 0xF0000000ULL);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_mem_map_too_small_header)
{
uint8_t buf[8];
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(struct mb2_mem_map_header) - 1;
mock_regions = NULL;
mock_region_count = 0;
ck_assert_int_eq(mb2_add_mem_map(&idx, &p, &max_size), -1);
}
END_TEST
START_TEST(test_mem_map_too_small_entry)
{
uint8_t buf[64] __attribute__((aligned(8)));
uint8_t *idx = buf;
struct stage2_parameter p = make_stage2();
unsigned max_size = sizeof(struct mb2_mem_map_header); /* fits header only */
struct mock_mem_region regions[] = {
{0, 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 1;
ck_assert_int_eq(mb2_add_mem_map(&idx, &p, &max_size), -1);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
/* ---- Group 7: mb2_build_boot_info_header with HOB ---- */
START_TEST(test_build_info_basic_mem)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[256] __attribute__((aligned(8)));
struct stage2_parameter p = make_stage2();
struct mb2_boot_info_header *bih;
struct mb2_basic_memory_info *meminfo;
struct mb2_tag *end_tag;
struct mock_mem_region regions[] = {
{0, 640 * 1024, EFI_RESOURCE_SYSTEM_MEMORY},
{1024 * 1024, 127ULL * 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 2;
build_header_with_info_req(header, 4);
memset(boot_info, 0, sizeof(boot_info));
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, &p,
sizeof(boot_info)), 0);
bih = (struct mb2_boot_info_header *)boot_info;
/* header + basic mem info + end tag (F-12060) */
ck_assert_uint_eq(bih->total_size,
sizeof(*bih) + sizeof(struct mb2_basic_memory_info) +
sizeof(struct mb2_tag));
meminfo = (struct mb2_basic_memory_info *)(boot_info + sizeof(*bih));
ck_assert_uint_eq(meminfo->type, 4);
ck_assert_uint_eq(meminfo->mem_lower, 640);
ck_assert_uint_eq(meminfo->mem_upper, 127 * 1024);
/* F-12060: the tag list must end with a proper end tag */
end_tag = (struct mb2_tag *)(boot_info + sizeof(*bih) +
sizeof(struct mb2_basic_memory_info));
ck_assert_uint_eq(end_tag->type, 0);
ck_assert_uint_eq(end_tag->flags, 0);
ck_assert_uint_eq(end_tag->size, sizeof(*end_tag));
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
START_TEST(test_build_info_mem_map)
{
uint8_t header[48] __attribute__((aligned(8)));
uint8_t boot_info[512] __attribute__((aligned(8)));
struct stage2_parameter p = make_stage2();
struct mb2_boot_info_header *bih;
struct mb2_mem_map_header *map_hdr;
struct mb2_mem_map_entry *entry;
struct mock_mem_region regions[] = {
{0x100000, 127ULL * 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 1;
build_header_with_info_req(header, 6);
memset(boot_info, 0, sizeof(boot_info));
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, &p,
sizeof(boot_info)), 0);
bih = (struct mb2_boot_info_header *)boot_info;
/* header + mem map (1 entry) + end tag (F-12060) */
ck_assert_uint_eq(bih->total_size,
sizeof(*bih) + sizeof(struct mb2_mem_map_header) +
sizeof(struct mb2_mem_map_entry) +
sizeof(struct mb2_tag));
map_hdr = (struct mb2_mem_map_header *)(boot_info + sizeof(*bih));
ck_assert_uint_eq(map_hdr->type, 6);
ck_assert_uint_eq(map_hdr->entry_size, sizeof(struct mb2_mem_map_entry));
ck_assert_uint_eq(map_hdr->size,
sizeof(struct mb2_mem_map_header) +
sizeof(struct mb2_mem_map_entry));
entry = (struct mb2_mem_map_entry *)((uint8_t *)map_hdr + sizeof(*map_hdr));
ck_assert(entry->base_addr == 0x100000);
ck_assert(entry->length == 127ULL * 1024 * 1024);
ck_assert_uint_eq(entry->type, 1);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
/* F-12060: consumer-style tag walk over the generated output section.
* A strict Multiboot2 consumer walks 8-byte-aligned tags until the end
* tag (type 0, size 8); the walk must find both requested tags, stop at
* a well-formed end tag, and the end tag must be the last structure
* inside total_size. */
START_TEST(test_build_info_end_tag_walk)
{
uint8_t header[64] __attribute__((aligned(8)));
uint8_t boot_info[256] __attribute__((aligned(8)));
struct stage2_parameter p = make_stage2();
struct mb2_header *h;
struct mb2_tag_info_req *info;
struct mb2_tag *term;
struct mb2_boot_info_header *bih;
struct mb2_tag *tag;
uint8_t *end;
int seen_basic = 0;
int seen_mmap = 0;
int seen_end = 0;
struct mock_mem_region regions[] = {
{0, 640 * 1024, EFI_RESOURCE_SYSTEM_MEMORY},
{1024 * 1024, 127ULL * 1024 * 1024, EFI_RESOURCE_SYSTEM_MEMORY}
};
mock_regions = regions;
mock_region_count = 2;
/* header requesting both basic mem info (4) and mem map (6) */
memset(header, 0, sizeof(header));
h = (struct mb2_header *)header;
h->magic = MB2_MAGIC;
h->architecture = 0;
h->header_length = 40; /* header(16) + info_req(16) + term(8) */
h->checksum = 0;
info = (struct mb2_tag_info_req *)(header + 16);
info->type = 1;
info->flags = 0;
info->size = 16; /* 8 + two uint32_t */
info->mbi_tag_types[0] = 4; /* MB2_REQ_TAG_BASIC_MEM_INFO */
info->mbi_tag_types[1] = 6; /* MB2_REQ_TAG_MEM_MAP */
term = (struct mb2_tag *)(header + 32);
term->type = 0;
term->flags = 0;
term->size = 8;
memset(boot_info, 0, sizeof(boot_info));
ck_assert_int_eq(
mb2_build_boot_info_header(boot_info, header, &p,
sizeof(boot_info)), 0);
bih = (struct mb2_boot_info_header *)boot_info;
end = boot_info + bih->total_size;
tag = (struct mb2_tag *)(boot_info + sizeof(*bih));
while ((uint8_t *)tag + sizeof(*tag) <= end) {
if (tag->type == 0) {
seen_end = 1;
ck_assert_uint_eq(tag->size, sizeof(*tag));
break;
}
if (tag->size < sizeof(*tag) ||
(uint8_t *)tag + tag->size > end)
fail("tag overruns total_size");
if (tag->type == 4)
seen_basic = 1;
if (tag->type == 6)
seen_mmap = 1;
tag = (struct mb2_tag *)mb2_align_address_up(
(uint8_t *)tag + tag->size, 8);
}
ck_assert_int_eq(seen_basic, 1);
ck_assert_int_eq(seen_mmap, 1);
ck_assert_int_eq(seen_end, 1);
/* the end tag must be the last structure in total_size */
ck_assert_ptr_eq((uint8_t *)tag + tag->size, end);
mock_regions = NULL;
mock_region_count = 0;
}
END_TEST
/* ---- Suite ---- */
Suite *wolfboot_suite(void)
{
Suite *s = suite_create("wolfboot-multiboot2");
TCase *tc_find = tcase_create("mb2_find_header");
tcase_add_test(tc_find, test_find_header_at_offset_0);
tcase_add_test(tc_find, test_find_header_at_8k);
tcase_add_test(tc_find, test_find_header_at_9k);
tcase_add_test(tc_find, test_find_header_at_max_offset);
tcase_add_test(tc_find, test_find_header_beyond_32k);
tcase_add_test(tc_find, test_find_header_no_magic);
tcase_add_test(tc_find, test_find_header_size_zero);
tcase_add_test(tc_find, test_find_header_negative_size);
tcase_add_test(tc_find, test_find_header_small_size);
suite_add_tcase(s, tc_find);
TCase *tc_tag = tcase_create("mb2_find_tag_by_type");
tcase_add_test(tc_tag, test_find_tag_single);
tcase_add_test(tc_tag, test_find_tag_multiple);
tcase_add_test(tc_tag, test_find_tag_not_found);
tcase_add_test(tc_tag, test_find_tag_empty_list);
tcase_add_test(tc_tag, test_find_tag_zero_size);
tcase_add_test(tc_tag, test_find_tag_no_terminator);
suite_add_tcase(s, tc_tag);
TCase *tc_align = tcase_create("mb2_align_address_up");
tcase_add_test(tc_align, test_align_already_aligned);
tcase_add_test(tc_align, test_align_needs_rounding);
tcase_add_test(tc_align, test_align_one_below);
suite_add_tcase(s, tc_align);
TCase *tc_build = tcase_create("mb2_build_boot_info_header");
tcase_add_test(tc_build, test_build_info_max_size_too_small);
tcase_add_test(tc_build, test_build_info_no_info_req_tag);
tcase_add_test(tc_build, test_build_info_unsupported_tag);
tcase_add_test(tc_build, test_build_info_malformed_size);
tcase_add_test(tc_build, test_build_info_header_length_underflow);
tcase_add_test(tc_build, test_build_info_header_length_overflow);
tcase_add_test(tc_build, test_dump_header_length_underflow);
suite_add_tcase(s, tc_build);
TCase *tc_basic = tcase_create("mb2_add_basic_mem_info");
tcase_add_test(tc_basic, test_basic_mem_info_normal);
tcase_add_test(tc_basic, test_basic_mem_info_no_lower);
tcase_add_test(tc_basic, test_basic_mem_info_no_upper);
tcase_add_test(tc_basic, test_basic_mem_info_too_small);
suite_add_tcase(s, tc_basic);
TCase *tc_mmap = tcase_create("mb2_add_mem_map");
tcase_add_test(tc_mmap, test_mem_map_single_ram);
tcase_add_test(tc_mmap, test_mem_map_mixed_types);
tcase_add_test(tc_mmap, test_mem_map_too_small_header);
tcase_add_test(tc_mmap, test_mem_map_too_small_entry);
suite_add_tcase(s, tc_mmap);
TCase *tc_hob = tcase_create("mb2_build_boot_info_with_hob");
tcase_add_test(tc_hob, test_build_info_basic_mem);
tcase_add_test(tc_hob, test_build_info_mem_map);
tcase_add_test(tc_hob, test_build_info_end_tag_walk);
suite_add_tcase(s, tc_hob);
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;
}