mirror of https://github.com/wolfSSL/wolfBoot.git
F-9750: decrypt the stored block before the encrypted RMW patch
Both partial-block read-modify-write paths in ext_flash_encrypt_write read the stored block (ciphertext), spliced the new plaintext in, and re-encrypted the whole block. The untouched bytes were therefore XOR'd with the keystream a second time: stored ciphertext came back as plaintext in flash, and the next read of those bytes returned raw ciphertext instead of the original data. Any encrypted update whose first or last block partially overlaps a block with previous content - e.g. a retry over a previously written update image - silently corrupted the neighbouring bytes. Decrypt the stored block before splicing (into the scratch buffer, so no backend has to handle in-place decrypt) and re-encrypt the merged plaintext. Erased (0xFF) bytes round-trip unchanged because the decrypt/encrypt pair is the identity on the stored value. The re-encryption re-syncs the stream to the block index first: the decrypt step consumes keystream, and on the ChaCha/PKCS#11 backends encrypt and decrypt share a single stream state, while on the AES backends the decrypt context had not advanced with the full-block writes. The tail path also syncs the decrypt context, which on the AES backends sits at the first block's index after the aligned writes. Fallback-IV offset handling mirrors ext_flash_decrypt_read. New unit-extflash tests (run under the plain, AES-128, AES-256 and ChaCha20 variants): a mid-block patch must leave the untouched bytes of a previously written block intact, a trailing partial block must leave the rest of a previously written block intact, and a stream written in small unaligned chunks must round-trip byte for byte. All three fail on the pre-fix code with every cipher.pull/868/head
parent
d64bf67151
commit
5caac27eb9
|
|
@ -2720,7 +2720,19 @@ int RAMFUNCTION ext_flash_encrypt_write(uintptr_t address, const uint8_t *data,
|
|||
!= ENCRYPT_BLOCK_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
/* The stored block is ciphertext: decrypt it so the untouched bytes
|
||||
* can be patched as plaintext and re-encrypted. Re-encrypting the
|
||||
* ciphertext as-is would double-XOR the untouched bytes and store
|
||||
* them in plaintext. */
|
||||
crypto_decrypt(enc_block, block, ENCRYPT_BLOCK_SIZE);
|
||||
XMEMCPY(block, enc_block, ENCRYPT_BLOCK_SIZE);
|
||||
XMEMCPY(block + row_offset, data, step);
|
||||
/* The decrypt above consumed keystream on backends that share one
|
||||
* stream state between encrypt and decrypt; re-sync the stream to
|
||||
* this block before re-encrypting. */
|
||||
if (fallback_iv_forced)
|
||||
encrypt_iv_offset = FALLBACK_IV_OFFSET;
|
||||
wolfBoot_crypto_set_iv(encrypt_iv_nonce, iv_counter);
|
||||
crypto_encrypt(enc_block, block, ENCRYPT_BLOCK_SIZE);
|
||||
ret = ext_flash_write(row_address, enc_block, ENCRYPT_BLOCK_SIZE);
|
||||
if (ret < 0)
|
||||
|
|
@ -2758,11 +2770,26 @@ int RAMFUNCTION ext_flash_encrypt_write(uintptr_t address, const uint8_t *data,
|
|||
* the same way the unaligned head above is handled. */
|
||||
step = sz & (ENCRYPT_BLOCK_SIZE - 1);
|
||||
if (step > 0) {
|
||||
/* "address" is block-aligned here; index of the block being patched */
|
||||
uint32_t tail_iv_counter = (address - WOLFBOOT_PARTITION_UPDATE_ADDRESS) /
|
||||
ENCRYPT_BLOCK_SIZE;
|
||||
if (ext_flash_read(address, block, ENCRYPT_BLOCK_SIZE)
|
||||
!= ENCRYPT_BLOCK_SIZE) {
|
||||
return -1;
|
||||
}
|
||||
/* Sync the decrypt context to this block (on backends with separate
|
||||
* encrypt/decrypt contexts it did not advance with the full-block
|
||||
* writes above), then decrypt the stored ciphertext so the untouched
|
||||
* tail bytes survive the re-encryption. */
|
||||
if (fallback_iv_forced)
|
||||
encrypt_iv_offset = FALLBACK_IV_OFFSET;
|
||||
wolfBoot_crypto_set_iv(encrypt_iv_nonce, tail_iv_counter);
|
||||
crypto_decrypt(enc_block, block, ENCRYPT_BLOCK_SIZE);
|
||||
XMEMCPY(block, enc_block, ENCRYPT_BLOCK_SIZE);
|
||||
XMEMCPY(block, data, step);
|
||||
if (fallback_iv_forced)
|
||||
encrypt_iv_offset = FALLBACK_IV_OFFSET;
|
||||
wolfBoot_crypto_set_iv(encrypt_iv_nonce, tail_iv_counter);
|
||||
crypto_encrypt(enc_block, block, ENCRYPT_BLOCK_SIZE);
|
||||
ret = ext_flash_write(address, enc_block, ENCRYPT_BLOCK_SIZE);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -373,6 +373,111 @@ START_TEST(test_ext_enc_flash_oversized_write) {
|
|||
}
|
||||
END_TEST
|
||||
|
||||
/* A mid-block patch must not destroy the untouched bytes of a block that
|
||||
* already holds encrypted content: the read-modify-write has to decrypt the
|
||||
* stored block, splice the patch in, and re-encrypt. Re-encrypting the
|
||||
* ciphertext as-is double-XORs the untouched bytes and stores plaintext,
|
||||
* which then reads back as raw ciphertext. */
|
||||
START_TEST(test_ext_enc_flash_rmw_head_preserves_neighbors) {
|
||||
uint32_t address = 0x1000;
|
||||
uint8_t block_a[TEST_BLOCK_SIZE];
|
||||
uint8_t block_p[TEST_BLOCK_SIZE];
|
||||
uint8_t expect[TEST_BLOCK_SIZE];
|
||||
uint8_t data[TEST_BLOCK_SIZE];
|
||||
const int off = 4, len = 8;
|
||||
int i, rres, wres;
|
||||
|
||||
for (i = 0; i < TEST_BLOCK_SIZE; i++) {
|
||||
block_a[i] = (uint8_t)(0xA0 + i);
|
||||
block_p[i] = (uint8_t)(0xB0 + i);
|
||||
}
|
||||
|
||||
/* Prime the block with known content */
|
||||
wres = ext_flash_check_write(address, block_a, TEST_BLOCK_SIZE);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
|
||||
/* Patch an unaligned mid-block subrange */
|
||||
wres = ext_flash_check_write(address + off, block_p, len);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
|
||||
memcpy(expect, block_a, TEST_BLOCK_SIZE);
|
||||
memcpy(expect + off, block_p, len);
|
||||
rres = ext_flash_check_read(address, data, TEST_BLOCK_SIZE);
|
||||
ck_assert_int_eq(rres, TEST_BLOCK_SIZE);
|
||||
ck_assert_mem_eq(data, expect, TEST_BLOCK_SIZE);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* A write whose trailing partial block lands on a block that already holds
|
||||
* encrypted content: the untouched tail of that block must survive. */
|
||||
START_TEST(test_ext_enc_flash_rmw_tail_preserves_neighbors) {
|
||||
uint32_t address = 0x1000;
|
||||
uint8_t block_b[TEST_BLOCK_SIZE];
|
||||
uint8_t payload[2 * TEST_BLOCK_SIZE];
|
||||
uint8_t expect[2 * TEST_BLOCK_SIZE];
|
||||
uint8_t data[2 * TEST_BLOCK_SIZE];
|
||||
const int tail = 8;
|
||||
int i, rres, wres;
|
||||
|
||||
for (i = 0; i < TEST_BLOCK_SIZE; i++) {
|
||||
block_b[i] = (uint8_t)(0xC0 + i);
|
||||
payload[i] = (uint8_t)(0xD0 + i);
|
||||
payload[TEST_BLOCK_SIZE + i] = (uint8_t)(0xE0 + i);
|
||||
}
|
||||
|
||||
/* Two primed blocks */
|
||||
wres = ext_flash_check_write(address, payload, TEST_BLOCK_SIZE);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
wres = ext_flash_check_write(address + TEST_BLOCK_SIZE, block_b,
|
||||
TEST_BLOCK_SIZE);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
|
||||
/* Aligned write of one full block plus a partial tail into block two */
|
||||
wres = ext_flash_check_write(address, payload, TEST_BLOCK_SIZE + tail);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
|
||||
/* Block one is fully replaced; block two holds the spliced tail and the
|
||||
* original bytes beyond it. */
|
||||
memcpy(expect, payload, TEST_BLOCK_SIZE + tail);
|
||||
memcpy(expect + TEST_BLOCK_SIZE + tail, block_b + tail,
|
||||
TEST_BLOCK_SIZE - tail);
|
||||
rres = ext_flash_check_read(address, data, 2 * TEST_BLOCK_SIZE);
|
||||
ck_assert_int_eq(rres, 2 * TEST_BLOCK_SIZE);
|
||||
ck_assert_mem_eq(data, expect, 2 * TEST_BLOCK_SIZE);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
/* A stream written in small, unaligned chunks must round-trip: every chunk
|
||||
* boundary exercises the partial-block read-modify-write against content the
|
||||
* previous chunks already encrypted. */
|
||||
START_TEST(test_ext_enc_flash_chunked_stream_roundtrip) {
|
||||
uint32_t address = 0x1000;
|
||||
const uint32_t total = 3 * TEST_BLOCK_SIZE + 7;
|
||||
static uint8_t payload[3 * TEST_BLOCK_SIZE + 7];
|
||||
static uint8_t data[3 * TEST_BLOCK_SIZE + 7];
|
||||
uint32_t written = 0;
|
||||
int i, rres, wres;
|
||||
|
||||
for (i = 0; i < (int)total; i++)
|
||||
payload[i] = (uint8_t)(i * 7 + 3);
|
||||
|
||||
while (written < total) {
|
||||
uint32_t chunk = total - written;
|
||||
if (chunk > 13)
|
||||
chunk = 13;
|
||||
wres = ext_flash_check_write(address + written, payload + written,
|
||||
chunk);
|
||||
ck_assert_int_eq(wres, 0);
|
||||
written += chunk;
|
||||
}
|
||||
|
||||
memset(data, 0xA5, sizeof(data));
|
||||
rres = ext_flash_check_read(address, data, total);
|
||||
ck_assert_int_eq(rres, (int)total);
|
||||
ck_assert_mem_eq(data, payload, total);
|
||||
}
|
||||
END_TEST
|
||||
|
||||
|
||||
Suite *wolfboot_suite(void)
|
||||
{
|
||||
|
|
@ -386,6 +491,7 @@ Suite *wolfboot_suite(void)
|
|||
TCase *ext_enc_flash_short_read = tcase_create("External encrypted flash short unaligned read");
|
||||
TCase *ext_enc_flash_short_write = tcase_create("External encrypted flash short unaligned write");
|
||||
TCase *ext_enc_flash_oversized_write = tcase_create("External encrypted flash oversized write");
|
||||
TCase *ext_enc_flash_rmw = tcase_create("External encrypted flash RMW neighbour preservation");
|
||||
|
||||
/* Set parameters + add to suite */
|
||||
tcase_add_test(ext_flash_operations, test_ext_flash_operations);
|
||||
|
|
@ -396,17 +502,25 @@ Suite *wolfboot_suite(void)
|
|||
test_ext_enc_flash_short_unaligned_write);
|
||||
tcase_add_test(ext_enc_flash_oversized_write,
|
||||
test_ext_enc_flash_oversized_write);
|
||||
tcase_add_test(ext_enc_flash_rmw,
|
||||
test_ext_enc_flash_rmw_head_preserves_neighbors);
|
||||
tcase_add_test(ext_enc_flash_rmw,
|
||||
test_ext_enc_flash_rmw_tail_preserves_neighbors);
|
||||
tcase_add_test(ext_enc_flash_rmw,
|
||||
test_ext_enc_flash_chunked_stream_roundtrip);
|
||||
|
||||
tcase_set_timeout(ext_flash_operations, 20);
|
||||
tcase_set_timeout(ext_enc_flash_operations, 20);
|
||||
tcase_set_timeout(ext_enc_flash_short_read, 20);
|
||||
tcase_set_timeout(ext_enc_flash_short_write, 20);
|
||||
tcase_set_timeout(ext_enc_flash_oversized_write, 20);
|
||||
tcase_set_timeout(ext_enc_flash_rmw, 20);
|
||||
suite_add_tcase(s, ext_flash_operations);
|
||||
suite_add_tcase(s, ext_enc_flash_operations);
|
||||
suite_add_tcase(s, ext_enc_flash_short_read);
|
||||
suite_add_tcase(s, ext_enc_flash_short_write);
|
||||
suite_add_tcase(s, ext_enc_flash_oversized_write);
|
||||
suite_add_tcase(s, ext_enc_flash_rmw);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue