Skip to content

Commit cea92c4

Browse files
committed
[otbn] Add compression option
Use LZ4 to create a compressed OTBN binary (both imem and dmem). The OTBN build script creates both the uncompressed and compressed binary relying on garbage collection to filter out the unused binary. Change the otbn.c and otbn.h in the cryptolib to use LZ4 from lib/base in order to decompress the loaded imem and dmem from an application. The cryptolib (lib/crypto) uses the compressed binaries, however, the applications from silicon_creator (using silicon_creator/lib/drivers/otbn.c/h) still use the uncompressed version. This option saves the cryptolib ~5400bytes while increasing ~400bytes to load LZ4.c while providing backwards compatability for the boot. Concerning the CRC: The CRC for the imem/dmem is calculated before compression. For loading to OTBN, the OTBN is given the CRC of the uncompressed payload before and checks itself the CRC of what it receives. I.e., the CRC still provides end-2-end protection. Signed-off-by: Siemen Dhooghe <sdhooghe@google.com>
1 parent 53ecd50 commit cea92c4

7 files changed

Lines changed: 273 additions & 88 deletions

File tree

sw/device/lib/crypto/drivers/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,7 @@ cc_library(
373373
"//sw/device/lib/base:bitfield",
374374
"//sw/device/lib/base:crc32",
375375
"//sw/device/lib/base:hardened",
376+
"//sw/device/lib/base:lz4",
376377
"//sw/device/lib/base:random_order",
377378
"//sw/device/lib/crypto/impl:state",
378379
"//sw/device/lib/crypto/impl:status",

sw/device/lib/crypto/drivers/otbn.c

Lines changed: 92 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include "sw/device/lib/base/bitfield.h"
99
#include "sw/device/lib/base/crc32.h"
1010
#include "sw/device/lib/base/hardened.h"
11+
#include "sw/device/lib/base/lz4.h"
1112
#include "sw/device/lib/base/macros.h"
1213
#include "sw/device/lib/base/random_order.h"
1314
#include "sw/device/lib/base/status.h"
@@ -291,30 +292,98 @@ status_t otbn_set_ctrl_software_errs_fatal(bool enable) {
291292
}
292293

293294
/**
294-
* Checks if the OTBN application's IMEM and DMEM address parameters are valid.
295+
* Checks if the OTBN application's compressed IMEM and DMEM address parameters
296+
* are valid.
295297
*
296298
* This function checks the following properties:
297-
* - IMEM and DMEM ranges must not be "backwards" in memory, with the end
298-
* address coming before the start address.
299-
* - The IMEM range must be non-empty.
299+
* - IMEM and DMEM compressed pointer ranges must not be "backwards" in memory.
300+
* - The uncompressed IMEM range must be non-empty.
300301
*
301302
* @param app the OTBN application to check
302303
* @return `OTCRYPTO_OK` if checks pass, otherwise `OTCRYPTO_BAD_ARGS`.
303304
*/
304305
static status_t check_app_address_ranges(const otbn_app_t *app) {
305-
// IMEM must not be backwards or empty.
306-
if (app->imem_end <= app->imem_start) {
306+
// Compressed IMEM must not be backwards, and uncompressed size must not
307+
// be empty.
308+
if (app->imem_compressed_end <= app->imem_compressed_start ||
309+
app->imem_uncompressed_words == 0) {
307310
// COVERAGE (SW ERR) This is an internal function, we only provide it valid
308311
// inputs.
309312
return OTCRYPTO_BAD_ARGS;
310313
}
311-
HARDENED_CHECK_LT(app->imem_start, app->imem_end);
314+
HARDENED_CHECK_LT((uintptr_t)app->imem_compressed_start,
315+
(uintptr_t)app->imem_compressed_end);
316+
HARDENED_CHECK_GT(app->imem_uncompressed_words, 0);
312317

313-
// DMEM data section must not be backwards.
314-
if (app->dmem_data_end < app->dmem_data_start) {
318+
// Compressed DMEM must not be backwards.
319+
if (app->dmem_compressed_end < app->dmem_compressed_start) {
315320
return OTCRYPTO_BAD_ARGS;
316321
}
317-
HARDENED_CHECK_LE(app->dmem_data_start, app->dmem_data_end);
322+
HARDENED_CHECK_LE((uintptr_t)app->dmem_compressed_start,
323+
(uintptr_t)app->dmem_compressed_end);
324+
325+
return OTCRYPTO_OK;
326+
}
327+
328+
/**
329+
* Decompresses an LZ4-compressed payload and writes it to an OTBN address.
330+
*
331+
* @param src Start of the compressed payload.
332+
* @param src_end End of the compressed payload.
333+
* @param mmio_addr Destination MMIO address in OTBN memory.
334+
* @param expected_words Expected uncompressed size in 32-bit words.
335+
* @return `OTCRYPTO_OK` if successful, otherwise `OTCRYPTO_FATAL_ERR`.
336+
*/
337+
static status_t decompress_load(const uint8_t *src, const uint8_t *src_end,
338+
uint32_t mmio_addr, size_t expected_words) {
339+
// A 1024-byte stack buffer (256 32-bit words) for chunked
340+
// decompression.
341+
uint32_t local_chunk_buf[1024 / sizeof(uint32_t)];
342+
343+
uint32_t words_written = 0;
344+
345+
while (src < src_end) {
346+
// Check the header size
347+
if ((size_t)(src_end - src) < 4) {
348+
return OTCRYPTO_FATAL_ERR;
349+
}
350+
// Parse the 4-byte chunk header as unsigned 32-bit integers
351+
uint32_t comp_len = (uint32_t)(src[0] | (src[1] << 8));
352+
uint32_t uncomp_len = (uint32_t)(src[2] | (src[3] << 8));
353+
src += 4;
354+
355+
if (comp_len > (size_t)(src_end - src)) {
356+
return OTCRYPTO_FATAL_ERR;
357+
}
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+
// Write the decompressed data portion to OTBN memory.
370+
uint32_t words = uncomp_len / (uint32_t)sizeof(uint32_t);
371+
if (words > expected_words - words_written) {
372+
return OTCRYPTO_FATAL_ERR;
373+
}
374+
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+
words_written += words;
382+
}
383+
384+
if (words_written != expected_words) {
385+
return OTCRYPTO_FATAL_ERR;
386+
}
318387

319388
return OTCRYPTO_OK;
320389
}
@@ -329,15 +398,16 @@ status_t otbn_load_app(const otbn_app_t app) {
329398
hardened_bool_t skip_imem = kHardenedBoolFalse;
330399
// Check whether the state is present and use it if so
331400
if (status_ok(read_state_pointer(&state)) && state != NULL) {
332-
if (launder32(state->imem_cache) == (uint32_t)(uintptr_t)app.imem_start) {
333-
HARDENED_CHECK_EQ(state->imem_cache, (uint32_t)(uintptr_t)app.imem_start);
401+
if (launder32(state->imem_cache) ==
402+
(uint32_t)(uintptr_t)app.imem_compressed_start) {
403+
HARDENED_CHECK_EQ(state->imem_cache,
404+
(uint32_t)(uintptr_t)app.imem_compressed_start);
334405
skip_imem = kHardenedBoolTrue;
335406
}
336407
}
337408

338-
const size_t imem_num_words = (size_t)(app.imem_end - app.imem_start);
339-
const size_t data_num_words =
340-
(size_t)(app.dmem_data_end - app.dmem_data_start);
409+
const size_t imem_num_words = app.imem_uncompressed_words;
410+
const size_t data_num_words = app.dmem_uncompressed_words;
341411

342412
if (launder32(skip_imem) != kHardenedBoolTrue) {
343413
HARDENED_TRY(otbn_imem_sec_wipe());
@@ -358,13 +428,9 @@ status_t otbn_load_app(const otbn_app_t app) {
358428
HARDENED_TRY(
359429
check_offset_len(imem_offset, imem_num_words, kOtbnIMemSizeBytes));
360430
uint32_t imem_start_addr = kBase + OTBN_IMEM_REG_OFFSET + imem_offset;
361-
uint32_t i = 0;
362-
for (; launder32(i) < imem_num_words; i++) {
363-
HARDENED_CHECK_LT(i, imem_num_words);
364-
abs_mmio_write32(imem_start_addr + i * sizeof(uint32_t),
365-
app.imem_start[i]);
366-
}
367-
HARDENED_CHECK_EQ(i, imem_num_words);
431+
HARDENED_TRY(decompress_load(app.imem_compressed_start,
432+
app.imem_compressed_end, imem_start_addr,
433+
imem_num_words));
368434
} else {
369435
HARDENED_CHECK_EQ(skip_imem, kHardenedBoolTrue);
370436
}
@@ -374,13 +440,9 @@ status_t otbn_load_app(const otbn_app_t app) {
374440
HARDENED_TRY(
375441
check_offset_len(data_offset, data_num_words, kOtbnDMemSizeBytes));
376442
uint32_t data_start_addr = kBase + OTBN_DMEM_REG_OFFSET + data_offset;
377-
uint32_t i = 0;
378-
for (; launder32(i) < data_num_words; i++) {
379-
HARDENED_CHECK_LT(i, data_num_words);
380-
abs_mmio_write32(data_start_addr + i * sizeof(uint32_t),
381-
app.dmem_data_start[i]);
382-
}
383-
HARDENED_CHECK_EQ(i, data_num_words);
443+
HARDENED_TRY(decompress_load(app.dmem_compressed_start,
444+
app.dmem_compressed_end, data_start_addr,
445+
data_num_words));
384446

385447
if (launder32(skip_imem) != kHardenedBoolTrue) {
386448
// Ensure that the checksum matches expectations.
@@ -394,7 +456,7 @@ status_t otbn_load_app(const otbn_app_t app) {
394456
HARDENED_CHECK_EQ(checksum, app.checksum);
395457

396458
if (state != NULL) {
397-
state->imem_cache = (uint32_t)(uintptr_t)app.imem_start;
459+
state->imem_cache = (uint32_t)(uintptr_t)app.imem_compressed_start;
398460
}
399461
} else {
400462
HARDENED_CHECK_EQ(skip_imem, kHardenedBoolTrue);

sw/device/lib/crypto/drivers/otbn.h

Lines changed: 58 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -67,36 +67,52 @@ typedef uint32_t otbn_addr_t;
6767
*/
6868
typedef struct otbn_app {
6969
/**
70-
* Start of OTBN instruction memory in the embedded program.
70+
* Start of the compressed OTBN instruction memory in the embedded program.
7171
*
7272
* This pointer references Ibex's memory.
7373
*/
74-
const uint32_t *imem_start;
74+
const uint8_t *imem_compressed_start;
7575
/**
76-
* The first word after OTBN instruction memory in the embedded program.
76+
* The first byte after the compressed OTBN instruction memory.
7777
*
7878
* This pointer references Ibex's memory.
7979
*
80-
* This address satifies `imem_start < imem_end`.
80+
* This address satisfies `imem_compressed_start <= imem_compressed_end`.
8181
*/
82-
const uint32_t *imem_end;
82+
const uint8_t *imem_compressed_end;
8383
/**
84-
* Start of initialized OTBN data in the embedded program.
84+
* The expected size of the uncompressed instruction memory, in 32-bit words.
85+
*
86+
* This defines exactly how many words the host CPU will write to the OTBN
87+
* IMEM registers after decompressing the payload.
88+
*/
89+
const size_t imem_uncompressed_words;
90+
91+
/**
92+
* Start of the compressed initialized OTBN data in the embedded program.
8593
*
8694
* This pointer references Ibex's memory.
8795
*
88-
* Data in between `dmem_data_start` and `dmem_data_end` will be copied to
89-
* OTBN at app load time.
96+
* Data between `dmem_compressed_start` and `dmem_compressed_end` will be
97+
* decompressed and copied to OTBN at app load time.
9098
*/
91-
const uint32_t *dmem_data_start;
99+
const uint8_t *dmem_compressed_start;
92100
/**
93-
* The first word after initialized OTBN data in the embedded program.
101+
* The first byte after the compressed initialized OTBN data.
94102
*
95103
* This pointer references Ibex's memory.
96104
*
97-
* Should satisfy `dmem_data_start <= dmem_data_end`.
105+
* Should satisfy `dmem_compressed_start <= dmem_compressed_end`.
98106
*/
99-
const uint32_t *dmem_data_end;
107+
const uint8_t *dmem_compressed_end;
108+
/**
109+
* The expected size of the uncompressed initialized data, in 32-bit words.
110+
*
111+
* This defines exactly how many words the host CPU will write to the OTBN
112+
* DMEM registers after decompressing the payload.
113+
*/
114+
const size_t dmem_uncompressed_words;
115+
100116
/**
101117
* Start of initialized data section in OTBN's DMEM.
102118
*
@@ -166,22 +182,25 @@ typedef struct otbn_app {
166182
/**
167183
* Makes an embedded OTBN application image available for use.
168184
*
169-
* Make symbols available that indicate the start and the end of instruction
170-
* and data memory regions, as they are stored in the device memory.
185+
* Makes symbols available that indicate the start and the end of the compressed
186+
* instruction and data memory regions, their uncompressed sizes, and the
187+
* expected checksum for integrity verification.
171188
*
172189
* Use this macro instead of manually declaring the symbols as symbol names
173190
* might change.
174191
*
175192
* @param app_name Name of the application to load, which is typically the
176193
* name of the main (assembly) source file.
177194
*/
178-
#define OTBN_DECLARE_APP_SYMBOLS(app_name) \
179-
OTBN_DECLARE_SYMBOL_PTR(app_name, _imem_start); \
180-
OTBN_DECLARE_SYMBOL_PTR(app_name, _imem_end); \
181-
OTBN_DECLARE_SYMBOL_PTR(app_name, _dmem_data_start); \
182-
OTBN_DECLARE_SYMBOL_PTR(app_name, _dmem_data_end); \
183-
OTBN_DECLARE_SYMBOL_ADDR(app_name, _dmem_data_start); \
184-
OTBN_DECLARE_SYMBOL_ADDR(app_name, _checksum)
195+
#define OTBN_DECLARE_APP_SYMBOLS(app_name) \
196+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, imem_compressed_start)[]; \
197+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, imem_compressed_end)[]; \
198+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, dmem_compressed_start)[]; \
199+
extern const uint8_t OTBN_SYMBOL_PTR(app_name, dmem_compressed_end)[]; \
200+
OTBN_DECLARE_SYMBOL_ADDR(app_name, _dmem_data_start); \
201+
OTBN_DECLARE_SYMBOL_ADDR(app_name, _checksum); \
202+
OTBN_DECLARE_SYMBOL_ADDR(app_name, imem_uncompressed_bytes); \
203+
OTBN_DECLARE_SYMBOL_ADDR(app_name, dmem_uncompressed_bytes)
185204

186205
/**
187206
* Initializes the OTBN application information structure.
@@ -190,17 +209,27 @@ typedef struct otbn_app {
190209
* through `OTBN_DECLARE_APP_SYMBOLS()`, use this macro to initialize an
191210
* `otbn_app_t` struct with those symbols.
192211
*
212+
* This macro automatically handles the mapping of the compressed payload
213+
* pointers and converts the uncompressed byte counts provided by the linker
214+
* into the 32-bit word counts expected by the OTBN loader.
215+
*
193216
* @param app_name Name of the application to load.
194217
* @see OTBN_DECLARE_APP_SYMBOLS()
195218
*/
196-
#define OTBN_APP_T_INIT(app_name) \
197-
((otbn_app_t){ \
198-
.imem_start = OTBN_SYMBOL_PTR(app_name, _imem_start), \
199-
.imem_end = OTBN_SYMBOL_PTR(app_name, _imem_end), \
200-
.dmem_data_start = OTBN_SYMBOL_PTR(app_name, _dmem_data_start), \
201-
.dmem_data_end = OTBN_SYMBOL_PTR(app_name, _dmem_data_end), \
202-
.dmem_data_start_addr = OTBN_ADDR_T_INIT(app_name, _dmem_data_start), \
203-
.checksum = OTBN_ADDR_T_INIT(app_name, _checksum), \
219+
#define OTBN_APP_T_INIT(app_name) \
220+
((otbn_app_t){ \
221+
.imem_compressed_start = \
222+
OTBN_SYMBOL_PTR(app_name, imem_compressed_start), \
223+
.imem_compressed_end = OTBN_SYMBOL_PTR(app_name, imem_compressed_end), \
224+
.imem_uncompressed_words = \
225+
OTBN_ADDR_T_INIT(app_name, imem_uncompressed_bytes) / 4, \
226+
.dmem_compressed_start = \
227+
OTBN_SYMBOL_PTR(app_name, dmem_compressed_start), \
228+
.dmem_compressed_end = OTBN_SYMBOL_PTR(app_name, dmem_compressed_end), \
229+
.dmem_uncompressed_words = \
230+
OTBN_ADDR_T_INIT(app_name, dmem_uncompressed_bytes) / 4, \
231+
.dmem_data_start_addr = OTBN_ADDR_T_INIT(app_name, _dmem_data_start), \
232+
.checksum = OTBN_ADDR_T_INIT(app_name, _checksum), \
204233
})
205234

206235
/**

sw/device/lib/crypto/drivers/otbn_test.c

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,16 +43,31 @@ static status_t run_negative_test(void) {
4343
// Test memory boundary check (offset + length exceeds memory)
4444
CHECK(otbn_dmem_read(0xFFFFFFFF, 0, NULL).value == OTCRYPTO_BAD_ARGS.value);
4545

46-
// App range check (imem_end comes before imem_start)
47-
static const uint32_t kDummyMem[] = {0};
48-
otbn_app_t bad_range_app = {.imem_start = kDummyMem + 1,
49-
.imem_end = kDummyMem, // Backwards!
50-
.dmem_data_start = kDummyMem,
51-
.dmem_data_end = kDummyMem,
46+
// Dummy byte array for testing invalid pointers
47+
static const uint8_t kDummyMem[] = {0};
48+
49+
// App range check (imem_compressed_end comes before imem_compressed_start)
50+
otbn_app_t bad_range_app = {.imem_compressed_start = kDummyMem + 1,
51+
.imem_compressed_end = kDummyMem, // Backwards!
52+
.imem_uncompressed_words = 1,
53+
.dmem_compressed_start = kDummyMem,
54+
.dmem_compressed_end = kDummyMem,
55+
.dmem_uncompressed_words = 0,
5256
.dmem_data_start_addr = 0,
5357
.checksum = 0};
5458
CHECK(otbn_load_app(bad_range_app).value == OTCRYPTO_BAD_ARGS.value);
5559

60+
// App range check (imem uncompressed size is 0 - not allowed)
61+
otbn_app_t empty_imem_app = {.imem_compressed_start = kDummyMem,
62+
.imem_compressed_end = kDummyMem + 1,
63+
.imem_uncompressed_words = 0, // Empty!
64+
.dmem_compressed_start = kDummyMem,
65+
.dmem_compressed_end = kDummyMem,
66+
.dmem_uncompressed_words = 0,
67+
.dmem_data_start_addr = 0,
68+
.checksum = 0};
69+
CHECK(otbn_load_app(empty_imem_app).value == OTCRYPTO_BAD_ARGS.value);
70+
5671
// Force OTBN out of the IDLE state by manually triggering a Secure Wipe.
5772
abs_mmio_write32(TOP_EARLGREY_OTBN_BASE_ADDR + OTBN_CMD_REG_OFFSET,
5873
CMD_SEC_WIPE_DMEM);
@@ -64,13 +79,21 @@ static status_t run_negative_test(void) {
6479
CHECK(otbn_set_ctrl_software_errs_fatal(true).value == OTCRYPTO_OK.value);
6580
CHECK(otbn_set_ctrl_software_errs_fatal(false).value == OTCRYPTO_OK.value);
6681

67-
// 6. Test bad checksum
68-
otbn_app_t bad_checksum_app = {.imem_start = kDummyMem,
69-
.imem_end = kDummyMem + 1,
70-
.dmem_data_start = kDummyMem,
71-
.dmem_data_end = kDummyMem + 1,
72-
.dmem_data_start_addr = 0,
73-
.checksum = 0xDEADBEEF};
82+
// Test bad checksum
83+
// We provide a tiny valid compressed block (0x40 = 4 literals, followed by
84+
// four 0x00 bytes) so decompression succeeds, ensuring we actually hit the
85+
// checksum verification failure.
86+
static const uint8_t kValidLz4[] = {0x40, 0x00, 0x00, 0x00, 0x00};
87+
88+
otbn_app_t bad_checksum_app = {
89+
.imem_compressed_start = kValidLz4,
90+
.imem_compressed_end = kValidLz4 + sizeof(kValidLz4),
91+
.imem_uncompressed_words = 1,
92+
.dmem_compressed_start = kDummyMem,
93+
.dmem_compressed_end = kDummyMem,
94+
.dmem_uncompressed_words = 0,
95+
.dmem_data_start_addr = 0,
96+
.checksum = 0xDEADBEEF};
7497
CHECK(otbn_load_app(bad_checksum_app).value == OTCRYPTO_FATAL_ERR.value);
7598

7699
return OTCRYPTO_OK;

0 commit comments

Comments
 (0)