futility: gscvd: Add -G flag for GBB ranges

This patch adds a new `--add_gbb`/`-G` flag to the `gscvd` command that
can be used as a shorthand option to add the `GBB` FMAP section to the
covered ranges. When adding the GBB, it is important that we exclude the
HWID and HWID digest from the covered ranges, because they can vary
between units and it would be too cumbersome to sign every possible HWID
separately. Figuring out the right ranges to pass that exclude these
individual fields of the structure manually from the build scripts would
be very awkward, so let's add this option here where we can write the
logic cleanly with C code and have all the vboot data structure
definitions readily available.

Also do some minor option parsing cleanups.

BRANCH=none
BUG=b:229015103
TEST=Created GSCVDs with this flag, manually confirmed with a hex editor
that the correct ranges were created.

Signed-off-by: Julius Werner <jwerner@chromium.org>
Change-Id: I3bef5355506d831353afe6f534ae9303334d014c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/3653203
Reviewed-by: Vadim Bendebury <vbendeb@chromium.org>
diff --git a/futility/cmd_gscvd.c b/futility/cmd_gscvd.c
index d6187c9..659716a 100644
--- a/futility/cmd_gscvd.c
+++ b/futility/cmd_gscvd.c
@@ -60,6 +60,7 @@
 	/* name       hasarg *flag  val */
 	{"outfile",       1, NULL, OPT_OUTFILE},
 	{"ranges",        1, NULL, 'R'},
+	{"add_gbb",       0, NULL, 'G'},
 	{"board_id",      1, NULL, 'b'},
 	{"root_pub_key",  1, NULL, 'r'},
 	{"keyblock",      1, NULL, 'k'},
@@ -68,7 +69,7 @@
 	{}
 };
 
-static const char *short_opts = "R:b:hk:p:r:";
+static const char *short_opts = "R:Gb:hk:p:r:";
 
 static const char usage[] =
 	"\n"
@@ -82,6 +83,11 @@
 	"                                     hex tuples <offset>:<size>, the\n"
 	"                                     areas of the RO covered by the\n"
 	"                                     signature\n"
+	"  -G|--add_gbb                     Add the `GBB` FMAP section to the\n"
+	"                                     ranges covered by the signature.\n"
+	"                                     This option takes special care\n"
+	"                                     to exclude the HWID (and its\n"
+	"                                     digest) from this range.\n"
 	"  -b|--board_id  <hex value>      The Board ID of the board for which\n"
 	"                                     the image is being signed\n"
 	"  -r|--root_pub_key  <file>        The main public key, in .vbpubk\n"
@@ -285,7 +291,6 @@
 		return -1;
 	}
 
