Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 1 | /* |
| 2 | * SPDX-License-Identifier: Apache-2.0 |
| 3 | * |
| 4 | * Copyright (c) 2020 Nordic Semiconductor ASA |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 5 | * Copyright (c) 2020 Arm Limited |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 6 | */ |
| 7 | |
| 8 | #include <assert.h> |
| 9 | #include "bootutil/image.h" |
| 10 | #include "bootutil_priv.h" |
| 11 | #include "bootutil/bootutil_log.h" |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 12 | #include "bootutil/fault_injection_hardening.h" |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 13 | |
| 14 | #include "mcuboot_config/mcuboot_config.h" |
| 15 | |
Carlos Falgueras GarcĂa | a4b4b0f | 2021-06-22 10:00:22 +0200 | [diff] [blame] | 16 | BOOT_LOG_MODULE_DECLARE(mcuboot); |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 17 | |
| 18 | /* Variables passed outside of unit via poiters. */ |
| 19 | static const struct flash_area *_fa_p; |
| 20 | static struct image_header _hdr = { 0 }; |
| 21 | |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 22 | #if defined(MCUBOOT_VALIDATE_PRIMARY_SLOT) || defined(MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE) |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 23 | /** |
| 24 | * Validate hash of a primary boot image. |
| 25 | * |
| 26 | * @param[in] fa_p flash area pointer |
| 27 | * @param[in] hdr boot image header pointer |
| 28 | * |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 29 | * @return FIH_SUCCESS on success, error code otherwise |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 30 | */ |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 31 | fih_int |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 32 | boot_image_validate(const struct flash_area *fa_p, |
| 33 | struct image_header *hdr) |
| 34 | { |
| 35 | static uint8_t tmpbuf[BOOT_TMPBUF_SZ]; |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 36 | fih_int fih_rc = FIH_FAILURE; |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 37 | |
Dominik Ermel | d8db025 | 2020-10-07 11:22:45 +0000 | [diff] [blame] | 38 | /* NOTE: The first argument to boot_image_validate, for enc_state pointer, |
| 39 | * is allowed to be NULL only because the single image loader compiles |
| 40 | * with BOOT_IMAGE_NUMBER == 1, which excludes the code that uses |
| 41 | * the pointer from compilation. |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 42 | */ |
| 43 | /* Validate hash */ |
Wouter Cappelle | 7679215 | 2022-01-19 17:28:55 +0100 | [diff] [blame^] | 44 | if (IS_ENCRYPTED(hdr)) |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 45 | { |
| 46 | /* Clear the encrypted flag we didn't supply a key |
| 47 | * This flag could be set if there was a decryption in place |
| 48 | * was performed. We will try to validate the image, and if still |
| 49 | * encrypted the validation will fail, and go in panic mode |
| 50 | */ |
Wouter Cappelle | 7679215 | 2022-01-19 17:28:55 +0100 | [diff] [blame^] | 51 | hdr->ih_flags &= ~(ENCRYPTIONFLAGS); |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 52 | } |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 53 | FIH_CALL(bootutil_img_validate, fih_rc, NULL, 0, hdr, fa_p, tmpbuf, |
| 54 | BOOT_TMPBUF_SZ, NULL, 0, NULL); |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 55 | |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 56 | FIH_RET(fih_rc); |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 57 | } |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 58 | #endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT || MCUBOOT_VALIDATE_PRIMARY_SLOT_ONCE*/ |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 59 | |
| 60 | |
| 61 | /** |
| 62 | * Attempts to load image header from flash; verifies flash header fields. |
| 63 | * |
| 64 | * @param[in] fa_p flash area pointer |
| 65 | * @param[out] hdr buffer for image header |
| 66 | * |
| 67 | * @return 0 on success, error code otherwise |
| 68 | */ |
| 69 | static int |
| 70 | boot_image_load_header(const struct flash_area *fa_p, |
| 71 | struct image_header *hdr) |
| 72 | { |
| 73 | uint32_t size; |
| 74 | int rc = flash_area_read(fa_p, 0, hdr, sizeof *hdr); |
| 75 | |
| 76 | if (rc != 0) { |
| 77 | rc = BOOT_EFLASH; |
| 78 | BOOT_LOG_ERR("Failed reading image header"); |
| 79 | return BOOT_EFLASH; |
| 80 | } |
| 81 | |
| 82 | if (hdr->ih_magic != IMAGE_MAGIC) { |
| 83 | BOOT_LOG_ERR("Bad image magic 0x%lx", (unsigned long)hdr->ih_magic); |
| 84 | |
| 85 | return BOOT_EBADIMAGE; |
| 86 | } |
| 87 | |
| 88 | if (hdr->ih_flags & IMAGE_F_NON_BOOTABLE) { |
| 89 | BOOT_LOG_ERR("Image not bootable"); |
| 90 | |
| 91 | return BOOT_EBADIMAGE; |
| 92 | } |
| 93 | |
| 94 | if (!boot_u32_safe_add(&size, hdr->ih_img_size, hdr->ih_hdr_size) || |
Dominik Ermel | 036d521 | 2021-07-01 11:07:41 +0000 | [diff] [blame] | 95 | size >= flash_area_get_size(fa_p)) { |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 96 | return BOOT_EBADIMAGE; |
| 97 | } |
| 98 | |
| 99 | return 0; |
| 100 | } |
| 101 | |
Wouter Cappelle | 953a761 | 2021-05-03 16:53:05 +0200 | [diff] [blame] | 102 | #ifdef MCUBOOT_ENC_IMAGES |
| 103 | |
| 104 | /** |
| 105 | * Validate hash of a primary boot image doing on the fly decryption as well |
| 106 | * |
| 107 | * @param[in] fa_p flash area pointer |
| 108 | * @param[in] hdr boot image header pointer |
| 109 | * |
| 110 | * @return FIH_SUCCESS on success, error code otherwise |
| 111 | */ |
| 112 | inline static fih_int |
| 113 | boot_image_validate_encrypted(const struct flash_area *fa_p, |
| 114 | struct image_header *hdr) |
| 115 | { |
| 116 | static uint8_t tmpbuf[BOOT_TMPBUF_SZ]; |
| 117 | fih_int fih_rc = FIH_FAILURE; |
| 118 | |
| 119 | struct boot_loader_state boot_data; |
| 120 | struct boot_loader_state *state = &boot_data; |
| 121 | struct boot_status _bs; |
| 122 | struct boot_status *bs = &_bs; |
| 123 | uint8_t image_index; |
| 124 | int rc; |
| 125 | |
| 126 | memset(&boot_data, 0, sizeof(struct boot_loader_state)); |
| 127 | image_index = BOOT_CURR_IMG(state); |
| 128 | if (MUST_DECRYPT(fa_p, image_index, hdr)) { |
| 129 | rc = boot_enc_load(BOOT_CURR_ENC(state), image_index, hdr, fa_p, bs); |
| 130 | if (rc < 0) { |
| 131 | FIH_RET(fih_rc); |
| 132 | } |
| 133 | if (rc == 0 && boot_enc_set_key(BOOT_CURR_ENC(state), 0, bs)) { |
| 134 | FIH_RET(fih_rc); |
| 135 | } |
| 136 | } |
| 137 | FIH_CALL(bootutil_img_validate, fih_rc, BOOT_CURR_ENC(state), image_index, |
| 138 | hdr, fa_p, tmpbuf, BOOT_TMPBUF_SZ, NULL, 0, NULL); |
| 139 | |
| 140 | FIH_RET(fih_rc); |
| 141 | } |
| 142 | |
| 143 | /* |
| 144 | * Compute the total size of the given image. Includes the size of |
| 145 | * the TLVs. |
| 146 | */ |
| 147 | static int |
| 148 | read_image_size(const struct flash_area *fa_p, |
| 149 | struct image_header *hdr, |
| 150 | uint32_t *size) |
| 151 | { |
| 152 | struct image_tlv_info info; |
| 153 | uint32_t off; |
| 154 | uint32_t protect_tlv_size; |
| 155 | int rc; |
| 156 | |
| 157 | off = BOOT_TLV_OFF(hdr); |
| 158 | |
| 159 | if (flash_area_read(fa_p, off, &info, sizeof(info))) { |
| 160 | rc = BOOT_EFLASH; |
| 161 | goto done; |
| 162 | } |
| 163 | |
| 164 | protect_tlv_size = hdr->ih_protect_tlv_size; |
| 165 | if (info.it_magic == IMAGE_TLV_PROT_INFO_MAGIC) { |
| 166 | if (protect_tlv_size != info.it_tlv_tot) { |
| 167 | rc = BOOT_EBADIMAGE; |
| 168 | goto done; |
| 169 | } |
| 170 | |
| 171 | if (flash_area_read(fa_p, off + info.it_tlv_tot, &info, sizeof(info))) { |
| 172 | rc = BOOT_EFLASH; |
| 173 | goto done; |
| 174 | } |
| 175 | } else if (protect_tlv_size != 0) { |
| 176 | rc = BOOT_EBADIMAGE; |
| 177 | goto done; |
| 178 | } |
| 179 | |
| 180 | if (info.it_magic != IMAGE_TLV_INFO_MAGIC) { |
| 181 | rc = BOOT_EBADIMAGE; |
| 182 | goto done; |
| 183 | } |
| 184 | |
| 185 | *size = off + protect_tlv_size + info.it_tlv_tot; |
| 186 | rc = 0; |
| 187 | |
| 188 | done: |
| 189 | return rc; |
| 190 | } |
| 191 | |
| 192 | |
| 193 | /** |
| 194 | * reads, decrypts in memory & write back the decrypted image in the same region |
| 195 | * This function is NOT power failsafe since the image is decrypted in ram (stack) |
| 196 | * |
| 197 | * @param flash_area The ID of the source flash area. |
| 198 | * @param off_src The offset within the flash area to |
| 199 | * copy from. |
| 200 | * @param sz The number of bytes to copy. should match erase sector |
| 201 | * |
| 202 | * @return 0 on success; nonzero on failure. |
| 203 | */ |
| 204 | int |
| 205 | decrypt_region_inplace(struct boot_loader_state *state, |
| 206 | const struct flash_area *fap, |
| 207 | struct image_header *hdr, |
| 208 | uint32_t off, uint32_t sz) |
| 209 | { |
| 210 | uint32_t bytes_copied; |
| 211 | int chunk_sz; |
| 212 | int rc; |
| 213 | uint32_t tlv_off; |
| 214 | size_t blk_off; |
| 215 | uint16_t idx; |
| 216 | uint32_t blk_sz; |
| 217 | uint8_t image_index; |
| 218 | |
| 219 | static uint8_t buf[1024] __attribute__((aligned)); |
| 220 | assert(sz <= sizeof buf); |
| 221 | |
| 222 | bytes_copied = 0; |
| 223 | while (bytes_copied < sz) { |
| 224 | if (sz - bytes_copied > sizeof buf) { |
| 225 | chunk_sz = sizeof buf; |
| 226 | } else { |
| 227 | chunk_sz = sz - bytes_copied; |
| 228 | } |
| 229 | |
| 230 | rc = flash_area_read(fap, off + bytes_copied, buf, chunk_sz); |
| 231 | if (rc != 0) { |
| 232 | return BOOT_EFLASH; |
| 233 | } |
| 234 | |
| 235 | image_index = BOOT_CURR_IMG(state); |
| 236 | if (IS_ENCRYPTED(hdr)) { |
| 237 | blk_sz = chunk_sz; |
| 238 | idx = 0; |
| 239 | if (off + bytes_copied < hdr->ih_hdr_size) { |
| 240 | /* do not decrypt header */ |
| 241 | if (hdr->ih_hdr_size > (off + bytes_copied + chunk_sz)) { |
| 242 | /* all bytes in header, skip decryption */ |
| 243 | blk_sz = 0; |
| 244 | } |
| 245 | else { |
| 246 | blk_sz = off + bytes_copied + chunk_sz - hdr->ih_hdr_size; |
| 247 | } |
| 248 | |
| 249 | blk_off = 0; |
| 250 | idx = hdr->ih_hdr_size; |
| 251 | } else { |
| 252 | blk_off = ((off + bytes_copied) - hdr->ih_hdr_size) & 0xf; |
| 253 | } |
| 254 | tlv_off = BOOT_TLV_OFF(hdr); |
| 255 | if (off + bytes_copied + chunk_sz > tlv_off) { |
| 256 | /* do not decrypt TLVs */ |
| 257 | if (off + bytes_copied >= tlv_off) { |
| 258 | blk_sz = 0; |
| 259 | } else { |
| 260 | blk_sz = tlv_off - (off + bytes_copied); |
| 261 | } |
| 262 | } |
| 263 | boot_encrypt(BOOT_CURR_ENC(state), image_index, fap, |
| 264 | (off + bytes_copied + idx) - hdr->ih_hdr_size, blk_sz, |
| 265 | blk_off, &buf[idx]); |
| 266 | } |
| 267 | rc = flash_area_erase(fap, off + bytes_copied, chunk_sz); |
| 268 | if (rc != 0) { |
| 269 | return BOOT_EFLASH; |
| 270 | } |
| 271 | rc = flash_area_write(fap, off + bytes_copied, buf, chunk_sz); |
| 272 | if (rc != 0) { |
| 273 | return BOOT_EFLASH; |
| 274 | } |
| 275 | |
| 276 | bytes_copied += chunk_sz; |
| 277 | |
| 278 | MCUBOOT_WATCHDOG_FEED(); |
| 279 | } |
| 280 | |
| 281 | return 0; |
| 282 | } |
| 283 | |
| 284 | /** |
| 285 | * Check if a image was encrypted into the first slot, and decrypt it |
| 286 | * in place. this operation is not power failsafe. |
| 287 | * |
| 288 | * The operation is done by checking the last flash sector, and using it as a |
| 289 | * temporarely scratch partition. The |
| 290 | * |
| 291 | * @param[in] fa_p flash area pointer |
| 292 | * @param[in] hdr boot image header pointer |
| 293 | * |
| 294 | * @return FIH_SUCCESS on success, error code otherwise |
| 295 | */ |
| 296 | inline static fih_int |
| 297 | decrypt_image_inplace(const struct flash_area *fa_p, |
| 298 | struct image_header *hdr) |
| 299 | { |
| 300 | fih_int fih_rc = FIH_FAILURE; |
| 301 | int rc; |
| 302 | struct boot_loader_state boot_data; |
| 303 | struct boot_loader_state *state = &boot_data; |
| 304 | struct boot_status _bs; |
| 305 | struct boot_status *bs = &_bs; |
| 306 | size_t size; |
| 307 | size_t sect_size; |
| 308 | size_t sect_count; |
| 309 | size_t sect; |
| 310 | uint8_t image_index; |
| 311 | struct flash_sector sector; |
| 312 | |
| 313 | memset(&boot_data, 0, sizeof(struct boot_loader_state)); |
| 314 | memset(&_bs, 0, sizeof(struct boot_status)); |
| 315 | |
| 316 | /* Get size from last sector to know page/sector erase size */ |
| 317 | rc = flash_area_sector_from_off(boot_status_off(fa_p), §or); |
| 318 | |
| 319 | |
| 320 | image_index = BOOT_CURR_IMG(state); |
| 321 | |
| 322 | if (MUST_DECRYPT(fa_p, image_index, hdr)) { |
| 323 | #if 0 //Skip this step?, the image will just not boot if it's not decrypted properly |
| 324 | /* First check if the encrypted image is a good image before decrypting */ |
| 325 | FIH_CALL(boot_image_validate_encrypted,fih_rc,_fa_p,&_hdr); |
| 326 | if (fih_not_eq(fih_rc, FIH_SUCCESS)) { |
| 327 | FIH_RET(fih_rc); |
| 328 | } |
| 329 | #endif |
| 330 | memset(&boot_data, 0, sizeof(struct boot_loader_state)); |
| 331 | /* Load the encryption keys into cache */ |
| 332 | rc = boot_enc_load(BOOT_CURR_ENC(state), image_index, hdr, fa_p, bs); |
| 333 | if (rc < 0) { |
| 334 | FIH_RET(fih_rc); |
| 335 | } |
| 336 | if (rc == 0 && boot_enc_set_key(BOOT_CURR_ENC(state), 0, bs)) { |
| 337 | FIH_RET(fih_rc); |
| 338 | } |
| 339 | } |
| 340 | else |
| 341 | { |
| 342 | /* Expected encrypted image! */ |
| 343 | FIH_RET(fih_rc); |
| 344 | } |
| 345 | |
| 346 | uint32_t src_size = 0; |
| 347 | rc = read_image_size(fa_p,hdr, &src_size); |
| 348 | if (rc != 0) { |
| 349 | FIH_RET(fih_rc); |
| 350 | } |
| 351 | |
| 352 | sect_size = sector.fs_size; |
| 353 | sect_count = fa_p->fa_size / sect_size; |
| 354 | for (sect = 0, size = 0; size < src_size && sect < sect_count; sect++) { |
| 355 | rc = decrypt_region_inplace(state, fa_p,hdr, size, sect_size); |
| 356 | if (rc != 0) { |
| 357 | FIH_RET(fih_rc); |
| 358 | } |
| 359 | size += sect_size; |
| 360 | } |
| 361 | |
| 362 | fih_rc = FIH_SUCCESS; |
| 363 | FIH_RET(fih_rc); |
| 364 | } |
| 365 | |
| 366 | int |
| 367 | boot_handle_enc_fw() |
| 368 | { |
| 369 | int rc = -1; |
| 370 | fih_int fih_rc = FIH_FAILURE; |
| 371 | |
| 372 | rc = flash_area_open(FLASH_AREA_IMAGE_PRIMARY(0), &_fa_p); |
| 373 | assert(rc == 0); |
| 374 | |
| 375 | rc = boot_image_load_header(_fa_p, &_hdr); |
| 376 | if (rc != 0) { |
| 377 | goto out; |
| 378 | } |
| 379 | |
| 380 | if (IS_ENCRYPTED(&_hdr)) { |
| 381 | //encrypted, we need to decrypt in place |
| 382 | FIH_CALL(decrypt_image_inplace,fih_rc,_fa_p,&_hdr); |
| 383 | if (fih_not_eq(fih_rc, FIH_SUCCESS)) { |
| 384 | rc = -1; |
| 385 | goto out; |
| 386 | } |
| 387 | } |
| 388 | else |
| 389 | { |
| 390 | rc = 0; |
| 391 | } |
| 392 | |
| 393 | out: |
| 394 | flash_area_close(_fa_p); |
| 395 | return rc; |
| 396 | } |
| 397 | #endif |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 398 | |
| 399 | /** |
| 400 | * Gather information on image and prepare for booting. |
| 401 | * |
| 402 | * @parami[out] rsp Parameters for booting image, on success |
| 403 | * |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 404 | * @return FIH_SUCCESS on success; nonzero on failure. |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 405 | */ |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 406 | fih_int |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 407 | boot_go(struct boot_rsp *rsp) |
| 408 | { |
| 409 | int rc = -1; |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 410 | fih_int fih_rc = FIH_FAILURE; |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 411 | |
| 412 | rc = flash_area_open(FLASH_AREA_IMAGE_PRIMARY(0), &_fa_p); |
| 413 | assert(rc == 0); |
| 414 | |
| 415 | rc = boot_image_load_header(_fa_p, &_hdr); |
| 416 | if (rc != 0) |
| 417 | goto out; |
| 418 | |
| 419 | #ifdef MCUBOOT_VALIDATE_PRIMARY_SLOT |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 420 | FIH_CALL(boot_image_validate, fih_rc, _fa_p, &_hdr); |
| 421 | if (fih_not_eq(fih_rc, FIH_SUCCESS)) { |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 422 | goto out; |
| 423 | } |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 424 | #else |
| 425 | fih_rc = FIH_SUCCESS; |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 426 | #endif /* MCUBOOT_VALIDATE_PRIMARY_SLOT */ |
| 427 | |
Dominik Ermel | 036d521 | 2021-07-01 11:07:41 +0000 | [diff] [blame] | 428 | rsp->br_flash_dev_id = flash_area_get_device_id(_fa_p); |
| 429 | rsp->br_image_off = flash_area_get_off(_fa_p); |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 430 | rsp->br_hdr = &_hdr; |
| 431 | |
| 432 | out: |
| 433 | flash_area_close(_fa_p); |
Tamas Ban | ee6615d | 2020-09-30 07:58:48 +0100 | [diff] [blame] | 434 | |
| 435 | FIH_RET(fih_rc); |
Dominik Ermel | 8101c0c | 2020-05-19 13:01:16 +0000 | [diff] [blame] | 436 | } |