cgpt: Add a callback to allow override of GPT entry priority

This can be used by implementations that want to request vboot to
favor a particular kernel entry for booting without affecting the
checks for rollback protection and image verification.

CQ-DEPEND=CL:274716, CL:274932, CL:275171
BUG=None
BRANCH=None
TEST=Compiles successfully. make -j runtests successful.

Change-Id: I6a4600020354f5d4118c17f083c353c2585c4181
Signed-off-by: Furquan Shaikh <furquan@google.com>
Reviewed-on: https://chromium-review.googlesource.com/274558
Reviewed-by: Furquan Shaikh <furquan@chromium.org>
Reviewed-by: Stefan Reinauer <reinauer@chromium.org>
Tested-by: Nicolas Boichat <drinkcat@chromium.org>
Commit-Queue: Nicolas Boichat <drinkcat@chromium.org>
Trybot-Ready: Nicolas Boichat <drinkcat@chromium.org>
diff --git a/Makefile b/Makefile
index 37ff4f7..e5535a7 100644
--- a/Makefile
+++ b/Makefile
@@ -459,6 +459,7 @@
 	firmware/lib/vboot_nvstorage.c \
 	firmware/stub/tpm_lite_stub.c \
 	firmware/stub/utility_stub.c \
+	firmware/stub/vboot_api_stub.c \
 	firmware/stub/vboot_api_stub_disk.c \
 	firmware/stub/vboot_api_stub_init.c \
 	firmware/stub/vboot_api_stub_sf.c \
@@ -487,6 +488,7 @@
 	firmware/lib/cgptlib/crc32.c \
 	firmware/lib/gpt_misc.c \
 	firmware/lib/utility_string.c \
+	firmware/stub/vboot_api_stub.c \
 	firmware/stub/vboot_api_stub_disk.c \
 	firmware/stub/vboot_api_stub_sf.c \
 	firmware/stub/utility_stub.c \
diff --git a/firmware/include/vboot_api.h b/firmware/include/vboot_api.h
index 38f9403..5310540 100644
--- a/firmware/include/vboot_api.h
+++ b/firmware/include/vboot_api.h
@@ -24,6 +24,8 @@
 #include <stdint.h>
 #include <stdlib.h>
 
+#include "gpt.h"
+
 /*****************************************************************************/
 /* Error codes */
 
@@ -1057,4 +1059,16 @@
  */
 VbError_t VbLockDevice(void);
 
+/**
+ * Check if the firmware wants to override GPT entry priority.
+ *
+ * In case of kernel entry, check if there is an override of priority
+ * available. This is used to select a particular partition to boot in the
+ * current boot cycle. Rollback protection, image verification and all other
+ * checks in LoadKernel still remain the same.
+ *
+ * @param e	Gpt Entry to check for priority override.
+ * @return 0 if no override, 1-15 for override priority.
+ */
+uint8_t VbExOverrideGptEntryPriority(const GptEntry *e);
 #endif  /* VBOOT_REFERENCE_VBOOT_API_H_ */
diff --git a/firmware/lib/cgptlib/cgptlib_internal.c b/firmware/lib/cgptlib/cgptlib_internal.c
index a389e69..2975630 100644
--- a/firmware/lib/cgptlib/cgptlib_internal.c
+++ b/firmware/lib/cgptlib/cgptlib_internal.c
@@ -370,6 +370,12 @@
 
 int GetEntryPriority(const GptEntry *e)
 {
+	int ret = VbExOverrideGptEntryPriority(e);
+
+	/* Ensure that the override priority is valid. */
+	if ((ret > 0) && (ret < 16))
+		return ret;
+
 	return (e->attrs.fields.gpt_att & CGPT_ATTRIBUTE_PRIORITY_MASK) >>
 		CGPT_ATTRIBUTE_PRIORITY_OFFSET;
 }
diff --git a/firmware/stub/vboot_api_stub.c b/firmware/stub/vboot_api_stub.c
index 6d82f12..7320b6c 100644
--- a/firmware/stub/vboot_api_stub.c
+++ b/firmware/stub/vboot_api_stub.c
@@ -168,3 +168,8 @@
 {
 	return 1;
 }
+
+uint8_t VbExOverrideGptEntryPriority(const GptEntry *e)
+{
+	return 0;
+}
diff --git a/tests/cgptlib_test.c b/tests/cgptlib_test.c
index 13244a5..6174752 100644
--- a/tests/cgptlib_test.c
+++ b/tests/cgptlib_test.c
@@ -53,6 +53,18 @@
 const char *progname = "CGPT-TEST";
 const char *command = "TEST";
 
