futility: update: load quirks from firmware image CBFS file

The firmware updater now looks at CBFS 'FW_MAIN_A' (RW A) and if a text
file 'updater_quirks' is found, the contents will be fetched to setup
default quirks.

This helps sharing same customization across multiple firmware images
(for different models) shared by same unibuild OS image.  Without that,
we have to maintain a large list of hard-coded model names in firmware
updater source.

BRANCH=none
BUG=b:169284414
TEST=make runtests

Signed-off-by: Hung-Te Lin <hungte@chromium.org>
Change-Id: I938bffe9f16bc3adee0dc3efb6976efe581c6d8c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/2426093
Reviewed-by: Karthikeyan Ramasubramanian <kramasub@chromium.org>
diff --git a/futility/updater.c b/futility/updater.c
index fd13bb8..71a498e 100644
--- a/futility/updater.c
+++ b/futility/updater.c
@@ -1288,10 +1288,15 @@
 				const struct updater_config_arguments *arg)
 {
 	int errorcnt = 0;
-	const char *quirks = updater_get_default_quirks(cfg);
+	const char *model_quirks = updater_get_model_quirks(cfg);
+	char *cbfs_quirks = updater_get_cbfs_quirks(cfg);
 
-	if (quirks)
-		errorcnt += !!setup_config_quirks(quirks, cfg);
+	if (model_quirks)
+		errorcnt += !!setup_config_quirks(model_quirks, cfg);
+	if (cbfs_quirks) {
+		errorcnt += !!setup_config_quirks(cbfs_quirks, cfg);
+		free(cbfs_quirks);
+	}
 	if (arg->quirks)
 		errorcnt += !!setup_config_quirks(arg->quirks, cfg);
 	return errorcnt;
diff --git a/futility/updater.h b/futility/updater.h
index 3ed8039..a4f5580 100644
--- a/futility/updater.h
+++ b/futility/updater.h
@@ -175,10 +175,16 @@
 int get_system_property(enum system_property_type property_type,
 			struct updater_config *cfg);
 /*
- * Gets the default quirk config string for target image.
+ * Gets the default quirk config string from target image name.
  * Returns a string (in same format as --quirks) to load or NULL if no quirks.
  */
-const char * const updater_get_default_quirks(struct updater_config *cfg);
+const char * const updater_get_model_quirks(struct updater_config *cfg);
+
+/*
+ * Gets the quirk config string from target image CBFS.
+ * Returns a string (in same format as --quirks) to load or NULL if no quirks.
+ */
+char * updater_get_cbfs_quirks(struct updater_config *cfg);
 
 /*
  * Overrides signature id if the device was shipped with known
diff --git a/futility/updater_quirks.c b/futility/updater_quirks.c
index e4056b0..b3dea93 100644
--- a/futility/updater_quirks.c
+++ b/futility/updater_quirks.c
@@ -476,10 +476,10 @@
 }
 
 /*
- * Gets the default quirk config string for target image.
+ * Gets the default quirk config string from target image name.
  * Returns a string (in same format as --quirks) to load or NULL if no quirks.
  */
-const char * const updater_get_default_quirks(struct updater_config *cfg)
+const char * const updater_get_model_quirks(struct updater_config *cfg)
 {
 	const char *pattern = cfg->image.ro_version;
 	int i;
@@ -500,6 +500,51 @@
 }
 
 /*
+ * Gets the quirk config string from target image CBFS.
+ * Returns a string (in same format as --quirks) to load or NULL if no quirks.
+ */
+char *updater_get_cbfs_quirks(struct updater_config *cfg)
+{
+	const char *entry_name = "updater_quirks";
+	const char *cbfs_region = "FW_MAIN_A";
+	struct firmware_section cbfs_section;
+
+	/* Before invoking cbfstool, try to search for CBFS file name. */
+	find_firmware_section(&cbfs_section, &cfg->image, cbfs_region);
+	if (!cbfs_section.size || !memmem(cbfs_section.data, cbfs_section.size,
+					  entry_name, strlen(entry_name))) {
+		if (!cbfs_section.size)
+			VB2_DEBUG("Missing region: %s\n", cbfs_region);
+		else
+			VB2_DEBUG("Cannot find entry: %s\n", entry_name);
+		return NULL;
+	}
+
+	const char *tmp_path = get_firmware_image_temp_file(
+			&cfg->image, &cfg->tempfiles);
+	uint8_t *data = NULL;
+	uint32_t size = 0;
+
+	/* Although the name exists, it may not be a real file. */
+	if (!cbfs_file_exists(tmp_path, cbfs_region, entry_name)) {
+		VB2_DEBUG("Found string '%s' but not a file.\n", entry_name);
+		return NULL;
+	}
+
+	VB2_DEBUG("Found %s from CBFS %s\n", entry_name, cbfs_region);
+	tmp_path = cbfs_extract_file(tmp_path, cbfs_region, entry_name,
+				     &cfg->tempfiles);
+	if (!tmp_path ||
+	    vb2_read_file(tmp_path, &data, &size) != VB2_SUCCESS) {
+		ERROR("Failed to read [%s] from CBFS [%s].\n",
+		      entry_name, cbfs_region);
+		return NULL;
+	}
+	VB2_DEBUG("Got quirks (%u bytes): %s\n", size, data);
+	return (char *)data;
+}
+
+/*
  * Overrides signature id if the device was shipped with known
  * special rootkey.
  */
diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh
index 655bab4..ae4a362 100755
--- a/tests/futility/test_update.sh
+++ b/tests/futility/test_update.sh
@@ -480,4 +480,16 @@
 		"${TMP}.from.smm" "${TMP}.expected.full_smm" \
 		-i "${TO_IMAGE}" --wp=0 --sys_props 0,0x10001,1 \
 		--quirks eve_smm_store
+
+	echo "min_platform_version=3" >"${TMP}.quirk"
+	cp -f "${TO_IMAGE}" "${TO_IMAGE}.quirk"
+	${FUTILITY} dump_fmap -x "${TO_IMAGE}" "BOOT_STUB:${TMP}.cbfs"
+	# Create a fake CBFS using FW_MAIN_A size.
+	truncate -s $((0x000dffc0)) "${TMP}.cbfs"
+	${FUTILITY} load_fmap "${TO_IMAGE}.quirk" "FW_MAIN_A:${TMP}.cbfs"
+	cbfstool "${TO_IMAGE}.quirk" add -r FW_MAIN_A -n updater_quirks \
+		-f "${TMP}.quirk" -t raw
+	test_update "Full update (failure by CBFS quirks)" \
+		"${FROM_IMAGE}" "!Need platform version >= 3 (current is 2)" \
+		-i "${TO_IMAGE}.quirk" --wp=0 --sys_props 0,0x10001,1,2
 fi