host/lib: add lightweight flashrom wrapper library

Lightweight wrapper around flashrom, exposing two APIs:

    flashrom_read(programmer, region, data_out, size_out)
    flashrom_write(programmer, region, data, size)

|region| can be NULL, in which case operate on the whole flash chip.

The intended usage of this wrapper library is to read/write VBNV from
SPI flash directly, avoiding the call thru mosys (which has deprecated
the command).  Bringing this logic into crossystem directly will also
help with expanding VBNV to 64-bytes.

BUG=chromium:1032351,chromium:1030473,chromium:789276
BRANCH=none
TEST=provided unit tests

Change-Id: I3997bd03a2db7e58e4e76fc200c637dd3b5b20a4
Signed-off-by: Jack Rosenthal <jrosenth@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2218888
diff --git a/Makefile b/Makefile
index d69a63a..19d586a 100644
--- a/Makefile
+++ b/Makefile
@@ -450,6 +450,7 @@
 	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 @@
 	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 @@
 	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 6c0bd16..8d69847 100644
--- a/firmware/2lib/include/2return_codes.h
+++ b/firmware/2lib/include/2return_codes.h
@@ -805,6 +805,9 @@
 	/* 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 0000000..061c5b8
--- /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 0000000..560fbb0
--- /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 0000000..f8dbd44
--- /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;
+}