vboot: Explicitly return rollback error from vb2api_load_kernel

Previously, vb2api_load_kernel() collapsed all kernel loading errors
into generic VB2_ERROR_LK_INVALID_KERNEL_FOUND or
VB2_ERROR_LK_NO_KERNEL_FOUND. This prevented the caller from
identifying rollback protection failures.

Modify vb2api_load_kernel() to specifically translate
VB2_ERROR_AVB_ERROR_ROLLBACK_INDEX into a new VB2_ERROR_LK_ROLLBACK
error code. This allows the UI layer to display a specific error
message for rollback violations (e.g., "Outdated recovery image")
instead of a generic failure screen.

BUG=b:488131983
BRANCH=none
TEST=Verified that the "Outdated recovery image" screen is displayed
     when a rollback error occurs during recovery.

Cq-Depend: chromium:7666120
Change-Id: I9c0c090730fdad7dd4acc06bd83fb04919c2e1b7
Signed-off-by: Grzegorz Bernacki <bernacki@google.com>
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/vboot_reference/+/7666079
Reviewed-by: Julius Werner <jwerner@chromium.org>
Reviewed-by: Yu-Ping Wu <yupingso@chromium.org>
Tested-by: Tomasz Michalec <tmichalec@google.com>
Reviewed-by: Konrad Adamczyk <konrada@google.com>
Commit-Queue: Tomasz Michalec <tmichalec@google.com>
diff --git a/firmware/2lib/2load_kernel.c b/firmware/2lib/2load_kernel.c
index eec806e..c64f1bf 100644
--- a/firmware/2lib/2load_kernel.c
+++ b/firmware/2lib/2load_kernel.c
@@ -674,6 +674,7 @@
 	int found_partitions = 0;
 	uint32_t kernel_version;
 	vb2_error_t rv = VB2_ERROR_LK_NO_KERNEL_FOUND;
+	vb2_error_t active_slot_rv = VB2_ERROR_LK_NO_KERNEL_FOUND;
 
 	/* Read GPT data */
 	GptData gpt;
@@ -735,6 +736,10 @@
 			rv = VB2_ERROR_LK_INVALID_KERNEL_FOUND;
 		}
 
+		/* Store error from active slot */
+		if (found_partitions == 1)
+			active_slot_rv = rv;
+
 		if (rv == VB2_SUCCESS)
 			break;
 
