summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRandall Spangler <rspangler@chromium.org>2011-03-03 10:11:17 -0800
committerRandall Spangler <rspangler@chromium.org>2011-03-03 10:11:17 -0800
commit640fb51d866e7ac8a92f61a2f69145bfe6b13699 (patch)
tree9a9ac1731579224f3fabd83bdf541d85d7b4557e
parenta933d11df84e83fccdd6e838070035cba4e59faf (diff)
downloadvboot-640fb51d866e7ac8a92f61a2f69145bfe6b13699.tar.gz
Reapply patch from http://codereview.chromium.org/6594092/
(somehow that never got committed) Change-Id: I304e594066c5dd72fc7cf37d31e27b1096ae1a38 BUG=12282 TEST=make && make runtests Review URL: http://codereview.chromium.org/6610019
-rw-r--r--firmware/include/vboot_nvstorage.h2
-rw-r--r--firmware/lib/vboot_kernel.c223
-rw-r--r--utility/load_kernel_test.c3
3 files changed, 143 insertions, 85 deletions
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
index f70cf521..5ae40b4b 100644
--- a/firmware/include/vboot_nvstorage.h
+++ b/firmware/include/vboot_nvstorage.h
@@ -85,6 +85,8 @@ typedef enum VbNvParam {
#define VBNV_RECOVERY_RW_INVALID_OS 0x43
/* TPM error in rewritable firmware */
#define VBNV_RECOVERY_RW_TPM_ERROR 0x44
+/* RW firmware in dev mode, but dev switch is off */
+#define VBNV_RECOVERY_RW_DEV_MISMATCH 0x45
/* Unspecified/unknown error in rewritable firmware */
#define VBNV_RECOVERY_RW_UNSPECIFIED 0x7F
/* DM-verity error */
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index bd8865bd..2fbcaa84 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;
}
diff --git a/utility/load_kernel_test.c b/utility/load_kernel_test.c
index 38e4104f..8d95ef0c 100644
--- a/utility/load_kernel_test.c
+++ b/utility/load_kernel_test.c
@@ -26,6 +26,7 @@
/* Global variables for stub functions */
static LoadKernelParams lkp;
+static VbNvContext vnc;
static FILE *image_file = NULL;
@@ -83,6 +84,8 @@ int main(int argc, char* argv[]) {
Memset(&lkp, 0, sizeof(LoadKernelParams));
lkp.bytes_per_lba = LBA_BYTES;
lkp.boot_flags = BOOT_FLAG_RECOVERY;
+ Memset(&vnc, 0, sizeof(VbNvContext));
+ lkp.nv_context = &vnc;
/* Parse options */
opterr = 0;