blob: 5fc66f14f965a856fdb17a253c5da2b985c4325b [file] [log] [blame]
/* Copyright 2021 The Chromium OS Authors. All rights reserved.
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*
* Tests for miniOS kernel selection, loading, verification, and booting.
*/
#include "2api.h"
#include "2common.h"
#include "2misc.h"
#include "2nvstorage.h"
#include "2secdata.h"
#include "load_kernel_fw.h"
#include "test_common.h"
#include "vboot_api.h"
#define MAX_MOCK_KERNELS 10
#define KBUF_SIZE 65536
/* Internal struct to simulate a stream for sector-based disks */
struct disk_stream {
/* Disk handle */
VbExDiskHandle_t handle;
/* Next sector to read */
uint64_t sector;
/* Number of sectors left */
uint64_t sectors_left;
};
/* Represent a "kernel" located on the disk */
struct mock_kernel {
/* Sector where the kernel begins */
uint64_t sector;
/* Return value from vb2_load_partition */
vb2_error_t rv;
/* Number of times the sector was read */
int read_count;
};
/* Mock data */
static struct vb2_context *ctx;
static struct vb2_shared_data *sd;
static struct vb2_workbuf wb;
static uint8_t workbuf[VB2_KERNEL_WORKBUF_RECOMMENDED_SIZE]
__attribute__((aligned(VB2_WORKBUF_ALIGN)));
static enum vb2_boot_mode *boot_mode;
static VbSelectAndLoadKernelParams lkp;
static VbDiskInfo disk_info;
static struct vb2_keyblock kbh;
static struct vb2_kernel_preamble kph;
static uint8_t kernel_buffer[80000];
static struct mock_kernel kernels[MAX_MOCK_KERNELS];
static int kernel_count;
static struct mock_kernel *cur_kernel;
static int mock_tpm_set_mode_calls;
static void add_mock_kernel(uint64_t sector, vb2_error_t rv)
{
if (kernel_count >= ARRAY_SIZE(kernels)) {
TEST_TRUE(0, " kernel_count ran out of entries!");
return;
}
kernels[kernel_count].sector = sector;
kernels[kernel_count].rv = rv;
kernel_count++;
}
static void reset_common_data(void)
{
TEST_SUCC(vb2api_init(workbuf, sizeof(workbuf), &ctx),
"vb2api_init failed");
vb2_workbuf_from_ctx(ctx, &wb);
vb2_nv_init(ctx);
vb2api_secdata_kernel_create(ctx);
vb2_secdata_kernel_init(ctx);
ctx->flags = VB2_CONTEXT_RECOVERY_MODE;
boot_mode = (enum vb2_boot_mode *)&ctx->boot_mode;
*boot_mode = VB2_BOOT_MODE_MANUAL_RECOVERY;
sd = vb2_get_sd(ctx);
sd->kernel_version_secdata = 0xabcdef | (1 << 24);
memset(&lkp, 0, sizeof(lkp));
lkp.kernel_buffer = kernel_buffer;
lkp.kernel_buffer_size = sizeof(kernel_buffer);
lkp.disk_handle = (VbExDiskHandle_t)1;
memset(&disk_info, 0, sizeof(disk_info));
disk_info.bytes_per_lba = 512;
disk_info.lba_count = 1024;
disk_info.handle = lkp.disk_handle;
memset(&kbh, 0, sizeof(kbh));
kbh.data_key.key_version = 2;
kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
| VB2_KEYBLOCK_FLAG_DEVELOPER_1
| VB2_KEYBLOCK_FLAG_RECOVERY_1
| VB2_KEYBLOCK_FLAG_MINIOS_1;
kbh.keyblock_size = sizeof(kbh);
memset(&kph, 0, sizeof(kph));
kph.kernel_version = 1;
kph.preamble_size = 4096 - kbh.keyblock_size;
kph.body_signature.data_size = 0;
kph.bootloader_address = 0xbeadd008;
kph.bootloader_size = 0x1234;
memset(&kernels, 0, sizeof(kernels));
kernel_count = 0;
cur_kernel = NULL;
mock_tpm_set_mode_calls = 0;
}
/* Mocks */
vb2_error_t VbExStreamOpen(VbExDiskHandle_t handle, uint64_t lba_start,
uint64_t lba_count, VbExStream_t *stream)
{
struct disk_stream *s;
uint64_t i;
if (!handle) {
*stream = NULL;
return VB2_ERROR_UNKNOWN;
}
if (lba_start + lba_count > disk_info.lba_count)
return VB2_ERROR_UNKNOWN;
s = malloc(sizeof(*s));
s->handle = handle;
s->sector = lba_start;
s->sectors_left = lba_count;
*stream = (void *)s;
for (i = 0; i < kernel_count; i++) {
if (kernels[i].sector == lba_start)
cur_kernel = &kernels[i];
}
return VB2_SUCCESS;
}
vb2_error_t VbExStreamRead(VbExStream_t stream, uint32_t bytes, void *buffer)
{
struct disk_stream *s = (struct disk_stream *)stream;
uint64_t sectors;
uint64_t i;
if (!s)
return VB2_ERROR_UNKNOWN;
/* For now, require reads to be a multiple of the LBA size */
if (bytes % disk_info.bytes_per_lba)
return VB2_ERROR_UNKNOWN;
/* Fail on overflow */
sectors = bytes / disk_info.bytes_per_lba;
if (sectors > s->sectors_left)
return VB2_ERROR_UNKNOWN;
memset(buffer, 0, bytes);
for (i = 0; i < kernel_count; i++) {
if (kernels[i].sector >= s->sector &&
kernels[i].sector < s->sector + sectors) {
VB2_DEBUG("Simulating kernel %" PRIu64 " match\n", i);
uint64_t buf_offset = (kernels[i].sector - s->sector)
* disk_info.bytes_per_lba;
memcpy(buffer + buf_offset, VB2_KEYBLOCK_MAGIC,
VB2_KEYBLOCK_MAGIC_SIZE);
kernels[i].read_count++;
TEST_TRUE(kernels[i].read_count <= 2,
" Max read count exceeded");
}
}
s->sector += sectors;
s->sectors_left -= sectors;
return VB2_SUCCESS;
}
void VbExStreamClose(VbExStream_t stream)
{
free(stream);
}
vb2_error_t vb2_unpack_key_buffer(struct vb2_public_key *key,
const uint8_t *buf, uint32_t size)
{
return cur_kernel->rv;
}
vb2_error_t vb2_verify_keyblock(struct vb2_keyblock *block, uint32_t size,
const struct vb2_public_key *key,
const struct vb2_workbuf *w)
{
/* Use this as an opportunity to override the keyblock */
memcpy((void *)block, &kbh, sizeof(kbh));
return cur_kernel->rv;
}
vb2_error_t vb2_verify_keyblock_hash(const struct vb2_keyblock *block,
uint32_t size,
const struct vb2_workbuf *w)
{
/* Use this as an opportunity to override the keyblock */
memcpy((void *)block, &kbh, sizeof(kbh));
return cur_kernel->rv;
}
vb2_error_t vb2_verify_kernel_preamble(struct vb2_kernel_preamble *preamble,
uint32_t size, const struct vb2_public_key *key,
const struct vb2_workbuf *w)
{
/* Use this as an opportunity to override the preamble */
memcpy((void *)preamble, &kph, sizeof(kph));
return cur_kernel->rv;
}
vb2_error_t vb2_verify_data(const uint8_t *data, uint32_t size,
struct vb2_signature *sig,
const struct vb2_public_key *key,
const struct vb2_workbuf *w)
{
return cur_kernel->rv;
}
vb2_error_t vb2_digest_buffer(const uint8_t *buf, uint32_t size,
enum vb2_hash_algorithm hash_alg, uint8_t *digest,
uint32_t digest_size)
{
return cur_kernel->rv;
}
vb2_error_t vb2ex_tpm_set_mode(enum vb2_tpm_mode mode_val)
{
mock_tpm_set_mode_calls++;
return VB2_SUCCESS;
}
/* Make sure nothing tested here ever calls this directly. */
void vb2api_fail(struct vb2_context *c, uint8_t reason, uint8_t subcode)
{
TEST_TRUE(0, " called vb2api_fail()");
}
/* Tests */
static void load_minios_kernel_tests(void)
{
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 1;
add_mock_kernel(0, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"{valid kernel}");
TEST_EQ(mock_tpm_set_mode_calls, 1,
" TPM disabled");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 1;
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND, "{no kernel}");
TEST_EQ(mock_tpm_set_mode_calls, 0,
" TPM not disabled");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(1, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"{no kernel, valid kernel}");
TEST_EQ(cur_kernel->sector, 1, " select kernel");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_ERROR_MOCK);
add_mock_kernel(1, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"{invalid kernel, valid kernel}");
TEST_EQ(cur_kernel->sector, 1, " select second kernel");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_ERROR_MOCK);
add_mock_kernel(1, VB2_ERROR_MOCK);
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"{invalid kernel, invalid kernel}");
TEST_EQ(mock_tpm_set_mode_calls, 0,
" TPM not disabled");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
add_mock_kernel(1, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"{valid kernel, valid kernel} minios_priority=0");
TEST_EQ(cur_kernel->sector, 0, " select first kernel");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
add_mock_kernel(1, VB2_SUCCESS);
vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"{valid kernel, valid kernel} minios_priority=1");
TEST_EQ(cur_kernel->sector, 1, " select second kernel");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
add_mock_kernel(1, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info,
VB_MINIOS_FLAG_NON_ACTIVE),
"{valid kernel, valid kernel} minios_priority=0 non-active");
TEST_EQ(cur_kernel->sector, 1, " select second kernel");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_ERROR_MOCK);
add_mock_kernel(1, VB2_SUCCESS);
vb2_nv_set(ctx, VB2_NV_MINIOS_PRIORITY, 1);
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info,
VB_MINIOS_FLAG_NON_ACTIVE),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"{invalid kernel, valid kernel} minios_priority=1 non-active");
reset_common_data();
disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
disk_info.lba_count = 4;
add_mock_kernel(1, VB2_SUCCESS);
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"valid kernel header near start of disk (disk too small)");
reset_common_data();
disk_info.bytes_per_lba = VB2_KEYBLOCK_MAGIC_SIZE;
disk_info.lba_count = 1000;
add_mock_kernel(999, VB2_SUCCESS);
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"valid kernel header near end of disk");
reset_common_data();
disk_info.bytes_per_lba = 1024;
disk_info.lba_count = 128;
add_mock_kernel(63, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"start/end overlap assuming >128 MB search range (start)");
reset_common_data();
disk_info.bytes_per_lba = 1024;
disk_info.lba_count = 128;
add_mock_kernel(64, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"start/end overlap assuming >128 MB search range (end)");
reset_common_data();
disk_info.bytes_per_lba = 128;
disk_info.lba_count = 1024;
add_mock_kernel(3, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"kernel at last sector in batch assuming 512 KB batches");
reset_common_data();
disk_info.bytes_per_lba = 256;
disk_info.lba_count = 1024;
add_mock_kernel(3, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"kernel at last sector in batch assuming 1 MB batches");
reset_common_data();
disk_info.bytes_per_lba = 512;
disk_info.lba_count = 1024;
add_mock_kernel(3, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"kernel at last sector in batch assuming 2 MB batches");
reset_common_data();
kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
| VB2_KEYBLOCK_FLAG_RECOVERY_1
| VB2_KEYBLOCK_FLAG_MINIOS_1;
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"kernel with minios keyblock flag");
reset_common_data();
kbh.keyblock_flags = VB2_KEYBLOCK_FLAG_DEVELOPER_0
| VB2_KEYBLOCK_FLAG_RECOVERY_1
| VB2_KEYBLOCK_FLAG_MINIOS_0;
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"kernel with !minios keyblock flag");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
sd->kernel_version_secdata = 5 << 24;
kph.kernel_version = 4;
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"kernel version too old");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
sd->kernel_version_secdata = 5 << 24;
kph.kernel_version = 0x100;
TEST_EQ(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
VB2_ERROR_LK_NO_KERNEL_FOUND,
"kernel version greater than 0xff");
reset_common_data();
disk_info.bytes_per_lba = KBUF_SIZE;
disk_info.lba_count = 2;
add_mock_kernel(0, VB2_SUCCESS);
sd->kernel_version_secdata = 5 << 24;
kph.kernel_version = 6;
TEST_SUCC(LoadMiniOsKernel(ctx, &lkp, &disk_info, 0),
"newer kernel version");
}
int main(void)
{
load_minios_kernel_tests();
return gTestSuccess ? 0 : 255;
}