diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | host/include/crossystem_vbnv.h | 14 | ||||
-rw-r--r-- | host/lib/crossystem.c | 102 | ||||
-rw-r--r-- | tests/vb2_host_nvdata_flashrom_tests.c | 279 |
4 files changed, 396 insertions, 0 deletions
@@ -711,6 +711,7 @@ TEST2X_NAMES = \ tests/vb2_gbb_tests \ tests/vb2_host_flashrom_tests \ tests/vb2_host_key_tests \ + tests/vb2_host_nvdata_flashrom_tests \ tests/vb2_kernel_tests \ tests/vb2_misc_tests \ tests/vb2_nvstorage_tests \ diff --git a/host/include/crossystem_vbnv.h b/host/include/crossystem_vbnv.h index 64b6b1d6..d61506c2 100644 --- a/host/include/crossystem_vbnv.h +++ b/host/include/crossystem_vbnv.h @@ -15,6 +15,20 @@ extern "C" { struct vb2_context; /** + * Attempt to read non-volatile storage using flashrom. + * + * Returns 0 if success, non-zero if error. + */ +int vb2_read_nv_storage_flashrom(struct vb2_context *ctx); + +/** + * Attempt to write non-volatile storage using flashrom. + * + * Returns 0 if success, non-zero if error. + */ +int vb2_write_nv_storage_flashrom(struct vb2_context* ctx); + +/** * Attempt to read non-volatile storage using mosys. * * Returns 0 if success, non-zero if error. diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c index cf7600e4..c5c72cfe 100644 --- a/host/lib/crossystem.c +++ b/host/lib/crossystem.c @@ -16,6 +16,7 @@ #include "crossystem.h" #include "crossystem_vbnv.h" #include "host_common.h" +#include "flashrom.h" #include "subprocess.h" #include "vboot_struct.h" @@ -655,6 +656,107 @@ int VbSetSystemPropertyString(const char* name, const char* value) return -1; } +/** + * Get index of the last valid VBNV entry in an EEPROM. + * + * @param buf Pointer to the beginning of the EEPROM. + * @param buf_sz Size of the EEPROM. + * @param vbnv_size The size of a single VBNV entry for this device. + * + * @return The index of the last valid VBNV entry on success, or -1 on + * failure. + */ +static int vb2_nv_index(const uint8_t *buf, uint32_t buf_sz, int vbnv_size) +{ + int index; + uint8_t blank[VB2_NVDATA_SIZE_V2]; + + /* The size of the buffer should be an even multiple of the + VBNV size. */ + if (buf_sz % vbnv_size != 0) { + VB2_DIE("The VBNV in flash (%u bytes) is not an even multiple " + "of the VBNV size (%u bytes). This is likely a " + "firmware bug.\n", buf_sz, vbnv_size); + } + + memset(blank, 0xff, sizeof(blank)); + for (index = 0; index < buf_sz / vbnv_size; index++) { + if (!memcmp(blank, &buf[index * vbnv_size], vbnv_size)) + break; + } + + if (!index || index == buf_sz / vbnv_size) { + fprintf(stderr, "VBNV is either uninitialized or corrupted.\n"); + return -1; + } + + return index - 1; +} + +#define VBNV_FMAP_REGION "RW_NVRAM" + +int vb2_read_nv_storage_flashrom(struct vb2_context *ctx) +{ + int index; + int vbnv_size = vb2_nv_get_size(ctx); + uint8_t *flash_buf; + uint32_t flash_size; + + if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION, + &flash_buf, &flash_size)) + return -1; + + index = vb2_nv_index(flash_buf, flash_size, vbnv_size); + if (index < 0) { + free(flash_buf); + return -1; + } + + memcpy(ctx->nvdata, &flash_buf[index * vbnv_size], vbnv_size); + free(flash_buf); + return 0; +} + +int vb2_write_nv_storage_flashrom(struct vb2_context *ctx) +{ + int rv = 0; + int current_index; + int next_index; + int vbnv_size = vb2_nv_get_size(ctx); + uint8_t *flash_buf; + uint32_t flash_size; + + if (flashrom_read(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION, + &flash_buf, &flash_size)) + return -1; + + current_index = vb2_nv_index(flash_buf, flash_size, vbnv_size); + if (current_index < 0) { + rv = -1; + goto exit; + } + + next_index = current_index + 1; + if ((next_index + 1) * vbnv_size == flash_size) { + /* VBNV is full. Erase and write at beginning. */ + memset(flash_buf, 0xff, flash_size); + next_index = 0; + } + + memcpy(&flash_buf[next_index * vbnv_size], ctx->nvdata, vbnv_size); + if (flashrom_write(FLASHROM_PROGRAMMER_INTERNAL_AP, VBNV_FMAP_REGION, + flash_buf, flash_size)) { + rv = -1; + goto exit; + } + + exit: + free(flash_buf); + return rv; +} + +/* TODO(crbug.com/1090803): remove these mosys nvdata functions + (approx. October 2020). */ int vb2_read_nv_storage_mosys(struct vb2_context *ctx) { /* Reserve extra 32 bytes */ diff --git a/tests/vb2_host_nvdata_flashrom_tests.c b/tests/vb2_host_nvdata_flashrom_tests.c new file mode 100644 index 00000000..7a147856 --- /dev/null +++ b/tests/vb2_host_nvdata_flashrom_tests.c @@ -0,0 +1,279 @@ +/* Copyright 2020 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 crossystem flashrom-based nvdata functions. + */ + +#include <fcntl.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "2api.h" +#include "2common.h" +#include "2constants.h" +#include "2nvstorage.h" +#include "2return_codes.h" +#include "crossystem_vbnv.h" +#include "flashrom.h" +#include "test_common.h" + +/* Mocked flashrom only supports host programmer, and RW_NVRAM + region. */ +static void assert_mock_params(const char *programmer, const char *region) +{ + TEST_STR_EQ(programmer, FLASHROM_PROGRAMMER_INTERNAL_AP, + "Using internal AP programmer"); + TEST_STR_EQ(region, "RW_NVRAM", "Using NVRAM region"); +} + +static bool mock_flashrom_fail; + +/* To support both 16-byte and 64-byte nvdata with the same fake + eeprom, we can size the flash chip to be 16x64. So, for 16-byte + nvdata, this is a flash chip with 64 entries, and for 64-byte + nvdata, this is a flash chip with 16 entries. */ +static uint8_t fake_flash_region[VB2_NVDATA_SIZE * VB2_NVDATA_SIZE_V2]; +static int fake_flash_entry_count; + +static const uint8_t test_nvdata_16b[] = { + 0x60, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x4e, + 0x00, 0xfe, 0xff, 0x00, 0x00, 0xff, 0xff, 0x5e, +}; + +static const uint8_t test_nvdata2_16b[] = { + 0x60, 0x10, 0x00, 0x00, 0x00, 0x02, 0x00, 0x4c, + 0x00, 0xfe, 0xff, 0x00, 0x00, 0xff, 0xff, 0x78, +}; + +static void reset_test_data(struct vb2_context *ctx, int nvdata_size) +{ + /* Initialize the context value. */ + ctx->flags = 0; + + switch (nvdata_size) { + case VB2_NVDATA_SIZE: + fake_flash_entry_count = VB2_NVDATA_SIZE_V2; + memcpy(ctx->nvdata, test_nvdata_16b, sizeof(test_nvdata_16b)); + break; + case VB2_NVDATA_SIZE_V2: + ctx->flags |= VB2_CONTEXT_NVDATA_V2; + fake_flash_entry_count = VB2_NVDATA_SIZE; + /* TODO: create some test data for 64-byte nvdata and + put it here. Right now, this only tests 16-byte + nvdata. */ + break; + default: + /* This is not valid. */ + TEST_TRUE(false, "Test failed, invalid nvdata size"); + fake_flash_entry_count = 0; + break; + } + + /* Clear the fake flash chip. */ + memset(fake_flash_region, 0xff, sizeof(fake_flash_region)); + + /* Flashrom succeeds unless the test says otherwise. */ + mock_flashrom_fail = false; +} + +/* Mocked flashrom_read for tests. */ +vb2_error_t flashrom_read(const char *programmer, const char *region, + uint8_t **data_out, uint32_t *size_out) +{ + if (mock_flashrom_fail) { + *data_out = NULL; + *size_out = 0; + return VB2_ERROR_FLASHROM; + } + + assert_mock_params(programmer, region); + + *data_out = malloc(sizeof(fake_flash_region)); + *size_out = sizeof(fake_flash_region); + memcpy(*data_out, fake_flash_region, sizeof(fake_flash_region)); + return VB2_SUCCESS; +} + +/* Mocked flashrom_write for tests. */ +vb2_error_t flashrom_write(const char *programmer, const char *region, + uint8_t *data, uint32_t data_size) +{ + if (mock_flashrom_fail) + return VB2_ERROR_FLASHROM; + + assert_mock_params(programmer, region); + + TEST_EQ(data_size, sizeof(fake_flash_region), + "The flash size is correct"); + memcpy(fake_flash_region, data, data_size); + return VB2_SUCCESS; +} + +static void test_read_ok_beginning(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata2_16b, sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0, + "Reading storage succeeds"); + TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)), + 0, "The nvdata in the vb2_context was updated from flash"); +} + +static void test_read_ok_2ndentry(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata2_16b, + sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0, + "Reading storage succeeds"); + TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)), + 0, "The nvdata in the vb2_context was updated from flash"); +} + +static void test_read_ok_full(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + + for (int entry = 0; entry < fake_flash_entry_count - 2; entry++) + memcpy(fake_flash_region + (entry * VB2_NVDATA_SIZE), + test_nvdata_16b, sizeof(test_nvdata_16b)); + + memcpy(fake_flash_region + + ((fake_flash_entry_count - 2) * VB2_NVDATA_SIZE), + test_nvdata2_16b, sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_read_nv_storage_flashrom(&ctx), 0, + "Reading storage succeeds"); + TEST_EQ(memcmp(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)), + 0, "The nvdata in the vb2_context was updated from flash"); +} + +static void test_read_fail_uninitialized(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + + TEST_NEQ(vb2_read_nv_storage_flashrom(&ctx), 0, + "Reading storage fails when flash is erased"); +} + +static void test_read_fail_flashrom(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b)); + mock_flashrom_fail = true; + + TEST_NEQ(vb2_read_nv_storage_flashrom(&ctx), 0, + "Reading storage fails when flashrom fails"); +} + +static void test_write_ok_beginning(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b)); + memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0, + "Writing storage succeeds"); + TEST_EQ(memcmp(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata2_16b, + sizeof(test_nvdata2_16b)), + 0, "The flash was updated with a new entry"); +} + +static void test_write_ok_2ndentry(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region + VB2_NVDATA_SIZE, test_nvdata_16b, + sizeof(test_nvdata_16b)); + memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0, + "Writing storage succeeds"); + TEST_EQ(memcmp(fake_flash_region + (2 * VB2_NVDATA_SIZE), + test_nvdata2_16b, sizeof(test_nvdata2_16b)), + 0, "The flash was updated with a new entry"); +} + +static void test_write_ok_full(void) +{ + struct vb2_context ctx; + uint8_t expected_flash[sizeof(fake_flash_region)]; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + + for (int entry = 0; entry < fake_flash_entry_count - 1; entry++) + memcpy(fake_flash_region + (entry * VB2_NVDATA_SIZE), + test_nvdata_16b, sizeof(test_nvdata_16b)); + + memcpy(expected_flash, test_nvdata2_16b, sizeof(test_nvdata2_16b)); + memset(expected_flash + VB2_NVDATA_SIZE, 0xff, + sizeof(expected_flash) - VB2_NVDATA_SIZE); + memcpy(ctx.nvdata, test_nvdata2_16b, sizeof(test_nvdata2_16b)); + + TEST_EQ(vb2_write_nv_storage_flashrom(&ctx), 0, + "Writing storage succeeds"); + TEST_EQ(memcmp(fake_flash_region, expected_flash, + sizeof(expected_flash)), + 0, + "The flash was erased and the new entry was placed at " + "the beginning"); +} + +static void test_write_fail_uninitialized(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + + TEST_NEQ(vb2_write_nv_storage_flashrom(&ctx), 0, + "Writing storage fails when the flash is erased"); +} + +static void test_write_fail_flashrom(void) +{ + struct vb2_context ctx; + + reset_test_data(&ctx, sizeof(test_nvdata_16b)); + memcpy(fake_flash_region, test_nvdata_16b, sizeof(test_nvdata_16b)); + mock_flashrom_fail = true; + + TEST_NEQ(vb2_write_nv_storage_flashrom(&ctx), 0, + "Writing storage fails when flashrom fails"); +} + +int main(int argc, char *argv[]) +{ + test_read_ok_beginning(); + test_read_ok_2ndentry(); + test_read_ok_full(); + test_read_fail_uninitialized(); + test_read_fail_flashrom(); + test_write_ok_beginning(); + test_write_ok_2ndentry(); + test_write_ok_full(); + test_write_fail_uninitialized(); + test_write_fail_flashrom(); + + return gTestSuccess ? 0 : 255; +} |