Index: firmware/lib/vboot_kernel.c |
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c |
index bd8865bd6e2bb72e0367dc41d9827d518daf5cb2..2fbcaa847c68ad16a1f8078be64c7e79bf26840e 100644 |
--- a/firmware/lib/vboot_kernel.c |
+++ b/firmware/lib/vboot_kernel.c |
@@ -18,6 +18,12 @@ |
#define KBUF_SIZE 65536 /* Bytes to read at start of kernel partition */ |
+typedef enum BootMode { |
+ kBootNormal, /* Normal firmware */ |
+ kBootDev, /* Dev firmware AND dev switch is on */ |
+ kBootRecovery /* Recovery firmware, regardless of dev switch position */ |
+} BootMode; |
+ |
/* Allocates and reads GPT data from the drive. The sector_bytes and |
* drive_sectors fields should be filled on input. The primary and |
@@ -112,6 +118,7 @@ int WriteAndFreeGptData(GptData* gptdata) { |
__pragma(warning(disable: 4127)) |
int LoadKernel(LoadKernelParams* params) { |
+ VbNvContext* vnc = params->nv_context; |
VbPublicKey* kernel_subkey; |
GptData gpt; |
uint64_t part_start, part_size; |
@@ -120,13 +127,22 @@ int LoadKernel(LoadKernelParams* params) { |
uint8_t* kbuf = NULL; |
int found_partitions = 0; |
int good_partition = -1; |
+ int good_partition_key_block_valid = 0; |
uint32_t tpm_version = 0; |
uint64_t lowest_version = 0xFFFFFFFF; |
- int is_dev; |
- int is_rec; |
- int is_normal; |
+ int rec_switch, dev_switch; |
+ BootMode boot_mode; |
uint32_t status; |
+ /* TODO: differentiate between finding an invalid kernel (found_partitions>0) |
+ * and not finding one at all. Right now we treat them the same, and return |
+ * LOAD_KERNEL_INVALID for both. */ |
+ int retval = LOAD_KERNEL_INVALID; |
+ int recovery = VBNV_RECOVERY_RO_UNSPECIFIED; |
+ |
+ /* Setup NV storage */ |
+ VbNvSetup(vnc); |
+ |
/* Sanity Checks */ |
if (!params || |
!params->bytes_per_lba || |
@@ -134,7 +150,7 @@ int LoadKernel(LoadKernelParams* params) { |
!params->kernel_buffer || |
!params->kernel_buffer_size) { |
VBDEBUG(("LoadKernel() called with invalid params\n")); |
- return LOAD_KERNEL_INVALID; |
+ goto LoadKernelExit; |
} |
/* Initialization */ |
@@ -143,20 +159,28 @@ int LoadKernel(LoadKernelParams* params) { |
kbuf_sectors = KBUF_SIZE / blba; |
if (0 == kbuf_sectors) { |
VBDEBUG(("LoadKernel() called with sector size > KBUF_SIZE\n")); |
- return LOAD_KERNEL_INVALID; |
+ goto LoadKernelExit; |
} |
- is_rec = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0); |
- if (is_rec || (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags)) { |
- /* Recovery or developer firmware, so accurately represent the |
- * state of the developer switch for the purposes of verified boot. */ |
- is_dev = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0); |
- } else { |
- /* Normal firmware always does a fully verified boot regardless of |
- * the state of the developer switch. */ |
- is_dev = 0; |
+ rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0); |
+ dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0); |
+ |
+ if (rec_switch) |
+ boot_mode = kBootRecovery; |
+ else if (BOOT_FLAG_DEV_FIRMWARE & params->boot_flags) |
+ if (!dev_switch) { |
+ /* Dev firmware should be signed such that it never boots with the dev |
+ * switch is off; so something is terribly wrong. */ |
+ VBDEBUG(("LoadKernel() called with dev firmware but dev switch off\n")); |
+ recovery = VBNV_RECOVERY_RW_DEV_MISMATCH; |
+ goto LoadKernelExit; |
+ } |
+ boot_mode = kBootDev; |
+ else { |
+ /* Normal firmware */ |
+ boot_mode = kBootNormal; |
+ dev_switch = 0; /* Always do a fully verified boot */ |
} |
- is_normal = (!is_dev && !is_rec); |
/* Clear output params in case we fail */ |
params->partition_number = 0; |
@@ -164,22 +188,23 @@ int LoadKernel(LoadKernelParams* params) { |
params->bootloader_size = 0; |
/* Let the TPM know if we're in recovery mode */ |
- if (is_rec) { |
- if (0 != RollbackKernelRecovery(is_dev)) { |
+ if (kBootRecovery == boot_mode) { |
+ if (0 != RollbackKernelRecovery(dev_switch)) { |
VBDEBUG(("Error setting up TPM for recovery kernel\n")); |
/* Ignore return code, since we need to boot recovery mode to |
* fix the TPM. */ |
} |
- } |
- |
- if (is_normal) { |
+ } else { |
/* Read current kernel key index from TPM. Assumes TPM is already |
* initialized. */ |
status = RollbackKernelRead(&tpm_version); |
if (0 != status) { |
VBDEBUG(("Unable to get kernel versions from TPM\n")); |
- return (status == TPM_E_MUST_REBOOT ? |
- LOAD_KERNEL_REBOOT : LOAD_KERNEL_RECOVERY); |
+ if (status == TPM_E_MUST_REBOOT) |
+ retval = LOAD_KERNEL_REBOOT; |
+ else |
+ recovery = VBNV_RECOVERY_RW_TPM_ERROR; |
+ goto LoadKernelExit; |
} |
} |
@@ -213,6 +238,7 @@ int LoadKernel(LoadKernelParams* params) { |
uint64_t body_offset; |
uint64_t body_offset_sectors; |
uint64_t body_sectors; |
+ int key_block_valid = 1; |
VBDEBUG(("Found kernel entry at %" PRIu64 " size %" PRIu64 "\n", |
part_start, part_size)); |
@@ -220,7 +246,7 @@ int LoadKernel(LoadKernelParams* params) { |
/* Found at least one kernel partition. */ |
found_partitions++; |
- /* Read the first part of the kernel partition */ |
+ /* Read the first part of the kernel partition. */ |
if (part_size < kbuf_sectors) { |
VBDEBUG(("Partition too small to hold kernel.\n")); |
goto bad_kernel; |
@@ -231,42 +257,54 @@ int LoadKernel(LoadKernelParams* params) { |
goto bad_kernel; |
} |
- /* Verify the key block. In developer mode, we ignore the key |
- * and use only the SHA-512 hash to verify the key block. */ |
+ /* Verify the key block. */ |
key_block = (VbKeyBlockHeader*)kbuf; |
- if ((0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, |
- is_dev && !is_rec))) { |
- VBDEBUG(("Verifying key block failed.\n")); |
- goto bad_kernel; |
- } |
+ if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 0)) { |
+ VBDEBUG(("Verifying key block signature failed.\n")); |
+ key_block_valid = 0; |
- /* Check the key block flags against the current boot mode in normal |
- * and recovery modes (not in developer mode booting from SSD). */ |
- if (is_rec || is_normal) { |
- if (!(key_block->key_block_flags & |
- (is_dev ? KEY_BLOCK_FLAG_DEVELOPER_1 : |
- KEY_BLOCK_FLAG_DEVELOPER_0))) { |
- VBDEBUG(("Developer flag mismatch.\n")); |
+ /* If we're not in developer mode, this kernel is bad. */ |
+ if (kBootDev != boot_mode) |
goto bad_kernel; |
- } |
- if (!(key_block->key_block_flags & |
- (is_rec ? KEY_BLOCK_FLAG_RECOVERY_1 : |
- KEY_BLOCK_FLAG_RECOVERY_0))) { |
- VBDEBUG(("Recovery flag mismatch.\n")); |
+ |
+ /* In developer mode, we can continue if the SHA-512 hash of the key |
+ * block is valid. */ |
+ if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 1)) { |
+ VBDEBUG(("Verifying key block hash failed.\n")); |
goto bad_kernel; |
} |
} |
- /* Check for rollback of key version. Note this is implicitly |
- * skipped in recovery and developer modes because those set |
- * key_version=0 above. */ |
+ /* Check the key block flags against the current boot mode. */ |
+ if (!(key_block->key_block_flags & |
+ (dev_switch ? KEY_BLOCK_FLAG_DEVELOPER_1 : |
+ KEY_BLOCK_FLAG_DEVELOPER_0))) { |
+ VBDEBUG(("Key block developer flag mismatch.\n")); |
+ key_block_valid = 0; |
+ } |
+ if (!(key_block->key_block_flags & |
+ (rec_switch ? KEY_BLOCK_FLAG_RECOVERY_1 : |
+ KEY_BLOCK_FLAG_RECOVERY_0))) { |
+ VBDEBUG(("Key block recovery flag mismatch.\n")); |
+ key_block_valid = 0; |
+ } |
+ |
+ /* Check for rollback of key version except in recovery mode. */ |
key_version = key_block->data_key.key_version; |
- if (key_version < (tpm_version >> 16)) { |
- VBDEBUG(("Key version too old.\n")); |
+ if (kBootRecovery != boot_mode) { |
+ if (key_version < (tpm_version >> 16)) { |
+ VBDEBUG(("Key version too old.\n")); |
+ key_block_valid = 0; |
+ } |
+ } |
+ |
+ /* If we're not in developer mode, require the key block to be valid. */ |
+ if (kBootDev != boot_mode && !key_block_valid) { |
+ VBDEBUG(("Key block is invalid.\n")); |
goto bad_kernel; |
} |
- /* Get the key for preamble/data verification from the key block */ |
+ /* Get the key for preamble/data verification from the key block. */ |
data_key = PublicKeyToRSA(&key_block->data_key); |
if (!data_key) { |
VBDEBUG(("Data key bad.\n")); |
@@ -282,20 +320,23 @@ int LoadKernel(LoadKernelParams* params) { |
goto bad_kernel; |
} |
- /* Check for rollback of kernel version. Note this is implicitly |
- * skipped in recovery and developer modes because rollback_index |
- * sets those to 0 in those modes. */ |
+ /* If the key block is valid and we're not in recovery mode, check for |
+ * rollback of the kernel version. */ |
combined_version = ((key_version << 16) | |
(preamble->kernel_version & 0xFFFF)); |
- if (combined_version < tpm_version) { |
- VBDEBUG(("Kernel version too low.\n")); |
- goto bad_kernel; |
+ if (key_block_valid && kBootRecovery != boot_mode) { |
+ if (combined_version < tpm_version) { |
+ VBDEBUG(("Kernel version too low.\n")); |
+ /* If we're not in developer mode, kernel version must be valid. */ |
+ if (kBootDev != boot_mode) |
+ goto bad_kernel; |
+ } |
} |
VBDEBUG(("Kernel preamble is good.\n")); |
/* Check for lowest version from a valid header. */ |
- if (lowest_version > combined_version) |
+ if (key_block_valid && lowest_version > combined_version) |
lowest_version = combined_version; |
/* If we already have a good kernel, no need to read another |
@@ -357,7 +398,8 @@ int LoadKernel(LoadKernelParams* params) { |
/* If we're still here, the kernel is valid. */ |
/* Save the first good partition we find; that's the one we'll boot */ |
- VBDEBUG(("Partiton is good.\n")); |
+ VBDEBUG(("Partition is good.\n")); |
+ good_partition_key_block_valid = key_block_valid; |
/* TODO: GPT partitions start at 1, but cgptlib starts them at 0. |
* Adjust here, until cgptlib is fixed. */ |
good_partition = gpt.current_kernel + 1; |
@@ -371,18 +413,18 @@ int LoadKernel(LoadKernelParams* params) { |
/* Update GPT to note this is the kernel we're trying */ |
GptUpdateKernelEntry(&gpt, GPT_UPDATE_ENTRY_TRY); |
- /* If we're in developer or recovery mode, there's no rollback |
- * protection, so we can stop at the first valid kernel. */ |
- if (!is_normal) { |
- VBDEBUG(("Boot_flags = !is_normal\n")); |
+ /* If we're in recovery mode or we're about to boot a dev-signed kernel, |
+ * there's no rollback protection, so we can stop at the first valid |
+ * kernel. */ |
+ if (kBootRecovery == boot_mode || !key_block_valid) { |
+ VBDEBUG(("In recovery mode or dev-signed kernel\n")); |
break; |
} |
- /* Otherwise, we're in normal boot mode, so we do care about the |
- * key index in the TPM. If the good partition's key version is |
- * the same as the tpm, then the TPM doesn't need updating; we |
- * can stop now. Otherwise, we'll check all the other headers |
- * to see if they contain a newer key. */ |
+ /* Otherwise, we do care about the key index in the TPM. If the good |
+ * partition's key version is the same as the tpm, then the TPM doesn't |
+ * need updating; we can stop now. Otherwise, we'll check all the other |
+ * headers to see if they contain a newer key. */ |
if (combined_version == tpm_version) { |
VBDEBUG(("Same kernel version\n")); |
break; |
@@ -415,20 +457,22 @@ int LoadKernel(LoadKernelParams* params) { |
VBDEBUG(("Good_partition >= 0\n")); |
/* See if we need to update the TPM */ |
- if (is_normal) { |
- /* We only update the TPM in normal boot mode. In developer |
- * mode, the kernel is self-signed by the developer, so we can't |
- * trust the key version and wouldn't want to roll the TPM |
- * forward. In recovery mode, the TPM stays PP-unlocked, so |
- * anything we write gets blown away by the firmware when we go |
- * back to normal mode. */ |
- VBDEBUG(("Boot_flags = is_normal\n")); |
+ if (kBootRecovery != boot_mode) { |
+ /* We only update the TPM in normal and developer boot modes. In |
+ * developer mode, we only advanced lowest_version for kernels with valid |
+ * key blocks, and didn't count self-signed key blocks. In recovery |
+ * mode, the TPM stays PP-unlocked, so anything we write gets blown away |
+ * by the firmware when we go back to normal mode. */ |
+ VBDEBUG(("Boot_flags = not recovery\n")); |
if (lowest_version > tpm_version) { |
status = RollbackKernelWrite((uint32_t)lowest_version); |
if (0 != status) { |
VBDEBUG(("Error writing kernel versions to TPM.\n")); |
- return (status == TPM_E_MUST_REBOOT ? |
- LOAD_KERNEL_REBOOT : LOAD_KERNEL_RECOVERY); |
+ if (status == TPM_E_MUST_REBOOT) |
+ retval = LOAD_KERNEL_REBOOT; |
+ else |
+ recovery = VBNV_RECOVERY_RW_TPM_ERROR; |
+ goto LoadKernelExit; |
} |
} |
} |
@@ -438,19 +482,28 @@ int LoadKernel(LoadKernelParams* params) { |
if (0 != status) { |
VBDEBUG(("Error locking kernel versions.\n")); |
/* Don't reboot to recovery mode if we're already there */ |
- if (!is_rec) |
- return (status == TPM_E_MUST_REBOOT ? |
- LOAD_KERNEL_REBOOT : LOAD_KERNEL_RECOVERY); |
+ if (kBootRecovery != boot_mode) { |
+ if (status == TPM_E_MUST_REBOOT) |
+ retval = LOAD_KERNEL_REBOOT; |
+ else |
+ recovery = VBNV_RECOVERY_RW_TPM_ERROR; |
+ goto LoadKernelExit; |
+ } |
} |
/* Success! */ |
- return LOAD_KERNEL_SUCCESS; |
+ retval = LOAD_KERNEL_SUCCESS; |
} |
- /* The BIOS may attempt to display different screens depending on whether |
- * we find an invalid kernel partition (return LOAD_KERNEL_INVALID) or not. |
- * But the flow is changing, so for now treating both cases as invalid gives |
- * slightly less confusing user feedback. Sigh. |
- */ |
- return LOAD_KERNEL_INVALID; |
+LoadKernelExit: |
+ |
+ /* Save whether the good partition's key block was fully verified */ |
+ VbNvSet(vnc, VBNV_FW_VERIFIED_KERNEL_KEY, good_partition_key_block_valid); |
+ |
+ /* Store recovery request, if any, then tear down non-volatile storage */ |
+ VbNvSet(vnc, VBNV_RECOVERY_REQUEST, LOAD_KERNEL_RECOVERY == retval ? |
+ recovery : VBNV_RECOVERY_NOT_REQUESTED); |
+ VbNvTeardown(vnc); |
+ |
+ return retval; |
} |