/* Copyright (c) 2010 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. * * Functions for loading a kernel from disk. * (Firmware portion) */ #include "load_kernel_fw.h" #include "boot_device.h" #include "cgptlib.h" #include "kernel_image_fw.h" #include "rollback_index.h" #include "utility.h" #include "vboot_kernel.h" #define GPT_ENTRIES_SIZE 16384 /* Bytes to read for GPT entries */ #ifdef PRINT_DEBUG_INFO // TODO: for testing #include #include /* For PRIu64 macro */ #include "cgptlib_internal.h" #endif #define KBUF_SIZE 65536 /* Bytes to read at start of kernel partition */ int LoadKernelOld(LoadKernelParams* params) { GptData gpt; uint64_t part_start, part_size; uint64_t blba = params->bytes_per_lba; uint8_t* kbuf = NULL; uint64_t kbuf_sectors; int found_partition = 0; int good_partition = -1; uint16_t tpm_kernel_key_version, tpm_kernel_version; uint16_t lowest_kernel_key_version = 0xFFFF; uint16_t lowest_kernel_version = 0xFFFF; KernelImage *kim = NULL; int is_dev = ((BOOT_FLAG_DEVELOPER & params->boot_flags) && !(BOOT_FLAG_RECOVERY & params->boot_flags)); int is_normal = (!(BOOT_FLAG_DEVELOPER & params->boot_flags) && !(BOOT_FLAG_RECOVERY & params->boot_flags)); /* Clear output params in case we fail */ params->partition_number = 0; params->bootloader_address = 0; params->bootloader_size = 0; if (is_normal) { /* Read current kernel key index from TPM. Assumes TPM is already * initialized. */ if (0 != GetStoredVersions(KERNEL_VERSIONS, &tpm_kernel_key_version, &tpm_kernel_version)) return LOAD_KERNEL_RECOVERY; } do { /* Read GPT data */ gpt.sector_bytes = blba; gpt.drive_sectors = params->ending_lba + 1; if (0 != AllocAndReadGptData(&gpt)) break; /* Initialize GPT library */ if (GPT_SUCCESS != GptInit(&gpt)) break; /* Allocate kernel header and image work buffers */ kbuf = (uint8_t*)Malloc(KBUF_SIZE); if (!kbuf) break; kbuf_sectors = KBUF_SIZE / blba; kim = (KernelImage*)Malloc(sizeof(KernelImage)); if (!kim) break; /* Loop over candidate kernel partitions */ while (GPT_SUCCESS == GptNextKernelEntry(&gpt, &part_start, &part_size)) { RSAPublicKey *kernel_sign_key = NULL; int kernel_start, kernel_sectors; /* Found at least one kernel partition. */ found_partition = 1; /* Read the first part of the kernel partition */ if (part_size < kbuf_sectors) continue; if (0 != BootDeviceReadLBA(part_start, kbuf_sectors, kbuf)) continue; /* Verify the kernel header and preamble */ if (VERIFY_KERNEL_SUCCESS != VerifyKernelHeader( params->header_sign_key_blob, kbuf, KBUF_SIZE, (is_dev ? 1 : 0), kim, &kernel_sign_key)) { continue; } #ifdef PRINT_DEBUG_INFO printf("Kernel header:\n"); printf("header version: %d\n", kim->header_version); printf("header len: %d\n", kim->header_len); printf("firmware sign alg: %d\n", kim->firmware_sign_algorithm); printf("kernel sign alg: %d\n", kim->kernel_sign_algorithm); printf("kernel key version: %d\n", kim->kernel_key_version); printf("kernel version: %d\n", kim->kernel_version); printf("kernel len: %" PRIu64 "\n", kim->kernel_len); printf("bootloader addr: %" PRIu64 "\n", kim->bootloader_offset); printf("bootloader size: %" PRIu64 "\n", kim->bootloader_size); printf("padded header size: %" PRIu64 "\n", kim->padded_header_size); #endif /* Check for rollback of key version */ if (kim->kernel_key_version < tpm_kernel_key_version) { RSAPublicKeyFree(kernel_sign_key); continue; } /* Check for rollback of kernel version */ if (kim->kernel_key_version == tpm_kernel_key_version && kim->kernel_version < tpm_kernel_version) { RSAPublicKeyFree(kernel_sign_key); continue; } /* Check for lowest key version from a valid header. */ if (lowest_kernel_key_version > kim->kernel_key_version) { lowest_kernel_key_version = kim->kernel_key_version; lowest_kernel_version = kim->kernel_version; } else if (lowest_kernel_key_version == kim->kernel_key_version && lowest_kernel_version > kim->kernel_version) { lowest_kernel_version = kim->kernel_version; } /* If we already have a good kernel, no need to read another * one; we only needed to look at the versions to check for * rollback. */ if (-1 != good_partition) continue; /* Verify kernel padding is a multiple of sector size. */ if (0 != kim->padded_header_size % blba) { RSAPublicKeyFree(kernel_sign_key); continue; } kernel_start = part_start + (kim->padded_header_size / blba); kernel_sectors = (kim->kernel_len + blba - 1) / blba; /* Read the kernel data */ if (0 != BootDeviceReadLBA(kernel_start, kernel_sectors, params->kernel_buffer)) { RSAPublicKeyFree(kernel_sign_key); continue; } /* Verify kernel data */ if (0 != VerifyKernelData(kernel_sign_key, kim->kernel_signature, params->kernel_buffer, kim->kernel_len, kim->kernel_sign_algorithm)) { RSAPublicKeyFree(kernel_sign_key); continue; } /* Done with the kernel signing key, so can free it now */ RSAPublicKeyFree(kernel_sign_key); /* If we're still here, the kernel is valid. */ /* Save the first good partition we find; that's the one we'll boot */ if (-1 == good_partition) { good_partition = gpt.current_kernel; params->partition_number = gpt.current_kernel; params->bootloader_address = kim->bootloader_offset; params->bootloader_size = kim->bootloader_size; /* 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) 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. */ if (kim->kernel_key_version == tpm_kernel_key_version && kim->kernel_version == tpm_kernel_version) break; } } /* while(GptNextKernelEntry) */ } while(0); /* Free kernel work and image buffers */ if (kbuf) Free(kbuf); if (kim) Free(kim); /* Write and free GPT data */ WriteAndFreeGptData(&gpt); /* Handle finding a good partition */ if (good_partition >= 0) { if (is_normal) { /* See if we need to update the TPM, for normal boot mode only. */ if ((lowest_kernel_key_version > tpm_kernel_key_version) || (lowest_kernel_key_version == tpm_kernel_key_version && lowest_kernel_version > tpm_kernel_version)) { if (0 != WriteStoredVersions(KERNEL_VERSIONS, lowest_kernel_key_version, lowest_kernel_version)) return LOAD_KERNEL_RECOVERY; } } if (!(BOOT_FLAG_RECOVERY & params->boot_flags)) { /* We can lock the TPM now, since we've decided which kernel we * like. If we don't find a good kernel, we leave the TPM * unlocked so we can try again on the next boot device. If no * kernels are good, we'll reboot to recovery mode, so it's ok to * leave the TPM unlocked in that case too. * * If we're already in recovery mode, we need to leave PP unlocked, * so don't lock the kernel versions. */ if (0 != LockKernelVersionsByLockingPP()) return LOAD_KERNEL_RECOVERY; } /* Success! */ return LOAD_KERNEL_SUCCESS; } /* Handle error cases */ if (found_partition) return LOAD_KERNEL_INVALID; else return LOAD_KERNEL_NOT_FOUND; }