blob: 93d9b4d1b5747f556f3e47a2328b000ce667851f [file] [edit]
/* 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 keyblock hash verification
*/
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include "2avb.h"
#include "2common.h"
#include "2nvstorage.h"
#include "2secdata.h"
#include "2struct.h"
#include "cgptlib_internal.h"
#include "common/tests.h"
#include "host_key.h"
#define NUM_OF_ENTRIES 8
#define BYTES_PER_LBA 512
#define PART_SIZE 16
#define VBMETA_LBA 100
#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 KERNEL_BUFFER_SIZE 0x10000
#define KERNEL_VERSION_SECDATA 1
// Disk related variables
GptHeader gpt_hdr;
GptEntry entries[NUM_OF_ENTRIES];
GptData gptdata;
static struct vb2_disk_info disk_info;
static const uint16_t vbmeta_a_name[] = {'v', 'b', 'm', 'e', 't', 'a', '_', 'a', 0};
static const uint16_t vbmeta_b_name[] = {'v', 'b', 'm', 'e', 't', 'a', '_', 'b', 0};
static const uint16_t boot_a_name[] = {'b', 'o', 'o', 't', '_', 'a', 0};
static const uint16_t boot_b_name[] = {'b', 'o', 'o', 't', '_', 'b', 0};
// Verification related variables
static uint8_t *avb_key_data;
static size_t avb_key_len;
static char *keys_dir;
static const char *const key_len[] = {"1024", "2048", "4096", "8192"};
static int key_alg[] = {VB2_ALG_RSA1024_SHA256, VB2_ALG_RSA2048_SHA256, VB2_ALG_RSA4096_SHA256,
VB2_ALG_RSA8192_SHA256};
// VBOOT API variables
uint8_t kernel_buffer[KERNEL_BUFFER_SIZE];
static uint8_t workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE]
__attribute__((aligned(VB2_WORKBUF_ALIGN)));
static struct vb2_context *vb2_ctx;
static struct vb2_shared_data *sd;
struct vb2_gbb_header gbb_hdr;
// Mocks
struct vb2_gbb_header *vb2_get_gbb(struct vb2_context *ctx) { return &gbb_hdr; }
vb2_error_t VbExStreamOpen(vb2ex_disk_handle_t handle, uint64_t lba_start, uint64_t lba_count,
VbExStream_t *stream_ptr)
{
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)
{
return VB2_SUCCESS;
}
void VbExStreamClose(VbExStream_t stream) {}
static int prepare_cros_key(const char *filename, uint32_t alg)
{
struct vb2_packed_key *test_key;
test_key = vb2_read_packed_keyb(filename, alg, 1);
if (!test_key) {
fprintf(stderr, "Error reading test key\n");
return -1;
}
sd->kernel_key_offset = sizeof(*sd);
sd->kernel_key_size =
sizeof(*test_key) + vb2_packed_key_size(vb2_crypto_to_signature(alg));
memcpy(sd + 1, test_key, sd->kernel_key_size);
free(test_key);
return 0;
}
static int prepare_avb_key(const char *filename)
{
FILE *fp;
struct stat sb;
int rv = 0;
fp = fopen(filename, "rb");
if (!fp) {
fprintf(stderr, "Couldn't open file %s!\n", filename);
return -1;
}
if (fstat(fileno(fp), &sb)) {
fprintf(stderr, "Can't fstat %s: %s\n", filename, strerror(errno));
goto err;
}
avb_key_len = sb.st_size;
avb_key_data = malloc(sb.st_size);
if (!avb_key_data)
goto err;
if (fread(avb_key_data, avb_key_len, 1, fp) != 1) {
fprintf(stderr, "Unable to read from %s: %s\n", filename, strerror(errno));
goto err;
}
fclose(fp);
return 0;
err:
if (avb_key_data) {
free(avb_key_data);
avb_key_data = NULL;
avb_key_len = 0;
}
fclose(fp);
return rv;
}
static int setup(int key_num)
{
char filename[256];
snprintf(filename, sizeof(filename), "%s/key_rsa%s.keyb", keys_dir, key_len[key_num]);
if (prepare_cros_key(filename, key_alg[key_num])) {
fprintf(stderr, "Error preparing AVB key\n");
return -1;
}
snprintf(filename, sizeof(filename), "%s/key_rsa%s.avb", keys_dir, key_len[key_num]);
if (prepare_avb_key(filename)) {
fprintf(stderr, "Error preparing AVB key\n");
return -1;
}
return 0;
}
static void clean(void)
{
if (avb_key_data) {
free(avb_key_data);
avb_key_data = NULL;
}
avb_key_len = 0;
}
static void setup_storage(void)
{
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 = 1024;
disk_info.handle = (vb2ex_disk_handle_t)1;
memset(&gpt_hdr, 0, sizeof(gpt_hdr));
gpt_hdr.number_of_entries = NUM_OF_ENTRIES;
memset(&entries, 0, sizeof(entries));
uint64_t lba = 0x100;
entries[0].starting_lba = lba;
entries[0].ending_lba = lba + PART_SIZE - 1;
lba += PART_SIZE;
memcpy(&entries[0].type, &guid_android_vbmeta, sizeof(guid_android_vbmeta));
memcpy(&entries[0].unique, &guid_basic_data, sizeof(guid_basic_data));
memcpy(&entries[0].name, &vbmeta_a_name, sizeof(vbmeta_a_name));
entries[1].starting_lba = lba;
entries[1].ending_lba = lba + PART_SIZE - 1;
lba += PART_SIZE;
memcpy(&entries[1].name, &boot_a_name, sizeof(boot_a_name));
memcpy(&entries[1].unique, &guid_linux_data, sizeof(guid_linux_data));
entries[2].starting_lba = lba;
entries[2].ending_lba = lba + PART_SIZE - 1;
lba += PART_SIZE;
memcpy(&entries[2].name, &vbmeta_b_name, sizeof(vbmeta_b_name));
memcpy(&entries[2].unique, &guid_efi, sizeof(guid_efi));
entries[3].starting_lba = lba;
entries[3].ending_lba = lba + PART_SIZE - 1;
lba += PART_SIZE;
memcpy(&entries[3].name, &boot_b_name, sizeof(boot_b_name));
memcpy(&entries[3].unique, &guid_unused, sizeof(guid_unused));
/* 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;
gptdata.sector_bytes = 512;
}
static void validate_vbmeta_public_key_tests(AvbOps *avb_ops)
{
AvbIOResult ret;
bool key_is_trusted;
int i;
for (i = 0; i < ARRAY_SIZE(key_len); i++) {
// Successful validation
TEST_EQ_S(setup(i), 0);
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len,
NULL, 0, &key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, true, "Key is trusted");
clean();
// Key size lesser than required
TEST_EQ_S(setup(i), 0);
avb_key_len = sizeof(AvbRSAPublicKeyHeader) - 1;
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len,
NULL, 0, &key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, false, "Key rejected - incorrect key size");
clean();
// n0inv corruption
TEST_EQ_S(setup(i), 0);
avb_key_data[4] ^= avb_key_data[4];
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len,
NULL, 0, &key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, false, "Key rejected - n0inv corrupted");
clean();
// rr corruption
TEST_EQ_S(setup(i), 0);
avb_key_data[avb_key_len - 1] ^= avb_key_data[avb_key_len - 1];
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len,
NULL, 0, &key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, false, "Key rejected - rr corrupted");
clean();
// n corruption
TEST_EQ_S(setup(i), 0);
avb_key_data[sizeof(AvbRSAPublicKeyHeader)] ^=
avb_key_data[sizeof(AvbRSAPublicKeyHeader)];
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len,
NULL, 0, &key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, false, "Key rejected - n corrupted");
clean();
}
// Try 2 different keys of the same length
char filename[256];
snprintf(filename, sizeof(filename), "%s/key_rsa2048_exp3.keyb", keys_dir);
TEST_EQ(prepare_cros_key(filename, VB2_ALG_RSA2048_SHA256), 0, "Prepare cros key");
snprintf(filename, sizeof(filename), "%s/key_rsa2048.avb", keys_dir);
TEST_EQ(prepare_avb_key(filename), 0, "Prepare avb key");
ret = avb_ops->validate_vbmeta_public_key(avb_ops, avb_key_data, avb_key_len, NULL, 0,
&key_is_trusted);
TEST_EQ(ret, AVB_IO_RESULT_OK, "validate_vbmeta_public_key - successful");
TEST_EQ(key_is_trusted, false, "Key rejected - different keys");
}
static void read_from_partition_tests(AvbOps *avb_ops)
{
char buffer[1024];
size_t bytes;
TEST_EQ(avb_ops->read_from_partition(avb_ops, "vbmeta_a", 0, 0x200, buffer, &bytes),
AVB_IO_RESULT_OK, "read_from_partition: vbmeta_a");
TEST_EQ(bytes, 0x200, "correct bytes read");
TEST_EQ(avb_ops->read_from_partition(avb_ops, "boot_b", 0, 0x300, buffer, &bytes),
AVB_IO_RESULT_OK, "read_from_partition: boot_b");
TEST_EQ(bytes, 0x300, "correct bytes read");
TEST_EQ(avb_ops->read_from_partition(avb_ops, "not_exists", 0, 0x300, buffer, &bytes),
AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, "read from non-existed partition");
TEST_EQ(bytes, 0x0, "correct bytes read");
TEST_EQ(avb_ops->read_from_partition(avb_ops, "vbmeta_a", -(PART_SIZE * 0x200 + 1),
0x300, buffer, &bytes),
AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION, "negative offset");
TEST_EQ(bytes, 0x0, "correct bytes read");
TEST_EQ(avb_ops->read_from_partition(avb_ops, "vbmeta_b", 0, (PART_SIZE + 1) * 0x200,
buffer, &bytes),
AVB_IO_RESULT_OK, "truncate if expected too much to read");
TEST_EQ(bytes, PART_SIZE * 0x200, "correct bytes read");
}
static void get_preload_partition_tests(AvbOps *avb_ops)
{
uint8_t *out_pointer;
size_t out_num_bytes;
out_pointer = NULL;
out_num_bytes = 0;
TEST_EQ(avb_ops->get_preloaded_partition(avb_ops, "boot_a", 0x200, &out_pointer,
&out_num_bytes),
AVB_IO_RESULT_OK, "get_preloaded_partitions: boot_a");
TEST_EQ(out_num_bytes, 0x200, "correct bytes read");
TEST_PTR_NEQ(out_pointer, NULL, "valid partition pointer");
out_pointer = NULL;
out_num_bytes = 0;
TEST_EQ(avb_ops->get_preloaded_partition(avb_ops, "boot", 0x200, &out_pointer,
&out_num_bytes),
AVB_IO_RESULT_OK, "get_preloaded_partitions: name without suffix");
TEST_EQ(out_num_bytes, 0, "correct bytes read");
TEST_PTR_EQ(out_pointer, NULL, "nulled partition pointer");
out_pointer = NULL;
out_num_bytes = 0;
TEST_EQ(avb_ops->get_preloaded_partition(avb_ops, "boot_b", 0x200, &out_pointer,
&out_num_bytes),
AVB_IO_RESULT_OK, "get_preloaded_partitions: incorrect suffix");
TEST_EQ(out_num_bytes, 0, "correct bytes read");
TEST_PTR_EQ(out_pointer, NULL, "nulled partition pointer");
out_pointer = NULL;
out_num_bytes = 0;
TEST_EQ(avb_ops->get_preloaded_partition(avb_ops, "boot_a", 0x2200, &out_pointer,
&out_num_bytes),
AVB_IO_RESULT_OK, "get_preloaded_partitions: truncate to partition size");
TEST_EQ(out_num_bytes, 0x2000, "correct bytes read");
TEST_PTR_NEQ(out_pointer, NULL, "nulled partition pointer");
}
static void read_rollback_tests(AvbOps *avb_ops)
{
uint64_t rollback_index;
TEST_EQ(avb_ops->read_rollback_index(avb_ops, 0, &rollback_index), AVB_IO_RESULT_OK,
"read rollback index with success");
TEST_EQ(rollback_index, KERNEL_VERSION_SECDATA, "correct rollback index");
gbb_hdr.flags = VB2_GBB_FLAG_DISABLE_ROLLBACK_CHECK;
TEST_EQ(avb_ops->read_rollback_index(avb_ops, 0, &rollback_index), AVB_IO_RESULT_OK,
"read rollback - disable check flag");
TEST_EQ(rollback_index, 0, "correct rollback index");
gbb_hdr.flags = 0;
TEST_EQ(avb_ops->read_rollback_index(avb_ops, 1, &rollback_index),
AVB_IO_RESULT_ERROR_NO_SUCH_VALUE, "read rollback - incorrect index");
TEST_EQ(avb_ops->read_rollback_index(avb_ops, 0, NULL),
AVB_IO_RESULT_ERROR_NO_SUCH_VALUE, "read rollback - nulled pointer");
}
static void read_is_device_unlocked(AvbOps *avb_ops)
{
bool unlocked = true;
sd->status |= VB2_SD_STATUS_SECDATA_FWMP_INIT;
struct vb2_secdata_fwmp *fwmp = (struct vb2_secdata_fwmp *)vb2_ctx->secdata_fwmp;
vb2_ctx->flags = 0;
sd->flags = 0;
vb2_set_boot_mode(vb2_ctx);
TEST_EQ(avb_ops->read_is_device_unlocked(avb_ops, &unlocked), AVB_IO_RESULT_OK,
"normal boot");
TEST_EQ(unlocked, false, "locked mode");
vb2_ctx->flags = VB2_CONTEXT_DEVELOPER_MODE;
sd->flags = VB2_SD_FLAG_DEV_MODE_ENABLED;
vb2_set_boot_mode(vb2_ctx);
TEST_EQ(avb_ops->read_is_device_unlocked(avb_ops, &unlocked), AVB_IO_RESULT_OK,
"developer boot");
TEST_EQ(unlocked, true, "unlocked mode");
vb2_ctx->flags = VB2_CONTEXT_DEVELOPER_MODE;
sd->flags = VB2_SD_FLAG_DEV_MODE_ENABLED;
vb2_set_boot_mode(vb2_ctx);
fwmp->flags = VB2_SECDATA_FWMP_DEV_USE_KEY_HASH;
TEST_EQ(avb_ops->read_is_device_unlocked(avb_ops, &unlocked), AVB_IO_RESULT_OK,
"developer mode - fwmp dev use key hash");
TEST_EQ(unlocked, false, "locked mode");
vb2_ctx->flags = VB2_CONTEXT_DEVELOPER_MODE;
sd->flags = VB2_SD_FLAG_DEV_MODE_ENABLED;
vb2_set_boot_mode(vb2_ctx);
fwmp->flags = VB2_SECDATA_FWMP_DEV_ENABLE_OFFICIAL_ONLY;
TEST_EQ(avb_ops->read_is_device_unlocked(avb_ops, &unlocked), AVB_IO_RESULT_OK,
"developer mode - fwmp dev enable official only");
TEST_EQ(unlocked, false, "locked mode");
vb2_ctx->flags = VB2_CONTEXT_DEVELOPER_MODE;
sd->flags = VB2_SD_FLAG_DEV_MODE_ENABLED;
vb2_set_boot_mode(vb2_ctx);
vb2_nv_set(vb2_ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1);
TEST_EQ(avb_ops->read_is_device_unlocked(avb_ops, &unlocked), AVB_IO_RESULT_OK,
"developer mode - boot signed only");
TEST_EQ(unlocked, false, "locked mode");
}
static void get_unique_guid_for_partition_test(AvbOps *avb_ops)
{
char guid[GUID_STRLEN], expected[GUID_STRLEN];
TEST_EQ(avb_ops->get_unique_guid_for_partition(avb_ops, "vbmeta_a", guid, GUID_STRLEN),
AVB_IO_RESULT_OK, "vbmeta_a - unique");
GptGuidToStr(&guid_basic_data, expected, GUID_STRLEN, GPT_GUID_LOWERCASE);
TEST_STR_EQ(guid, expected, "correct guid");
TEST_EQ(avb_ops->get_unique_guid_for_partition(avb_ops, "vbmeta_b", guid, GUID_STRLEN),
AVB_IO_RESULT_OK, "vbmeta_b - unique");
GptGuidToStr(&guid_efi, expected, GUID_STRLEN, GPT_GUID_LOWERCASE);
TEST_STR_EQ(guid, expected, "correct guid");
TEST_EQ(avb_ops->get_unique_guid_for_partition(avb_ops, "boot_a", guid, GUID_STRLEN),
AVB_IO_RESULT_OK, "boot_a - unique");
GptGuidToStr(&guid_linux_data, expected, GUID_STRLEN, GPT_GUID_LOWERCASE);
TEST_STR_EQ(guid, expected, "correct guid");
TEST_EQ(avb_ops->get_unique_guid_for_partition(avb_ops, "boot_b", guid, GUID_STRLEN),
AVB_IO_RESULT_OK, "boot_b - unique");
GptGuidToStr(&guid_unused, expected, GUID_STRLEN, GPT_GUID_LOWERCASE);
TEST_STR_EQ(guid, expected, "correct guid");
TEST_EQ(avb_ops->get_unique_guid_for_partition(avb_ops, "boot", guid, GUID_STRLEN),
AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, "unique for non exist partition");
}
int main(int argc, char *argv[])
{
struct vb2_kernel_params vb2_kp = {
.kernel_buffer = kernel_buffer,
.kernel_buffer_size = KERNEL_BUFFER_SIZE,
};
AvbOps *avb_ops;
if (argc != 2) {
fprintf(stderr, "Usage: %s <keys_dir>", argv[0]);
return -1;
}
keys_dir = argv[1];
setup_storage();
/* Set up context */
if (vb2api_init(workbuf, sizeof(workbuf), &vb2_ctx)) {
printf("Failed to initialize workbuf.\n");
return -1;
}
sd = vb2_get_sd(vb2_ctx);
sd->kernel_version_secdata = KERNEL_VERSION_SECDATA;
avb_ops = vboot_avb_ops_new(vb2_ctx, &vb2_kp, &gptdata, NULL, "_a");
validate_vbmeta_public_key_tests(avb_ops);
read_from_partition_tests(avb_ops);
get_preload_partition_tests(avb_ops);
read_rollback_tests(avb_ops);
read_is_device_unlocked(avb_ops);
get_unique_guid_for_partition_test(avb_ops);
vboot_avb_ops_free(avb_ops);
return gTestSuccess ? 0 : 255;
}