+static int override_priority = 0;
+static int override_counter = 0;
+
+uint8_t VbExOverrideGptEntryPriority(const GptEntry *e)
+{
+	if (override_counter == 0)
+		return override_priority;
+
+	override_counter--;
+	return 0;
+}
+
 /*
  * Copy a random-for-this-program-only Guid into the dest. The num parameter
  * completely determines the Guid.
@@ -1064,16 +1076,49 @@
 	EXPECT(0xFFF0FFFFFFFFFFFFULL == e->attrs.whole);
 	EXPECT(0 == GetEntryPriority(e));
 
+	e->attrs.whole = 0x0000000000000000ULL;
+	SetEntryPriority(e, 15);
+	override_priority = 10;
+	EXPECT(0x000F000000000000ULL == e->attrs.whole);
+	EXPECT(10 == GetEntryPriority(e));
+	e->attrs.whole = 0xFFFFFFFFFFFFFFFFULL;
+	SetEntryPriority(e, 0);
+	EXPECT(0xFFF0FFFFFFFFFFFFULL == e->attrs.whole);
+	EXPECT(10 == GetEntryPriority(e));
+	override_priority = 0;
+
 	e->attrs.whole = 0xFFFFFFFFFFFFFFFFULL;
 	EXPECT(1 == GetEntrySuccessful(e));
 	EXPECT(15 == GetEntryPriority(e));
 	EXPECT(15 == GetEntryTries(e));
 
+	override_priority = 10;
+	e->attrs.whole = 0xFFFFFFFFFFFFFFFFULL;
+	EXPECT(1 == GetEntrySuccessful(e));
+	EXPECT(10 == GetEntryPriority(e));
+	EXPECT(15 == GetEntryTries(e));
+	override_priority = 0;
+
 	e->attrs.whole = 0x0123000000000000ULL;
 	EXPECT(1 == GetEntrySuccessful(e));
 	EXPECT(2 == GetEntryTries(e));
 	EXPECT(3 == GetEntryPriority(e));
 
+	override_priority = 10;
+	e->attrs.whole = 0x0123000000000000ULL;
+	EXPECT(1 == GetEntrySuccessful(e));
+	EXPECT(2 == GetEntryTries(e));
+	EXPECT(10 == GetEntryPriority(e));
+	override_priority = 0;
+
+	/* Invalid priority */
+	override_priority = 100;
+	e->attrs.whole = 0x0123000000000000ULL;
+	EXPECT(1 == GetEntrySuccessful(e));
+	EXPECT(2 == GetEntryTries(e));
+	EXPECT(3 == GetEntryPriority(e));
+	override_priority = 0;
+
 	return TEST_OK;
 }
 
@@ -1307,6 +1352,38 @@
 	return TEST_OK;
 }
 
+static int GptOverridePriorityTest(void)
+{
+	GptData *gpt = GetEmptyGptData();
+	GptEntry *e = (GptEntry *)(gpt->primary_entries);
+	uint64_t start, size;
+
+	/* Tries=nonzero is attempted just like success, but tries=0 isn't */
+	BuildTestGptData(gpt);
+	FillEntry(e + KERNEL_A, 1, 4, 1, 0);
+	FillEntry(e + KERNEL_B, 1, 3, 0, 2);
+	FillEntry(e + KERNEL_X, 1, 2, 0, 2);
+	RefreshCrc32(gpt);
+	GptInit(gpt);
+	gpt->modified = 0;  /* Nothing modified yet */
+
+	override_counter = 1;
+	override_priority = 15;
+
+	/* Kernel returned should be B instead of A */
+	EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, &start, &size));
+	EXPECT(KERNEL_B == gpt->current_kernel);
+
+	override_counter = 0;
+	override_priority = 0;
+
+	/* Now, we should get A */
+	EXPECT(GPT_SUCCESS == GptNextKernelEntry(gpt, &start, &size));
+	EXPECT(KERNEL_A == gpt->current_kernel);
+
+	return TEST_OK;
+}
+
 /*
  * Give an invalid kernel type, and expect GptUpdateKernelEntry() returns
  * GPT_ERROR_INVALID_UPDATE_TYPE.
@@ -1514,6 +1591,7 @@
 		{ TEST_CASE(GetNextPrioTest), },
 		{ TEST_CASE(GetNextTriesTest), },
 		{ TEST_CASE(GptUpdateTest), },
+		{ TEST_CASE(GptOverridePriorityTest), },
 		{ TEST_CASE(UpdateInvalidKernelTypeTest), },
 		{ TEST_CASE(DuplicateUniqueGuidTest), },
 		{ TEST_CASE(TestCrc32TestVectors), },