blob: 2e669223ec9f4a6426b371ccea15fc103f76d93b [file]
/* Copyright 2025 The ChromiumOS Authors
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Tests for vboot_kernel.c
*/
#include "2api.h"
#include "2avb.h"
#include "2common.h"
#include "2misc.h"
#include "2nvstorage.h"
#include "2secdata.h"
#include "cgptlib.h"
#include "cgptlib_internal.h"
#include "common/boot_mode.h"
#include "common/tests.h"
#include "gpt.h"
#include "vb2_android_bootimg.h"
#include "vboot_api.h"
#define INIT_KERN_SECDATA 0x20001
#define PARENT_HANDLE 1
#define CHILD_HANDLE 2
#define DISK_SIZE (1 << 20)
#define NUM_OF_ENTRIES 8
#define BYTES_PER_LBA 512
#define PART_SIZE 16
#define VBMETA_LBA 8192
#define BOOT_LBA (VBMETA_LBA + PART_SIZE)
#define VENDOR_BOOT_LBA (BOOT_LBA + PART_SIZE)
#define INIT_BOOT_LBA (VENDOR_BOOT_LBA + PART_SIZE)
#define OTA_RECOVERY_A_PART 4096
#define OTA_RECOVERY_B_PART (DISK_SIZE - 4096)
GptHeader gpt_hdr;
GptEntry entries[NUM_OF_ENTRIES];
struct vendor_boot_img_hdr_v4 vendor_boot_hdr;
struct boot_img_hdr_v4 init_boot_hdr;
enum avb_fail {NO_FAIL, VERIFY_FAIL, ROLLBACK_FAIL};
uint64_t rollback_value;
enum avb_fail avb_verification_fails;
bool init_boot_missing, vendor_boot_missing;
uint64_t sector_to_read;
uint64_t ota_recovery_sector;
static const uint16_t vbmeta_a_name[] = {'v', 'b', 'm', 'e', 't', 'a', '_', 'a', 0};
static const uint16_t boot_a_name[] = {'b', 'o', 'o', 't', '_', 'a', 0};
static const uint16_t vendor_boot_a_name[] = {'v', 'e', 'n', 'd', 'o', 'r', '_', 'b', 'o', 'o', 't', '_', 'a', 0};
static const uint16_t init_boot_a_name[] = {'i', 'n', 'i', 't', '_', 'b', 'o', 'o', 't', '_', 'a', 0};
static const char fake_guid[] = "FakeGuid";
static uint8_t kernel_buffer[80000];
static char bootconfig_cmdline[1000];
/* Mock data */
static struct vb2_gbb_header gbb;
static struct vb2_kernel_params lkp;
static struct vb2_disk_info disk_info;
static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
__attribute__((aligned(VB2_WORKBUF_ALIGN)));
static struct vb2_context *ctx;
static struct vb2_shared_data *sd;
/* Mocked functions */
int AllocAndReadGptData(vb2ex_disk_handle_t disk_handle, GptData *gptdata)
{
/* No data to be written yet */
gptdata->modified = 0;
/* This should get overwritten by GptInit() */
gptdata->ignored = 0;
/* Allocate all buffers */
gptdata->primary_header = (uint8_t *)&gpt_hdr;
gptdata->secondary_header = (uint8_t *)&gpt_hdr;
gptdata->primary_entries = (uint8_t *)&entries;
gptdata->secondary_entries = (uint8_t *)&entries;
return 0;
}
int WriteAndFreeGptData(vb2ex_disk_handle_t disk_handle, GptData *gptdata) { return 0; }
int GptInit(GptData *gpt)
{
gpt->modified = 0;
gpt->current_kernel = CGPT_KERNEL_ENTRY_NOT_FOUND;
gpt->current_priority = 999;
return GPT_SUCCESS;
}
AvbSlotVerifyResult avb_slot_verify(AvbOps *ops, const char *const *requested_partitions,
const char *ab_suffix, AvbSlotVerifyFlags flags,
AvbHashtreeErrorMode hashtree_error_mode,
AvbSlotVerifyData **out_data)
{
AvbSlotVerifyData *verify_data;
uint8_t *out_pointer;
size_t num_bytes, part_size_bytes;
part_size_bytes = PART_SIZE * BYTES_PER_LBA;
ops->get_preloaded_partition(ops, "boot_a", part_size_bytes, &out_pointer, &num_bytes);
if (!init_boot_missing)
ops->get_preloaded_partition(ops, "init_boot_a", part_size_bytes, &out_pointer,
&num_bytes);
if (!vendor_boot_missing)
ops->get_preloaded_partition(ops, "vendor_boot_a", part_size_bytes, &out_pointer,
&num_bytes);
verify_data = malloc(sizeof(*verify_data));
memset(verify_data, 0, sizeof(*verify_data));
verify_data->rollback_indexes[0] = rollback_value;
verify_data->cmdline = (char *)"";
*out_data = verify_data;
switch (avb_verification_fails) {
case VERIFY_FAIL:
return AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
case ROLLBACK_FAIL:
return AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
case NO_FAIL:
default:
return AVB_SLOT_VERIFY_RESULT_OK;
}
}
void avb_slot_verify_data_free(AvbSlotVerifyData *data)
{
free(data);
}
vb2_error_t VbExStreamOpen(vb2ex_disk_handle_t handle, uint64_t lba_start, uint64_t lba_count,
VbExStream_t *stream_ptr)
{
sector_to_read = lba_start;
*stream_ptr = (VbExStream_t)handle;
return VB2_SUCCESS;
}
vb2_error_t VbExStreamSkip(VbExStream_t stream, uint32_t bytes) { return VB2_SUCCESS; }
vb2_error_t VbExStreamRead(VbExStream_t stream, uint32_t bytes, void *buffer)
{
memset(buffer, 0, bytes);
if (sector_to_read == VENDOR_BOOT_LBA)
memcpy(buffer, &vendor_boot_hdr, sizeof(vendor_boot_hdr));
else if (sector_to_read == INIT_BOOT_LBA)
memcpy(buffer, &init_boot_hdr, sizeof(init_boot_hdr));
if ((uintptr_t)stream == PARENT_HANDLE && sector_to_read == ota_recovery_sector) {
const Guid recovery_disk_uuid = GPT_DISK_UUID_RECOVERY;
memcpy(&gpt_hdr.disk_uuid, &recovery_disk_uuid, sizeof(Guid));
memcpy(buffer, &gpt_hdr, sizeof(gpt_hdr));
}
return VB2_SUCCESS;
}
void VbExStreamClose(VbExStream_t stream) {}
vb2_error_t vb2ex_slice_disk(vb2ex_disk_handle_t parent, uint64_t offset, uint64_t size,
struct vb2_disk_info **child_out)
{
TEST_EQ(offset, ota_recovery_sector - 1, "correct ota_recovery offset");
struct vb2_disk_info *slice = malloc(sizeof(*slice));
if (slice == NULL)
return VB2_ERROR_UNKNOWN;
memcpy(slice, &disk_info, sizeof(*slice));
slice->handle = (vb2ex_disk_handle_t)CHILD_HANDLE;
*child_out = slice;
return VB2_SUCCESS;
}
/* Reset mock data (for use before each test) */
static void reset_mocks(void)
{
rollback_value = 0x1;
avb_verification_fails = NO_FAIL;
init_boot_missing = false;
vendor_boot_missing = false;
ota_recovery_sector = OTA_RECOVERY_A_PART;
memset(&gbb, 0, sizeof(gbb));
gbb.major_version = VB2_GBB_MAJOR_VER;
gbb.minor_version = VB2_GBB_MINOR_VER;
gbb.flags = 0;
memset(&lkp, 0, sizeof(lkp));
lkp.kernel_buffer = kernel_buffer;
lkp.kernel_buffer_size = sizeof(kernel_buffer);
lkp.bootconfig_cmdline_buffer = bootconfig_cmdline;
lkp.bootconfig_cmdline_size = sizeof(bootconfig_cmdline);
memset(&disk_info, 0, sizeof(disk_info));
disk_info.bytes_per_lba = BYTES_PER_LBA;
disk_info.streaming_lba_count = 1024;
disk_info.lba_count = DISK_SIZE;
disk_info.handle = (vb2ex_disk_handle_t)PARENT_HANDLE;
memset(&gpt_hdr, 0, sizeof(gpt_hdr));
gpt_hdr.number_of_entries = NUM_OF_ENTRIES;
gpt_hdr.my_lba = GPT_PMBR_SECTORS;
memset(&entries, 0, sizeof(entries));
entries[0].starting_lba = VBMETA_LBA;
entries[0].ending_lba = VBMETA_LBA;
memcpy(&entries[0].unique, fake_guid, sizeof(fake_guid));
memcpy(&entries[0].type, &guid_android_vbmeta, sizeof(guid_android_vbmeta));
memcpy(&entries[0].name, &vbmeta_a_name, sizeof(vbmeta_a_name));
SetEntrySuccessful(&entries[0], 1);
SetEntryPriority(&entries[0], 1);
entries[1].starting_lba = BOOT_LBA;
entries[1].ending_lba = (BOOT_LBA + PART_SIZE - 1);
memcpy(&entries[1].name, &boot_a_name, sizeof(boot_a_name));
entries[2].starting_lba = VENDOR_BOOT_LBA;
entries[2].ending_lba = (VENDOR_BOOT_LBA + PART_SIZE - 1);
memcpy(&entries[2].name, &vendor_boot_a_name, sizeof(vendor_boot_a_name));
entries[3].starting_lba = INIT_BOOT_LBA;
entries[3].ending_lba = (INIT_BOOT_LBA + PART_SIZE - 1);
memcpy(&entries[3].name, &init_boot_a_name, sizeof(init_boot_a_name));
memset(&vendor_boot_hdr, 0, sizeof(vendor_boot_hdr));
memcpy(vendor_boot_hdr.magic, VENDOR_BOOT_MAGIC, VENDOR_BOOT_MAGIC_SIZE);
vendor_boot_hdr.vendor_ramdisk_table_entry_size =
sizeof(struct vendor_ramdisk_table_entry_v4);
vendor_boot_hdr.page_size = 512;
memset(&init_boot_hdr, 0, sizeof(init_boot_hdr));
memcpy(init_boot_hdr.magic, BOOT_MAGIC, BOOT_MAGIC_SIZE);
vb2api_init(workbuf, sizeof(workbuf), &ctx);
vb2_nv_init(ctx);
vb2_nv_set(ctx, VB2_NV_KERNEL_MAX_ROLLFORWARD, 0xfffffffe);
vb2api_secdata_kernel_create(ctx);
vb2_secdata_kernel_init(ctx);
sd = vb2_get_sd(ctx);
sd->status |= VB2_SD_STATUS_SECDATA_FWMP_INIT;
SET_BOOT_MODE(ctx, VB2_BOOT_MODE_NORMAL);
}
static void load_android_tests(void)
{
reset_mocks();
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_SUCCESS, "Boot android");
TEST_EQ(lkp.flags, VB2_KERNEL_TYPE_BOOTIMG, " bootimg type flag");
TEST_STR_EQ((char *)lkp.partition_guid.u.raw, fake_guid, " guid");
TEST_NEQ(sd->flags & VB2_SD_FLAG_KERNEL_SIGNED, 0, " use signature");
// No valid slot (successful=0 and tries=0)
reset_mocks();
SetEntrySuccessful(&entries[0], 0);
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_NO_KERNEL_FOUND,
"No valid slot");
// Incorrect slot '_c'
reset_mocks();
entries[0].name[7] = 'c';
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"Incorrect slot type");
// AVB verification fails
reset_mocks();
avb_verification_fails = VERIFY_FAIL;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"AVB verification fails");
// AVB verification fails in developer mode
reset_mocks();
avb_verification_fails = VERIFY_FAIL;
SET_BOOT_MODE(ctx, VB2_BOOT_MODE_DEVELOPER);
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_SUCCESS,
"AVB verification fails in developer mode");
// Rollback protection in developer mode
reset_mocks();
avb_verification_fails = ROLLBACK_FAIL;
SET_BOOT_MODE(ctx, VB2_BOOT_MODE_DEVELOPER);
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_SUCCESS,
"Rollback protection in developer mode");
// Rollback protection in normal mode
reset_mocks();
avb_verification_fails = ROLLBACK_FAIL;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_ROLLBACK,
"Rollback protection in normal mode");
// Missing init_boot partition
reset_mocks();
init_boot_missing = true;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"Missing init_boot partition");
// init_boot partition too small
reset_mocks();
entries[3].ending_lba = INIT_BOOT_LBA;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"init_boot partition too small");
// init_boot with non-zero kernel size
reset_mocks();
init_boot_hdr.kernel_size = 0x1000;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"Non zero kernel size");
// Missing vendor_boot
reset_mocks();
vendor_boot_missing = true;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"Missing vendor_boot partition");
// vendor_boot partition too small
reset_mocks();
entries[2].ending_lba = VENDOR_BOOT_LBA;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"vendor_boot partition too small");
// ramdisk table size too big
reset_mocks();
vendor_boot_hdr.vendor_ramdisk_size = 16384;
TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
"vendor_boot ramdisk table too big");
}
static void load_ota_recovery_tests(void)
{
reset_mocks();
TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0), VB2_SUCCESS,
"Boot ota_recovery_a");
TEST_PTR_EQ(lkp.disk_handle, (void *)CHILD_HANDLE, " fill disk_handle when success");
reset_mocks();
avb_verification_fails = ROLLBACK_FAIL;
TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0), VB2_ERROR_LK_NO_KERNEL_FOUND,
"Boot ota_recovery_a");
reset_mocks();
ota_recovery_sector = OTA_RECOVERY_B_PART;
TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0), VB2_SUCCESS,
"Boot ota_recovery_b");
TEST_PTR_EQ(lkp.disk_handle, (void *)CHILD_HANDLE, " fill disk_handle when success");
reset_mocks();
ota_recovery_sector = disk_info.lba_count + 1;
TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND, "No recovery");
}
int main(void)
{
load_android_tests();
load_ota_recovery_tests();
return gTestSuccess ? 0 : 255;
}