diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | firmware/2lib/include/2return_codes.h | 3 | ||||
-rw-r--r-- | host/lib/flashrom.c | 158 | ||||
-rw-r--r-- | host/lib/include/flashrom.h | 50 | ||||
-rw-r--r-- | tests/vb2_host_flashrom_tests.c | 238 |
5 files changed, 453 insertions, 0 deletions
@@ -450,6 +450,7 @@ UTILLIB_SRCS = \ host/lib/crossystem.c \ host/lib/crypto.c \ host/lib/file_keys.c \ + host/lib/flashrom.c \ host/lib/fmap.c \ host/lib/host_common.c \ host/lib/host_key2.c \ @@ -509,9 +510,11 @@ HOSTLIB_SRCS = \ host/lib/crossystem.c \ host/lib/crypto.c \ host/lib/extract_vmlinuz.c \ + host/lib/flashrom.c \ host/lib/fmap.c \ host/lib/host_misc.c \ host/lib/subprocess.c \ + host/lib21/host_misc.c \ ${TLCL_SRCS} HOSTLIB_OBJS = ${HOSTLIB_SRCS:%.c=${BUILD}/%.o} @@ -706,6 +709,7 @@ TEST2X_NAMES = \ tests/vb2_crypto_tests \ tests/vb2_ec_sync_tests \ tests/vb2_gbb_tests \ + tests/vb2_host_flashrom_tests \ tests/vb2_host_key_tests \ tests/vb2_kernel_tests \ tests/vb2_misc_tests \ diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h index 6c0bd164..8d69847c 100644 --- a/firmware/2lib/include/2return_codes.h +++ b/firmware/2lib/include/2return_codes.h @@ -805,6 +805,9 @@ enum vb2_return_code { /* Unable to convert string to struct vb_id */ VB2_ERROR_STR_TO_ID, + /* Flashrom exited with failure status */ + VB2_ERROR_FLASHROM, + /********************************************************************** * Errors generated by host library key functions */ diff --git a/host/lib/flashrom.c b/host/lib/flashrom.c new file mode 100644 index 00000000..061c5b84 --- /dev/null +++ b/host/lib/flashrom.c @@ -0,0 +1,158 @@ +/* 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. + */ + +/* For strdup */ +#define _POSIX_C_SOURCE 200809L + +#include <fcntl.h> +#include <limits.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "2api.h" +#include "2return_codes.h" +#include "host_misc.h" +#include "flashrom.h" +#include "subprocess.h" + +#define FLASHROM_EXEC_NAME "flashrom" + +/** + * Helper to create a temporary file, and optionally write some data + * into it. + * + * @param data If data needs to be written to the file, a + * pointer to the buffer. Pass NULL to just + * create an empty temporary file. + * @param data_size The size of the buffer to write, if applicable. + * @param path_out An output pointer for the filename. Caller + * should free. + * + * @return VB2_SUCCESS on success, or a relevant error. + */ +static vb2_error_t write_temp_file(const uint8_t *data, uint32_t data_size, + char **path_out) +{ + int fd; + ssize_t write_rv; + vb2_error_t rv; + char *path; + + *path_out = NULL; + path = strdup(P_tmpdir "/vb2_flashrom.XXXXXX"); + + fd = mkstemp(path); + if (fd < 0) { + rv = VB2_ERROR_WRITE_FILE_OPEN; + goto fail; + } + + while (data && data_size > 0) { + write_rv = write(fd, data, data_size); + if (write_rv < 0) { + close(fd); + unlink(path); + rv = VB2_ERROR_WRITE_FILE_DATA; + goto fail; + } + + data_size -= write_rv; + data += write_rv; + } + + close(fd); + *path_out = path; + return VB2_SUCCESS; + + fail: + free(path); + return rv; +} + +static vb2_error_t run_flashrom(const char *const argv[]) +{ + int status = subprocess_run(argv, &subprocess_null, &subprocess_null, + &subprocess_null); + if (status) { + fprintf(stderr, "Flashrom invocation failed (exit status %d):", + status); + + for (const char *const *argp = argv; *argp; argp++) + fprintf(stderr, " %s", *argp); + + fprintf(stderr, "\n"); + return VB2_ERROR_FLASHROM; + } + + return VB2_SUCCESS; +} + +vb2_error_t flashrom_read(const char *programmer, const char *region, + uint8_t **data_out, uint32_t *size_out) +{ + char *tmpfile; + char region_param[PATH_MAX]; + vb2_error_t rv; + + *data_out = NULL; + *size_out = 0; + + VB2_TRY(write_temp_file(NULL, 0, &tmpfile)); + + if (region) + snprintf(region_param, sizeof(region_param), "%s:%s", region, + tmpfile); + + const char *const argv[] = { + FLASHROM_EXEC_NAME, + "-p", + programmer, + "-r", + region ? "-i" : tmpfile, + region ? region_param : NULL, + NULL, + }; + + rv = run_flashrom(argv); + if (rv == VB2_SUCCESS) + rv = vb2_read_file(tmpfile, data_out, size_out); + + unlink(tmpfile); + free(tmpfile); + return rv; +} + +vb2_error_t flashrom_write(const char *programmer, const char *region, + uint8_t *data, uint32_t size) +{ + char *tmpfile; + char region_param[PATH_MAX]; + vb2_error_t rv; + + VB2_TRY(write_temp_file(data, size, &tmpfile)); + + if (region) + snprintf(region_param, sizeof(region_param), "%s:%s", region, + tmpfile); + + const char *const argv[] = { + FLASHROM_EXEC_NAME, + "-p", + programmer, + "-w", + region ? "-i" : tmpfile, + region ? region_param : NULL, + NULL, + }; + + rv = run_flashrom(argv); + unlink(tmpfile); + free(tmpfile); + return rv; +} diff --git a/host/lib/include/flashrom.h b/host/lib/include/flashrom.h new file mode 100644 index 00000000..560fbb0e --- /dev/null +++ b/host/lib/include/flashrom.h @@ -0,0 +1,50 @@ +/* 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. + * + * Host utilites to execute flashrom command. + */ + +#include <stdint.h> + +#include "2return_codes.h" + +#define FLASHROM_PROGRAMMER_INTERNAL_AP "host" +#define FLASHROM_PROGRAMMER_INTERNAL_EC "ec" + +/** + * Read using flashrom into an allocated buffer. + * + * @param programmer The name of the programmer to use. There are + * named constants FLASHROM_PROGRAMMER_INTERNAL_AP + * and FLASHROM_PROGRAMMER_INTERNAL_EC available + * for the AP and EC respectively, or a custom + * programmer string can be provided. + * @param region The name of the fmap region to read, or NULL to + * read the entire flash chip. + * @param data_out Output parameter of allocated buffer to read into. + * The caller should free the buffer. + * @param size_out Output parameter of buffer size. + * + * @return VB2_SUCCESS on success, or a relevant error. + */ +vb2_error_t flashrom_read(const char *programmer, const char *region, + uint8_t **data_out, uint32_t *size_out); + +/** + * Write using flashrom from a buffer. + * + * @param programmer The name of the programmer to use. There are + * named constants FLASHROM_PROGRAMMER_INTERNAL_AP + * and FLASHROM_PROGRAMMER_INTERNAL_EC available + * for the AP and EC respectively, or a custom + * programmer string can be provided. + * @param region The name of the fmap region to write, or NULL to + * write the entire flash chip. + * @param data The buffer to write. + * @param size The size of the buffer to write. + * + * @return VB2_SUCCESS on success, or a relevant error. + */ +vb2_error_t flashrom_write(const char *programmer, const char *region, + uint8_t *data, uint32_t size); diff --git a/tests/vb2_host_flashrom_tests.c b/tests/vb2_host_flashrom_tests.c new file mode 100644 index 00000000..f8dbd442 --- /dev/null +++ b/tests/vb2_host_flashrom_tests.c @@ -0,0 +1,238 @@ +/* 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 host flashrom utilities. + */ + +/* For strdup */ +#define _POSIX_C_SOURCE 200809L + +#include <fcntl.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "2common.h" +#include "2return_codes.h" +#include "host_misc.h" +#include "flashrom.h" +#include "subprocess.h" +#include "test_common.h" + +#define MOCK_TMPFILE_NAME "/tmp/vb2_unittest" +#define MOCK_ROM_CONTENTS "bloop123" + +static bool flashrom_mock_success = true; +static enum { FLASHROM_NONE, FLASHROM_READ, FLASHROM_WRITE } captured_operation; +static const char *captured_op_filename; +static const char *captured_region_param; +static const char *captured_programmer; +static uint8_t *captured_rom_contents; +static uint32_t captured_rom_size; + +/* Mocked mkstemp for tests. */ +int mkstemp(char *template_name) +{ + strncpy(template_name, MOCK_TMPFILE_NAME, strlen(template_name)); + return open(template_name, O_RDWR | O_CREAT | O_TRUNC, 0666); +} + +/* Mocked subprocess_run for tests. */ +int subprocess_run(const char *const argv[], + struct subprocess_target *input, + struct subprocess_target *output, + struct subprocess_target *error) +{ + int argc; + int opt; + int rv; + + /* Reset static variables to their defaults. */ + captured_operation = FLASHROM_NONE; + captured_op_filename = NULL; + captured_region_param = NULL; + captured_programmer = NULL; + captured_rom_contents = NULL; + captured_rom_size = 0; + optind = 0; + + /* Count the number of arguments, a required formalism for + getopt. */ + for (argc = 0; argv[argc]; argc++) + continue; + + /* We only understand the subset of arguments used by the + wrapper library. If it's updated to support more modes of + operation, this unit test code should be updated too. */ + while ((opt = getopt(argc, (char *const *)argv, ":p:r:w:i:")) != -1) { + /* Always consume the next argument if it does not + start with a dash. We have to muck with getopt's + global variables to make this happen. */ + if (opt == ':' && argv[optind] && argv[optind][0] != '-') { + optarg = strdup(argv[optind]); + optind++; + opt = optopt; + } else if (optarg && optarg[0] == '-') { + optarg = NULL; + optind--; + } else if (optarg) { + optarg = strdup(optarg); + } + + switch (opt) { + case 'p': + captured_programmer = optarg; + break; + case 'r': + captured_operation = FLASHROM_READ; + captured_op_filename = optarg; + break; + case 'w': + captured_operation = FLASHROM_WRITE; + captured_op_filename = optarg; + break; + case 'i': + captured_region_param = optarg; + break; + default: + return 1; + } + } + + if (optind != argc) { + /* Extra arguments we don't understand. */ + return 1; + } + + rv = !flashrom_mock_success; + + if (captured_operation == FLASHROM_READ) { + /* Write the mocked string we read from the ROM. */ + rv |= vb2_write_file(MOCK_TMPFILE_NAME, MOCK_ROM_CONTENTS, + strlen(MOCK_ROM_CONTENTS)); + } else if (captured_operation == FLASHROM_WRITE) { + /* Capture the buffer contents we wrote to the ROM. */ + rv |= vb2_read_file(MOCK_TMPFILE_NAME, &captured_rom_contents, + &captured_rom_size); + } + + return rv; +} + +static void test_read_whole_chip(void) +{ + uint8_t *buf; + uint32_t buf_sz; + + TEST_SUCC(flashrom_read("someprog", NULL, &buf, &buf_sz), + "Flashrom read succeeds"); + TEST_STR_EQ(captured_programmer, "someprog", + "Using specified programmer"); + TEST_EQ(captured_operation, FLASHROM_READ, "Doing a read operation"); + TEST_STR_EQ(captured_op_filename, MOCK_TMPFILE_NAME, + "Reading to correct file"); + TEST_PTR_EQ(captured_region_param, NULL, "Not operating on a region"); + TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size"); + TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz), + "Buffer has correct contents"); + + free(buf); +} + +static void test_read_region(void) +{ + uint8_t *buf; + uint32_t buf_sz; + + TEST_SUCC(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz), + "Flashrom read succeeds"); + TEST_STR_EQ(captured_programmer, "someprog", + "Using specified programmer"); + TEST_EQ(captured_operation, FLASHROM_READ, "Doing a read operation"); + TEST_PTR_EQ(captured_op_filename, NULL, + "Not doing a read of the whole ROM"); + TEST_STR_EQ(captured_region_param, "SOME_REGION:" MOCK_TMPFILE_NAME, + "Reading to correct file and from correct region"); + TEST_EQ(buf_sz, strlen(MOCK_ROM_CONTENTS), "Contents correct size"); + TEST_SUCC(memcmp(buf, MOCK_ROM_CONTENTS, buf_sz), + "Buffer has correct contents"); + + free(buf); +} + +static void test_read_failure(void) +{ + uint8_t *buf; + uint32_t buf_sz; + + flashrom_mock_success = false; + TEST_NEQ(flashrom_read("someprog", "SOME_REGION", &buf, &buf_sz), + VB2_SUCCESS, "Flashrom read fails"); + flashrom_mock_success = true; +} + +static void test_write_whole_chip(void) +{ + uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1]; + + memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf)); + + TEST_SUCC(flashrom_write("someprog", NULL, buf, sizeof(buf)), + "Flashrom write succeeds"); + TEST_STR_EQ(captured_programmer, "someprog", + "Using specified programmer"); + TEST_EQ(captured_operation, FLASHROM_WRITE, "Doing a write operation"); + TEST_STR_EQ(captured_op_filename, MOCK_TMPFILE_NAME, + "Writing to correct file"); + TEST_PTR_EQ(captured_region_param, NULL, "Not operating on a region"); + TEST_EQ(captured_rom_size, strlen(MOCK_ROM_CONTENTS), + "Contents correct size"); + TEST_SUCC(memcmp(captured_rom_contents, MOCK_ROM_CONTENTS, + captured_rom_size), "Buffer has correct contents"); +} + +static void test_write_region(void) +{ + uint8_t buf[sizeof(MOCK_ROM_CONTENTS) - 1]; + + memcpy(buf, MOCK_ROM_CONTENTS, sizeof(buf)); + + TEST_SUCC(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)), + "Flashrom write succeeds"); + TEST_STR_EQ(captured_programmer, "someprog", + "Using specified programmer"); + TEST_EQ(captured_operation, FLASHROM_WRITE, "Doing a write operation"); + TEST_PTR_EQ(captured_op_filename, NULL, + "Not doing a write of the whole ROM"); + TEST_STR_EQ(captured_region_param, "SOME_REGION:" MOCK_TMPFILE_NAME, + "Writing to correct file and from correct region"); + TEST_EQ(captured_rom_size, strlen(MOCK_ROM_CONTENTS), + "Contents correct size"); + TEST_SUCC(memcmp(captured_rom_contents, MOCK_ROM_CONTENTS, + captured_rom_size), "Buffer has correct contents"); +} + +static void test_write_failure(void) +{ + uint8_t buf[20] = { 0 }; + + flashrom_mock_success = false; + TEST_NEQ(flashrom_write("someprog", "SOME_REGION", buf, sizeof(buf)), + VB2_SUCCESS, "Flashrom write fails"); + flashrom_mock_success = true; +} + +int main(int argc, char *argv[]) +{ + test_read_whole_chip(); + test_read_region(); + test_read_failure(); + test_write_whole_chip(); + test_write_region(); + test_write_failure(); + + return gTestSuccess ? 0 : 255; +} |