|
8 | 8 | #include "sw/device/lib/base/bitfield.h" |
9 | 9 | #include "sw/device/lib/base/crc32.h" |
10 | 10 | #include "sw/device/lib/base/hardened.h" |
| 11 | +#include "sw/device/lib/base/lz4.h" |
11 | 12 | #include "sw/device/lib/base/macros.h" |
12 | 13 | #include "sw/device/lib/base/random_order.h" |
13 | 14 | #include "sw/device/lib/base/status.h" |
@@ -283,79 +284,145 @@ status_t otbn_set_ctrl_software_errs_fatal(bool enable) { |
283 | 284 | } |
284 | 285 |
|
285 | 286 | /** |
286 | | - * Checks if the OTBN application's IMEM and DMEM address parameters are valid. |
| 287 | + * Checks if the OTBN application's compressed IMEM and DMEM address parameters |
| 288 | + * are valid. |
287 | 289 | * |
288 | 290 | * This function checks the following properties: |
289 | | - * - IMEM and DMEM ranges must not be "backwards" in memory, with the end |
290 | | - * address coming before the start address. |
291 | | - * - The IMEM range must be non-empty. |
| 291 | + * - IMEM and DMEM compressed pointer ranges must not be "backwards" in memory. |
| 292 | + * - The uncompressed IMEM range must be non-empty. |
292 | 293 | * |
293 | 294 | * @param app the OTBN application to check |
294 | 295 | * @return `OTCRYPTO_OK` if checks pass, otherwise `OTCRYPTO_BAD_ARGS`. |
295 | 296 | */ |
296 | 297 | static status_t check_app_address_ranges(const otbn_app_t *app) { |
297 | | - // IMEM must not be backwards or empty. |
298 | | - if (app->imem_end <= app->imem_start) { |
| 298 | + // Compressed IMEM must not be backwards, and uncompressed size must not |
| 299 | + // be empty. |
| 300 | + if (app->imem_compressed_end <= app->imem_compressed_start || |
| 301 | + app->imem_uncompressed_words == 0) { |
299 | 302 | // COVERAGE (SW ERR) This is an internal function, we only provide it valid |
300 | 303 | // inputs. |
301 | 304 | return OTCRYPTO_BAD_ARGS; |
302 | 305 | } |
303 | | - HARDENED_CHECK_LT(app->imem_start, app->imem_end); |
| 306 | + HARDENED_CHECK_LT((uintptr_t)app->imem_compressed_start, |
| 307 | + (uintptr_t)app->imem_compressed_end); |
| 308 | + HARDENED_CHECK_GT(app->imem_uncompressed_words, 0); |
304 | 309 |
|
305 | | - // DMEM data section must not be backwards. |
306 | | - if (app->dmem_data_end < app->dmem_data_start) { |
| 310 | + // Compressed DMEM must not be backwards. |
| 311 | + if (app->dmem_compressed_end < app->dmem_compressed_start) { |
307 | 312 | return OTCRYPTO_BAD_ARGS; |
308 | 313 | } |
309 | | - HARDENED_CHECK_LE(app->dmem_data_start, app->dmem_data_end); |
| 314 | + HARDENED_CHECK_LE((uintptr_t)app->dmem_compressed_start, |
| 315 | + (uintptr_t)app->dmem_compressed_end); |
310 | 316 |
|
311 | 317 | return OTCRYPTO_OK; |
312 | 318 | } |
313 | 319 |
|
314 | 320 | status_t otbn_load_app(const otbn_app_t app) { |
315 | 321 | HARDENED_TRY(check_app_address_ranges(&app)); |
316 | 322 |
|
| 323 | + // Ensure that the total uncompressed IMEM section fits in IMEM. |
| 324 | + // IMEM always starts at offset 0. |
| 325 | + HARDENED_TRY( |
| 326 | + check_offset_len(0, app.imem_uncompressed_words, kOtbnIMemSizeBytes)); |
| 327 | + |
| 328 | + // Ensure that the total uncompressed data section fits in DMEM. |
| 329 | + HARDENED_TRY(check_offset_len(app.dmem_data_start_addr, |
| 330 | + app.dmem_uncompressed_words, |
| 331 | + kOtbnDMemSizeBytes)); |
| 332 | + |
317 | 333 | // Ensure OTBN is idle. |
318 | 334 | HARDENED_TRY(otbn_assert_idle()); |
319 | 335 |
|
320 | | - const size_t imem_num_words = (size_t)(app.imem_end - app.imem_start); |
321 | | - const size_t data_num_words = |
322 | | - (size_t)(app.dmem_data_end - app.dmem_data_start); |
323 | | - |
324 | 336 | HARDENED_TRY(otbn_imem_sec_wipe()); |
325 | 337 | HARDENED_TRY(otbn_dmem_sec_wipe()); |
326 | 338 |
|
327 | 339 | // Reset the LOAD_CHECKSUM register. |
328 | 340 | abs_mmio_write32(kBase + OTBN_LOAD_CHECKSUM_REG_OFFSET, 0); |
329 | 341 |
|
330 | | - // Ensure that the IMEM section fits in IMEM and the data section fits in |
331 | | - // DMEM. |
332 | | - HARDENED_TRY(check_offset_len(app.dmem_data_start_addr, data_num_words, |
333 | | - kOtbnDMemSizeBytes)); |
| 342 | + // --- INCREASED CHUNK BUFFER --- |
| 343 | + // A 1024-byte stack buffer (256 32-bit words) for faster chunked |
| 344 | + // decompression. Requires ~1KB of stack space, which is well within safe |
| 345 | + // limits if RAM is ample. |
| 346 | + uint32_t local_chunk_buf[1024 / sizeof(uint32_t)]; |
| 347 | + |
| 348 | + // IMEM decompression and loading |
| 349 | + const uint8_t *src = app.imem_compressed_start; |
| 350 | + uint32_t mmio_addr = kBase + OTBN_IMEM_REG_OFFSET; |
| 351 | + uint32_t imem_words_written = 0; |
| 352 | + |
| 353 | + while (src < app.imem_compressed_end) { |
| 354 | + // Parse the 4-byte chunk header as unsigned 32-bit integers |
| 355 | + uint32_t comp_len = (uint32_t)(src[0] | (src[1] << 8)); |
| 356 | + uint32_t uncomp_len = (uint32_t)(src[2] | (src[3] << 8)); |
| 357 | + src += 4; |
| 358 | + |
| 359 | + if (uncomp_len % sizeof(uint32_t) != 0) { |
| 360 | + return OTCRYPTO_FATAL_ERR; |
| 361 | + } |
| 362 | + |
| 363 | + if (LZ4_decompress((const char *)src, (char *)local_chunk_buf, |
| 364 | + (int)comp_len, |
| 365 | + (int)sizeof(local_chunk_buf)) != (int)uncomp_len) { |
| 366 | + return OTCRYPTO_FATAL_ERR; |
| 367 | + } |
| 368 | + |
| 369 | + uint32_t words = uncomp_len / (uint32_t)sizeof(uint32_t); |
| 370 | + if (words > app.imem_uncompressed_words - imem_words_written) { |
| 371 | + return OTCRYPTO_FATAL_ERR; |
| 372 | + } |
| 373 | + |
| 374 | + // Write to IMEM. Always starts at zero on the OTBN side. |
| 375 | + for (uint32_t i = 0; launder32(i) < words; i++) { |
| 376 | + abs_mmio_write32(mmio_addr + i * sizeof(uint32_t), local_chunk_buf[i]); |
| 377 | + } |
| 378 | + |
| 379 | + src += comp_len; |
| 380 | + mmio_addr += uncomp_len; |
| 381 | + imem_words_written += words; |
| 382 | + } |
334 | 383 |
|
335 | | - // Write to IMEM. Always starts at zero on the OTBN side. |
336 | | - otbn_addr_t imem_offset = 0; |
337 | | - HARDENED_TRY( |
338 | | - check_offset_len(imem_offset, imem_num_words, kOtbnIMemSizeBytes)); |
339 | | - uint32_t imem_start_addr = kBase + OTBN_IMEM_REG_OFFSET + imem_offset; |
340 | | - uint32_t i = 0; |
341 | | - for (; launder32(i) < imem_num_words; i++) { |
342 | | - HARDENED_CHECK_LT(i, imem_num_words); |
343 | | - abs_mmio_write32(imem_start_addr + i * sizeof(uint32_t), app.imem_start[i]); |
| 384 | + if (imem_words_written != app.imem_uncompressed_words) { |
| 385 | + return OTCRYPTO_FATAL_ERR; |
344 | 386 | } |
345 | | - HARDENED_CHECK_EQ(i, imem_num_words); |
346 | 387 |
|
347 | | - // Write the data portion to DMEM. |
348 | | - otbn_addr_t data_offset = app.dmem_data_start_addr; |
349 | | - HARDENED_TRY( |
350 | | - check_offset_len(data_offset, data_num_words, kOtbnDMemSizeBytes)); |
351 | | - uint32_t data_start_addr = kBase + OTBN_DMEM_REG_OFFSET + data_offset; |
352 | | - i = 0; |
353 | | - for (; launder32(i) < data_num_words; i++) { |
354 | | - HARDENED_CHECK_LT(i, data_num_words); |
355 | | - abs_mmio_write32(data_start_addr + i * sizeof(uint32_t), |
356 | | - app.dmem_data_start[i]); |
| 388 | + // DMEM decompression and loading |
| 389 | + src = app.dmem_compressed_start; |
| 390 | + mmio_addr = kBase + OTBN_DMEM_REG_OFFSET + app.dmem_data_start_addr; |
| 391 | + uint32_t dmem_words_written = 0; |
| 392 | + |
| 393 | + while (src < app.dmem_compressed_end) { |
| 394 | + uint32_t comp_len = (uint32_t)(src[0] | (src[1] << 8)); |
| 395 | + uint32_t uncomp_len = (uint32_t)(src[2] | (src[3] << 8)); |
| 396 | + src += 4; |
| 397 | + |
| 398 | + if (uncomp_len % sizeof(uint32_t) != 0) { |
| 399 | + return OTCRYPTO_FATAL_ERR; |
| 400 | + } |
| 401 | + |
| 402 | + if (LZ4_decompress((const char *)src, (char *)local_chunk_buf, |
| 403 | + (int)comp_len, |
| 404 | + (int)sizeof(local_chunk_buf)) != (int)uncomp_len) { |
| 405 | + return OTCRYPTO_FATAL_ERR; |
| 406 | + } |
| 407 | + |
| 408 | + // Write the data portion to DMEM. |
| 409 | + uint32_t words = uncomp_len / (uint32_t)sizeof(uint32_t); |
| 410 | + if (words > app.dmem_uncompressed_words - dmem_words_written) { |
| 411 | + return OTCRYPTO_FATAL_ERR; |
| 412 | + } |
| 413 | + |
| 414 | + for (uint32_t i = 0; launder32(i) < words; i++) { |
| 415 | + abs_mmio_write32(mmio_addr + i * sizeof(uint32_t), local_chunk_buf[i]); |
| 416 | + } |
| 417 | + |
| 418 | + src += comp_len; |
| 419 | + mmio_addr += uncomp_len; |
| 420 | + dmem_words_written += words; |
| 421 | + } |
| 422 | + |
| 423 | + if (dmem_words_written != app.dmem_uncompressed_words) { |
| 424 | + return OTCRYPTO_FATAL_ERR; |
357 | 425 | } |
358 | | - HARDENED_CHECK_EQ(i, data_num_words); |
359 | 426 |
|
360 | 427 | // Ensure that the checksum matches expectations. |
361 | 428 | uint32_t checksum = abs_mmio_read32(kBase + OTBN_LOAD_CHECKSUM_REG_OFFSET); |
|
0 commit comments