@@ -743,7 +748,9 @@
 	}
 
 	if (rv) {
-		if (found_partitions > 0)
+		if (active_slot_rv == VB2_ERROR_AVB_ERROR_ROLLBACK_INDEX)
+			rv = VB2_ERROR_LK_ROLLBACK;
+		else if (found_partitions > 0)
 			rv = VB2_ERROR_LK_INVALID_KERNEL_FOUND;
 		else
 			rv = VB2_ERROR_LK_NO_KERNEL_FOUND;
diff --git a/firmware/2lib/2recovery_reasons.c b/firmware/2lib/2recovery_reasons.c
index 7590bf6..ce2e92c 100644
--- a/firmware/2lib/2recovery_reasons.c
+++ b/firmware/2lib/2recovery_reasons.c
@@ -164,6 +164,8 @@
 		return "Attempt to escape from NO_BOOT mode was detected";
 	/* 0x65 */ case VB2_RECOVERY_WIDEVINE_PREPARE:
 		return "Failed to prepare widevine";
+	/* 0x66 */ case VB2_RECOVERY_KERNEL_ROLLBACK:
+		return "Kernel version too old (rollback protection)";
 	/* 0x7f */ case VB2_RECOVERY_RW_UNSPECIFIED:
 		return "Unspecified/unknown error in RW firmware";
 	/* 0x81 */ case VB2_RECOVERY_DEPRECATED_KE_DM_VERITY:
diff --git a/firmware/2lib/include/2recovery_reasons.h b/firmware/2lib/include/2recovery_reasons.h
index 4dd2eaf..fb6c0dd 100644
--- a/firmware/2lib/include/2recovery_reasons.h
+++ b/firmware/2lib/include/2recovery_reasons.h
@@ -283,6 +283,9 @@
 	/* Failed to prepare the widevine functionality */
 	VB2_RECOVERY_WIDEVINE_PREPARE = 0x65,
 
+	/* Kernel version too old */
+	VB2_RECOVERY_KERNEL_ROLLBACK = 0x66,
+
 	/* Unspecified/unknown error in rewritable firmware */
 	VB2_RECOVERY_RW_UNSPECIFIED = 0x7f,
 
diff --git a/firmware/2lib/include/2return_codes.h b/firmware/2lib/include/2return_codes.h
index ec8a436..c35745c 100644
--- a/firmware/2lib/include/2return_codes.h
+++ b/firmware/2lib/include/2return_codes.h
@@ -785,6 +785,9 @@
 	/* No working block devices were found */
 	VB2_ERROR_LK_NO_DISK_FOUND = 0x100b3000,
 
+	/* Invalid kernel version (Rollback Protection) */
+	VB2_ERROR_LK_ROLLBACK = 0x100b4000,
+
 	/**********************************************************************
 	 * UI errors
 	 */
diff --git a/tests/vb2_load_android_tests.c b/tests/vb2_load_android_tests.c
index 2e7a67f..2e66922 100644
--- a/tests/vb2_load_android_tests.c
+++ b/tests/vb2_load_android_tests.c
@@ -42,8 +42,10 @@
 struct vendor_boot_img_hdr_v4 vendor_boot_hdr;
 struct boot_img_hdr_v4 init_boot_hdr;
 
+enum avb_fail {NO_FAIL, VERIFY_FAIL, ROLLBACK_FAIL};
+
 uint64_t rollback_value;
-bool avb_verification_fails;
+enum avb_fail avb_verification_fails;
 bool init_boot_missing, vendor_boot_missing;
 uint64_t sector_to_read;
 uint64_t ota_recovery_sector;
@@ -119,10 +121,15 @@
 	verify_data->cmdline = (char *)"";
 	*out_data = verify_data;
 
-	if (avb_verification_fails)
+	switch (avb_verification_fails) {
+	case VERIFY_FAIL:
 		return AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION;
-	else
+	case ROLLBACK_FAIL:
+		return AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX;
+	case NO_FAIL:
+	default:
 		return AVB_SLOT_VERIFY_RESULT_OK;
+	}
 }
 
 void avb_slot_verify_data_free(AvbSlotVerifyData *data)
@@ -179,7 +186,7 @@
 static void reset_mocks(void)
 {
 	rollback_value = 0x1;
-	avb_verification_fails = false;
+	avb_verification_fails = NO_FAIL;
 	init_boot_missing = false;
 	vendor_boot_missing = false;
 	ota_recovery_sector = OTA_RECOVERY_A_PART;
@@ -267,17 +274,31 @@
 
 	// AVB verification fails
 	reset_mocks();
-	avb_verification_fails = true;
+	avb_verification_fails = VERIFY_FAIL;
 	TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_INVALID_KERNEL_FOUND,
 		"AVB verification fails");
 
 	// AVB verification fails in developer mode
 	reset_mocks();
-	avb_verification_fails = true;
+	avb_verification_fails = VERIFY_FAIL;
 	SET_BOOT_MODE(ctx, VB2_BOOT_MODE_DEVELOPER);
 	TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_SUCCESS,
 		"AVB verification fails in developer mode");
 
+	// Rollback protection in developer mode
+	reset_mocks();
+	avb_verification_fails = ROLLBACK_FAIL;
+	SET_BOOT_MODE(ctx, VB2_BOOT_MODE_DEVELOPER);
+	TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_SUCCESS,
+		"Rollback protection in developer mode");
+
+	// Rollback protection in normal mode
+	reset_mocks();
+	avb_verification_fails = ROLLBACK_FAIL;
+	TEST_EQ(vb2api_load_kernel(ctx, &lkp, &disk_info), VB2_ERROR_LK_ROLLBACK,
+		"Rollback protection in normal mode");
+
+
 	// Missing init_boot partition
 	reset_mocks();
 	init_boot_missing = true;
@@ -323,6 +344,11 @@
 	TEST_PTR_EQ(lkp.disk_handle, (void *)CHILD_HANDLE, "  fill disk_handle when success");
 
 	reset_mocks();
+	avb_verification_fails = ROLLBACK_FAIL;
+	TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0), VB2_ERROR_LK_NO_KERNEL_FOUND,
+		"Boot ota_recovery_a");
+
+	reset_mocks();
 	ota_recovery_sector = OTA_RECOVERY_B_PART;
 	TEST_EQ(vb2api_load_nbr_kernel(ctx, &lkp, &disk_info, 0), VB2_SUCCESS,
 		"Boot ota_recovery_b");