| /* Copyright 2015 The ChromiumOS Authors |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "dcrypto.h" |
| #include "debug_printf.h" |
| #include "key_ladder.h" |
| #include "registers.h" |
| #include "rom_flash.h" |
| #include "setup.h" |
| #include "signed_header.h" |
| #include "uart.h" |
| #include "verify.h" |
| |
| static int unlockedForExecution(void) |
| { |
| return GREAD_FIELD(GLOBALSEC, SB_COMP_STATUS, SB_BL_SIG_MATCH); |
| } |
| |
| void _jump_to_address(const void *addr) |
| { |
| REG32(GC_M3_VTOR_ADDR) = (unsigned)addr; /* Set vector base. */ |
| |
| __asm__ volatile("ldr sp, [%0]; \ |
| ldr pc, [%0, #4];" |
| : : "r"(addr) |
| : "memory"); |
| |
| __builtin_unreachable(); |
| } |
| |
| void tryLaunch(uint32_t adr, size_t max_size) |
| { |
| static struct { |
| uint32_t img_hash[SHA256_DIGEST_WORDS]; |
| uint32_t fuses_hash[SHA256_DIGEST_WORDS]; |
| uint32_t info_hash[SHA256_DIGEST_WORDS]; |
| } hashes; |
| static uint32_t hash[SHA256_DIGEST_WORDS]; |
| static uint32_t fuses[FUSE_MAX]; |
| static uint32_t info[INFO_MAX]; |
| int i; |
| uint32_t major; |
| const uint32_t FAKE_rom_hash[8] = {1, 2, 3, 4, 5, 6, 7, 8}; |
| const struct SignedHeader *hdr = (const struct SignedHeader *)(adr); |
| |
| memset(&hashes, 0, sizeof(hashes)); |
| |
| /* Validity check image header. */ |
| if (hdr->magic != -1) |
| return; |
| if (hdr->image_size > max_size) |
| return; |
| |
| /* Validity checks that image belongs at adr. */ |
| if (hdr->ro_base < adr) |
| return; |
| if (hdr->ro_max > adr + max_size) |
| return; |
| if (hdr->rx_base < adr) |
| return; |
| if (hdr->rx_max > adr + max_size) |
| return; |
| |
| VERBOSE("considering image at 0x%8x\n", hdr); |
| VERBOSE("image size 0x%8x\n", hdr->image_size); |
| VERBOSE("hashing from 0x%8x to 0x%8x\n", |
| &hdr->tag, adr + hdr->image_size); |
| |
| /* Setup candidate execution region 1 based on header information. */ |
| /* TODO: harden against glitching: multi readback, check? */ |
| GREG32(GLOBALSEC, CPU0_I_STAGING_REGION1_BASE_ADDR) = hdr->rx_base; |
| GREG32(GLOBALSEC, CPU0_I_STAGING_REGION1_SIZE) = |
| hdr->rx_max - hdr->rx_base - 1; |
| GWRITE_FIELD(GLOBALSEC, CPU0_I_STAGING_REGION1_CTRL, EN, 1); |
| GWRITE_FIELD(GLOBALSEC, CPU0_I_STAGING_REGION1_CTRL, RD_EN, 1); |
| DCRYPTO_SHA256_hash((uint8_t *) &hdr->tag, |
| hdr->image_size - offsetof(struct SignedHeader, tag), |
| (uint8_t *) hashes.img_hash); |
| |
| VERBOSE("img_hash : %ph\n", HEX_BUF(hashes.img_hash, 32)); |
| |
| /* Sense fuses into RAM array; hash array. */ |
| /* TODO: is this glitch resistant enough? Certainly is simple.. */ |
| for (i = 0; i < FUSE_MAX; ++i) |
| fuses[i] = FUSE_IGNORE; |
| |
| for (i = 0; i < FUSE_MAX; ++i) { |
| /* |
| * For the fuses the header cares about, read their values |
| * into the map. |
| */ |
| if (hdr->fusemap[i>>5] & (1 << (i&31))) { |
| /* |
| * BNK0_INTG_CHKSUM is the first fuse and as such the |
| * best reference to the base address of the fuse |
| * memory map. |
| */ |
| fuses[i] = GREG32_ADDR(FUSE, BNK0_INTG_CHKSUM)[i]; |
| } |
| } |
| |
| DCRYPTO_SHA256_hash((uint8_t *) fuses, sizeof(fuses), |
| (uint8_t *) hashes.fuses_hash); |
| |
| VERBOSE("fuses_hash: %ph\n", HEX_BUF(hashes.fuses_hash, 32)); |
| |
| /* Sense info into RAM array; hash array. */ |
| for (i = 0; i < INFO_MAX; ++i) |
| info[i] = INFO_IGNORE; |
| |
| for (i = 0; i < INFO_MAX; ++i) { |
| if (hdr->infomap[i>>5] & (1 << (i&31))) { |
| uint32_t val = 0; |
| /* read 2nd bank of info */ |
| int retval = flash_info_read(i + INFO_MAX, &val); |
| |
| info[i] ^= val ^ retval; |
| } |
| } |
| |
| DCRYPTO_SHA256_hash((uint8_t *) info, sizeof(info), |
| (uint8_t *) hashes.info_hash); |
| VERBOSE("info_hash : %ph\n", HEX_BUF(hashes.info_hash, 32)); |
| |
| /* Hash our set of hashes to get final hash. */ |
| DCRYPTO_SHA256_hash((uint8_t *) &hashes, sizeof(hashes), |
| (uint8_t *) hash); |
| |
| /* |
| * Write measured hash to unlock register to try and unlock execution. |
| * This would match when doing warm-boot from suspend, so we can avoid |
| * the slow RSA verify. |
| */ |
| for (i = 0; i < SHA256_DIGEST_WORDS; ++i) |
| GREG32_ADDR(GLOBALSEC, SB_BL_SIG0)[i] = hash[i]; |
| |
| /* |
| * Unlock attempt. Value written is irrelevant, as long as something |
| * is written. |
| */ |
| GREG32(GLOBALSEC, SIG_UNLOCK) = 1; |
| |
| if (!unlockedForExecution()) { |
| /* Assume warm-boot failed; do full RSA verify. */ |
| LOADERKEY_verify(&hdr->keyid, hdr->signature, hash); |
| /* |
| * PWRDN_SCRATCH* should be write-locked, tied to successful |
| * SIG_MATCH. Thus ARM is only able to write this hash if |
| * signature was correct. |
| */ |
| for (i = 0; i < SHA256_DIGEST_WORDS; ++i) |
| /* TODO: verify written values as glitch protection? */ |
| GREG32_ADDR(PMU, PWRDN_SCRATCH8)[i] = hash[i]; |
| } |
| |
| |
| if (!unlockedForExecution()) { |
| debug_printf("Failed to unlock for execution image at 0x%08x\n", |
| adr); |
| return; |
| } |
| |
| /* |
| * Write PMU_PWRDN_SCRATCH_LOCK1_OFFSET to lock against rewrites. |
| * TODO: glitch resist |
| */ |
| GREG32(PMU, PWRDN_SCRATCH_LOCK1) = 1; |
| |
| /* |
| * Drop software level to stop SIG_MATCH from future write-unlocks. |
| * TODO: glitch detect / verify? |
| */ |
| GREG32(GLOBALSEC, SOFTWARE_LVL) = 0x33; |
| |
| /* Write hdr->tag, hdr->epoch_ to KDF engine FWR[0..7] */ |
| for (i = 0; i < ARRAY_SIZE(hdr->tag); ++i) |
| GREG32_ADDR(KEYMGR, HKEY_FWR0)[i] = hdr->tag[i]; |
| |
| GREG32(KEYMGR, HKEY_FWR7) = hdr->epoch_; |
| |
| /* Crank keyladder */ |
| if (!(GREAD(FUSE, FLASH_PERSO_PAGE_LOCK) & |
| (GC_FUSE_HIK_CREATE_LOCK_VAL_MASK << |
| GC_FUSE_HIK_CREATE_LOCK_VAL_LSB))) { |
| VERBOSE("Re-reading INFO0\n"); |
| /* |
| * Needed because FUSE_FLASH_PERSO_PAGE_LOCK_OFFSET isn't |
| * blown) wipe out the flash secrets saved in keymgr and |
| * re-read info0 |
| */ |
| GREG32(KEYMGR, FLASH_RCV_WIPE) = 1; |
| GREG32(FLASH, FSH_ENABLE_INFO0_SHADOW_READ) = 1; |
| } |
| |
| /* Turn up random stalls for SHA */ |
| GREG32(KEYMGR, SHA_RAND_STALL_CTL_FREQ) = 0; /* 0:50% */ |
| |
| major = hdr->major_; |
| GREG32(KEYMGR, FW_MAJOR_VERSION) = major; |
| |
| /* |
| * Lock FWR (NOTE: needs to happen after writing major!) TODO: glitch |
| * protect? |
| */ |
| GREG32(KEYMGR, FWR_VLD) = 2; |
| GREG32(KEYMGR, FWR_LOCK) = 1; |
| |
| key_ladder_step(40, FAKE_rom_hash); |
| |
| /* TODO: do cert #40 and lock in ROM? */ |
| GREG32(GLOBALSEC, HIDE_ROM) = 1; |
| |
| /* TODO: bump runlevel(s) according to signature header */ |
| /* |
| * Flash write protect entire image area (to guard signed blob) |
| * REGION0 protects boot_loader, use REGION1 to protect app |
| */ |
| GREG32(GLOBALSEC, FLASH_REGION1_BASE_ADDR) = adr; |
| GREG32(GLOBALSEC, FLASH_REGION1_SIZE) = hdr->image_size - 1; |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, EN, 1); |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, RD_EN, 1); |
| GWRITE_FIELD(GLOBALSEC, FLASH_REGION1_CTRL, WR_EN, 0); |
| |
| /* TODO: lock FLASH_REGION 1? */ |
| disarmRAMGuards(); |
| |
| debug_printf("Valid image found at 0x%pP, jumping", hdr); |
| uart_tx_flush(); |
| |
| _jump_to_address(&hdr[1]); |
| } |