-	output->range_count = 0;
 	cursor = str;
 	do {
 		char *colon;
@@ -335,6 +340,71 @@
 }
 
 /**
+ * Add GBB to ranges.
+ *
+ * Splits the `GBB` FMAP section into separate ranges to exclude the HWID string
+ * and the `hwid_digest` field in the header. Will also exclude the empty area
+ * behind the end of the actual GBB data.
+ *
+ * @param ranges pointer to the ranges container
+ * @param file   pointer to the AP firmware file layout descriptor
+ */
+static int add_gbb(struct gscvd_ro_ranges *ranges, const struct file_buf *file)
+{
+	FmapAreaHeader *area;
+
+	if (!fmap_find_by_name(file->data, file->len, NULL, "GBB", &area)) {
+		ERROR("Could not find a GBB area in the FMAP.\n");
+		return 1;
+	}
+
+	struct vb2_gbb_header *gbb = (void *)file->data + area->area_offset;
+	uint32_t maxlen;
+
+	if (!futil_valid_gbb_header(gbb, area->area_size, &maxlen)) {
+		ERROR("GBB is invalid.\n");
+		return 1;
+	}
+
+	/*
+	 * This implementation relies on the fact that no meaningful fields come
+	 * after the `hwid_digest` field in the header. If we ever make new GBB
+	 * versions that add more fields, the code below needs to be adapted.
+	 * Older versions than 1.2 or GBBs with a bmpblk are not expected with
+	 * GSCVD images.
+	 */
+	if (gbb->major_version != 1 || gbb->minor_version != 2 ||
+	    gbb->bmpfv_size != 0) {
+		ERROR("Unsupported GBB version.\n");
+		return 1;
+	}
+
+	uint32_t lower_key_offset = VB2_MIN(gbb->rootkey_offset,
+					    gbb->recovery_key_offset);
+	if (gbb->hwid_offset > lower_key_offset) {
+		ERROR("Weird GBB layout (HWID should come first)\n");
+		return 1;
+	}
+
+	if (ranges->range_count >= ARRAY_SIZE(ranges->ranges) - 2) {
+		ERROR("Too many ranges, can't fit GBB!\n");
+		return 1;
+	}
+
+	ranges->ranges[ranges->range_count].offset = area->area_offset;
+	ranges->ranges[ranges->range_count].size =
+				offsetof(struct vb2_gbb_header, hwid_digest);
+	ranges->range_count++;
+
+	ranges->ranges[ranges->range_count].offset = area->area_offset +
+						     lower_key_offset;
+	ranges->ranges[ranges->range_count].size = maxlen - lower_key_offset;
+	ranges->range_count++;
+
+	return 0;
+}
+
+/**
  * Calculate hash of the RO ranges.
  *
  * @param ap_firmware_file  pointer to the AP firmware file layout descriptor
@@ -887,6 +957,7 @@
 {
 	int i;
 	int longindex;
+	bool do_gbb = false;
 	char *infile = NULL;
 	char *outfile = NULL;
 	char *work_file = NULL;
@@ -915,6 +986,9 @@
 				errorcount++;
 			}
 			break;
+		case 'G':
+			do_gbb = true;
+			break;
 		case 'b': {
 			char *e;
 			long long bid;
@@ -977,15 +1051,36 @@
 		return validate_gscvd(argc - 1, argv + 1);
 
 	if (optind != (argc - 1)) {
-		ERROR("Misformatted command line\n%s\n", usage);
-		return 1;
+		ERROR("Misformatted command line\n");
+		goto usage_out;
 	}
 
-	if (errorcount || !ranges.range_count || !root_pubk || !kblock ||
-	    !plat_privk || (board_id == UINT32_MAX)) {
-		/* Error message(s) should have been printed by now. */
-		ERROR("%s\n", usage);
-		return 1;
+	if (errorcount) /* Error message(s) should have been printed by now. */
+		goto usage_out;
+
+	if (!root_pubk) {
+		ERROR("Missing --root_pub_key argument\n");
+		goto usage_out;
+	}
+
+	if (!kblock) {
+		ERROR("Missing --keyblock argument\n");
+		goto usage_out;
+	}
+
+	if (!plat_privk) {
+		ERROR("Missing --platform_priv argument\n");
+		goto usage_out;
+	}
+
+	if (board_id == UINT32_MAX) {
+		ERROR("Missing --board_id argument\n");
+		goto usage_out;
+	}
+
+	if (!ranges.range_count && !do_gbb) {
+		ERROR("Missing --ranges argument\n");
+		goto usage_out;
 	}
 
 	infile = argv[optind];
@@ -1009,6 +1104,9 @@
 		if (load_ap_firmware(work_file, &ap_firmware_file, FILE_RW))
 			break;
 
+		if (do_gbb && add_gbb(&ranges, &ap_firmware_file))
+			break;
+
 		if (verify_ranges(&ranges, &ap_firmware_file))
 			break;
 
@@ -1036,6 +1134,10 @@
 					   ap_firmware_file.len);
 
 	return rv;
+
+usage_out:
+	fputs(usage, stderr);
+	return 1;
 }
 
 DECLARE_FUTIL_COMMAND(gscvd, do_gscvd, VBOOT_VERSION_2_1,