diff options
-rw-r--r-- | common/new_nvmem.c | 2956 | ||||
-rw-r--r-- | include/config.h | 5 | ||||
-rw-r--r-- | include/flash_log.h | 32 | ||||
-rw-r--r-- | include/new_nvmem.h | 155 |
4 files changed, 3148 insertions, 0 deletions
diff --git a/common/new_nvmem.c b/common/new_nvmem.c new file mode 100644 index 0000000000..50433feb4c --- /dev/null +++ b/common/new_nvmem.c @@ -0,0 +1,2956 @@ +/* Copyright 2019 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include <stdint.h> +#include <string.h> + +#include "test/nvmem_test.h" + +#include "common.h" +#include "board.h" +#include "console.h" +#include "crypto_api.h" +#include "flash.h" +#include "flash_log.h" +#include "new_nvmem.h" +#include "nvmem.h" +#include "nvmem_vars.h" +#include "shared_mem.h" +#include "system.h" +#include "timer.h" + +/* + * ==== Overview + * + * This file is the implementation of the new TPM NVMEM flash storage layer. + * These are major differences compared to the legacy implementation: + * + * NVMEM TPM objects are stored in flash in separate containers, each one + * protected by hash and possibly encrypted. When nvmem_commit() is invoked, + * only objects changed in the NVMEM cache are updated in the flash. + * + * The (key, value) pairs are also stored in flash in the same kind of + * separate containers. There is no special area allocated for the (key, value) + * pairs in flash, they are interleaved with TPM objects. + * + * The (key, value) pairs are not kept in the NVMEM cache, they are stored in + * flash only. This causes a few deviations from the legacy (key, value) pair + * interface: + * + * - no need to initialize (key, value) storage separately, initvars() API is + * not there. + * + * - when the user is retrieving a (key, value) object, he/she is given a + * dynamically allocated buffer, which needs to be explicitly released by + * calling the new API: freevar(). + * + * - the (key. value) pairs, if modified, are updated in flash immediately, + * not after nvmem_commit() is called. + * + * Storing (key, value) pairs in the flash frees up 272 bytes of the cache + * space previously used, but makes it more difficult to control how flash + * memory is split between TPM objects and (key, value) pairs. A soft limit of + * 1K is introduced, limiting the total space used by (key, value) pairs data, + * not including tuple headers. + * + * ===== Organizing flash storage + * + * The total space used by the NVMEM in flash is reduced from 24 to 20 + * kilobytes, five 2K pages at the top of each flash bank. These pages are + * concatenated into a single storage space, based on the page header placed + * at the bottom of each page (struct nn_page_header). The page header + * includes a 21 bit page number (this allows to order pages properly on + * initialization), the offset of the first data object in the page and the + * hash of the entire header. + * + * Yet another limitation of the new scheme is that no object stored in NVMEM + * can exceed the flash page size less the page header size and container + * header size. This allows for objects as large as 2035 bytes. Objects can + * span flash pages. Note that reserved TPM object STATE_CLEAR_DATA exceeds 2K + * in size, this is why one of its components (the .pcrSave field) is stored + * in flash separately. + * + * ===== Object containers + * + * The container header (struct nn_container) describes the contained TPM + * object or (key, value) pair, along with the size, and also includes the + * hash of the entire container calculated when the hash field is set to zero. + * + * When an object needs to be updated, it is stored at the end of the used + * flash space in a container with the higher .generation field value, and + * then the older container's type field is erased, thus marking it as a + * deleted object. The idea is that when initializing NVMEM cache after reset, + * in case two instances of the same object are found in the flash because the + * new instance was saved, but the old instance was not erased because of some + * failure, the instance with larger .generation field value wins. Note that + * this error recovery procedure is supplemented by use of transaction + * delimiter objects described below. + * + * The container type field is duplicated in the container header, this allows + * verification of the container hash after even the object was erased. + * + * In order to be able to erase the type the container must start at the 4 + * byte boundary. This in turn requires that each container is padded such + * that total storage taken by the container is divisible by 4. + * + * To be able to tell if two containers contain two instances of the same + * object, one needs to be able to identify the object stored in the container. + * For the three distinct types of objects it works as follows: + * + * - (key, value) pair: key, stored in the contained tuple. + * + * - reserved tpm object: the first byte stored in the container. PCR + * values from STATE_CLEAR_DATA.pcrSave field are stored as separate + * reserved objects with the appropriate first bytes. + * + * - evictable tpm object: the first 4 bytes stored in the container, the + * evictable TTPM object ID. + * + * Don't forget that the contents are usually encrypted. Decryption is needed + * each time a stored object needs to be examined. + * + * Reserved objects of types STATE_CLEAR_DATA and STATE_RESET_DATA are very + * big and are stored in the flash in marshaled form. On top of 'regular' TPM2 + * style marshaling, PCRs found in the STATE_CLEAR_DATA object are stored in + * separate containers. + * + * ===== Storage compaction + * + * Keeping adding changed values at the end of the flash space would + * inevitably cause space overflow, unless something is done about it. This is + * where flash compaction kicks in: as soon as there are just three free flash + * pages left the stored objects are moved to the end of the space, which + * results in earlier used pages being freed and added to the pool of + * available flash pages. + * + * A great improvement to this storage compaction process would be grouping + * the objects such that the rarely changing ones occupy flash pages at the + * lower page indices. In this case when compaction starts, the pages not + * containing erased objects would not have to be re-written. This + * optimization is left as a future enhancement. + * + * ===== Committing TPM changes + * + * When nvmem_commit() is invoked it is necessary to identify which TPM + * objects in the cache have changed and require saving. Remember, that (key, + * value) pairs are not held in the cache any more and are saved in the flash + * immediately, so they do not have to be dealt with during commit. + * + * The commit procedure starts with iterating over the evictable objects space + * in the NVMEM cache, storing in an array offsets of all evictable objects it + * finds there. Then it iterates over flash contents skipping over (key, + * value) pairs. + * + * For each reserved object stored in flash, it compares its stored value with + * the value stored in the cache at known fixed location. If the value has + * changed, a new reserved object instance is saved in flash. This approach + * requires that all reserved objects are present in the flash, otherwise + * there is nothing to compare the cached instance of the object with. This is + * enforced by the init function. + * + * For each evictable object stored in flash, it checks if that object is + * still in the cache using the previously collected array of offsets. If the + * object is not in the cache, it must have been deleted by the TPM. The + * function deletes it from the flash as well. If the object is in the cache, + * its offset is removed from the array to indicate that the object has been + * processed. Then if the object value has changed, the new instance is added + * and the old instance erased. After this the only offsets left in the array + * are offsets of new objects, not yet saved in the flash. All these remaining + * objects get saved. + * + * To ensure transaction integrity, object deletions are just scheduled and + * not processed immediately, the deletion happens after all new instances + * have been saved in flash. See more about transaction delimiters below. + * + * ===== Migration from legacy storage and reclaiming flash space + * + * To be able to migrate existing devices from the legacy storage format the + * initialization code checks if a full 12K flash partition is still present, + * and if so - copies its contents into the cache and invokes the migration + * function. The function erases the alternative partition and creates a list + * of 5 pages available for the new format (remember, the flash footprint of + * the new scheme is smaller, only 10K is available in each half). + * + * The (key, value) pairs and TPM objects are stored in the new format as + * described, and then the legacy partition is erased and its pages are added + * to the list of free pages. This approach would fail if the existing TPM + * storage would exceed 10K, but this is extremely unlikely, especially since + * the large reserved objects are stored by the new scheme in marshaled form. + * This frees up a lot of flash space. + * + * Eventually it will be possible to reclaim the bottom 2K page per flash half + * currently used by the legacy scheme, but this would be possible only after + * migration is over. The plan is to keep a few Cr50 versions supporting the + * migration process, and then drop the migration code and rearrange the + * memory map and reclaim the freed pages. Chrome OS will always carry a + * migrating capable Cr50 version along with the latest one to make sure that + * even Chrome OS devices which had not updated their Cr50 code in a long + * while can be migrated in two steps. + * + * ===== Initialization, including erased/corrupted flash + * + * On regular startup (no legacy partition found) the flash pages dedicated to + * NVMEM storage are examined, pages with valid headers are included in the + * list of available pages, sorted by the page number. Erased pages are kept + * in a separate list. Pages which are not fully erased (but do not have a + * valid header) are considered corrupted, are erased, and added to the second + * list. + * + * After that the contents of the ordered flash pages is read, all discovered + * TPM objects are verified and saved in the cache. + * + * To enforce that all reserved TPM objects are present in the flash, the init + * routine maintains a bitmap of the reserved objects it found while + * initializing. In the case when after the scan of the entire NVMEM flash it + * turns out that some reserved objects have not been encountered, the init + * routine creates new flash instances of the missing reserved objects with + * default value of zero. This takes care of both initializing from empty + * flash and a case when a reserved object disappears due to a bug. + * + * ===== Transactions support + * + * It is important to make sure that TPM changes are grouped together. It came + * naturally with the legacy scheme where each time nvmem_save() was called, + * the entire cache snapshot was saved in the flash. With the new scheme some + * extra effort is required. + * + * Transaction delimiters are represented by containers of the appropriate + * type and the payload size of zero. When nvmem_save() operation is started, + * the new objects get written into flash and the objects requiring deletion + * are kept in the list. Once all new objects are added to the flash, the + * transaction delimiter is written, ending up at the top of the used flash. + * After that the objects scheduled for deletion are deleted, and then the + * transaction delimiter is also marked 'deleted'. + * + * So, during initialization the flash could be in one of three states: + * + * - thre is an erased transaction delimiter at the top + * . this is the normal state after successful commit operation. + * + * - there is transaction delimiter at the top, but it is not erased. + * . this is the case where the new objects were saved in flash, but some of + * the old instances might still be present not erased. The recovery + * procedure finds all duplicates of the objects present between two most + * recent delimiters and erases them. + * + * - there is no transaction delimiter on the top. + * . this is the case where nvmem_save() was interrupted before all new + * values have been written into the flash. The recovery procedure finds + * all TPM objects above the last valid delimiter in the flash and erases + * them all. + * + * ===== Handling failures + * + * This implementation is no better in handling failures than the legacy one, + * it in fact is even worse, because if a failure happened during legacy + * commit operation, at least the earlier saved partition would be available. + * If failure happens during this implementation's save or compaction process, + * there is a risk of ending up with a corrupted or inconsistent flash + * contents, even though the use of transaction delimiters narrows the failure + * window significantly. + * + * This first draft is offered for review and to facilitate testing and + * discussion about how failures should be addressed. + * + * ===== Missing stuff + * + * Presently not much thought has been given to locking and preventing + * problems caused by task preemption. The legacy scheme is still in place, + * but it might require improvements. + */ + +/* + * This code relies on the fact that space dedicated to flash NVMEM storage is + * sufficient to guarantee that the entire NVMEM snapshot can fit into it + * comfortably. The assert below is a very liberal computation which + * guarantees this assumption. Note that marshaling huge reserved structures + * reduces amount of required flash space, and this is not accounted for in + * this calculation. Space allocated for 200 container descriptors is also way + * more than required. + */ + +/* + * Fuzz testing does not enforce proper size definitions, causing the below + * assert failure. + */ +BUILD_ASSERT((NEW_NVMEM_TOTAL_PAGES * CONFIG_FLASH_BANK_SIZE) > + (MAX_VAR_TOTAL_SPACE + + NV_MEMORY_SIZE + + 200 * (sizeof(struct nn_container)) + + CONFIG_FLASH_BANK_SIZE * 2)); + +/* Maximum number of evictable objects we support. */ +#define MAX_STORED_EVICTABLE_OBJECTS 20 +/* + * Container for storing (key, value) pairs, a.k.a. vars during read. Actual + * vars would never be this large, but when looking for vars we need to be + * able to iterate over and verify all objects in the flash, hence the max + * body size. + */ +struct max_var_container { + struct nn_container c_header; + struct tuple t_header; + uint8_t body[CONFIG_FLASH_BANK_SIZE - sizeof(struct nn_container) - + sizeof(struct tuple)]; +} __packed; + +/* + * Limit of the number of objects which can be updated in one TPM transaction, + * reserved and evictable total. This is much more than practical maximum. + */ +#define MAX_DELETE_CANDIDATES 30 +static struct delete_candidates { + size_t num_candidates; + const struct nn_container *candidates[MAX_DELETE_CANDIDATES]; +} *del_candidates; + +/* + * This array contains a list of flash pages indices (0..255 range) sorted by + * the page header page number filed. Erased pages are kept at the tail of the + * list. + */ +static uint8_t page_list[NEW_NVMEM_TOTAL_PAGES]; +static uint8_t migration_in_progress; +static uint32_t next_evict_obj_base; + +/* + * Total space taken by key, value pairs in flash. It is limited to give TPM + * objects priority. + */ +test_export_static uint16_t total_var_space; + +/* The main context used when adding objects to NVMEM. */ +test_export_static struct access_tracker master_at; + +test_export_static enum ec_error_list browse_flash_contents(int print); +static enum ec_error_list save_container(struct nn_container *nc); + +/* Log NVMEM problem as per passed in payload and size, and reboot. */ +static void report_failure(struct nvmem_failure_payload *payload, + size_t payload_union_size) +{ + flash_log_add_event( + FE_LOG_NVMEM, + payload_union_size + + offsetof(struct nvmem_failure_payload, size), + payload); + ccprintf("Logging failure %d\n", payload->failure_type); + system_reset(SYSTEM_RESET_MANUALLY_TRIGGERED | SYSTEM_RESET_HARD); +} + +static void report_no_payload_failure(enum nvmem_failure_type type) +{ + struct nvmem_failure_payload fp; + + fp.failure_type = NVMEMF_INCONSISTENT_FLASH_CONTENTS; + report_failure(&fp, 0); +} + +/* + * This function allocates a buffer of the requested size. + * + * Heap space could be very limited and at times there could be not enough + * memory in the heap to allocate. This should not be considered a failure, + * polling should be used instead. On a properly functioning device the memory + * would become available. If it is not - there is not much we can do, we'll + * have to reboot adding a log entry. + */ +static void *get_scratch_buffer(size_t size) +{ + char *buf; + int i; + struct nvmem_failure_payload fp; + + /* + * Wait up to a 5 seconds in case some other operation is under + * way. + */ + for (i = 0; i < 50; i++) { + int rv; + + rv = shared_mem_acquire(size, &buf); + if (rv == EC_SUCCESS) { + if (i) + ccprintf("%s: waited %d cycles!\n", __func__, + i); + return buf; + } + usleep(100 * MSEC); + } + + fp.failure_type = NVMEMF_MALLOC; + fp.size = size; + report_failure(&fp, sizeof(fp.size)); + + /* Just to keep the compiler happy, this is never reached. */ + return NULL; +} + +/* Helper function returning actual size used by NVMEM in flash. */ +static size_t total_used_size(void) +{ + return master_at.list_index * CONFIG_FLASH_BANK_SIZE + + master_at.mt.data_offset; +} +/* + * Helper functions to set a bit a bit at a certain index in a bitmap array + * and to check if the bit is set. The caller must guarantee that the bitmap + * array is large enough for the index. + */ +static int bitmap_bit_check(const uint8_t *bitmap, size_t index) +{ + return bitmap[index / 8] & (1 << (index % 8)); +} + +static int bitmap_bit_set(uint8_t *bitmap, size_t index) +{ + return bitmap[index / 8] |= (1 << (index % 8)); +} + +/* Convenience functions used to reduce amount of typecasting. */ +static void app_compute_hash_wrapper(void *buf, size_t size, void *hash, + size_t hash_size) +{ + app_compute_hash(buf, size, hash, hash_size); +} + +static STATE_CLEAR_DATA *get_scd(void) +{ + NV_RESERVED_ITEM ri; + + NvGetReserved(NV_STATE_CLEAR, &ri); + + return (STATE_CLEAR_DATA *)((uint8_t *)nvmem_cache_base(NVMEM_TPM) + + ri.offset); +} + +/* Veirify page header hash. */ +static int page_header_is_valid(struct nn_page_header *ph) +{ + uint32_t ph_hash; + + app_compute_hash_wrapper(ph, offsetof(struct nn_page_header, page_hash), + &ph_hash, sizeof(ph_hash)); + + return ph_hash == ph->page_hash; +} + +/* Convert flash page number in 0..255 range into actual flash address. */ +static struct nn_page_header *flash_index_to_ph(uint8_t index) +{ + return (struct nn_page_header *)((index * CONFIG_FLASH_BANK_SIZE) + + CONFIG_PROGRAM_MEMORY_BASE); +} + +static const void *page_cursor(const struct page_tracker *pt) +{ + return (void *)((uintptr_t)pt->ph + pt->data_offset); +} + +/* + * Return flash page pointed at by a certain page_list element if the page is + * valid. If the index is out of range, or page is not initialized properly + * return NULL. + */ +test_export_static struct nn_page_header *list_element_to_ph(size_t el) +{ + struct nn_page_header *ph; + + if (el >= ARRAY_SIZE(page_list)) + return NULL; + + ph = flash_index_to_ph(page_list[el]); + + if (page_header_is_valid(ph)) + return ph; + + return NULL; +} + +/* + * Read into buf or skip if buf is NULL the next num_bytes in the storage, at + * the location determined by the passed in access tracker. Start from the + * very beginning if the passed in access tracker is empty. + * + * If necessary - concatenate contents from different pages bypassing page + * headers. + * + * If user is reading the container header (as specified by the + * container_fetch argument), save in the context the location of the + * container. + * + * If not enough bytes are available in the storage to satisfy the request - + * log error and reboot. + */ +static size_t nvmem_read_bytes(struct access_tracker *at, size_t num_bytes, + void *buf, int container_fetch) +{ + size_t togo; + struct nvmem_failure_payload fp; + + if (!at->list_index && !at->mt.data_offset) { + /* Start from the beginning. */ + at->mt.ph = list_element_to_ph(0); + at->mt.data_offset = at->mt.ph->data_offset; + } + + if (container_fetch) { + at->ct.data_offset = at->mt.data_offset; + at->ct.ph = at->mt.ph; + } + + if ((at->mt.data_offset + num_bytes) < CONFIG_FLASH_BANK_SIZE) { + /* + * All requested data fits and does not even reach the top of + * the page. + */ + if (buf) + memcpy(buf, page_cursor(&at->mt), num_bytes); + + at->mt.data_offset += num_bytes; + return num_bytes; + } + + /* Data is split between pages. */ + /* To go in the current page. */ + togo = CONFIG_FLASH_BANK_SIZE - at->mt.data_offset; + if (buf) { + memcpy(buf, page_cursor(&at->mt), togo); + /* Next portion goes here. */ + buf = (uint8_t *)buf + togo; + } + + /* + * Determine how much is there to read in the next page. + * + * Since object size is limited to page size + * less page header size, we are guaranteed that the object would not + * span more than one page boundary. + */ + togo = num_bytes - togo; + + /* Move to the next page. */ + at->list_index++; + at->mt.ph = list_element_to_ph(at->list_index); + + if (!at->mt.ph && togo) { + /* + * No more data to read. Could the end of used flash be close + * to the page boundary, so that there is no room to read an + * erased container header? + */ + if (!container_fetch) { + fp.failure_type = NVMEMF_READ_UNDERRUN; + fp.underrun_size = num_bytes - togo; + /* This will never return. */ + report_failure(&fp, sizeof(fp.underrun_size)); + } + + /* + * Simulate reading of the container header filled with all + * ones, which would be an indication of the end of storage, + * the caller will roll back ph, data_offset and list index as + * appropriate. + */ + memset(buf, 0xff, togo); + } else if (at->mt.ph) { + if (at->mt.ph->data_offset < (sizeof(*at->mt.ph) + togo)) { + fp.failure_type = NVMEMF_PH_SIZE_MISMATCH; + fp.ph.ph_offset = at->mt.ph->data_offset; + fp.ph.expected = sizeof(*at->mt.ph) + togo; + /* This will never return. */ + report_failure(&fp, sizeof(fp.ph)); + } + if (buf) + memcpy(buf, at->mt.ph + 1, togo); + + at->mt.data_offset = sizeof(*at->mt.ph) + togo; + } + + return num_bytes; +} + +/* + * Convert passed in absolute address into flash memory offset and write the + * passed in blob into the flash. + */ +static enum ec_error_list write_to_flash(const void *flash_addr, + const void *obj, size_t size) +{ + return flash_physical_write( + (uintptr_t)flash_addr - CONFIG_PROGRAM_MEMORY_BASE, size, obj); +} + +/* + * When initializing flash for the first time - set the proper first page + * header. + */ +static enum ec_error_list set_first_page_header(void) +{ + struct nn_page_header ph = {}; + enum ec_error_list rv; + struct nn_page_header *fph; /* Address in flash. */ + + ph.data_offset = sizeof(ph); + app_compute_hash_wrapper(&ph, + offsetof(struct nn_page_header, page_hash), + &ph.page_hash, sizeof(ph.page_hash)); + + fph = flash_index_to_ph(page_list[0]); + rv = write_to_flash(fph, &ph, sizeof(ph)); + + if (rv == EC_SUCCESS) { + /* Make sure master page tracker is ready. */ + memset(&master_at, 0, sizeof(master_at)); + master_at.mt.data_offset = ph.data_offset; + master_at.mt.ph = fph; + } + + return rv; +} + +/* + * Verify that the passed in container is valid, specifically that its hash + * matches its contents. + */ +static int container_is_valid(struct nn_container *ch) +{ + struct nn_container dummy_c; + uint32_t hash; + uint32_t preserved_hash; + uint8_t preserved_type; + + preserved_hash = ch->container_hash; + preserved_type = ch->container_type; + + ch->container_type = ch->container_type_copy; + ch->container_hash = 0; + app_compute_hash_wrapper(ch, ch->size + sizeof(*ch), &hash, + sizeof(hash)); + + ch->container_hash = preserved_hash; + ch->container_type = preserved_type; + + dummy_c.container_hash = hash; + + return dummy_c.container_hash == ch->container_hash; +} + +static uint32_t aligned_container_size(const struct nn_container *ch) +{ + const size_t alignment_mask = CONFIG_FLASH_WRITE_SIZE - 1; + + return (ch->size + sizeof(*ch) + alignment_mask) & ~alignment_mask; +} + +/* + * Function which allows to iterate through all objects stored in flash. The + * passed in context keeps track of where the previous object retrieval ended. + * + * Return: + * EC_SUCCESS if an object is retrieved and verified + * EC_ERROR_MEMORY_ALLOCATION if 'erased' object reached (not an error). + * EC_ERROR_INVAL if verification failed or read is out of sync. + */ +enum ec_error_list get_next_object(struct access_tracker *at, + struct nn_container *ch, int include_deleted) +{ + uint32_t salt[4]; + uint8_t ctype; + + salt[3] = 0; + + do { + size_t aligned_remaining_size; + struct nn_container temp_ch; + + nvmem_read_bytes(at, sizeof(temp_ch), &temp_ch, 1); + ctype = temp_ch.container_type; + + /* Should we check for the container being all 0xff? */ + if (ctype == NN_OBJ_ERASED) { + /* Roll back container size. */ + at->mt.data_offset = at->ct.data_offset; + at->mt.ph = at->ct.ph; + + /* + * If the container header happened to span between + * two pages - roll back page index saved in the + * context. + */ + if ((CONFIG_FLASH_BANK_SIZE - at->mt.data_offset) < + sizeof(struct nn_container)) + at->list_index--; + + return EC_ERROR_MEMORY_ALLOCATION; + } + + /* + * The read data is a container header, copy it into the user + * provided space and continue reading there. + */ + *ch = temp_ch; + aligned_remaining_size = + aligned_container_size(ch) - sizeof(*ch); + + if (aligned_remaining_size) { + if (aligned_remaining_size > + (CONFIG_FLASH_BANK_SIZE - sizeof(*ch))) { + /* Never returns. */ + report_no_payload_failure( + NVMEMF_INCONSISTENT_FLASH_CONTENTS); + } + + nvmem_read_bytes(at, aligned_remaining_size, ch + 1, 0); + + salt[0] = at->ct.ph->page_number; + salt[1] = at->ct.data_offset; + salt[2] = ch->container_hash; + + /* Decrypt in place. */ + app_cipher(salt, ch + 1, ch + 1, ch->size); + } + + /* And calculate hash. */ + if (!container_is_valid(ch)) { + ccprintf("%s: container hash mismatch!\n", __func__); + return EC_ERROR_INVAL; + } + + /* + * Keep track of the most recently encountered delimiter, + * finalized or not. + */ + if (ch->container_type_copy == NN_OBJ_TRANSACTION_DEL) { + include_deleted = 1; /* Always return all delimiters. */ + + /* But keep track only of finalized ones. */ + if (ch->container_type == NN_OBJ_OLD_COPY) { + at->dt.ph = at->ct.ph; + at->dt.data_offset = at->ct.data_offset; + } + } + + } while (!include_deleted && (ctype == NN_OBJ_OLD_COPY)); + + return EC_SUCCESS; +} + +/* + * Add a delimiter object at the top of the flash. The container type field is + * not erased. + * + * This is an indication that after nvmem_commit() invocation all updated + * objects have been saved in the flash, but the old instances of the objects + * have not yet been deleted. + */ +static enum ec_error_list add_delimiter(void) +{ + struct nn_container ch; + + memset(&ch, 0, sizeof(ch)); + + ch.container_type = ch.container_type_copy = NN_OBJ_TRANSACTION_DEL; + + return save_container(&ch); +} + +/* + * Erase the container type field of the previously saved delimiter, thus + * indicating that nvmem save transaction is completed. + */ +static enum ec_error_list finalize_delimiter(const struct nn_container *del) +{ + struct nn_container c; + + c = *del; + c.container_type = NN_OBJ_OLD_COPY; + + return write_to_flash(del, &c, sizeof(c)); +} + +/* Add delimiter indicating that flash is in a consistent state. */ +static enum ec_error_list add_final_delimiter(void) +{ + const struct nn_container *del; + + del = page_cursor(&master_at.mt); + add_delimiter(); + + return finalize_delimiter(del); +} + +/* Erase flash page and add it to the pool of empty pages. */ +static void release_flash_page(struct access_tracker *at) +{ + uint8_t page_index = page_list[0]; + void *flash; + + flash = flash_index_to_ph(page_index); + flash_physical_erase((uintptr_t)flash - CONFIG_PROGRAM_MEMORY_BASE, + CONFIG_FLASH_BANK_SIZE); + memmove(page_list, page_list + 1, + (ARRAY_SIZE(page_list) - 1) * sizeof(page_list[0])); + page_list[ARRAY_SIZE(page_list) - 1] = page_index; + at->list_index--; + master_at.list_index--; +} + +/* Reshuffle flash contents dropping deleted objects. */ +test_export_static enum ec_error_list compact_nvmem(void) +{ + const void *fence_ph; + enum ec_error_list rv = EC_SUCCESS; + size_t before; + struct nn_container *ch; + struct access_tracker at = {}; + int saved_object_count; + int final_delimiter_needed = 1; + + /* How much space was used before compaction. */ + before = total_used_size(); + + /* One page is enough even for the largest object. */ + ch = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + /* + * Page where we should stop compaction, all pages before this would + * be recycled. + */ + fence_ph = master_at.mt.ph; + saved_object_count = 0; + + do { + switch (get_next_object(&at, ch, 0)) { + case EC_SUCCESS: + break; + + case EC_ERROR_MEMORY_ALLOCATION: + shared_mem_release(ch); + return EC_SUCCESS; + + default: + /* + * The error has been reported already. + * + * This must be compaction after startup with + * inconsistent nvmemory state, let's make sure the + * top page is recycled. + */ + if (at.mt.ph != fence_ph) + release_flash_page(&at); + shared_mem_release(ch); + return EC_ERROR_INVAL; + } + + /* Re-store the object in compacted flash. */ + switch (ch->container_type) { + case NN_OBJ_TUPLE: + case NN_OBJ_TPM_RESERVED: + case NN_OBJ_TPM_EVICTABLE: + ch->generation++; + if (save_container(ch) != EC_SUCCESS) { + ccprintf("%s: Saving FAILED\n", __func__); + shared_mem_release(ch); + return EC_ERROR_INVAL; + } + saved_object_count++; + break; + default: + break; + } + + if (at.list_index != 0) { + /* + * We are done with a pre-compaction page, use a + * delimiter to indicate that a bunch of objects are + * being deleted and finalize the delimiter once the + * old page is erased. + * + * Return the erased page to the pool of empty pages + * and rearrange the list of active pages. + */ + const void *del; + + if (saved_object_count) { + del = page_cursor(&master_at.mt); + add_delimiter(); + } + + release_flash_page(&at); +#if defined(NVMEM_TEST_BUILD) + if (failure_mode == TEST_FAIL_WHEN_COMPACTING) { + shared_mem_release(ch); + return EC_SUCCESS; + } +#endif + + if (saved_object_count) { + finalize_delimiter(del); + saved_object_count = 0; + } + /* + * No need in another delimiter if data ends on a page + * boundary. + */ + final_delimiter_needed = 0; + } else { + final_delimiter_needed = 1; + } + } while (at.mt.ph != fence_ph); + + shared_mem_release(ch); + + if (final_delimiter_needed) + add_final_delimiter(); + + ccprintf("Compaction done, went from %d to %d bytes\n", before, + total_used_size()); + return rv; +} + +static void start_new_flash_page(size_t data_size) +{ + struct nn_page_header ph = {}; + + ph.data_offset = sizeof(ph) + data_size; + ph.page_number = master_at.mt.ph->page_number + 1; + app_compute_hash_wrapper(&ph, + offsetof(struct nn_page_header, page_hash), + &ph.page_hash, sizeof(ph.page_hash)); + master_at.list_index++; + if (master_at.list_index == ARRAY_SIZE(page_list)) + report_no_payload_failure(NVMEMF_PAGE_LIST_OVERFLOW); + + master_at.mt.ph = + (const void *)(((uintptr_t)page_list[master_at.list_index] * + CONFIG_FLASH_BANK_SIZE) + + CONFIG_PROGRAM_MEMORY_BASE); + + write_to_flash(master_at.mt.ph, &ph, sizeof(ph)); + master_at.mt.data_offset = sizeof(ph); +} + +/* + * Save in the flash an object represented by the passed in container. Add new + * pages to the list of used pages if necessary. + */ +static enum ec_error_list save_object(const struct nn_container *cont) +{ + const void *save_data = cont; + size_t save_size = aligned_container_size(cont); + size_t top_room; + + top_room = CONFIG_FLASH_BANK_SIZE - master_at.mt.data_offset; + if (save_size >= top_room) { + + /* Let's finish the current page. */ + write_to_flash((uint8_t *)master_at.mt.ph + + master_at.mt.data_offset, + cont, top_room); + + /* Remaining data and size to be written on the next page. */ + save_data = (const void *)((uintptr_t)save_data + top_room); + save_size -= top_room; + start_new_flash_page(save_size); + } + + if (save_size) { + write_to_flash((uint8_t *)master_at.mt.ph + + master_at.mt.data_offset, + save_data, save_size); + master_at.mt.data_offset += save_size; + } + + return EC_SUCCESS; +} + +/* + * Functions to check if the passed in blob is all zeros or all 0xff, in both + * cases would be considered an uninitialized value. This is used when + * marshaling certaing structures and PCRs. + */ +static int is_all_value(const uint8_t *p, size_t size, uint8_t value) +{ + size_t i; + + for (i = 0; i < size; i++) + if (p[i] != value) + return 0; + + return 1; +} + +test_export_static int is_uninitialized(const void *p, size_t size) +{ + return is_all_value(p, size, 0xff); +} + +static int is_all_zero(const void *p, size_t size) +{ + return is_all_value(p, size, 0); +} + +static int is_empty(const void *pcr_base, size_t pcr_size) +{ + return is_uninitialized(pcr_base, pcr_size) || + is_all_zero(pcr_base, pcr_size); +} + +/* + * A convenience function checking if the passed in blob is not empty, and if + * so - save the blob in the destination memory. + * + * Return number of bytes placed in dst or zero, if the blob was empty. + */ +static size_t copy_pcr(const uint8_t *pcr_base, size_t pcr_size, uint8_t *dst) +{ + /* + * We rely on the fact that all 16 PCRs of every PCR bank saved in the + * NVMEM's reserved space are originally set to all zeros. + * + * If all 0xFF is read - this is considered an artifact of trying to + * retrieve PCRs from legacy flash snapshot from the state when PCRs + * were not saved in the reserved space at all, i.e. also indicates an + * empty PCR. + */ + if (is_empty(pcr_base, pcr_size)) + return 0; /* No need to save this. */ + + memcpy(dst, pcr_base, pcr_size); + + return pcr_size; +} + +/* + * A convenience structure and array, allowing quick access to PCR banks + * contained in the STATE_CLEAR_DATA:pcrSave field. This helps when + * marshailing/unmarshaling PCR contents. + */ +struct pcr_descriptor { + uint16_t pcr_array_offset; + uint8_t pcr_size; +} __packed; + +static const struct pcr_descriptor pcr_arrays[] = { + {offsetof(PCR_SAVE, sha1), SHA1_DIGEST_SIZE}, + {offsetof(PCR_SAVE, sha256), SHA256_DIGEST_SIZE}, + {offsetof(PCR_SAVE, sha384), SHA384_DIGEST_SIZE}, + {offsetof(PCR_SAVE, sha512), SHA512_DIGEST_SIZE} +}; +#define NUM_OF_PCRS (ARRAY_SIZE(pcr_arrays) * NUM_STATIC_PCR) + +/* Just in case we ever get to reducing the PCR set one way or another. */ +BUILD_ASSERT(ARRAY_SIZE(pcr_arrays) == 4); +BUILD_ASSERT(NUM_OF_PCRS == 64); +/* + * Iterate over PCRs contained in the STATE_CLEAR_DATA structure in the NVMEM + * cache and save nonempty ones in the flash. + */ +static void migrate_pcr(STATE_CLEAR_DATA *scd, size_t array_index, + size_t pcr_index, struct nn_container *ch) +{ + const struct pcr_descriptor *pdsc; + uint8_t *p_container_body; + uint8_t *pcr_base; + uint8_t reserved_index; /* Unique ID of this PCR in reserved storage. */ + + p_container_body = (uint8_t *)(ch + 1); + pdsc = pcr_arrays + array_index; + pcr_base = (uint8_t *)&scd->pcrSave + pdsc->pcr_array_offset + + pdsc->pcr_size * pcr_index; + reserved_index = NV_VIRTUAL_RESERVE_LAST + + array_index * NUM_STATIC_PCR + pcr_index; + + if (!copy_pcr(pcr_base, pdsc->pcr_size, p_container_body + 1)) + return; + + p_container_body[0] = reserved_index; + ch->size = pdsc->pcr_size + 1; + save_container(ch); +} + +/* + * Some NVMEM structures end up in the NVMEM cache with a wrong alignment. If + * a passed in pointer is not aligned at a 4 byte boundary, this function will + * save the 4 bytes above the blob in the passed in space and then move the + * blob up so that it is properly aligned. + */ +static void *preserve_struct(void *p, size_t size, uint32_t *preserved) +{ + uint32_t misalignment = ((uintptr_t)p & 3); + void *new_p; + + if (!misalignment) + return p; /* Nothing to adjust. */ + + memcpy(preserved, (uint8_t *)p + size, sizeof(*preserved)); + new_p = (void *)((((uintptr_t)p) + 3) & ~3); + memmove(new_p, p, size); + + return new_p; +} + +static void maybe_restore_struct(void *new_p, void *old_p, size_t size, + uint32_t *preserved) +{ + if (!memcmp(new_p, old_p, size)) + return; + + memmove(old_p, new_p, size); + memcpy((uint8_t *)old_p + size, preserved, sizeof(*preserved)); +} + +/* + * Note that PCRs are not marshaled here, but the rest of the structre, below + * and above the PCR array is. + */ +static uint16_t marshal_state_clear(STATE_CLEAR_DATA *scd, uint8_t *dst) +{ + PCR_AUTHVALUE *new_pav; + STATE_CLEAR_DATA *new_scd; + size_t bottom_size; + size_t i; + size_t top_size; + uint32_t preserved; + uint8_t *base; + int room; + + bottom_size = offsetof(STATE_CLEAR_DATA, pcrSave); + top_size = sizeof(scd->pcrAuthValues); + + if (is_empty(scd, bottom_size) && + is_empty(&scd->pcrAuthValues, top_size) && + is_empty(&scd->pcrSave.pcrCounter, sizeof(scd->pcrSave.pcrCounter))) + return 0; + + /* Marshaling STATE_CLEAR_DATA will never need this much. */ + room = CONFIG_FLASH_BANK_SIZE; + + new_scd = preserve_struct(scd, bottom_size, &preserved); + + base = dst; + + *dst++ = (!!new_scd->shEnable) | ((!!new_scd->ehEnable) << 1) | + ((!!new_scd->phEnableNV) << 2); + + memcpy(dst, &new_scd->platformAlg, sizeof(new_scd->platformAlg)); + dst += sizeof(new_scd->platformAlg); + + room -= (dst - base); + + TPM2B_DIGEST_Marshal(&new_scd->platformPolicy, &dst, &room); + + TPM2B_AUTH_Marshal(&new_scd->platformAuth, &dst, &room); + + memcpy(dst, &new_scd->pcrSave.pcrCounter, + sizeof(new_scd->pcrSave.pcrCounter)); + dst += sizeof(new_scd->pcrSave.pcrCounter); + room -= sizeof(new_scd->pcrSave.pcrCounter); + + maybe_restore_struct(new_scd, scd, bottom_size, &preserved); + + new_pav = preserve_struct(&scd->pcrAuthValues, top_size, &preserved); + for (i = 0; i < ARRAY_SIZE(new_scd->pcrAuthValues.auth); i++) + TPM2B_DIGEST_Marshal(new_pav->auth + i, &dst, &room); + + maybe_restore_struct(new_pav, &scd->pcrAuthValues, top_size, + &preserved); + + return dst - base; +} + +static uint16_t marshal_state_reset_data(STATE_RESET_DATA *srd, uint8_t *dst) +{ + STATE_RESET_DATA *new_srd; + uint32_t preserved; + uint8_t *base; + int room; + + if (is_empty(srd, sizeof(*srd))) + return 0; + + /* Marshaling STATE_RESET_DATA will never need this much. */ + room = CONFIG_FLASH_BANK_SIZE; + + new_srd = preserve_struct(srd, sizeof(*srd), &preserved); + + base = dst; + + TPM2B_AUTH_Marshal(&new_srd->nullProof, &dst, &room); + TPM2B_DIGEST_Marshal((TPM2B_DIGEST *)(&new_srd->nullSeed), &dst, &room); + UINT32_Marshal(&new_srd->clearCount, &dst, &room); + UINT64_Marshal(&new_srd->objectContextID, &dst, &room); + + memcpy(dst, new_srd->contextArray, sizeof(new_srd->contextArray)); + room -= sizeof(new_srd->contextArray); + dst += sizeof(new_srd->contextArray); + + memcpy(dst, &new_srd->contextCounter, sizeof(new_srd->contextCounter)); + room -= sizeof(new_srd->contextCounter); + dst += sizeof(new_srd->contextCounter); + + TPM2B_DIGEST_Marshal(&new_srd->commandAuditDigest, &dst, &room); + UINT32_Marshal(&new_srd->restartCount, &dst, &room); + UINT32_Marshal(&new_srd->pcrCounter, &dst, &room); + +#ifdef TPM_ALG_ECC + UINT64_Marshal(&new_srd->commitCounter, &dst, &room); + TPM2B_NONCE_Marshal(&new_srd->commitNonce, &dst, &room); + + memcpy(dst, new_srd->commitArray, sizeof(new_srd->commitArray)); + room -= sizeof(new_srd->commitArray); + dst += sizeof(new_srd->commitArray); +#endif + + maybe_restore_struct(new_srd, srd, sizeof(*srd), &preserved); + + return dst - base; +} + +/* + * Migrate all reserved objects found in the NVMEM cache after intializing + * from legacy NVMEM storage. + */ +static enum ec_error_list migrate_tpm_reserved(struct nn_container *ch) +{ + STATE_CLEAR_DATA *scd; + STATE_RESET_DATA *srd; + size_t pcr_type_index; + uint8_t *p_tpm_nvmem = nvmem_cache_base(NVMEM_TPM); + uint8_t *p_container_body = (uint8_t *)(ch + 1); + uint8_t index; + + ch->container_type = ch->container_type_copy = NN_OBJ_TPM_RESERVED; + + for (index = 0; index < NV_VIRTUAL_RESERVE_LAST; index++) { + NV_RESERVED_ITEM ri; + int copy_needed = 1; + + NvGetReserved(index, &ri); + p_container_body[0] = index; + + switch (index) { + case NV_STATE_CLEAR: + scd = (STATE_CLEAR_DATA *)(p_tpm_nvmem + ri.offset); + ri.size = + marshal_state_clear(scd, p_container_body + 1); + copy_needed = 0; + break; + + case NV_STATE_RESET: + srd = (STATE_RESET_DATA *)(p_tpm_nvmem + ri.offset); + ri.size = marshal_state_reset_data( + srd, p_container_body + 1); + copy_needed = 0; + break; + } + + if (copy_needed) { + /* + * Copy data into the stage area unless already done + * by marshaling function above. + */ + memcpy(p_container_body + 1, p_tpm_nvmem + ri.offset, + ri.size); + } + + ch->size = ri.size + 1; + save_container(ch); + } + + /* + * Now all components but the PCRs from STATE_CLEAR_DATA have been + * saved, let's deal with those PCR arrays. We want to save each PCR + * in a separate container, as if all PCRs are extended, the total + * combined size of the arrays would exceed flash page size. Also, + * PCRs are most likely to change one or very few at a time. + */ + for (pcr_type_index = 0; pcr_type_index < ARRAY_SIZE(pcr_arrays); + pcr_type_index++) { + size_t pcr_index; + + for (pcr_index = 0; pcr_index < NUM_STATIC_PCR; pcr_index++) + migrate_pcr(scd, pcr_type_index, pcr_index, ch); + } + + return EC_SUCCESS; +} + +/* + * Migrate all evictable objects found in the NVMEM cache after intializing + * from legacy NVMEM storage. + */ +static enum ec_error_list migrate_objects(struct nn_container *ch) +{ + uint32_t next_obj_base; + uint32_t obj_base; + uint32_t obj_size; + void *obj_addr; + + ch->container_type = ch->container_type_copy = NN_OBJ_TPM_EVICTABLE; + + obj_base = s_evictNvStart; + obj_addr = nvmem_cache_base(NVMEM_TPM) + obj_base; + memcpy(&next_obj_base, obj_addr, sizeof(next_obj_base)); + + while (next_obj_base && (next_obj_base <= s_evictNvEnd)) { + + obj_size = next_obj_base - obj_base - sizeof(obj_size); + memcpy(ch + 1, (uint32_t *)obj_addr + 1, obj_size); + + ch->size = obj_size; + save_container(ch); + + obj_base = next_obj_base; + obj_addr = nvmem_cache_base(NVMEM_TPM) + obj_base; + + memcpy(&next_obj_base, obj_addr, sizeof(next_obj_base)); + } + + return EC_SUCCESS; +} + +static enum ec_error_list migrate_tpm_nvmem(struct nn_container *ch) +{ + /* Call this to initialize NVMEM indices. */ + NvEarlyStageFindHandle(0); + + migrate_tpm_reserved(ch); + migrate_objects(ch); + + return EC_SUCCESS; +} + +static enum ec_error_list save_var(const uint8_t *key, uint8_t key_len, + const uint8_t *val, uint8_t val_len, + struct max_var_container *vc) +{ + const int total_size = + key_len + val_len + offsetof(struct max_var_container, body); + enum ec_error_list rv; + int local_alloc = !vc; + + if (local_alloc) { + vc = get_scratch_buffer(total_size); + vc->c_header.generation = 0; + } + + /* Fill up tuple body. */ + vc->t_header.key_len = key_len; + vc->t_header.val_len = val_len; + memcpy(vc->body, key, key_len); + memcpy(vc->body + key_len, val, val_len); + + /* Set up container header. */ + vc->c_header.container_type_copy = vc->c_header.container_type = + NN_OBJ_TUPLE; + vc->c_header.encrypted = 1; + vc->c_header.size = sizeof(struct tuple) + val_len + key_len; + + rv = save_container(&vc->c_header); + if (rv == EC_SUCCESS) { + total_var_space += key_len + val_len; + if (!migration_in_progress) + add_final_delimiter(); + } + + if (local_alloc) + shared_mem_release(vc); + + return rv; +} + +/* + * Migrate all (key, value) pairs found in the NVMEM cache after intializing + * from legacy NVMEM storage. + */ +static enum ec_error_list migrate_vars(struct nn_container *ch) +{ + const struct tuple *var; + + /* + * During migration (key, value) pairs need to be manually copied from + * the NVMEM cache. + */ + set_local_copy(); + var = NULL; + total_var_space = 0; + + while ((var = getnextvar(var)) != NULL) + save_var(var->data_, var->key_len, var->data_ + var->key_len, + var->val_len, (struct max_var_container *)ch); + + return EC_SUCCESS; +} + +static int erase_partition(unsigned int act_partition, int erase_backup) +{ + enum ec_error_list rv; + size_t flash_base; + + /* + * This is the first time we save using the new scheme, let's prepare + * the flash space. First determine which half is the backup now and + * erase it. + */ + flash_base = (act_partition ^ erase_backup) ? CONFIG_FLASH_NVMEM_BASE_A + : CONFIG_FLASH_NVMEM_BASE_B; + flash_base -= CONFIG_PROGRAM_MEMORY_BASE; + + rv = flash_physical_erase(flash_base, NVMEM_PARTITION_SIZE); + + if (rv != EC_SUCCESS) { + ccprintf("%s: flash erase failed", __func__); + return -rv; + } + + return flash_base + CONFIG_FLASH_BANK_SIZE; +} + +/* + * This function is called once in a lifetime, when Cr50 boots up and a legacy + * partition if found in the flash. + */ +enum ec_error_list new_nvmem_migrate(unsigned int act_partition) +{ + int flash_base; + int i; + int j; + struct nn_container *ch; + + /* + * This is the first time we save using the new scheme, let's prepare + * the flash space. First determine which half is the backup now and + * erase it. + */ + flash_base = erase_partition(act_partition, 1); + if (flash_base < 0) { + ccprintf("%s: backup partition erase failed", __func__); + return -flash_base; + } + + ch = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + /* Populate half of page_list with available page offsets. */ + for (i = 0; i < ARRAY_SIZE(page_list) / 2; i++) + page_list[i] = flash_base / CONFIG_FLASH_BANK_SIZE + i; + + set_first_page_header(); + + ch->encrypted = 1; + ch->generation = 0; + + migration_in_progress = 1; + migrate_vars(ch); + migrate_tpm_nvmem(ch); + migration_in_progress = 0; + + shared_mem_release(ch); + + add_final_delimiter(); + + if (browse_flash_contents(0) != EC_SUCCESS) + /* Never returns. */ + report_no_payload_failure(NVMEMF_MIGRATION_FAILURE); + + ccprintf("Migration success, used %d bytes of flash\n", + total_used_size()); + + /* + * Now we can erase the active partition and add its flash to the pool. + */ + flash_base = erase_partition(act_partition, 0); + if (flash_base < 0) + /* Never returns. */ + report_no_payload_failure(NVMEMF_LEGACY_ERASE_FAILURE); + + /* + * Populate the second half of the page_list with pages retrieved from + * legacy partition. + */ + for (j = 0; j < ARRAY_SIZE(page_list) / 2; j++) + page_list[i + j] = flash_base / CONFIG_FLASH_BANK_SIZE + j; + + return EC_SUCCESS; +} + +/* Check if the passed in flash page is empty, if not - erase it. */ +static void verify_empty_page(void *ph) +{ + uint32_t *word_p = ph; + size_t i; + + for (i = 0; i < (CONFIG_FLASH_BANK_SIZE / sizeof(*word_p)); i++) { + if (word_p[i] != (uint32_t)~0) { + ccprintf("%s: corrupted page at %p!\n", __func__, + word_p); + flash_physical_erase((uintptr_t)word_p - + CONFIG_PROGRAM_MEMORY_BASE, + CONFIG_FLASH_BANK_SIZE); + break; + } + } +} + +/* + * At startup initialize the list of pages which contain NVMEM data and erased + * pages. The list (in fact an array containing indices of the pages) is + * sorted by the page number found in the page header. Pages which do not + * contain valid page header are checked to be erased and are placed at the + * tail of the list. + */ +static void init_page_list(void) +{ + size_t i; + size_t j; + size_t page_list_index = 0; + size_t tail_index; + struct nn_page_header *ph; + + tail_index = ARRAY_SIZE(page_list); + + for (i = 0; i < ARRAY_SIZE(page_list); i++) { + uint32_t page_index; + + /* + * This will yield indices of top pages first, first from the + * bottom half of the flash, and then from the top half. We + * know that flash is 512K in size, and pages are 2K in size, + * the indices will be in 123..127 and 251..255 range. + */ + if (i < (ARRAY_SIZE(page_list) / 2)) { + page_index = (CONFIG_FLASH_NEW_NVMEM_BASE_A - + CONFIG_PROGRAM_MEMORY_BASE) / + CONFIG_FLASH_BANK_SIZE + + i; + } else { + page_index = (CONFIG_FLASH_NEW_NVMEM_BASE_B - + CONFIG_PROGRAM_MEMORY_BASE) / + CONFIG_FLASH_BANK_SIZE - + ARRAY_SIZE(page_list) / 2 + i; + } + + ph = flash_index_to_ph(page_index); + + if (!page_header_is_valid(ph)) { + /* + * this is not a valid page, let's plug it in into the + * tail of the list. + */ + page_list[--tail_index] = page_index; + verify_empty_page(ph); + continue; + } + + /* This seems a valid page, let's put it in order. */ + for (j = 0; j < page_list_index; j++) { + struct nn_page_header *prev_ph; + + prev_ph = list_element_to_ph(j); + + if (prev_ph->page_number > ph->page_number) { + /* Need to move up. */ + memmove(page_list + j + 1, page_list + j, + sizeof(page_list[0]) * + (page_list_index - j)); + break; + } + } + + page_list[j] = page_index; + page_list_index++; + } + + if (!page_list_index) { + ccprintf("Init nvmem from scratch\n"); + set_first_page_header(); + page_list_index++; + } +} + +/* + * The passed in pointer contains marshaled STATE_CLEAR structure as retrieved + * from flash. This function unmarshals it and places in the NVMEM cache where + * it belongs. Note that PCRs were not marshaled. + */ +static void unmarshal_state_clear(uint8_t *pad, int size, uint32_t offset) +{ + STATE_CLEAR_DATA *real_scd; + STATE_CLEAR_DATA *scd; + size_t i; + uint32_t preserved; + uint8_t booleans; + + real_scd = (STATE_CLEAR_DATA *)((uint8_t *)nvmem_cache_base(NVMEM_TPM) + + offset); + + memset(real_scd, 0, sizeof(*real_scd)); + if (!size) + return; + + memcpy(&preserved, real_scd + 1, sizeof(preserved)); + + scd = (void *)(((uintptr_t)real_scd + 3) & ~3); + + /* Need proper unmarshal. */ + booleans = *pad++; + scd->shEnable = !!(booleans & 1); + scd->ehEnable = !!(booleans & (1 << 1)); + scd->phEnableNV = !!(booleans & (1 << 2)); + size--; + + memcpy(&scd->platformAlg, pad, sizeof(scd->platformAlg)); + pad += sizeof(scd->platformAlg); + size -= sizeof(scd->platformAlg); + + TPM2B_DIGEST_Unmarshal(&scd->platformPolicy, &pad, &size); + TPM2B_AUTH_Unmarshal(&scd->platformAuth, &pad, &size); + + memcpy(&scd->pcrSave.pcrCounter, pad, sizeof(scd->pcrSave.pcrCounter)); + pad += sizeof(scd->pcrSave.pcrCounter); + size -= sizeof(scd->pcrSave.pcrCounter); + + for (i = 0; i < ARRAY_SIZE(scd->pcrAuthValues.auth); i++) + TPM2B_DIGEST_Unmarshal(scd->pcrAuthValues.auth + i, &pad, + &size); + + memmove(real_scd, scd, sizeof(*scd)); + memcpy(real_scd + 1, &preserved, sizeof(preserved)); +} + +/* + * The passed in pointer contains marshaled STATE_RESET structure as retrieved + * from flash. This function unmarshals it and places in the NVMEM cache where + * it belongs. + */ +static void unmarshal_state_reset(uint8_t *pad, int size, uint32_t offset) +{ + STATE_RESET_DATA *real_srd; + STATE_RESET_DATA *srd; + uint32_t preserved; + + real_srd = (STATE_RESET_DATA *)((uint8_t *)nvmem_cache_base(NVMEM_TPM) + + offset); + + memset(real_srd, 0, sizeof(*real_srd)); + if (!size) + return; + + memcpy(&preserved, real_srd + 1, sizeof(preserved)); + + srd = (void *)(((uintptr_t)real_srd + 3) & ~3); + + TPM2B_AUTH_Unmarshal(&srd->nullProof, &pad, &size); + TPM2B_DIGEST_Unmarshal((TPM2B_DIGEST *)(&srd->nullSeed), &pad, &size); + UINT32_Unmarshal(&srd->clearCount, &pad, &size); + UINT64_Marshal(&srd->objectContextID, &pad, &size); + + memcpy(srd->contextArray, pad, sizeof(srd->contextArray)); + size -= sizeof(srd->contextArray); + pad += sizeof(srd->contextArray); + + memcpy(&srd->contextCounter, pad, sizeof(srd->contextCounter)); + size -= sizeof(srd->contextCounter); + pad += sizeof(srd->contextCounter); + + TPM2B_DIGEST_Unmarshal(&srd->commandAuditDigest, &pad, &size); + UINT32_Unmarshal(&srd->restartCount, &pad, &size); + UINT32_Unmarshal(&srd->pcrCounter, &pad, &size); + +#ifdef TPM_ALG_ECC + UINT64_Unmarshal(&srd->commitCounter, &pad, &size); + TPM2B_NONCE_Unmarshal(&srd->commitNonce, &pad, &size); + + memcpy(srd->commitArray, pad, sizeof(srd->commitArray)); + size -= sizeof(srd->commitArray); +#endif + + memmove(real_srd, srd, sizeof(*srd)); + memcpy(real_srd + 1, &preserved, sizeof(preserved)); +} + +/* + * Based on the passed in index, find the location of the PCR in the NVMEM + * cache and copy it there. + */ +static void restore_pcr(size_t pcr_index, uint8_t *pad, size_t size) +{ + const STATE_CLEAR_DATA *scd; + const struct pcr_descriptor *pcrd; + void *cached; /* This PCR's position in the NVMEM cache. */ + + if (pcr_index > NUM_OF_PCRS) + return; /* This is an error. */ + + pcrd = pcr_arrays + pcr_index / NUM_STATIC_PCR; + if (pcrd->pcr_size != size) + return; /* This is an error. */ + + scd = get_scd(); + cached = (uint8_t *)&scd->pcrSave + pcrd->pcr_array_offset + + pcrd->pcr_size * (pcr_index % NUM_STATIC_PCR); + + memcpy(cached, pad, size); +} + +/* Restore a reserved object found in flash on initialization. */ +static void restore_reserved(void *pad, size_t size, uint8_t *bitmap) +{ + NV_RESERVED_ITEM ri; + uint16_t type; + void *cached; + + /* + * Index is saved as a single byte, update pad to point at the + * payload. + */ + type = *(uint8_t *)pad++; + size--; + + if (type < NV_VIRTUAL_RESERVE_LAST) { + NvGetReserved(type, &ri); + + bitmap_bit_set(bitmap, type); + + switch (type) { + case NV_STATE_CLEAR: + unmarshal_state_clear(pad, size, ri.offset); + break; + + case NV_STATE_RESET: + unmarshal_state_reset(pad, size, ri.offset); + break; + + default: + cached = ((uint8_t *)nvmem_cache_base(NVMEM_TPM) + + ri.offset); + memcpy(cached, pad, size); + break; + } + return; + } + + restore_pcr(type - NV_VIRTUAL_RESERVE_LAST, pad, size); +} + +/* Restore an evictable object found in flash on initialization. */ +static void restore_object(void *pad, size_t size) +{ + uint8_t *dest; + + if (!next_evict_obj_base) + next_evict_obj_base = s_evictNvStart; + + dest = ((uint8_t *)nvmem_cache_base(NVMEM_TPM) + next_evict_obj_base); + next_evict_obj_base += size + sizeof(next_evict_obj_base); + memcpy(dest, &next_evict_obj_base, sizeof(next_evict_obj_base)); + + dest += sizeof(next_evict_obj_base); + memcpy(dest, pad, size); + dest += size; + + memset(dest, 0, sizeof(next_evict_obj_base)); +} + +/* + * When starting from scratch (flash fully erased) there would be no reserved + * objects in NVMEM, and for the commit to work properly, every single + * reserved object needs to be present in the flash so that its value is + * compared with the cache contents. + * + * There is also an off chance of a bug where a reserved value is lost in the + * flash - it would never be reinstated even after TPM reinitializes. + * + * The reserved_bitmap array is a bitmap of all detected reserved objects, + * those not in the array are initialized to a dummy initial value. + */ +static enum ec_error_list verify_reserved(uint8_t *reserved_bitmap, + struct nn_container *ch) +{ + enum ec_error_list rv; + int i; + uint8_t *container_body; + int delimiter_needed = 0; + + /* All uninitted reserved objects set to zero. */ + memset(ch, 0, CONFIG_FLASH_BANK_SIZE); + + ch->container_type = ch->container_type_copy = NN_OBJ_TPM_RESERVED; + ch->encrypted = 1; + container_body = (uint8_t *)(ch + 1); + + rv = EC_SUCCESS; + + for (i = 0; i < NV_VIRTUAL_RESERVE_LAST; i++) { + NV_RESERVED_ITEM ri; + + if (bitmap_bit_check(reserved_bitmap, i)) + continue; + + NvGetReserved(i, &ri); + container_body[0] = i; + + switch (i) { + /* + * No need to save these on initialization from + * scratch, unmarshaling code will properly expand + * size of zero. + */ + case NV_STATE_CLEAR: + case NV_STATE_RESET: + ri.size = 0; + break; + + /* + * This is used for Ram Index field, prepended by + * size. Set the size to minimum, the size of the size + * field. + */ + case NV_RAM_INDEX_SPACE: + ri.size = sizeof(uint32_t); + break; + + default: + break; + } + + delimiter_needed = 1; + + ch->size = ri.size + 1; + rv = save_container(ch); + + /* Clean up encrypted contents. */ + memset(container_body + 1, 0, ri.size); + + if (rv != EC_SUCCESS) + break; + } + + if (delimiter_needed && (rv == EC_SUCCESS)) + add_final_delimiter(); + + return rv; +} + +static enum ec_error_list invalidate_object(const struct nn_container *ch) +{ + struct nn_container c_copy; + + c_copy = *ch; + c_copy.container_type = NN_OBJ_OLD_COPY; + + return write_to_flash(ch, &c_copy, sizeof(uint32_t)); +} + +static enum ec_error_list delete_object(const struct access_tracker *at, + struct nn_container *ch) +{ + const void *flash_ch; + + flash_ch = page_cursor(&at->ct); + + if (memcmp(ch, flash_ch, sizeof(uint32_t))) + report_no_payload_failure(NVMEMF_PRE_ERASE_MISMATCH); + + if (!del_candidates) + return invalidate_object(flash_ch); + + /* + * Do not delete the object yet, save it in the list of delete + * candidates. + */ + if (del_candidates->num_candidates == + ARRAY_SIZE(del_candidates->candidates)) + report_no_payload_failure(NVMEMF_EXCESS_DELETE_OBJECTS); + + del_candidates->candidates[del_candidates->num_candidates++] = flash_ch; + return EC_SUCCESS; +} + +static enum ec_error_list verify_last_section( + const struct page_tracker *prev_del, struct nn_container *ch) +{ + /* + * This is very inefficient, but we do this only when recovering from + * botched nvmem saves. + * + * For each object found between prev_del and last_del we need to + * check if there are earlier instances of these objects in the flash + * which are not yet deleted, and delete them if found. + */ + struct object { + uint8_t cont_type; + union { + uint32_t handle; /* For evictables. */ + uint8_t id; /* For reserved objects. */ + }; + }; + struct new_objects { + uint8_t num_objects; + struct object objects[2 * MAX_DELETE_CANDIDATES]; + }; + + struct access_tracker at; + struct new_objects *newobjs; + struct object *po; + uint8_t ctype; + struct page_tracker top_del; + int i; + + newobjs = get_scratch_buffer(sizeof(struct new_objects)); + + at.mt = *prev_del; + for (i = 0; i < ARRAY_SIZE(page_list); i++) + if (list_element_to_ph(i) == at.mt.ph) { + at.list_index = i; + break; + } + + po = newobjs->objects; + + while (get_next_object(&at, ch, 0) == EC_SUCCESS) { + ctype = ch->container_type; + + /* Speculative assignment, might be unused. */ + po->cont_type = ctype; + switch (ctype) { + case NN_OBJ_TPM_RESERVED: + po->id = *((uint8_t *)(ch + 1)); + break; + + case NN_OBJ_TPM_EVICTABLE: + po->handle = *((uint32_t *)(ch + 1)); + break; + default: + continue; + } + if (++(newobjs->num_objects) == ARRAY_SIZE(newobjs->objects)) + /* Never returns. */ + report_no_payload_failure(NVMEMF_SECTION_VERIFY); + po++; + } + + /* + * Last object read from flash should have been a non-finalized + * delimiter. + */ + if (ch->container_type != NN_OBJ_TRANSACTION_DEL) { + struct nvmem_failure_payload fp; + + fp.failure_type = NVMEMF_UNEXPECTED_LAST_OBJ; + fp.last_obj_type = ch->container_type; + /* Never returns. */ + report_failure(&fp, sizeof(fp.last_obj_type)); + } + + /* + * Now we have a cache of of objects which were updated but their old + * instances could have been left in the flash. Let's iterate over the + * flash and delete those if found. + */ + memset(&at, 0, sizeof(at)); + while ((at.mt.ph != prev_del->ph) && + (at.mt.data_offset != prev_del->data_offset)) { + size_t i; + size_t key_size; + uint32_t key; + + if (get_next_object(&at, ch, 0) != EC_SUCCESS) + report_no_payload_failure(NVMEMF_MISSING_OBJECT); + + ctype = ch->container_type; + + switch (ctype) { + case NN_OBJ_TPM_RESERVED: + key = *((uint8_t *)(ch + 1)); + key_size = sizeof(uint8_t); + break; + + case NN_OBJ_TPM_EVICTABLE: + key = *((uint32_t *)(ch + 1)); + key_size = sizeof(uint32_t); + break; + default: + continue; + } + + for (i = 0, po = newobjs->objects; i < newobjs->num_objects; + i++, po++) { + if ((po->cont_type != ctype) || + ((key_size == 1) && (po->id != key)) || + ((key_size == 4) && (po->handle != key))) + continue; + + /* + * This indeed is a leftover which needs to be + * deleted. + */ + delete_object(&at, ch); + } + } + shared_mem_release(newobjs); + if (master_at.mt.data_offset > sizeof(struct nn_page_header)) { + top_del.ph = master_at.mt.ph; + top_del.data_offset = + master_at.mt.data_offset - sizeof(struct nn_container); + } else { + top_del.ph = list_element_to_ph(master_at.list_index - 1); + top_del.data_offset = + CONFIG_FLASH_BANK_SIZE - -sizeof(struct nn_container); + } + + return finalize_delimiter(page_cursor(&top_del)); +} + +/* + * This function is called during initialization after the entire flash + * contents were scanned, to verify that flash is in a valid state. + */ +static enum ec_error_list verify_delimiter(struct nn_container *nc) +{ + enum ec_error_list rv; + /* Used to read starting at last good delimiter. */ + struct access_tracker dpt = {}; + + if ((master_at.list_index == 0) && + (master_at.mt.data_offset == sizeof(struct nn_page_header))) { + /* This must be an init from scratch, no delimiter yet. */ + if (!master_at.dt.ph) + return EC_SUCCESS; + + /* This is bad, will have to wipe out everything. */ + return EC_ERROR_INVAL; + } + + if (nc->container_type_copy == NN_OBJ_TRANSACTION_DEL) { + if (nc->container_type == NN_OBJ_OLD_COPY) + return EC_SUCCESS; + /* + * The delimiter is there, but it has not been finalized, + * which means that there might be objects in the flash which + * were not updated after the last delimiter was written. + */ + return verify_last_section(&master_at.dt, nc); + } + + /* + * The delimiter is not there, everything above the last verified + * delimiter must go. + * + * First, create a context for retrieving objects starting at the last + * valid delimiter, make sure list index is set properly. + */ + dpt.mt = master_at.dt; + if (dpt.mt.ph == master_at.mt.ph) { + dpt.list_index = master_at.list_index; + } else { + uint8_t i; + + for (i = 0; i < master_at.list_index; i++) + if (list_element_to_ph(i) == dpt.mt.ph) + dpt.list_index = i; + } + + while ((rv = get_next_object(&dpt, nc, 0)) == EC_SUCCESS) { + if (nc->container_type == NN_OBJ_TUPLE) + continue; + delete_object(&dpt, nc); + } + + if (rv == EC_ERROR_INVAL) { + /* + * There must have been an interruption of the saving process, + * let's wipe out flash to the end of the current page and + * compact the storage. + */ + size_t remainder_size; + const void *p = page_cursor(&master_at.ct); + + remainder_size = + CONFIG_FLASH_BANK_SIZE - master_at.ct.data_offset; + memset(nc, 0, remainder_size); + write_to_flash(p, nc, remainder_size); + /* Make sure compaction starts with the new page. */ + start_new_flash_page(0); + compact_nvmem(); + } else { + /* Add delimiter at the very top. */ + add_final_delimiter(); + } + + /* Need to re-read the NVMEM cache. */ + return EC_ERROR_TRY_AGAIN; +} + +/* + * At startup iterate over flash contents and move TPM objects into the + * appropriate locations in the NVMEM cache. + */ +static enum ec_error_list retrieve_nvmem_contents(void) +{ + int rv; + int tries; + struct max_var_container *vc; + struct nn_container *nc; + uint8_t res_bitmap[(NV_PSEUDO_RESERVE_LAST + 7) / 8]; + + /* No saved object will exceed CONFIG_FLASH_BANK_SIZE in size. */ + nc = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + /* Depending on the state of flash, we might have to do this twice. */ + for (tries = 0; tries < 2; tries++) { + memset(&master_at, 0, sizeof(master_at)); + memset(nvmem_cache_base(NVMEM_TPM), 0, + nvmem_user_sizes[NVMEM_TPM]); + memset(res_bitmap, 0, sizeof(res_bitmap)); + next_evict_obj_base = 0; + + while ((rv = get_next_object(&master_at, nc, 0)) == + EC_SUCCESS) { + switch (nc->container_type) { + case NN_OBJ_TUPLE: + vc = (struct max_var_container *)nc; + total_var_space += vc->t_header.key_len + + vc->t_header.val_len; + break; /* Keep tuples in flash. */ + case NN_OBJ_TPM_RESERVED: + restore_reserved(nc + 1, nc->size, res_bitmap); + break; + + case NN_OBJ_TPM_EVICTABLE: + restore_object(nc + 1, nc->size); + break; + default: + break; + } + } + + rv = verify_delimiter(nc); + + if (rv != EC_ERROR_TRY_AGAIN) + break; + } + + if (rv != EC_SUCCESS) { + /* + * Something is really messed up, need to wipe out and start + * from scratch? + */ + ccprintf("%s:%d FAILURE!\n", __func__, __LINE__); + } + + rv = verify_reserved(res_bitmap, nc); + + shared_mem_release(nc); + + return rv; +} + +enum ec_error_list new_nvmem_init(void) +{ + enum ec_error_list rv; + timestamp_t start, init; + + total_var_space = 0; + + /* Initialize NVMEM indices. */ + NvEarlyStageFindHandle(0); + + init_page_list(); + + start = get_time(); + + rv = retrieve_nvmem_contents(); + + init = get_time(); + + ccprintf("init took %d\n", (uint32_t)(init.val - start.val)); + + return rv; +} + +/* + * Browse through the flash storage and save all evictable objects' offsets in + * the passed in array. This is used to keep track of objects added or deleted + * by the TPM library. + */ +test_export_static size_t init_object_offsets(uint16_t *offsets, size_t count) +{ + size_t num_objects = 0; + uint32_t next_obj_base; + uint32_t obj_base; + void *obj_addr; + + obj_base = s_evictNvStart; + obj_addr = (uint8_t *)nvmem_cache_base(NVMEM_TPM) + obj_base; + memcpy(&next_obj_base, obj_addr, sizeof(next_obj_base)); + + while (next_obj_base && (next_obj_base <= s_evictNvEnd)) { + if (num_objects == count) { + /* What do we do here?! */ + ccprintf("Too many objects!\n"); + break; + } + + offsets[num_objects++] = + obj_base - s_evictNvStart + sizeof(next_obj_base); + + obj_addr = nvmem_cache_base(NVMEM_TPM) + next_obj_base; + obj_base = next_obj_base; + memcpy(&next_obj_base, obj_addr, sizeof(next_obj_base)); + } + + return num_objects; +} + +static enum ec_error_list update_object(const struct access_tracker *at, + struct nn_container *ch, + void *cached_object, size_t new_size) +{ + size_t copy_size = new_size; + size_t preserved_size; + uint32_t preserved_hash; + uint8_t *dst = (uint8_t *)(ch + 1); + + preserved_size = ch->size; + preserved_hash = ch->container_hash; + + /* + * Need to copy data into the container, skip reserved type if it is a + * reserved object. + */ + if (ch->container_type == NN_OBJ_TPM_RESERVED) { + dst++; + copy_size--; + } + memcpy(dst, cached_object, copy_size); + + ch->generation++; + ch->size = new_size; + save_container(ch); + + ch->generation--; + ch->size = preserved_size; + ch->container_hash = preserved_hash; + return delete_object(at, ch); +} + +static enum ec_error_list update_pcr(const struct access_tracker *at, + struct nn_container *ch, uint8_t index, + uint8_t *cached) +{ + uint8_t preserved; + + cached--; + preserved = cached[0]; + cached[0] = index; + update_object(at, ch, cached, ch->size); + cached[0] = preserved; + + return EC_SUCCESS; +} + +static enum ec_error_list save_pcr(struct nn_container *ch, + uint8_t reserved_index, const void *pcr, + size_t pcr_size) +{ + uint8_t *container_body; + + ch->container_type = ch->container_type_copy = NN_OBJ_TPM_RESERVED; + ch->encrypted = 1; + ch->size = pcr_size + 1; + ch->generation = 0; + + container_body = (uint8_t *)(ch + 1); + container_body[0] = reserved_index; + memcpy(container_body + 1, pcr, pcr_size); + + return save_container(ch); +} + +static enum ec_error_list maybe_save_pcr(struct nn_container *ch, + size_t pcr_index) +{ + const STATE_CLEAR_DATA *scd; + const struct pcr_descriptor *pcrd; + const void *cached; + size_t pcr_size; + + pcrd = pcr_arrays + pcr_index / NUM_STATIC_PCR; + scd = get_scd(); + + pcr_size = pcrd->pcr_size; + + cached = (const uint8_t *)&scd->pcrSave + pcrd->pcr_array_offset + + pcr_size * (pcr_index % NUM_STATIC_PCR); + + if (is_empty(cached, pcr_size)) + return EC_SUCCESS; + + return save_pcr(ch, pcr_index + NV_VIRTUAL_RESERVE_LAST, cached, + pcr_size); +} + +/* + * The process_XXX functions below are used to check and if necessary add, + * update or delete objects from the flash based on the NVMEM cache + * contents. + */ +static enum ec_error_list process_pcr(const struct access_tracker *at, + struct nn_container *ch, uint8_t index, + const uint8_t *saved, uint8_t *pcr_bitmap) +{ + STATE_CLEAR_DATA *scd; + const struct pcr_descriptor *pcrd; + size_t pcr_bitmap_index; + size_t pcr_index; + size_t pcr_size; + uint8_t *cached; + + pcr_bitmap_index = index - NV_VIRTUAL_RESERVE_LAST; + + if (pcr_bitmap_index > NUM_OF_PCRS) + return EC_ERROR_INVAL; + + pcrd = pcr_arrays + pcr_bitmap_index / NUM_STATIC_PCR; + pcr_index = pcr_bitmap_index % NUM_STATIC_PCR; + + pcr_size = pcrd->pcr_size; + + if (pcr_size != (ch->size - 1)) + return EC_ERROR_INVAL; /* This is an error. */ + + /* Find out base address of the cached PCR. */ + scd = get_scd(); + cached = (uint8_t *)&scd->pcrSave + pcrd->pcr_array_offset + + pcr_size * pcr_index; + + /* Set bitmap bit to indicate that this PCR was looked at. */ + bitmap_bit_set(pcr_bitmap, pcr_bitmap_index); + + if (memcmp(saved, cached, pcr_size)) + return update_pcr(at, ch, index, cached); + + return EC_SUCCESS; +} + +static enum ec_error_list process_reserved(const struct access_tracker *at, + struct nn_container *ch, + uint8_t *pcr_bitmap) +{ + NV_RESERVED_ITEM ri; + size_t new_size; + uint8_t *saved; + uint8_t index; + void *cached; + + /* + * Find out this object's location in the cache (first byte of the + * contents is the index of the reserved object. + */ + saved = (uint8_t *)(ch + 1); + index = *saved++; + + NvGetReserved(index, &ri); + + if (ri.size) { + void *marshaled; + + cached = (uint8_t *)nvmem_cache_base(NVMEM_TPM) + ri.offset; + + /* + * For NV_STATE_CLEAR and NV_STATE_RESET cases Let's marshal + * cached data to be able to compare it with saved data. + */ + if (index == NV_STATE_CLEAR) { + marshaled = ((uint8_t *)(ch + 1)) + ch->size; + new_size = marshal_state_clear(cached, marshaled); + cached = marshaled; + } else if (index == NV_STATE_RESET) { + marshaled = ((uint8_t *)(ch + 1)) + ch->size; + new_size = marshal_state_reset_data(cached, marshaled); + cached = marshaled; + } else { + new_size = ri.size; + } + + if ((new_size == (ch->size - 1)) && + !memcmp(saved, cached, new_size)) + return EC_SUCCESS; + + return update_object(at, ch, cached, new_size + 1); + } + + /* This must be a PCR. */ + return process_pcr(at, ch, index, saved, pcr_bitmap); +} + +static enum ec_error_list process_object(const struct access_tracker *at, + struct nn_container *ch, + uint16_t *tpm_object_offsets, + size_t *num_objects) +{ + size_t i; + uint32_t cached_size; + uint32_t cached_type; + uint32_t flash_type; + uint32_t next_obj_base; + uint8_t *evict_start; + void *pcache; + + evict_start = (uint8_t *)nvmem_cache_base(NVMEM_TPM) + s_evictNvStart; + memcpy(&flash_type, ch + 1, sizeof(flash_type)); + for (i = 0; i < *num_objects; i++) { + + /* Find TPM object in the NVMEM cache. */ + pcache = evict_start + tpm_object_offsets[i]; + memcpy(&cached_type, pcache, sizeof(cached_type)); + if (cached_type == flash_type) + break; + } + + if (i == *num_objects) { + /* + * This object is not in the cache any more, delete it from + * flash. + */ + return delete_object(at, ch); + } + + memcpy(&next_obj_base, (uint8_t *)pcache - sizeof(next_obj_base), + sizeof(next_obj_base)); + cached_size = next_obj_base - s_evictNvStart - tpm_object_offsets[i]; + if ((cached_size != ch->size) || memcmp(ch + 1, pcache, cached_size)) { + /* + * Object changed. Let's delete the old copy and save the new + * one. + */ + update_object(at, ch, pcache, ch->size); + } + + tpm_object_offsets[i] = tpm_object_offsets[*num_objects - 1]; + *num_objects -= 1; + + return EC_SUCCESS; +} + +static enum ec_error_list save_new_object(uint16_t obj_base, void *buf) +{ + size_t obj_size; + struct nn_container *ch = buf; + uint32_t next_obj_base; + void *obj_addr; + + obj_addr = (uint8_t *)nvmem_cache_base(NVMEM_TPM) + obj_base + + s_evictNvStart; + memcpy(&next_obj_base, obj_addr - sizeof(next_obj_base), + sizeof(next_obj_base)); + obj_size = next_obj_base - obj_base - s_evictNvStart; + + ch->container_type_copy = ch->container_type = NN_OBJ_TPM_EVICTABLE; + ch->encrypted = 1; + ch->size = obj_size; + ch->generation = 0; + memcpy(ch + 1, obj_addr, obj_size); + + return save_container(ch); +} + +enum ec_error_list new_nvmem_save(void) +{ + const void *fence_ph; + size_t i; + size_t num_objs; + struct nn_container *ch; + struct access_tracker at = {}; + uint16_t fence_offset; + /* We don't foresee ever storing this many objects. */ + uint16_t tpm_object_offsets[MAX_STORED_EVICTABLE_OBJECTS]; + uint8_t pcr_bitmap[(NUM_STATIC_PCR * ARRAY_SIZE(pcr_arrays) + 7) / 8]; + + /* See if compaction is needed. */ + if (master_at.list_index >= (ARRAY_SIZE(page_list) - 3)) { + enum ec_error_list rv; + + rv = compact_nvmem(); + if (rv != EC_SUCCESS) + return rv; + } + + fence_ph = master_at.mt.ph; + fence_offset = master_at.mt.data_offset; + + num_objs = init_object_offsets(tpm_object_offsets, + ARRAY_SIZE(tpm_object_offsets)); + + memset(pcr_bitmap, 0, sizeof(pcr_bitmap)); + del_candidates = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE + + sizeof(struct delete_candidates)); + ch = (void *)(del_candidates + 1); + del_candidates->num_candidates = 0; + + while ((fence_ph != at.mt.ph) || (fence_offset != at.mt.data_offset)) { + int rv; + + rv = get_next_object(&at, ch, 0); + + if (rv == EC_ERROR_MEMORY_ALLOCATION) + break; + + if (rv != EC_SUCCESS) { + ccprintf("%s: failed to read flash when saving (%d)!\n", + __func__, rv); + shared_mem_release(ch); + return rv; + } + + if (ch->container_type == NN_OBJ_TPM_RESERVED) { + process_reserved(&at, ch, pcr_bitmap); + continue; + } + + if (ch->container_type == NN_OBJ_TPM_EVICTABLE) { + process_object(&at, ch, tpm_object_offsets, &num_objs); + continue; + } + } + + /* Now save new objects, if any. */ + for (i = 0; i < num_objs; i++) + save_new_object(tpm_object_offsets[i], ch); + + /* And new pcrs, if any. */ + for (i = 0; i < NUM_OF_PCRS; i++) { + if (bitmap_bit_check(pcr_bitmap, i)) + continue; + maybe_save_pcr(ch, i); + } + +#if defined(NVMEM_TEST_BUILD) + if (failure_mode == TEST_FAIL_WHEN_SAVING) { + shared_mem_release(del_candidates); + del_candidates = NULL; + return EC_SUCCESS; + } +#endif + /* + * Add a delimiter if there have been new containers added to the + * flash. + */ + if (del_candidates->num_candidates || + (fence_offset != master_at.mt.data_offset) || + (fence_ph != master_at.mt.ph)) { + const void *del = page_cursor(&master_at.mt); + + add_delimiter(); + + if (del_candidates->num_candidates) { + /* Now delete objects which need to be deleted. */ + for (i = 0; i < del_candidates->num_candidates; i++) + invalidate_object( + del_candidates->candidates[i]); + } + +#if defined(NVMEM_TEST_BUILD) + if (failure_mode == TEST_FAIL_WHEN_INVALIDATING) { + shared_mem_release(del_candidates); + del_candidates = NULL; + return EC_SUCCESS; + } +#endif + finalize_delimiter(del); + } + + shared_mem_release(del_candidates); + del_candidates = NULL; + + return EC_SUCCESS; +} + +/* Caller must free memory allocated by this function! */ +static struct max_var_container *find_var(const uint8_t *key, size_t key_len, + struct access_tracker *at) +{ + int rv; + struct max_var_container *vc; + + vc = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + /* + * Let's iterate over all objects there are and look for matching + * tuples. + */ + while ((rv = get_next_object(at, &vc->c_header, 0)) == EC_SUCCESS) { + + if (vc->c_header.container_type != NN_OBJ_TUPLE) + continue; + + /* Verify consistency, first that the sizes match */ + if ((vc->t_header.key_len + vc->t_header.val_len + + sizeof(vc->t_header)) != vc->c_header.size) { + ccprintf("%s: - inconsistent sizes!\n", __func__); + /* report error here. */ + continue; + } + + /* Ok, found a tuple, does the key match? */ + if ((key_len == vc->t_header.key_len) && + !memcmp(key, vc->body, key_len)) + /* Yes, it does! */ + return vc; + } + + shared_mem_release(vc); + return NULL; +} + +struct tuple *getvar(const uint8_t *key, uint8_t key_len) +{ + struct max_var_container *vc; + struct access_tracker at = {}; + + if (!key || !key_len) + return NULL; + + vc = find_var(key, key_len, &at); + + if (vc) + return &vc->t_header; + + return NULL; +} + +int freevar(struct tuple *var) +{ + void *vc; + + vc = (uint8_t *)var - offsetof(struct max_var_container, t_header); + shared_mem_release(vc); + + return EC_SUCCESS; /* Could verify var first before releasing. */ +} + +static enum ec_error_list save_container(struct nn_container *nc) +{ + uint32_t hash; + uint32_t salt[4]; + + nc->container_hash = 0; + app_compute_hash_wrapper(nc, sizeof(*nc) + nc->size, &hash, + sizeof(hash)); + nc->container_hash = hash; /* This will truncate it. */ + + /* Skip transactions delimiters. */ + if (nc->size) { + salt[0] = master_at.mt.ph->page_number; + salt[1] = master_at.mt.data_offset; + salt[2] = nc->container_hash; + salt[3] = 0; + + app_cipher(salt, nc + 1, nc + 1, nc->size); + } + + return save_object(nc); +} + +int setvar(const uint8_t *key, uint8_t key_len, const uint8_t *val, + uint8_t val_len) +{ + enum ec_error_list rv; + int erase_request; + size_t new_var_space; + size_t old_var_space; + struct max_var_container *vc; + struct access_tracker at = {}; + + if (!key || !key_len) + return EC_ERROR_INVAL; + + new_var_space = key_len + val_len; + + if (new_var_space > MAX_VAR_BODY_SPACE) + /* Too much space would be needed. */ + return EC_ERROR_INVAL; + + erase_request = !val || !val_len; + + /* See if compaction is needed. */ + if (!erase_request && + (master_at.list_index >= (ARRAY_SIZE(page_list) - 3))) { + rv = compact_nvmem(); + if (rv != EC_SUCCESS) + return rv; + } + + vc = find_var(key, key_len, &at); + + if (erase_request) { + if (!vc) + /* Nothing to erase. */ + return EC_SUCCESS; + + rv = invalidate_object( + (struct nn_container *)((uintptr_t)at.ct.ph + + at.ct.data_offset)); + + if (rv == EC_SUCCESS) + total_var_space -= + vc->t_header.key_len + vc->t_header.val_len; + + shared_mem_release(vc); + return rv; + } + + /* Is this variable already there? */ + if (!vc) { + /* No, it is not. Will it fit? */ + if ((new_var_space + total_var_space) > MAX_VAR_TOTAL_SPACE) + /* No, it will not. */ + return EC_ERROR_OVERFLOW; + + return save_var(key, key_len, val, val_len, vc); + } + + /* The variable was found, let's see if the value is being changed. */ + if (vc->t_header.val_len == val_len && + !memcmp(val, vc->body + key_len, val_len)) { + shared_mem_release(vc); + return EC_SUCCESS; + } + + /* Ok, the variable was found, and is of a different value. */ + old_var_space = vc->t_header.val_len + vc->t_header.key_len; + + if ((old_var_space < new_var_space) && + ((total_var_space + new_var_space - old_var_space) > + MAX_VAR_BODY_SPACE)) + return EC_ERROR_OVERFLOW; + + /* Save the new instance first with the larger generation number. */ + vc->c_header.generation++; + rv = save_var(key, key_len, val, val_len, vc); + shared_mem_release(vc); + if (rv == EC_SUCCESS) { + rv = invalidate_object( + (struct nn_container *)((uintptr_t)at.ct.ph + + at.ct.data_offset)); + if (rv == EC_SUCCESS) + total_var_space -= old_var_space; + } + return rv; +} + +static void dump_contents(const struct nn_container *ch) +{ + const uint8_t *buf = (const void *)ch; + size_t i; + size_t total_size = sizeof(*ch) + ch->size; + + for (i = 0; i < total_size; i++) { + if (!(i % 16)) { + ccprintf("\n"); + cflush(); + } + ccprintf(" %02x", buf[i]); + } + ccprintf("\n"); +} + +/* + * Clear tpm data from nvmem. First fill up the current top page with erased + * objects, then compact the flash storage, removing all TPM related objects. + * This would guarantee that all pages where TPM objecs were stored would be + * erased. + * + * TODO(vbendeb): need to seed reserved objects after this is done. + */ +int nvmem_erase_tpm_data(void) +{ + const uint8_t *key; + const uint8_t *val; + int rv; + struct nn_container *ch; + struct access_tracker at = {}; + uint8_t saved_list_index; + uint8_t key_len; + + ch = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + while (get_next_object(&at, ch, 0) == EC_SUCCESS) { + + if ((ch->container_type != NN_OBJ_TPM_RESERVED) && + (ch->container_type != NN_OBJ_TPM_EVICTABLE)) + continue; + + delete_object(&at, ch); + } + + shared_mem_release(ch); + + /* + * Now fill up the current flash page with erased objects to make sure + * that it would be erased during next compaction. Use dummy key, + * value pairs as the erase objects. + */ + saved_list_index = master_at.list_index; + key = (const uint8_t *)nvmem_erase_tpm_data; + val = (const uint8_t *)nvmem_erase_tpm_data; + key_len = MAX_VAR_BODY_SPACE - 255; + do { + size_t to_go_in_page; + uint8_t val_len; + + to_go_in_page = + CONFIG_FLASH_BANK_SIZE - master_at.mt.data_offset; + if (to_go_in_page > + (MAX_VAR_BODY_SPACE + + offsetof(struct max_var_container, body) - 1)) { + val_len = MAX_VAR_BODY_SPACE - key_len; + } else { + /* + * Let's not write more than we have to get over the + * page limit. The minimum size we need is: + * + * <container header size> + <tuple header size> + 2 + * + * (where key and value are of one byte each). + */ + if (to_go_in_page < + (offsetof(struct max_var_container, body) + 2)) { + /* + * There is very little room left, even key + * and value of size of one each is enough to + * go over. + */ + key_len = 1; + val_len = 1; + } else { + size_t need_to_cover; + + /* How much space key and value should cover? */ + need_to_cover = + to_go_in_page - + offsetof(struct max_var_container, + body) + 1; + key_len = need_to_cover / 2; + val_len = need_to_cover - key_len; + } + } + if (setvar(key, key_len, val, val_len) != EC_SUCCESS) + ccprintf("%s: adding var failed!\n", __func__); + if (setvar(key, key_len, NULL, 0) != EC_SUCCESS) + ccprintf("%s: deleting var failed!\n", __func__); + + } while (master_at.list_index != (saved_list_index + 1)); + + rv = compact_nvmem(); + return rv; +} + +/* + * Function which verifes flash contents integrity (and printing objects it + * finds, if requested by the caller). All objects' active and deleted alike + * integrity is verified by get_next_object(). + */ +test_export_static enum ec_error_list browse_flash_contents(int print) +{ + int active = 0; + int count = 0; + int rv = EC_SUCCESS; + size_t line_len = 0; + struct nn_container *ch; + struct access_tracker at = {}; + + ch = get_scratch_buffer(CONFIG_FLASH_BANK_SIZE); + + while ((rv = get_next_object(&at, ch, 1)) == EC_SUCCESS) { + uint8_t ctype = ch->container_type; + + count++; + + if ((ctype != NN_OBJ_OLD_COPY) && + (ctype != NN_OBJ_TRANSACTION_DEL)) + active++; + + if (print) { + char erased; + + if (ctype == NN_OBJ_OLD_COPY) + erased = 'x'; + else + erased = ' '; + + if (ch->container_type_copy == NN_OBJ_TPM_RESERVED) { + ccprintf("%cR:%02x.%d ", erased, + *((uint8_t *)(ch + 1)), + ch->generation); + } else { + uint32_t index; + char tag; + + switch (ch->container_type_copy) { + case NN_OBJ_TPM_EVICTABLE: + tag = 'E'; + break; + + case NN_OBJ_TUPLE: + tag = 'T'; + break; + + case NN_OBJ_TRANSACTION_DEL: + tag = 's'; /* 's' for separator. */ + break; + + default: + tag = '?'; + break; + } + + if (ch->container_type_copy != + NN_OBJ_TRANSACTION_DEL) + memcpy(&index, ch + 1, sizeof(index)); + else + index = 0; + ccprintf("%c%c:%08x.%d ", erased, tag, index, + ch->generation); + } + if (print > 1) { + dump_contents(ch); + continue; + } + + if (line_len > 70) { + ccprintf("\n"); + cflush(); + line_len = 0; + } else { + line_len += 11; + } + } + } + + shared_mem_release(ch); + + if (rv == EC_ERROR_MEMORY_ALLOCATION) { + ccprintf("%schecked %d objects, %d active\n", print ? "\n" : "", + count, active); + rv = EC_SUCCESS; + } + + return rv; +} + +static int command_dump_nvmem(int argc, char **argv) +{ + nvmem_disable_commits(); + + browse_flash_contents(1 + (argc > 1)); + + nvmem_enable_commits(); + + return 0; +} +DECLARE_SAFE_CONSOLE_COMMAND(dump_nvmem, command_dump_nvmem, "", ""); diff --git a/include/config.h b/include/config.h index 6fb463b7b1..53d983f12b 100644 --- a/include/config.h +++ b/include/config.h @@ -1606,6 +1606,11 @@ /* Address of start of Nvmem area */ #undef CONFIG_FLASH_NVMEM_BASE_A #undef CONFIG_FLASH_NVMEM_BASE_B + +/* Flash offsets for the 'new' (as of 1/2019) nvmem storage scheme. */ +#undef CONFIG_FLASH_NEW_NVMEM_BASE_A +#undef CONFIG_FLASH_NEW_NVMEM_BASE_B + /* Size in bytes of NvMem area */ #undef CONFIG_FLASH_NVMEM_SIZE diff --git a/include/flash_log.h b/include/flash_log.h index 19093ba40b..b4882ebe36 100644 --- a/include/flash_log.h +++ b/include/flash_log.h @@ -17,6 +17,7 @@ enum flash_event_type { FE_TPM_I2C_ERROR = 2, FE_LOG_OVERFLOWS = 3, /* A single byte, overflow counter. */ FE_LOG_LOCKS = 4, /* A single byte, lock failures counter. */ + FE_LOG_NVMEM = 5, /* NVMEM failure, variable structure. */ /* * Fixed padding value makes it easier to parse log space @@ -45,6 +46,37 @@ struct flash_log_entry { uint8_t payload[0]; /* optional additional data payload: 0..63 bytes. */ } __packed; +/* Payloads for various log events. */ +/* NVMEM failures. */ +enum nvmem_failure_type { + NVMEMF_MALLOC = 0, + NVMEMF_PH_SIZE_MISMATCH = 1, + NVMEMF_READ_UNDERRUN = 2, + NVMEMF_INCONSISTENT_FLASH_CONTENTS = 3, + NVMEMF_MIGRATION_FAILURE = 4, + NVMEMF_LEGACY_ERASE_FAILURE = 5, + NVMEMF_EXCESS_DELETE_OBJECTS = 6, + NVMEMF_UNEXPECTED_LAST_OBJ = 7, + NVMEMF_MISSING_OBJECT = 8, + NVMEMF_SECTION_VERIFY = 9, + NVMEMF_PRE_ERASE_MISMATCH = 10, + NVMEMF_PAGE_LIST_OVERFLOW = 11 +}; + +/* Not all nvmem failures require payload. */ +struct nvmem_failure_payload { + uint8_t failure_type; + union { + uint16_t size; /* How much memory was requested. */ + struct { + uint16_t ph_offset; + uint16_t expected; + } ph __packed; + uint16_t underrun_size; /* How many bytes short. */ + uint8_t last_obj_type; + } __packed; +} __packed; + /* Returned in the "type" field, when there is no entry available */ #define FLASH_LOG_NO_ENTRY 0xff #define MAX_FLASH_LOG_PAYLOAD_SIZE ((1 << 6) - 1) diff --git a/include/new_nvmem.h b/include/new_nvmem.h new file mode 100644 index 0000000000..110cfd1667 --- /dev/null +++ b/include/new_nvmem.h @@ -0,0 +1,155 @@ +/* Copyright 2019 The Chromium OS Authors. All rights reserved. + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef __TPM2_NVMEM_TEST_NEW_NVMEM_H +#define __TPM2_NVMEM_TEST_NEW_NVMEM_H + +#include "common.h" +#include "nvmem.h" +#include "nvmem_vars.h" +#include "util.h" + +#define NVMEM_NOT_INITIALIZED ((unsigned int)-1) + +/* + * A totally arbitrary byte limit for space occupied by (key, value) pairs in + * the flash. This is an improvement compared to the legacy case where there + * were just 272 bytes dedicated to the (key, value) pairs storage. + */ +#define MAX_VAR_TOTAL_SPACE 1000 + +/* + * Let's be reasonable: we're unlikely to have keys longer than 40 or so + * bytes, and leave full 255 bytes for the value. Total data space occupied by + * a (key, value) pair is not to exceed the value below. + */ +#define MAX_VAR_BODY_SPACE 300 + +enum nn_object_type { + NN_OBJ_OLD_COPY = 0, + NN_OBJ_TUPLE = 1, + NN_OBJ_TPM_RESERVED = 2, + NN_OBJ_TPM_EVICTABLE = 3, + NN_OBJ_TRANSACTION_DEL = 4, + NN_OBJ_ESCAPE = 5, + NN_OBJ_ERASED = 7, +}; + +/* + * Structure placed at the base of each flash page used for NVMEM storage. + * + * page_number: allows to arrange pages in order they were added + * + * data_offset: the offset of the first element in the page (space above + * page header and below data_offset could be taken by the + * 'tail' of the object stored on the previous page). + * + * page_hash: is used to verify page header integrity + */ +struct nn_page_header { + unsigned int page_number : 21; + unsigned int data_offset : 11; + uint32_t page_hash; +} __packed; + +/* + * Index of the 'virtual' last reserved object. RAM index space and max + * counter objects stored at fixed location in the NVMEM cache are considered + * reserved objects by this NVMEM flash layer. + */ +#define NV_VIRTUAL_RESERVE_LAST (NV_RESERVE_LAST + 2) + +/* + * Container header for all blobs stored in flash. + * + * container_type: type of object stored in the container. MAKE SURE THIS + * FIELD TYPE IS THE FIRST FIELD IN THIS STRUCTURE, it is + * supposed to be in the first word of the container so that + * the type can be erased when object is deleted. + * + * container_type_copy: immutable copy of the container_type field, used to + * verify contents of deleted objects. + * + * encrypted: set to 1 if contents are encrypted. + * + * size: size of the payload, 12 bits allocated, 11 bits would be enough for + * this use case. + * + * generation: a free running counter, used to compare ages of two containers + * + * container_hash: hash of the ENTIRE container, both header and body + * included. This field is set to zero before hash is calculated + */ +struct nn_container { + unsigned int container_type : 3; + unsigned int container_type_copy : 3; + unsigned int encrypted : 1; + unsigned int size : 11; + unsigned int generation : 2; + unsigned int container_hash : 12; +} __packed; + +/* + * A structure to keep context of accessing to a page, page header and offset + * define where the next access would happen. + */ +struct page_tracker { + const struct nn_page_header *ph; + uint16_t data_offset; +}; + +/* + * Helper structure to keep track of accesses to the flash storage. + * + * mt: main tracker for read or write accesses. + * + * ct: keeps track of container fetches, as the location of containers has + * special significance: it is both part of the seed used when + * encrypting/decryping container contents, and also is necessary to + * unwind reading of the container header when the end of storage is + * reached and a header of all 0xff is read. + * + * dt: keeps track of delimiters which is important when assessing flash + * contents integrity. If during startup the last item in flash is not a + * delimiter, this is an indication of a failed transaction, all data + * after the previous delimiter needs to be discarded. + * + * list_index; index of the current page in the list of pages, useful when + * sequential reading and need to get to the next page in the + * list. + */ + +struct access_tracker { + struct page_tracker mt; /* Main tracker. */ + struct page_tracker ct; /* Container tracker. */ + struct page_tracker dt; /* Delimiter tracker.*/ + uint8_t list_index; +}; + +enum ec_error_list new_nvmem_init(void); +enum ec_error_list new_nvmem_migrate(unsigned int nvmem_act_partition); +enum ec_error_list new_nvmem_save(void); + +enum ec_error_list get_next_object(struct access_tracker *at, + struct nn_container *ch, + int include_deleted); + +#if defined(TEST_BUILD) && !defined(TEST_FUZZ) +#define NVMEM_TEST_BUILD +enum ec_error_list browse_flash_contents(int); +enum ec_error_list compact_nvmem(void); +extern struct access_tracker master_at; +extern uint16_t total_var_space; +int is_uninitialized(const void *p, size_t size); +size_t init_object_offsets(uint16_t *offsets, size_t count); +struct nn_page_header *list_element_to_ph(size_t el); +void *evictable_offs_to_addr(uint16_t offset); +#endif + +/* + * Clear tpm data from nvmem. + */ +int nvmem_erase_tpm_data(void); + +#endif /* ! __TPM2_NVMEM_TEST_NEW_NVMEM_H */ |