Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 1 | /* Copyright (c) 2010 The Chromium OS Authors. All rights reserved. |
| 2 | * Use of this source code is governed by a BSD-style license that can be |
| 3 | * found in the LICENSE file. |
| 4 | * |
| 5 | * Functions for loading a kernel from disk. |
| 6 | * (Firmware portion) |
| 7 | */ |
| 8 | |
| 9 | #include "load_kernel_fw.h" |
| 10 | |
| 11 | #include "boot_device.h" |
| 12 | #include "cgptlib.h" |
| 13 | #include "kernel_image_fw.h" |
| 14 | #include "rollback_index.h" |
| 15 | #include "utility.h" |
Randall Spangler | 83c88cf | 2010-06-11 16:14:18 -0700 | [diff] [blame] | 16 | #include "vboot_kernel.h" |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 17 | |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 18 | #define GPT_ENTRIES_SIZE 16384 /* Bytes to read for GPT entries */ |
| 19 | |
Randall Spangler | 58efd70 | 2010-06-03 10:53:48 -0700 | [diff] [blame] | 20 | #ifdef PRINT_DEBUG_INFO |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 21 | // TODO: for testing |
| 22 | #include <stdio.h> |
Randall Spangler | 19d1313 | 2010-06-03 09:43:43 -0700 | [diff] [blame] | 23 | #include <inttypes.h> /* For PRIu64 macro */ |
Randall Spangler | 58efd70 | 2010-06-03 10:53:48 -0700 | [diff] [blame] | 24 | #include "cgptlib_internal.h" |
Randall Spangler | 58efd70 | 2010-06-03 10:53:48 -0700 | [diff] [blame] | 25 | #endif |
| 26 | |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 27 | |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 28 | #define KBUF_SIZE 65536 /* Bytes to read at start of kernel partition */ |
| 29 | |
| 30 | int LoadKernel(LoadKernelParams* params) { |
| 31 | |
| 32 | GptData gpt; |
| 33 | uint64_t part_start, part_size; |
| 34 | uint64_t blba = params->bytes_per_lba; |
| 35 | uint8_t* kbuf = NULL; |
| 36 | uint64_t kbuf_sectors; |
| 37 | int found_partition = 0; |
| 38 | int good_partition = -1; |
| 39 | uint16_t tpm_kernel_key_version, tpm_kernel_version; |
| 40 | uint16_t lowest_kernel_key_version = 0xFFFF; |
| 41 | uint16_t lowest_kernel_version = 0xFFFF; |
| 42 | KernelImage *kim = NULL; |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 43 | int is_dev = ((BOOT_FLAG_DEVELOPER & params->boot_flags) && |
| 44 | !(BOOT_FLAG_RECOVERY & params->boot_flags)); |
| 45 | int is_normal = (!(BOOT_FLAG_DEVELOPER & params->boot_flags) && |
| 46 | !(BOOT_FLAG_RECOVERY & params->boot_flags)); |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 47 | |
Randall Spangler | 19d1313 | 2010-06-03 09:43:43 -0700 | [diff] [blame] | 48 | /* Clear output params in case we fail */ |
| 49 | params->partition_number = 0; |
| 50 | params->bootloader_address = 0; |
| 51 | params->bootloader_size = 0; |
| 52 | |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 53 | if (is_normal) { |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 54 | /* Read current kernel key index from TPM. Assumes TPM is already |
| 55 | * initialized. */ |
| 56 | if (0 != GetStoredVersions(KERNEL_VERSIONS, |
| 57 | &tpm_kernel_key_version, |
| 58 | &tpm_kernel_version)) |
| 59 | return LOAD_KERNEL_RECOVERY; |
| 60 | } |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 61 | |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 62 | do { |
| 63 | /* Read GPT data */ |
| 64 | gpt.sector_bytes = blba; |
| 65 | gpt.drive_sectors = params->ending_lba + 1; |
| 66 | if (0 != AllocAndReadGptData(&gpt)) |
| 67 | break; |
| 68 | |
| 69 | /* Initialize GPT library */ |
| 70 | if (GPT_SUCCESS != GptInit(&gpt)) |
| 71 | break; |
| 72 | |
| 73 | /* Allocate kernel header and image work buffers */ |
| 74 | kbuf = (uint8_t*)Malloc(KBUF_SIZE); |
| 75 | if (!kbuf) |
| 76 | break; |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 77 | |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 78 | kbuf_sectors = KBUF_SIZE / blba; |
| 79 | kim = (KernelImage*)Malloc(sizeof(KernelImage)); |
| 80 | if (!kim) |
| 81 | break; |
| 82 | |
| 83 | /* Loop over candidate kernel partitions */ |
| 84 | while (GPT_SUCCESS == GptNextKernelEntry(&gpt, &part_start, &part_size)) { |
| 85 | RSAPublicKey *kernel_sign_key = NULL; |
| 86 | int kernel_start, kernel_sectors; |
| 87 | |
| 88 | /* Found at least one kernel partition. */ |
| 89 | found_partition = 1; |
| 90 | |
| 91 | /* Read the first part of the kernel partition */ |
| 92 | if (part_size < kbuf_sectors) |
| 93 | continue; |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 94 | if (0 != BootDeviceReadLBA(part_start, kbuf_sectors, kbuf)) |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 95 | continue; |
| 96 | |
| 97 | /* Verify the kernel header and preamble */ |
| 98 | if (VERIFY_KERNEL_SUCCESS != VerifyKernelHeader( |
| 99 | params->header_sign_key_blob, |
| 100 | kbuf, |
| 101 | KBUF_SIZE, |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 102 | (is_dev ? 1 : 0), |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 103 | kim, |
| 104 | &kernel_sign_key)) { |
| 105 | continue; |
| 106 | } |
| 107 | |
Randall Spangler | 58efd70 | 2010-06-03 10:53:48 -0700 | [diff] [blame] | 108 | #ifdef PRINT_DEBUG_INFO |
Randall Spangler | 19d1313 | 2010-06-03 09:43:43 -0700 | [diff] [blame] | 109 | printf("Kernel header:\n"); |
| 110 | printf("header version: %d\n", kim->header_version); |
| 111 | printf("header len: %d\n", kim->header_len); |
| 112 | printf("firmware sign alg: %d\n", kim->firmware_sign_algorithm); |
| 113 | printf("kernel sign alg: %d\n", kim->kernel_sign_algorithm); |
| 114 | printf("kernel key version: %d\n", kim->kernel_key_version); |
| 115 | printf("kernel version: %d\n", kim->kernel_version); |
| 116 | printf("kernel len: %" PRIu64 "\n", kim->kernel_len); |
| 117 | printf("bootloader addr: %" PRIu64 "\n", kim->bootloader_offset); |
| 118 | printf("bootloader size: %" PRIu64 "\n", kim->bootloader_size); |
| 119 | printf("padded header size: %" PRIu64 "\n", kim->padded_header_size); |
Randall Spangler | 58efd70 | 2010-06-03 10:53:48 -0700 | [diff] [blame] | 120 | #endif |
Randall Spangler | 3dcf9dc | 2010-06-02 12:46:17 -0700 | [diff] [blame] | 121 | |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 122 | /* Check for rollback of key version */ |
| 123 | if (kim->kernel_key_version < tpm_kernel_key_version) { |
| 124 | RSAPublicKeyFree(kernel_sign_key); |
| 125 | continue; |
| 126 | } |
| 127 | |
| 128 | /* Check for rollback of kernel version */ |
| 129 | if (kim->kernel_key_version == tpm_kernel_key_version && |
| 130 | kim->kernel_version < tpm_kernel_version) { |
| 131 | RSAPublicKeyFree(kernel_sign_key); |
| 132 | continue; |
| 133 | } |
| 134 | |
| 135 | /* Check for lowest key version from a valid header. */ |
| 136 | if (lowest_kernel_key_version > kim->kernel_key_version) { |
| 137 | lowest_kernel_key_version = kim->kernel_key_version; |
| 138 | lowest_kernel_version = kim->kernel_version; |
| 139 | } |
| 140 | else if (lowest_kernel_key_version == kim->kernel_key_version && |
| 141 | lowest_kernel_version > kim->kernel_version) { |
| 142 | lowest_kernel_version = kim->kernel_version; |
| 143 | } |
| 144 | |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 145 | /* If we already have a good kernel, no need to read another |
| 146 | * one; we only needed to look at the versions to check for |
| 147 | * rollback. */ |
| 148 | if (-1 != good_partition) |
| 149 | continue; |
| 150 | |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 151 | /* Verify kernel padding is a multiple of sector size. */ |
| 152 | if (0 != kim->padded_header_size % blba) { |
| 153 | RSAPublicKeyFree(kernel_sign_key); |
| 154 | continue; |
| 155 | } |
| 156 | |
| 157 | kernel_start = part_start + (kim->padded_header_size / blba); |
| 158 | kernel_sectors = (kim->kernel_len + blba - 1) / blba; |
| 159 | |
| 160 | /* Read the kernel data */ |
| 161 | if (0 != BootDeviceReadLBA(kernel_start, kernel_sectors, |
| 162 | params->kernel_buffer)) { |
| 163 | RSAPublicKeyFree(kernel_sign_key); |
| 164 | continue; |
| 165 | } |
| 166 | |
| 167 | /* Verify kernel data */ |
| 168 | if (0 != VerifyKernelData(kernel_sign_key, |
| 169 | kim->kernel_signature, |
| 170 | params->kernel_buffer, |
| 171 | kim->kernel_len, |
| 172 | kim->kernel_sign_algorithm)) { |
| 173 | RSAPublicKeyFree(kernel_sign_key); |
| 174 | continue; |
| 175 | } |
| 176 | |
| 177 | /* Done with the kernel signing key, so can free it now */ |
| 178 | RSAPublicKeyFree(kernel_sign_key); |
| 179 | |
| 180 | /* If we're still here, the kernel is valid. */ |
| 181 | /* Save the first good partition we find; that's the one we'll boot */ |
| 182 | if (-1 == good_partition) { |
| 183 | good_partition = gpt.current_kernel; |
| 184 | params->partition_number = gpt.current_kernel; |
Randall Spangler | 19d1313 | 2010-06-03 09:43:43 -0700 | [diff] [blame] | 185 | params->bootloader_address = kim->bootloader_offset; |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 186 | params->bootloader_size = kim->bootloader_size; |
| 187 | |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 188 | /* If we're in developer or recovery mode, there's no rollback |
| 189 | * protection, so we can stop at the first valid kernel. */ |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 190 | if (!is_normal) |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 191 | break; |
| 192 | |
| 193 | /* Otherwise, we're in normal boot mode, so we do care about |
| 194 | * the key index in the TPM. If the good partition's key |
| 195 | * version is the same as the tpm, then the TPM doesn't need |
| 196 | * updating; we can stop now. Otherwise, we'll check all the |
| 197 | * other headers to see if they contain a newer key. */ |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 198 | if (kim->kernel_key_version == tpm_kernel_key_version && |
| 199 | kim->kernel_version == tpm_kernel_version) |
| 200 | break; |
| 201 | } |
| 202 | } /* while(GptNextKernelEntry) */ |
| 203 | } while(0); |
| 204 | |
| 205 | /* Free kernel work and image buffers */ |
| 206 | if (kbuf) |
| 207 | Free(kbuf); |
| 208 | if (kim) |
| 209 | Free(kim); |
| 210 | |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 211 | /* Write and free GPT data */ |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 212 | WriteAndFreeGptData(&gpt); |
| 213 | |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 214 | /* Handle finding a good partition */ |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 215 | if (good_partition >= 0) { |
| 216 | |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 217 | if (is_normal) { |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 218 | /* See if we need to update the TPM, for normal boot mode only. */ |
| 219 | if ((lowest_kernel_key_version > tpm_kernel_key_version) || |
| 220 | (lowest_kernel_key_version == tpm_kernel_key_version && |
| 221 | lowest_kernel_version > tpm_kernel_version)) { |
| 222 | if (0 != WriteStoredVersions(KERNEL_VERSIONS, |
| 223 | lowest_kernel_key_version, |
| 224 | lowest_kernel_version)) |
| 225 | return LOAD_KERNEL_RECOVERY; |
| 226 | } |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 227 | } |
| 228 | |
Randall Spangler | d183644 | 2010-06-10 09:59:04 -0700 | [diff] [blame] | 229 | if (!(BOOT_FLAG_RECOVERY & params->boot_flags)) { |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 230 | /* We can lock the TPM now, since we've decided which kernel we |
| 231 | * like. If we don't find a good kernel, we leave the TPM |
| 232 | * unlocked so we can try again on the next boot device. If no |
| 233 | * kernels are good, we'll reboot to recovery mode, so it's ok to |
| 234 | * leave the TPM unlocked in that case too. |
| 235 | * |
| 236 | * If we're already in recovery mode, we need to leave PP unlocked, |
| 237 | * so don't lock the kernel versions. */ |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 238 | if (0 != LockKernelVersionsByLockingPP()) |
| 239 | return LOAD_KERNEL_RECOVERY; |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 240 | } |
| 241 | |
| 242 | /* Success! */ |
| 243 | return LOAD_KERNEL_SUCCESS; |
| 244 | } |
| 245 | |
Randall Spangler | 4a7a9e3 | 2010-06-08 15:44:24 -0700 | [diff] [blame] | 246 | /* Handle error cases */ |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 247 | if (found_partition) |
| 248 | return LOAD_KERNEL_INVALID; |
| 249 | else |
| 250 | return LOAD_KERNEL_NOT_FOUND; |
Randall Spangler | 0ff6fea | 2010-05-27 18:36:02 -0700 | [diff] [blame] | 251 | } |