mirror of
https://github.com/trezor/trezor-firmware.git
synced 2024-11-27 18:08:15 +00:00
ed6aa48726
[no changelog]
328 lines
10 KiB
C
328 lines
10 KiB
C
/*
|
|
* This file is part of the Trezor project, https://trezor.io/
|
|
*
|
|
* Copyright (c) SatoshiLabs
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <stdbool.h>
|
|
#include "flash_area.h"
|
|
|
|
#define COUNTER_TAIL_WORDS 0
|
|
// Small items are encoded more efficiently.
|
|
#define NORCOW_SMALL_ITEM_SIZE \
|
|
(FLASH_BLOCK_SIZE - NORCOW_LEN_LEN - NORCOW_KEY_LEN)
|
|
#define NORCOW_VALID_FLAG 0xFF
|
|
#define NORCOW_VALID_FLAG_LEN 1
|
|
#define NORCOW_DATA_OPT_SIZE (FLASH_BLOCK_SIZE - NORCOW_VALID_FLAG_LEN)
|
|
#define NORCOW_MAX_PREFIX_LEN (FLASH_BLOCK_SIZE + NORCOW_VALID_FLAG_LEN)
|
|
|
|
/**
|
|
* Blockwise NORCOW storage.
|
|
*
|
|
* The items can have two different formats:
|
|
*
|
|
* 1. Small items
|
|
* Small items are stored in one block, the first two bytes are the key, the
|
|
* next two bytes are the length of the value, followed by the value itself.
|
|
* This format is used for items with length <= NORCOW_SMALL_ITEM_SIZE.
|
|
*
|
|
* 2. Large items
|
|
* Large items are stored in multiple blocks, the first block contains the key
|
|
* and the length of the value.
|
|
* Next blocks contain the value itself. If the last value block is not full,
|
|
* it includes the valid flag NORCOW_VALID_FLAG. Otherwise the valid flag is
|
|
* stored in the next block separately.
|
|
* This format is used for items with length > NORCOW_SMALL_ITEM_SIZE.
|
|
*
|
|
*
|
|
* For both formats, the remaining space in the blocks is padded with 0xFF.
|
|
*/
|
|
|
|
// Buffer for update bytes function, used to avoid writing partial blocks
|
|
CONFIDENTIAL static flash_block_t norcow_write_buffer = {0};
|
|
// Tracks how much data is in the buffer, not yet flashed
|
|
CONFIDENTIAL static uint16_t norcow_write_buffer_filled = 0;
|
|
// Key of the item being updated, -1 if no update is in progress
|
|
CONFIDENTIAL static int32_t norcow_write_buffer_key = -1;
|
|
|
|
/*
|
|
* Writes data to given sector, starting from offset
|
|
*/
|
|
static secbool write_item(uint8_t sector, uint32_t offset, uint16_t key,
|
|
const uint8_t *data, uint16_t len, uint32_t *pos) {
|
|
if (sector >= NORCOW_SECTOR_COUNT) {
|
|
return secfalse;
|
|
}
|
|
|
|
flash_block_t block = {((uint32_t)len << 16) | key};
|
|
if (len <= NORCOW_SMALL_ITEM_SIZE) {
|
|
// the whole item fits into one block, let's not waste space
|
|
if (offset + FLASH_BLOCK_SIZE > NORCOW_SECTOR_SIZE) {
|
|
return secfalse;
|
|
}
|
|
|
|
if (len > 0) {
|
|
memcpy(&block[1], data, len); // write data
|
|
}
|
|
|
|
ensure(flash_unlock_write(), NULL);
|
|
ensure(flash_area_write_block(&STORAGE_AREAS[sector], offset, block), NULL);
|
|
ensure(flash_lock_write(), NULL);
|
|
*pos = offset + FLASH_BLOCK_SIZE;
|
|
} else {
|
|
if (offset + FLASH_ALIGN(NORCOW_MAX_PREFIX_LEN + len) >
|
|
NORCOW_SECTOR_SIZE) {
|
|
return secfalse;
|
|
}
|
|
|
|
ensure(flash_unlock_write(), NULL);
|
|
|
|
// write len
|
|
ensure(flash_area_write_block(&STORAGE_AREAS[sector], offset, block), NULL);
|
|
offset += FLASH_BLOCK_SIZE;
|
|
|
|
*pos = FLASH_ALIGN(offset + NORCOW_VALID_FLAG_LEN + len);
|
|
if (data != NULL) {
|
|
// write all blocks except the last one
|
|
while ((uint32_t)(len + NORCOW_VALID_FLAG_LEN) > FLASH_BLOCK_SIZE) {
|
|
memcpy(block, data, FLASH_BLOCK_SIZE);
|
|
ensure(flash_area_write_block(&STORAGE_AREAS[sector], offset, block),
|
|
NULL);
|
|
offset += FLASH_BLOCK_SIZE;
|
|
data += FLASH_BLOCK_SIZE;
|
|
len -= FLASH_BLOCK_SIZE;
|
|
}
|
|
// write the last block
|
|
memset(block, 0xFF, sizeof(block));
|
|
memcpy(block, data, len);
|
|
((uint8_t *)block)[len] = NORCOW_VALID_FLAG;
|
|
ensure(flash_area_write_block(&STORAGE_AREAS[sector], offset, block),
|
|
NULL);
|
|
}
|
|
|
|
ensure(flash_lock_write(), NULL);
|
|
}
|
|
return sectrue;
|
|
}
|
|
|
|
/*
|
|
* Reads one item starting from offset
|
|
*/
|
|
static secbool read_item(uint8_t sector, uint32_t offset, uint16_t *key,
|
|
const void **val, uint16_t *len, uint32_t *pos) {
|
|
*pos = offset;
|
|
|
|
const void *k = norcow_ptr(sector, *pos, NORCOW_KEY_LEN);
|
|
if (k == NULL) {
|
|
return secfalse;
|
|
}
|
|
*pos += NORCOW_KEY_LEN;
|
|
|
|
const void *l = norcow_ptr(sector, *pos, NORCOW_LEN_LEN);
|
|
if (l == NULL) return secfalse;
|
|
memcpy(len, l, sizeof(uint16_t));
|
|
|
|
if (*len <= NORCOW_SMALL_ITEM_SIZE) {
|
|
memcpy(key, k, sizeof(uint16_t));
|
|
if (*key == NORCOW_KEY_FREE) {
|
|
return secfalse;
|
|
}
|
|
*pos += NORCOW_LEN_LEN;
|
|
} else {
|
|
*pos = offset + FLASH_BLOCK_SIZE;
|
|
|
|
uint32_t flg_pos = *pos + *len;
|
|
|
|
const void *flg = norcow_ptr(sector, flg_pos, NORCOW_VALID_FLAG_LEN);
|
|
if (flg == NULL) {
|
|
return secfalse;
|
|
}
|
|
|
|
if (*((const uint8_t *)flg) != NORCOW_VALID_FLAG) {
|
|
// Deleted item.
|
|
*key = NORCOW_KEY_DELETED;
|
|
} else {
|
|
memcpy(key, k, sizeof(uint16_t));
|
|
if (*key == NORCOW_KEY_FREE) {
|
|
return secfalse;
|
|
}
|
|
}
|
|
}
|
|
|
|
*val = norcow_ptr(sector, *pos, *len);
|
|
if (*val == NULL) return secfalse;
|
|
if (*len <= NORCOW_SMALL_ITEM_SIZE) {
|
|
*pos = FLASH_ALIGN(*pos + *len);
|
|
} else {
|
|
*pos = FLASH_ALIGN(*pos + *len + NORCOW_VALID_FLAG_LEN);
|
|
}
|
|
return sectrue;
|
|
}
|
|
|
|
void norcow_delete_item(const flash_area_t *area, uint32_t len,
|
|
uint32_t val_offset) {
|
|
uint32_t end;
|
|
|
|
// Move to the beginning of the block.
|
|
if (len <= NORCOW_SMALL_ITEM_SIZE) {
|
|
// Will delete the entire small item, setting the length to 0
|
|
end = val_offset + NORCOW_SMALL_ITEM_SIZE;
|
|
val_offset -= NORCOW_LEN_LEN + NORCOW_KEY_LEN;
|
|
} else {
|
|
end = val_offset + len + NORCOW_VALID_FLAG_LEN;
|
|
}
|
|
|
|
// Delete the item head + data.
|
|
ensure(flash_unlock_write(), NULL);
|
|
flash_block_t block = {0};
|
|
while (val_offset < end) {
|
|
ensure(flash_area_write_block(area, val_offset, block), NULL);
|
|
val_offset += FLASH_BLOCK_SIZE;
|
|
}
|
|
|
|
ensure(flash_lock_write(), NULL);
|
|
}
|
|
|
|
static secbool flash_area_write_bytes(const flash_area_t *area, uint32_t offset,
|
|
uint16_t dest_len, const void *val,
|
|
uint16_t len) {
|
|
uint8_t *ptr = (uint8_t *)flash_area_get_address(area, offset, dest_len);
|
|
|
|
if (val == NULL || ptr == NULL || dest_len != len) {
|
|
return secfalse;
|
|
}
|
|
|
|
return memcmp(val, ptr, len) == 0 ? sectrue : secfalse;
|
|
}
|
|
|
|
secbool norcow_next_counter(uint16_t key, uint32_t *count) {
|
|
uint16_t len = 0;
|
|
const uint32_t *val_stored = NULL;
|
|
if (sectrue != norcow_get(key, (const void **)&val_stored, &len)) {
|
|
*count = 0;
|
|
return norcow_set_counter(key, 0);
|
|
}
|
|
|
|
if (len != sizeof(uint32_t)) {
|
|
return secfalse;
|
|
}
|
|
|
|
*count = *val_stored + 1;
|
|
if (*count < *val_stored) {
|
|
// Value overflow.
|
|
return secfalse;
|
|
}
|
|
|
|
return norcow_set_counter(key, *count);
|
|
}
|
|
|
|
/*
|
|
* Update the value of the given key. The value is updated sequentially,
|
|
* starting from position 0, caller needs to ensure that all bytes are updated
|
|
* by calling this function enough times.
|
|
*
|
|
* The new value is flashed by blocks, if the data
|
|
* passed here do not fill the block it is stored until next call in buffer.
|
|
*/
|
|
secbool norcow_update_bytes(const uint16_t key, const uint8_t *data,
|
|
const uint16_t len) {
|
|
const void *ptr = NULL;
|
|
uint16_t allocated_len = 0;
|
|
if (sectrue != find_item(norcow_write_sector, key, &ptr, &allocated_len)) {
|
|
return secfalse;
|
|
}
|
|
|
|
if (allocated_len <= NORCOW_SMALL_ITEM_SIZE) {
|
|
// small items are not updated in place
|
|
return secfalse;
|
|
}
|
|
|
|
uint32_t sector_offset =
|
|
(const uint8_t *)ptr -
|
|
(const uint8_t *)norcow_ptr(norcow_write_sector, 0, NORCOW_SECTOR_SIZE);
|
|
|
|
const flash_area_t *area = &STORAGE_AREAS[norcow_write_sector];
|
|
|
|
if (norcow_write_buffer_key != key && norcow_write_buffer_key != -1) {
|
|
// some other update bytes is in process, abort
|
|
return secfalse;
|
|
}
|
|
|
|
if (norcow_write_buffer_key == -1) {
|
|
memset(norcow_write_buffer, 0xFF, sizeof(norcow_write_buffer));
|
|
norcow_write_buffer_key = key;
|
|
norcow_write_buffer_filled = 0;
|
|
norcow_write_buffer_flashed = 0;
|
|
}
|
|
|
|
if (norcow_write_buffer_flashed + norcow_write_buffer_filled + len >
|
|
allocated_len) {
|
|
return secfalse;
|
|
}
|
|
|
|
uint16_t tmp_len = len;
|
|
uint16_t flash_offset = sector_offset + norcow_write_buffer_flashed;
|
|
|
|
ensure(flash_unlock_write(), NULL);
|
|
while (tmp_len > 0) {
|
|
uint16_t buffer_space = FLASH_BLOCK_SIZE - norcow_write_buffer_filled;
|
|
uint16_t data_to_copy = (tmp_len > buffer_space ? buffer_space : tmp_len);
|
|
memcpy(&((uint8_t *)norcow_write_buffer)[norcow_write_buffer_filled], data,
|
|
data_to_copy);
|
|
data += data_to_copy;
|
|
norcow_write_buffer_filled += data_to_copy;
|
|
tmp_len -= data_to_copy;
|
|
|
|
bool all_data_received = (norcow_write_buffer_filled +
|
|
norcow_write_buffer_flashed) == allocated_len;
|
|
bool block_full = norcow_write_buffer_filled == FLASH_BLOCK_SIZE;
|
|
|
|
if (block_full || all_data_received) {
|
|
if (!block_full) {
|
|
// all data has been received, add valid flag to last block
|
|
((uint8_t *)norcow_write_buffer)[norcow_write_buffer_filled] =
|
|
NORCOW_VALID_FLAG;
|
|
}
|
|
|
|
ensure(flash_area_write_block(area, flash_offset, norcow_write_buffer),
|
|
NULL);
|
|
flash_offset += FLASH_BLOCK_SIZE;
|
|
|
|
if (block_full && all_data_received) {
|
|
// last block of data couldn't fit the valid flag, write it in next
|
|
// block
|
|
memset(norcow_write_buffer, 0xFF, sizeof(norcow_write_buffer));
|
|
((uint8_t *)norcow_write_buffer)[0] = NORCOW_VALID_FLAG;
|
|
ensure(flash_area_write_block(area, flash_offset, norcow_write_buffer),
|
|
NULL);
|
|
flash_offset += FLASH_BLOCK_SIZE;
|
|
}
|
|
|
|
norcow_write_buffer_filled = 0;
|
|
norcow_write_buffer_flashed += FLASH_BLOCK_SIZE;
|
|
memset(norcow_write_buffer, 0xFF, sizeof(norcow_write_buffer));
|
|
|
|
if (all_data_received) {
|
|
norcow_write_buffer_key = -1;
|
|
norcow_write_buffer_flashed = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
ensure(flash_lock_write(), NULL);
|
|
return sectrue;
|
|
}
|