/* * 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; }