/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include static uint32_t blVerifyOsImage(const uint8_t *addr, struct OsUpdateHdr **start, uint32_t *size); //for comms protocol #define BL_SYNC_IN 0x5A #define BL_ACK 0x79 #define BL_NAK 0x1F #define BL_SYNC_OUT 0xA5 #define BL_CMD_GET 0x00 #define BL_CMD_READ_MEM 0x11 #define BL_CMD_WRITE_MEM 0x31 #define BL_CMD_ERASE 0x44 #define BL_CMD_GET_SIZES 0xEE /* our own command. reports: {u32 osSz, u32 sharedSz, u32 eeSz} all in big endian */ #define BL_CMD_UPDATE_FINISHED 0xEF /* our own command. attempts to verify the update -> ACK/NAK. MUST be called after upload to mark it as completed */ #define BL_ERROR 0xDEADBEAF /* returned in place of command in case of exchange errors */ #define BL_SHARED_AREA_FAKE_ERASE_BLK 0xFFF0 #define BL_SHARED_AREA_FAKE_ADDR 0x50000000 //linker provides these extern uint32_t __pubkeys_start[]; extern uint32_t __pubkeys_end[]; extern uint8_t __eedata_start[]; extern uint8_t __eedata_end[]; extern uint8_t __code_start[]; extern uint8_t __code_end[]; extern uint8_t __shared_start[]; extern uint8_t __shared_end[]; enum BlFlashType { BL_FLASH_BL, BL_FLASH_EEDATA, BL_FLASH_KERNEL, BL_FLASH_SHARED }; static const struct blFlashTable // For erase code, we need to know which page a given memory address is in { uint8_t *address; uint32_t length; uint32_t type; } mBlFlashTable[] = #ifndef BL_FLASH_TABLE { { (uint8_t *)(&BL), 0x04000, BL_FLASH_BL }, { (uint8_t *)(__eedata_start), 0x04000, BL_FLASH_EEDATA }, { (uint8_t *)(__eedata_start + 0x04000), 0x04000, BL_FLASH_EEDATA }, { (uint8_t *)(__code_start), 0x04000, BL_FLASH_KERNEL }, { (uint8_t *)(__code_start + 0x04000), 0x10000, BL_FLASH_KERNEL }, { (uint8_t *)(__shared_start), 0x20000, BL_FLASH_SHARED }, { (uint8_t *)(__shared_start + 0x20000), 0x20000, BL_FLASH_SHARED }, { (uint8_t *)(__shared_start + 0x40000), 0x20000, BL_FLASH_SHARED }, }; #else BL_FLASH_TABLE; #endif static const char mOsUpdateMagic[] = OS_UPDT_MAGIC; #ifdef DEBUG_UART_PIN static bool blLogPutcharF(void *userData, char ch) { if (ch == '\n') gpioBitbangedUartOut('\r'); gpioBitbangedUartOut(ch); return true; } void blLog(const char *str, ...) { va_list vl; va_start(vl, str); cvprintf(blLogPutcharF, NULL, str, vl); va_end(vl); } #else #define blLog(...) #endif static uint32_t blExtApiGetVersion(void) { return BL_VERSION_CUR; } static bool blProgramFlash(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2) { const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable); uint32_t offset, i, j = 0; uint8_t *ptr; if (((length == 0)) || ((0xFFFFFFFF - (uint32_t)dst) < (length - 1)) || ((dst < mBlFlashTable[0].address)) || ((dst + length) > (mBlFlashTable[sector_cnt-1].address + mBlFlashTable[sector_cnt-1].length))) { return false; } // compute which flash block we are starting from for (i = 0; i < sector_cnt; i++) { if (dst >= mBlFlashTable[i].address && dst < (mBlFlashTable[i].address + mBlFlashTable[i].length)) { break; } } // now loop through all the flash blocks and see if we have to do any // 0 -> 1 transitions of a bit. If so, return false // 1 -> 0 transitions of a bit do not require an erase offset = (uint32_t)(dst - mBlFlashTable[i].address); ptr = mBlFlashTable[i].address; while (j < length && i < sector_cnt) { if (offset == mBlFlashTable[i].length) { i++; offset = 0; ptr = mBlFlashTable[i].address; } if ((ptr[offset] & src[j]) != src[j]) { return false; } else { j++; offset++; } } if (!blPlatProgramFlash(dst, src, length, key1, key2)) return false; return !memcmp(dst, src, length); } static void blExtApiGetSnum(uint32_t *snum, uint32_t length) { blGetSnum(snum, length); } static bool blProgramTypedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t type, uint32_t key1, uint32_t key2) { const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable); uint32_t i; for (i = 0; i < sector_cnt; i++) { if ((dst >= mBlFlashTable[i].address && dst < (mBlFlashTable[i].address + mBlFlashTable[i].length)) || (dst < mBlFlashTable[i].address && (dst + length > mBlFlashTable[i].address))) { if (mBlFlashTable[i].type != type) return false; } } return blProgramFlash(dst, src, length, key1, key2); } static bool blExtApiProgramSharedArea(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2) { return blProgramTypedArea(dst, src, length, BL_FLASH_SHARED, key1, key2); } static bool blExtApiProgramEe(uint8_t *dst, const uint8_t *src, uint32_t length, uint32_t key1, uint32_t key2) { return blProgramTypedArea(dst, src, length, BL_FLASH_EEDATA, key1, key2); } static bool blEraseTypedArea(uint32_t type, uint32_t key1, uint32_t key2) { const uint32_t sector_cnt = sizeof(mBlFlashTable) / sizeof(struct blFlashTable); uint32_t i, erase_cnt = 0; uint8_t erase_mask[sector_cnt]; for (i = 0; i < sector_cnt; i++) { if (mBlFlashTable[i].type == type) { erase_mask[i] = 1; erase_cnt++; } else { erase_mask[i] = 0; } } if (erase_cnt) blEraseSectors(sector_cnt, erase_mask, key1, key2); return true; //we assume erase worked } static bool blExtApiEraseSharedArea(uint32_t key1, uint32_t key2) { return blEraseTypedArea(BL_FLASH_SHARED, key1, key2); } static uint32_t blVerifyOsUpdate(struct OsUpdateHdr **start, uint32_t *size) { uint32_t ret; int i; for (i = 0; i < BL_SCAN_OFFSET; i += 4) { ret = blVerifyOsImage(__shared_start + i, start, size); if (ret != OS_UPDT_HDR_CHECK_FAILED) break; } return ret; } static uint32_t blExtApiVerifyOsUpdate(void) { return blVerifyOsUpdate(NULL, NULL); } static void blExtApiReboot(void) { blReboot(); } static const uint32_t *blExtApiGetRsaKeyInfo(uint32_t *numKeys) { uint32_t numWords = __pubkeys_end - __pubkeys_start; if (numWords % RSA_WORDS) // something is wrong return NULL; *numKeys = numWords / RSA_WORDS; return __pubkeys_start; } static const uint32_t* blExtApiSigPaddingVerify(const uint32_t *rsaResult) { uint32_t i; //all but first and last word of padding MUST have no zero bytes for (i = SHA2_HASH_WORDS + 1; i < RSA_WORDS - 1; i++) { if (!(uint8_t)(rsaResult[i] >> 0)) return NULL; if (!(uint8_t)(rsaResult[i] >> 8)) return NULL; if (!(uint8_t)(rsaResult[i] >> 16)) return NULL; if (!(uint8_t)(rsaResult[i] >> 24)) return NULL; } //first padding word must have all nonzero bytes except low byte if ((rsaResult[SHA2_HASH_WORDS] & 0xff) || !(rsaResult[SHA2_HASH_WORDS] & 0xff00) || !(rsaResult[SHA2_HASH_WORDS] & 0xff0000) || !(rsaResult[SHA2_HASH_WORDS] & 0xff000000)) return NULL; //last padding word must have 0x0002 in top 16 bits and nonzero random bytes in lower bytes if ((rsaResult[RSA_WORDS - 1] >> 16) != 2) return NULL; if (!(rsaResult[RSA_WORDS - 1] & 0xff00) || !(rsaResult[RSA_WORDS - 1] & 0xff)) return NULL; return rsaResult; } static void blApplyVerifiedUpdate(const struct OsUpdateHdr *os) //only called if an update has been found to exist and be valid, signed, etc! { //copy shared to code, and if successful, erase shared area if (blEraseTypedArea(BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2)) if (blProgramTypedArea(__code_start, (const uint8_t*)(os + 1), os->size, BL_FLASH_KERNEL, BL_FLASH_KEY1, BL_FLASH_KEY2)) (void)blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2); } static void blWriteMark(struct OsUpdateHdr *hdr, uint32_t mark) { uint8_t dstVal = mark; (void)blExtApiProgramSharedArea(&hdr->marker, &dstVal, sizeof(hdr->marker), BL_FLASH_KEY1, BL_FLASH_KEY2); } static void blUpdateMark(uint32_t old, uint32_t new) { struct OsUpdateHdr *hdr = (struct OsUpdateHdr *)__shared_start; if (hdr->marker != old) return; blWriteMark(hdr, new); } static uint32_t blVerifyOsImage(const uint8_t *addr, struct OsUpdateHdr **start, uint32_t *size) { const uint32_t *rsaKey, *osSigHash, *osSigPubkey, *ourHash, *rsaResult, *expectedHash = NULL; struct OsUpdateHdr *hdr = (struct OsUpdateHdr*)addr; struct OsUpdateHdr cpy; uint32_t i, numRsaKeys = 0, rsaStateVar1, rsaStateVar2, rsaStep = 0; const uint8_t *updateBinaryData; bool isValid = false; struct Sha2state sha; struct RsaState rsa; uint32_t ret = OS_UPDT_HDR_CHECK_FAILED; const uint32_t overhead = sizeof(*hdr) + 2 * RSA_WORDS; // header does not fit or is not aligned if (addr < __shared_start || addr > (__shared_end - overhead) || ((uintptr_t)addr & 3)) return OS_UPDT_HDR_CHECK_FAILED; // image does not fit if (hdr->size > (__shared_end - addr - overhead)) return OS_UPDT_HDR_CHECK_FAILED; // OS magic does not match if (memcmp(hdr->magic, mOsUpdateMagic, sizeof(hdr->magic)) != 0) return OS_UPDT_HDR_CHECK_FAILED; // we don't allow shortcuts on success path, but we want to fail quickly if (hdr->marker == OS_UPDT_MARKER_INVALID) return OS_UPDT_HDR_MARKER_INVALID; // download did not finish if (hdr->marker == OS_UPDT_MARKER_INPROGRESS) return OS_UPDT_HDR_MARKER_INVALID; //get pointers updateBinaryData = (const uint8_t*)(hdr + 1); osSigHash = (const uint32_t*)(updateBinaryData + hdr->size); osSigPubkey = osSigHash + RSA_WORDS; //make sure the pub key is known for (i = 0, rsaKey = blExtApiGetRsaKeyInfo(&numRsaKeys); i < numRsaKeys; i++, rsaKey += RSA_WORDS) { if (memcmp(rsaKey, osSigPubkey, RSA_BYTES) == 0) break; } if (i == numRsaKeys) { ret = OS_UPDT_UNKNOWN_PUBKEY; //signed with an unknown key -> fail goto fail; } //decode sig using pubkey do { rsaResult = rsaPubOpIterative(&rsa, osSigHash, osSigPubkey, &rsaStateVar1, &rsaStateVar2, &rsaStep); } while (rsaStep); if (!rsaResult) { //decode fails -> invalid sig ret = OS_UPDT_INVALID_SIGNATURE; goto fail; } //verify padding expectedHash = blExtApiSigPaddingVerify(rsaResult); if (!expectedHash) { //padding check fails -> invalid sig ret = OS_UPDT_INVALID_SIGNATURE_HASH; goto fail; } //hash the update sha2init(&sha); memcpy(&cpy, hdr, sizeof(cpy)); cpy.marker = OS_UPDT_MARKER_INPROGRESS; sha2processBytes(&sha, &cpy, sizeof(cpy)); sha2processBytes(&sha, (uint8_t*)(hdr + 1), hdr->size); ourHash = sha2finish(&sha); //verify hash match if (memcmp(expectedHash, ourHash, SHA2_HASH_SIZE) != 0) { //hash does not match -> data tampered with ret = OS_UPDT_INVALID_SIGNATURE_HASH; // same error; do not disclose nature of hash problem goto fail; } //it is valid isValid = true; ret = OS_UPDT_SUCCESS; if (start) *start = hdr; if (size) *size = hdr->size; fail: //mark it appropriately blWriteMark(hdr, isValid ? OS_UPDT_MARKER_VERIFIED : OS_UPDT_MARKER_INVALID); return ret; } static inline bool blUpdateVerify() { return blVerifyOsImage(__shared_start, NULL, NULL) == OS_UPDT_SUCCESS; } static uint8_t blLoaderRxByte() { return blSpiTxRxByte(0); } static void blLoaderTxByte(uint32_t val) { blSpiTxRxByte(val); } static void blLoaderTxBytes(const void *data, uint32_t len) { const uint8_t *buf = (const uint8_t*)data; blLoaderTxByte(len - 1); while (len--) blLoaderTxByte(*buf++); } static bool blLoaderSendSyncOut() { return blSpiTxRxByte(BL_SYNC_OUT) == BL_SYNC_IN; } static bool blLoaderSendAck(bool ack) { blLoaderRxByte(); blLoaderTxByte(ack ? BL_ACK : BL_NAK); return blLoaderRxByte() == BL_ACK; } static uint32_t blLoaderRxCmd() { uint8_t cmd = blLoaderRxByte(); uint8_t cmdNot = blSpiTxRxByte(BL_ACK); return (cmd ^ cmdNot) == 0xFF ? cmd : BL_ERROR; } static void blLoader(bool force) { bool seenErase = false; uint32_t nextAddr = 0; uint32_t expectedSize = 0; blSetup(); //if int pin is not low, do not bother any further if (blHostActive() || force) { blConfigIo(); //if we saw a sync, do the bootloader thing if (blSyncWait(BL_SYNC_IN)) { static const uint8_t supportedCmds[] = {BL_CMD_GET, BL_CMD_READ_MEM, BL_CMD_WRITE_MEM, BL_CMD_ERASE, BL_CMD_GET_SIZES, BL_CMD_UPDATE_FINISHED}; uint32_t allSizes[] = {__builtin_bswap32(__code_end - __code_start), __builtin_bswap32(__shared_end - __shared_start), __builtin_bswap32(__eedata_end - __eedata_start)}; bool ack = true; //we ack the sync ack = blLoaderSendSyncOut(); //loop forever listening to commands while (1) { uint32_t sync, cmd, addr = 0, len, checksum = 0, i; uint8_t data[256]; //send ack or NAK for last thing if (!blLoaderSendAck(ack)) goto out; while ((sync = blLoaderRxByte()) != BL_SYNC_IN); cmd = blLoaderRxCmd(); ack = false; if (sync == BL_SYNC_IN && cmd != BL_ERROR) switch (cmd) { case BL_CMD_GET: //ACK the command (void)blLoaderSendAck(true); blLoaderTxBytes(supportedCmds, sizeof(supportedCmds)); ack = true; break; case BL_CMD_READ_MEM: if (!seenErase) //no reading till we erase the shared area (this way we do not leak encrypted apps' plaintexts) break; //ACK the command (void)blLoaderSendAck(true); //get address for (i = 0; i < 4; i++) { uint32_t byte = blLoaderRxByte(); checksum ^= byte; addr = (addr << 8) + byte; } //reject addresses outside of our fake area or on invalid checksum if (blLoaderRxByte() != checksum || addr < BL_SHARED_AREA_FAKE_ADDR || addr - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start) break; //ack the address (void)blLoaderSendAck(true); //get the length len = blLoaderRxByte(); //reject invalid checksum if (blLoaderRxByte() != (uint8_t)~len || addr + len - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start) break; len++; //reject reads past the end of the shared area if (addr + len - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start) break; //ack the length (void)blLoaderSendAck(true); //read the data & send it blLoaderTxBytes(__shared_start + addr - BL_SHARED_AREA_FAKE_ADDR, len); ack = true; break; case BL_CMD_WRITE_MEM: if (!seenErase) //no writing till we erase the shared area (this way we do not purposefully modify encrypted apps' plaintexts in a nefarious fashion) break; //ACK the command (void)blLoaderSendAck(true); //get address for (i = 0; i < 4; i++) { uint32_t byte = blLoaderRxByte(); checksum ^= byte; addr = (addr << 8) + byte; } //reject addresses outside of our fake area or on invalid checksum if (blLoaderRxByte() != checksum || addr < BL_SHARED_AREA_FAKE_ADDR || addr - BL_SHARED_AREA_FAKE_ADDR > __shared_end - __shared_start) break; addr -= BL_SHARED_AREA_FAKE_ADDR; if (addr != nextAddr) break; //ack the address (void)blLoaderSendAck(true); //get the length checksum = len = blLoaderRxByte(); len++; //get bytes for (i = 0; i < len; i++) { uint32_t byte = blLoaderRxByte(); checksum ^= byte; data[i] = byte; } //reject writes that takes out outside fo shared area or invalid checksums if (blLoaderRxByte() != checksum || addr + len > __shared_end - __shared_start) break; // OBSOLETE: superseded by sequential contiguous write requirement //if (addr && addr < sizeof(struct OsUpdateHdr)) // break; //a write starting at zero must be big enough to contain a full OS update header if (!addr) { const struct OsUpdateHdr *hdr = (const struct OsUpdateHdr*)data; //verify it is at least as big as the header if (len < sizeof(struct OsUpdateHdr)) break; //check for magic for (i = 0; i < sizeof(hdr->magic) && hdr->magic[i] == mOsUpdateMagic[i]; i++); //verify magic check passed & marker is properly set to inprogress if (i != sizeof(hdr->magic) || hdr->marker != OS_UPDT_MARKER_INPROGRESS) break; expectedSize = sizeof(*hdr) + hdr->size + 2 * RSA_BYTES; } if (addr + len > expectedSize) break; //do it ack = blExtApiProgramSharedArea(__shared_start + addr, data, len, BL_FLASH_KEY1, BL_FLASH_KEY2); blResetRxData(); nextAddr += len; break; case BL_CMD_ERASE: //ACK the command (void)blLoaderSendAck(true); //get address for (i = 0; i < 2; i++) { uint32_t byte = blLoaderRxByte(); checksum ^= byte; addr = (addr << 8) + byte; } //reject addresses that are not our magic address or on invalid checksum if (blLoaderRxByte() != checksum || addr != BL_SHARED_AREA_FAKE_ERASE_BLK) break; //do it ack = blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2); if (ack) { seenErase = true; nextAddr = 0; expectedSize = 0; } blResetRxData(); break; case BL_CMD_GET_SIZES: //ACK the command (void)blLoaderSendAck(true); blLoaderTxBytes(allSizes, sizeof(allSizes)); break; case BL_CMD_UPDATE_FINISHED: blUpdateMark(OS_UPDT_MARKER_INPROGRESS, OS_UPDT_MARKER_DOWNLOADED); ack = blUpdateVerify(); break; } } } } out: blCleanup(); } void blMain(uint32_t appBase) { bool forceLoad = false; blLog("NanohubOS bootloader up @ %p\n", &blMain); //enter SPI loader if requested do { uint32_t res; struct OsUpdateHdr *os; blLoader(forceLoad); res = blVerifyOsUpdate(&os, NULL); if (res == OS_UPDT_SUCCESS) blApplyVerifiedUpdate(os); else if (res != OS_UPDT_HDR_CHECK_FAILED) blExtApiEraseSharedArea(BL_FLASH_KEY1, BL_FLASH_KEY2); forceLoad = true; } while (*(volatile uint32_t*)appBase == 0xFFFFFFFF); } const struct BlApiTable __attribute__((section(".blapi"))) __BL_API = { .blGetVersion = &blExtApiGetVersion, .blReboot = &blExtApiReboot, .blGetSnum = &blExtApiGetSnum, .blProgramShared = &blExtApiProgramSharedArea, .blEraseShared = &blExtApiEraseSharedArea, .blProgramEe = &blExtApiProgramEe, .blGetPubKeysInfo = &blExtApiGetRsaKeyInfo, .blRsaPubOpIterative = &rsaPubOpIterative, .blSha2init = &sha2init, .blSha2processBytes = &sha2processBytes, .blSha2finish = &sha2finish, .blAesInitForEncr = &aesInitForEncr, .blAesInitForDecr = &aesInitForDecr, .blAesEncr = &aesEncr, .blAesDecr = &aesDecr, .blAesCbcInitForEncr = &aesCbcInitForEncr, .blAesCbcInitForDecr = &aesCbcInitForDecr, .blAesCbcEncr = &aesCbcEncr, .blAesCbcDecr = &aesCbcDecr, .blSigPaddingVerify = &blExtApiSigPaddingVerify, .blVerifyOsUpdate = &blExtApiVerifyOsUpdate, };