summaryrefslogtreecommitdiff
path: root/core/tee/fs_htree.c
diff options
context:
space:
mode:
Diffstat (limited to 'core/tee/fs_htree.c')
-rw-r--r--core/tee/fs_htree.c937
1 files changed, 937 insertions, 0 deletions
diff --git a/core/tee/fs_htree.c b/core/tee/fs_htree.c
new file mode 100644
index 0000000..53cb81e
--- /dev/null
+++ b/core/tee/fs_htree.c
@@ -0,0 +1,937 @@
+/*
+ * Copyright (c) 2017, Linaro Limited
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <assert.h>
+#include <initcall.h>
+#include <kernel/tee_common_otp.h>
+#include <optee_msg_supplicant.h>
+#include <stdlib.h>
+#include <string_ext.h>
+#include <string.h>
+#include <tee/fs_htree.h>
+#include <tee/tee_cryp_provider.h>
+#include <tee/tee_fs_key_manager.h>
+#include <tee/tee_fs_rpc.h>
+#include <utee_defines.h>
+#include <util.h>
+
+#define TEE_FS_HTREE_CHIP_ID_SIZE 32
+#define TEE_FS_HTREE_HASH_ALG TEE_ALG_SHA256
+#define TEE_FS_HTREE_TSK_SIZE TEE_FS_HTREE_HASH_SIZE
+#define TEE_FS_HTREE_ENC_ALG TEE_ALG_AES_ECB_NOPAD
+#define TEE_FS_HTREE_ENC_SIZE TEE_AES_BLOCK_SIZE
+#define TEE_FS_HTREE_SSK_SIZE TEE_FS_HTREE_HASH_SIZE
+
+#define TEE_FS_HTREE_AUTH_ENC_ALG TEE_ALG_AES_GCM
+#define TEE_FS_HTREE_HMAC_ALG TEE_ALG_HMAC_SHA256
+
+#define BLOCK_NUM_TO_NODE_ID(num) ((num) + 1)
+
+#define NODE_ID_TO_BLOCK_NUM(id) ((id) - 1)
+
+/*
+ * The hash tree is implemented as a binary tree with the purpose to ensure
+ * integrity of the data in the nodes. The data in the nodes their turn
+ * provides both integrity and confidentiality of the data blocks.
+ *
+ * The hash tree is saved in a file as:
+ * +----------------------------+
+ * | htree_image.0 |
+ * | htree_image.1 |
+ * +----------------------------+
+ * | htree_node_image.1.0 |
+ * | htree_node_image.1.1 |
+ * +----------------------------+
+ * | htree_node_image.2.0 |
+ * | htree_node_image.2.1 |
+ * +----------------------------+
+ * | htree_node_image.3.0 |
+ * | htree_node_image.3.1 |
+ * +----------------------------+
+ * | htree_node_image.4.0 |
+ * | htree_node_image.4.1 |
+ * +----------------------------+
+ * ...
+ *
+ * htree_image is the header of the file, there's two instances of it. One
+ * which is committed and the other is used when updating the file. Which
+ * is committed is indicated by the "counter" field, the one with the
+ * largest value is selected.
+ *
+ * htree_node_image is a node in the hash tree, each node has two instances
+ * which is committed is decided by the parent node .flag bit
+ * HTREE_NODE_COMMITTED_CHILD. Which version is the committed version of
+ * node 1 is determined by the by the lowest bit of the counter field in
+ * the header.
+ *
+ * Note that nodes start counting at 1 while blocks at 0, this means that
+ * block 0 is represented by node 1.
+ *
+ * Where different elements are stored in the file is managed by the file
+ * system. In the case of SQL FS the version of the node/block is ignored
+ * as the atomic update is finalized with a call to
+ * tee_fs_rpc_end_transaction().
+ */
+
+#define HTREE_NODE_COMMITTED_BLOCK BIT32(0)
+/* n is 0 or 1 */
+#define HTREE_NODE_COMMITTED_CHILD(n) BIT32(1 + (n))
+
+struct htree_node {
+ size_t id;
+ bool dirty;
+ bool block_updated;
+ struct tee_fs_htree_node_image node;
+ struct htree_node *parent;
+ struct htree_node *child[2];
+};
+
+struct tee_fs_htree {
+ struct htree_node root;
+ struct tee_fs_htree_image head;
+ uint8_t fek[TEE_FS_HTREE_FEK_SIZE];
+ struct tee_fs_htree_imeta imeta;
+ bool dirty;
+ const struct tee_fs_htree_storage *stor;
+ void *stor_aux;
+};
+
+struct traverse_arg;
+typedef TEE_Result (*traverse_cb_t)(struct traverse_arg *targ,
+ struct htree_node *node);
+struct traverse_arg {
+ struct tee_fs_htree *ht;
+ traverse_cb_t cb;
+ void *arg;
+};
+
+static TEE_Result rpc_read(struct tee_fs_htree *ht, enum tee_fs_htree_type type,
+ size_t idx, size_t vers, void *data, size_t dlen)
+{
+ TEE_Result res;
+ struct tee_fs_rpc_operation op;
+ size_t bytes;
+ void *p;
+
+ res = ht->stor->rpc_read_init(ht->stor_aux, &op, type, idx, vers, &p);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ res = ht->stor->rpc_read_final(&op, &bytes);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ if (bytes != dlen)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ memcpy(data, p, dlen);
+ return TEE_SUCCESS;
+}
+
+static TEE_Result rpc_read_head(struct tee_fs_htree *ht, size_t vers,
+ struct tee_fs_htree_image *head)
+{
+ return rpc_read(ht, TEE_FS_HTREE_TYPE_HEAD, 0, vers,
+ head, sizeof(*head));
+}
+
+static TEE_Result rpc_read_node(struct tee_fs_htree *ht, size_t node_id,
+ size_t vers,
+ struct tee_fs_htree_node_image *node)
+{
+ return rpc_read(ht, TEE_FS_HTREE_TYPE_NODE, node_id - 1, vers,
+ node, sizeof(*node));
+}
+
+static TEE_Result rpc_write(struct tee_fs_htree *ht,
+ enum tee_fs_htree_type type, size_t idx,
+ size_t vers, const void *data, size_t dlen)
+{
+ TEE_Result res;
+ struct tee_fs_rpc_operation op;
+ void *p;
+
+ res = ht->stor->rpc_write_init(ht->stor_aux, &op, type, idx, vers, &p);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ memcpy(p, data, dlen);
+ return ht->stor->rpc_write_final(&op);
+}
+
+static TEE_Result rpc_write_head(struct tee_fs_htree *ht, size_t vers,
+ const struct tee_fs_htree_image *head)
+{
+ return rpc_write(ht, TEE_FS_HTREE_TYPE_HEAD, 0, vers,
+ head, sizeof(*head));
+}
+
+static TEE_Result rpc_write_node(struct tee_fs_htree *ht, size_t node_id,
+ size_t vers,
+ const struct tee_fs_htree_node_image *node)
+{
+ return rpc_write(ht, TEE_FS_HTREE_TYPE_NODE, node_id - 1, vers,
+ node, sizeof(*node));
+}
+
+static TEE_Result traverse_post_order(struct traverse_arg *targ,
+ struct htree_node *node)
+{
+ TEE_Result res;
+
+ /*
+ * This function is recursing but not very deep, only with Log(N)
+ * maximum depth.
+ */
+
+ if (!node)
+ return TEE_SUCCESS;
+
+ res = traverse_post_order(targ, node->child[0]);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ res = traverse_post_order(targ, node->child[1]);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ return targ->cb(targ, node);
+}
+
+static TEE_Result htree_traverse_post_order(struct tee_fs_htree *ht,
+ traverse_cb_t cb, void *arg)
+{
+ struct traverse_arg targ = { ht, cb, arg };
+
+ return traverse_post_order(&targ, &ht->root);
+}
+
+static size_t node_id_to_level(size_t node_id)
+{
+ assert(node_id && node_id < UINT_MAX);
+ /* Calculate level of the node, root node (1) has level 1 */
+ return sizeof(unsigned int) * 8 - __builtin_clz(node_id);
+}
+
+static struct htree_node *find_closest_node(struct tee_fs_htree *ht,
+ size_t node_id)
+{
+ struct htree_node *node = &ht->root;
+ size_t level = node_id_to_level(node_id);
+ size_t n;
+
+ /* n = 1 because root node is level 1 */
+ for (n = 1; n < level; n++) {
+ struct htree_node *child;
+ size_t bit_idx;
+
+ /*
+ * The difference between levels of the current node and
+ * the node we're looking for tells which bit decides
+ * direction in the tree.
+ *
+ * As the first bit has index 0 we'll subtract 1
+ */
+ bit_idx = level - n - 1;
+ child = node->child[((node_id >> bit_idx) & 1)];
+ if (!child)
+ return node;
+ node = child;
+ }
+
+ return node;
+}
+
+static struct htree_node *find_node(struct tee_fs_htree *ht, size_t node_id)
+{
+ struct htree_node *node = find_closest_node(ht, node_id);
+
+ if (node && node->id == node_id)
+ return node;
+ return NULL;
+}
+
+static TEE_Result get_node(struct tee_fs_htree *ht, bool create,
+ size_t node_id, struct htree_node **node_ret)
+{
+ struct htree_node *node;
+ struct htree_node *nc;
+ size_t n;
+
+ node = find_closest_node(ht, node_id);
+ if (!node)
+ return TEE_ERROR_GENERIC;
+ if (node->id == node_id)
+ goto ret_node;
+
+ /*
+ * Trying to read beyond end of file should be caught earlier than
+ * here.
+ */
+ if (!create)
+ return TEE_ERROR_GENERIC;
+
+ /*
+ * Add missing nodes, some nodes may already be there. When we've
+ * processed the range all nodes up to node_id will be in the tree.
+ */
+ for (n = node->id + 1; n <= node_id; n++) {
+ node = find_closest_node(ht, n);
+ if (node->id == n)
+ continue;
+ /* Node id n should be a child of node */
+ assert((n >> 1) == node->id);
+ assert(!node->child[n & 1]);
+
+ nc = calloc(1, sizeof(*nc));
+ if (!nc)
+ return TEE_ERROR_OUT_OF_MEMORY;
+ nc->id = n;
+ nc->parent = node;
+ node->child[n & 1] = nc;
+ node = nc;
+ }
+
+ if (node->id > ht->imeta.max_node_id)
+ ht->imeta.max_node_id = node->id;
+
+ret_node:
+ *node_ret = node;
+ return TEE_SUCCESS;
+}
+
+static int get_idx_from_counter(uint32_t counter0, uint32_t counter1)
+{
+ if (!(counter0 & 1)) {
+ if (!(counter1 & 1))
+ return 0;
+ if (counter0 > counter1)
+ return 0;
+ else
+ return 1;
+ }
+
+ if (counter1 & 1)
+ return 1;
+ else
+ return -1;
+}
+
+static TEE_Result init_head_from_data(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ struct tee_fs_htree_image head[2];
+ int idx;
+
+ for (idx = 0; idx < 2; idx++) {
+ res = rpc_read_head(ht, idx, head + idx);
+ if (res != TEE_SUCCESS)
+ return res;
+ }
+
+ idx = get_idx_from_counter(head[0].counter, head[1].counter);
+ if (idx < 0)
+ return TEE_ERROR_SECURITY;
+
+ res = rpc_read_node(ht, 1, idx, &ht->root.node);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ ht->head = head[idx];
+ ht->root.id = 1;
+
+ return TEE_SUCCESS;
+}
+
+static TEE_Result init_tree_from_data(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ struct tee_fs_htree_node_image node_image;
+ struct htree_node *node;
+ struct htree_node *nc;
+ size_t committed_version;
+ size_t node_id = 2;
+
+ while (node_id <= ht->imeta.max_node_id) {
+ node = find_node(ht, node_id >> 1);
+ if (!node)
+ return TEE_ERROR_GENERIC;
+ committed_version = !!(node->node.flags &
+ HTREE_NODE_COMMITTED_CHILD(node_id & 1));
+
+ res = rpc_read_node(ht, node_id, committed_version,
+ &node_image);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ res = get_node(ht, true, node_id, &nc);
+ if (res != TEE_SUCCESS)
+ return res;
+ nc->node = node_image;
+ node_id++;
+ }
+
+ return TEE_SUCCESS;
+}
+
+static TEE_Result calc_node_hash(struct htree_node *node, void *ctx,
+ uint8_t *digest)
+{
+ TEE_Result res;
+ uint32_t alg = TEE_FS_HTREE_HASH_ALG;
+ uint8_t *ndata = (uint8_t *)&node->node + sizeof(node->node.hash);
+ size_t nsize = sizeof(node->node) - sizeof(node->node.hash);
+
+ res = crypto_ops.hash.init(ctx, alg);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ res = crypto_ops.hash.update(ctx, alg, ndata, nsize);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ if (node->child[0]) {
+ res = crypto_ops.hash.update(ctx, alg,
+ node->child[0]->node.hash,
+ sizeof(node->child[0]->node.hash));
+ if (res != TEE_SUCCESS)
+ return res;
+ }
+
+ if (node->child[1]) {
+ res = crypto_ops.hash.update(ctx, alg,
+ node->child[1]->node.hash,
+ sizeof(node->child[1]->node.hash));
+ if (res != TEE_SUCCESS)
+ return res;
+ }
+
+ return crypto_ops.hash.final(ctx, alg, digest, TEE_FS_HTREE_HASH_SIZE);
+}
+
+static TEE_Result authenc_init(void **ctx_ret, TEE_OperationMode mode,
+ struct tee_fs_htree *ht,
+ struct tee_fs_htree_node_image *ni,
+ size_t payload_len)
+{
+ TEE_Result res = TEE_SUCCESS;
+ const uint32_t alg = TEE_FS_HTREE_AUTH_ENC_ALG;
+ uint8_t *ctx;
+ size_t ctx_size;
+ size_t aad_len = TEE_FS_HTREE_FEK_SIZE + TEE_FS_HTREE_IV_SIZE;
+ uint8_t *iv;
+
+ if (ni) {
+ iv = ni->iv;
+ } else {
+ iv = ht->head.iv;
+ aad_len += TEE_FS_HTREE_HASH_SIZE + sizeof(ht->head.counter);
+ }
+
+ if (mode == TEE_MODE_ENCRYPT) {
+ res = crypto_ops.prng.read(iv, TEE_FS_HTREE_IV_SIZE);
+ if (res != TEE_SUCCESS)
+ return res;
+ }
+
+ res = crypto_ops.authenc.get_ctx_size(alg, &ctx_size);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ ctx = malloc(ctx_size);
+ if (!ctx) {
+ EMSG("request memory size %zu failed", ctx_size);
+ return TEE_ERROR_OUT_OF_MEMORY;
+ }
+
+ res = crypto_ops.authenc.init(ctx, alg, mode,
+ ht->fek, TEE_FS_HTREE_FEK_SIZE,
+ iv, TEE_FS_HTREE_IV_SIZE,
+ TEE_FS_HTREE_TAG_SIZE, aad_len,
+ payload_len);
+ if (res != TEE_SUCCESS)
+ goto exit;
+
+ if (!ni) {
+ res = crypto_ops.authenc.update_aad(ctx, alg, mode,
+ ht->root.node.hash,
+ TEE_FS_HTREE_FEK_SIZE);
+ if (res != TEE_SUCCESS)
+ goto exit;
+
+ res = crypto_ops.authenc.update_aad(ctx, alg, mode,
+ (void *)&ht->head.counter,
+ sizeof(ht->head.counter));
+ if (res != TEE_SUCCESS)
+ goto exit;
+ }
+
+ res = crypto_ops.authenc.update_aad(ctx, alg, mode, ht->head.enc_fek,
+ TEE_FS_HTREE_FEK_SIZE);
+ if (res != TEE_SUCCESS)
+ goto exit;
+
+ res = crypto_ops.authenc.update_aad(ctx, alg, mode, iv,
+ TEE_FS_HTREE_IV_SIZE);
+
+exit:
+ if (res == TEE_SUCCESS)
+ *ctx_ret = ctx;
+ else
+ free(ctx);
+
+ return res;
+}
+
+static TEE_Result authenc_decrypt_final(void *ctx, const uint8_t *tag,
+ const void *crypt, size_t len,
+ void *plain)
+{
+ TEE_Result res;
+ size_t out_size = len;
+
+ res = crypto_ops.authenc.dec_final(ctx, TEE_FS_HTREE_AUTH_ENC_ALG,
+ crypt, len, plain, &out_size,
+ tag, TEE_FS_HTREE_TAG_SIZE);
+ crypto_ops.authenc.final(ctx, TEE_FS_HTREE_AUTH_ENC_ALG);
+ free(ctx);
+
+ if (res == TEE_SUCCESS && out_size != len)
+ return TEE_ERROR_GENERIC;
+ if (res == TEE_ERROR_MAC_INVALID)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ return res;
+}
+
+static TEE_Result authenc_encrypt_final(void *ctx, uint8_t *tag,
+ const void *plain, size_t len,
+ void *crypt)
+{
+ TEE_Result res;
+ size_t out_size = len;
+ size_t out_tag_size = TEE_FS_HTREE_TAG_SIZE;
+
+ res = crypto_ops.authenc.enc_final(ctx, TEE_FS_HTREE_AUTH_ENC_ALG,
+ plain, len, crypt, &out_size,
+ tag, &out_tag_size);
+ crypto_ops.authenc.final(ctx, TEE_FS_HTREE_AUTH_ENC_ALG);
+ free(ctx);
+
+ if (res == TEE_SUCCESS &&
+ (out_size != len || out_tag_size != TEE_FS_HTREE_TAG_SIZE))
+ return TEE_ERROR_GENERIC;
+
+ return res;
+}
+
+static TEE_Result verify_root(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ void *ctx;
+
+ res = tee_fs_fek_crypt(TEE_MODE_DECRYPT, ht->head.enc_fek,
+ sizeof(ht->fek), ht->fek);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ res = authenc_init(&ctx, TEE_MODE_DECRYPT, ht, NULL, sizeof(ht->imeta));
+ if (res != TEE_SUCCESS)
+ return res;
+
+ return authenc_decrypt_final(ctx, ht->head.tag, ht->head.imeta,
+ sizeof(ht->imeta), &ht->imeta);
+}
+
+static TEE_Result verify_node(struct traverse_arg *targ,
+ struct htree_node *node)
+{
+ void *ctx = targ->arg;
+ TEE_Result res;
+ uint8_t digest[TEE_FS_HTREE_HASH_SIZE];
+
+ res = calc_node_hash(node, ctx, digest);
+ if (res == TEE_SUCCESS &&
+ buf_compare_ct(digest, node->node.hash, sizeof(digest)))
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ return res;
+}
+
+static TEE_Result verify_tree(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ size_t size;
+ void *ctx;
+
+ if (!crypto_ops.hash.get_ctx_size || !crypto_ops.hash.init ||
+ !crypto_ops.hash.update || !crypto_ops.hash.final)
+ return TEE_ERROR_NOT_SUPPORTED;
+
+ res = crypto_ops.hash.get_ctx_size(TEE_FS_HTREE_HASH_ALG, &size);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ ctx = malloc(size);
+ if (!ctx)
+ return TEE_ERROR_OUT_OF_MEMORY;
+
+ res = htree_traverse_post_order(ht, verify_node, ctx);
+ free(ctx);
+
+ return res;
+}
+
+static TEE_Result init_root_node(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ size_t size;
+ void *ctx;
+
+ res = crypto_ops.hash.get_ctx_size(TEE_FS_HTREE_HASH_ALG, &size);
+ if (res != TEE_SUCCESS)
+ return res;
+ ctx = malloc(size);
+ if (!ctx)
+ return TEE_ERROR_OUT_OF_MEMORY;
+
+ ht->root.id = 1;
+
+ res = calc_node_hash(&ht->root, ctx, ht->root.node.hash);
+ free(ctx);
+
+ return res;
+}
+
+TEE_Result tee_fs_htree_open(bool create,
+ const struct tee_fs_htree_storage *stor,
+ void *stor_aux, struct tee_fs_htree **ht_ret)
+{
+ TEE_Result res;
+ struct tee_fs_htree *ht = calloc(1, sizeof(*ht));
+
+ if (!ht)
+ return TEE_ERROR_OUT_OF_MEMORY;
+
+ ht->stor = stor;
+ ht->stor_aux = stor_aux;
+
+ if (create) {
+ const struct tee_fs_htree_image dummy_head = { .counter = 0 };
+
+ res = crypto_ops.prng.read(ht->fek, sizeof(ht->fek));
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = tee_fs_fek_crypt(TEE_MODE_ENCRYPT, ht->fek,
+ sizeof(ht->fek), ht->head.enc_fek);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = init_root_node(ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ ht->dirty = true;
+ res = tee_fs_htree_sync_to_storage(&ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+ res = rpc_write_head(ht, 0, &dummy_head);
+ } else {
+ res = init_head_from_data(ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = verify_root(ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = init_tree_from_data(ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = verify_tree(ht);
+ }
+out:
+ if (res == TEE_SUCCESS)
+ *ht_ret = ht;
+ else
+ tee_fs_htree_close(&ht);
+ return res;
+}
+
+struct tee_fs_htree_meta *tee_fs_htree_get_meta(struct tee_fs_htree *ht)
+{
+ return &ht->imeta.meta;
+}
+
+static TEE_Result free_node(struct traverse_arg *targ __unused,
+ struct htree_node *node)
+{
+ if (node->parent)
+ free(node);
+ return TEE_SUCCESS;
+}
+
+void tee_fs_htree_close(struct tee_fs_htree **ht)
+{
+ if (!*ht)
+ return;
+ htree_traverse_post_order(*ht, free_node, NULL);
+ free(*ht);
+ *ht = NULL;
+}
+
+static TEE_Result htree_sync_node_to_storage(struct traverse_arg *targ,
+ struct htree_node *node)
+{
+ TEE_Result res;
+ uint8_t vers;
+
+ /*
+ * The node can be dirty while the block isn't updated due to
+ * updated children, but if block is updated the node has to be
+ * dirty.
+ */
+ assert(node->dirty >= node->block_updated);
+
+ if (!node->dirty)
+ return TEE_SUCCESS;
+
+ if (node->parent) {
+ uint32_t f = HTREE_NODE_COMMITTED_CHILD(node->id & 1);
+
+ node->parent->dirty = true;
+ node->parent->node.flags ^= f;
+ vers = !!(node->parent->node.flags & f);
+ } else {
+ /*
+ * Counter isn't updated yet, it's increased just before
+ * writing the header.
+ */
+ vers = !(targ->ht->head.counter & 1);
+ }
+
+ res = calc_node_hash(node, targ->arg, node->node.hash);
+ if (res != TEE_SUCCESS)
+ return res;
+
+ node->dirty = false;
+ node->block_updated = false;
+
+ return rpc_write_node(targ->ht, node->id, vers, &node->node);
+}
+
+static TEE_Result update_root(struct tee_fs_htree *ht)
+{
+ TEE_Result res;
+ void *ctx;
+
+ ht->head.counter++;
+
+ res = authenc_init(&ctx, TEE_MODE_ENCRYPT, ht, NULL, sizeof(ht->imeta));
+ if (res != TEE_SUCCESS)
+ return res;
+
+ return authenc_encrypt_final(ctx, ht->head.tag, &ht->imeta,
+ sizeof(ht->imeta), &ht->head.imeta);
+}
+
+TEE_Result tee_fs_htree_sync_to_storage(struct tee_fs_htree **ht_arg)
+{
+ TEE_Result res;
+ struct tee_fs_htree *ht = *ht_arg;
+ size_t size;
+ void *ctx;
+
+ if (!ht)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ if (!ht->dirty)
+ return TEE_SUCCESS;
+
+ res = crypto_ops.hash.get_ctx_size(TEE_FS_HTREE_HASH_ALG, &size);
+ if (res != TEE_SUCCESS)
+ return res;
+ ctx = malloc(size);
+ if (!ctx)
+ return TEE_ERROR_OUT_OF_MEMORY;
+
+ res = htree_traverse_post_order(ht, htree_sync_node_to_storage, ctx);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ /* All the nodes are written to storage now. Time to update root. */
+ res = update_root(ht);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = rpc_write_head(ht, ht->head.counter & 1, &ht->head);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ ht->dirty = false;
+out:
+ free(ctx);
+ if (res != TEE_SUCCESS)
+ tee_fs_htree_close(ht_arg);
+ return res;
+}
+
+static TEE_Result get_block_node(struct tee_fs_htree *ht, bool create,
+ size_t block_num, struct htree_node **node)
+{
+ TEE_Result res;
+ struct htree_node *nd;
+
+ res = get_node(ht, create, BLOCK_NUM_TO_NODE_ID(block_num), &nd);
+ if (res == TEE_SUCCESS)
+ *node = nd;
+
+ return res;
+}
+
+TEE_Result tee_fs_htree_write_block(struct tee_fs_htree **ht_arg,
+ size_t block_num, const void *block)
+{
+ struct tee_fs_htree *ht = *ht_arg;
+ TEE_Result res;
+ struct tee_fs_rpc_operation op;
+ struct htree_node *node = NULL;
+ uint8_t block_vers;
+ void *ctx;
+ void *enc_block;
+
+ if (!ht)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ res = get_block_node(ht, true, block_num, &node);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ if (!node->block_updated)
+ node->node.flags ^= HTREE_NODE_COMMITTED_BLOCK;
+
+ block_vers = !!(node->node.flags & HTREE_NODE_COMMITTED_BLOCK);
+ res = ht->stor->rpc_write_init(ht->stor_aux, &op,
+ TEE_FS_HTREE_TYPE_BLOCK, block_num,
+ block_vers, &enc_block);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = authenc_init(&ctx, TEE_MODE_ENCRYPT, ht, &node->node,
+ ht->stor->block_size);
+ if (res != TEE_SUCCESS)
+ goto out;
+ res = authenc_encrypt_final(ctx, node->node.tag, block,
+ ht->stor->block_size, enc_block);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = ht->stor->rpc_write_final(&op);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ node->block_updated = true;
+ node->dirty = true;
+ ht->dirty = true;
+out:
+ if (res != TEE_SUCCESS)
+ tee_fs_htree_close(ht_arg);
+ return res;
+}
+
+TEE_Result tee_fs_htree_read_block(struct tee_fs_htree **ht_arg,
+ size_t block_num, void *block)
+{
+ struct tee_fs_htree *ht = *ht_arg;
+ TEE_Result res;
+ struct tee_fs_rpc_operation op;
+ struct htree_node *node;
+ uint8_t block_vers;
+ size_t len;
+ void *ctx;
+ void *enc_block;
+
+ if (!ht)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ res = get_block_node(ht, false, block_num, &node);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ block_vers = !!(node->node.flags & HTREE_NODE_COMMITTED_BLOCK);
+ res = ht->stor->rpc_read_init(ht->stor_aux, &op,
+ TEE_FS_HTREE_TYPE_BLOCK, block_num,
+ block_vers, &enc_block);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = ht->stor->rpc_read_final(&op, &len);
+ if (res != TEE_SUCCESS)
+ goto out;
+ if (len != ht->stor->block_size) {
+ res = TEE_ERROR_CORRUPT_OBJECT;
+ goto out;
+ }
+
+ res = authenc_init(&ctx, TEE_MODE_DECRYPT, ht, &node->node,
+ ht->stor->block_size);
+ if (res != TEE_SUCCESS)
+ goto out;
+
+ res = authenc_decrypt_final(ctx, node->node.tag, enc_block,
+ ht->stor->block_size, block);
+out:
+ if (res != TEE_SUCCESS)
+ tee_fs_htree_close(ht_arg);
+ return res;
+}
+
+TEE_Result tee_fs_htree_truncate(struct tee_fs_htree **ht_arg, size_t block_num)
+{
+ struct tee_fs_htree *ht = *ht_arg;
+ size_t node_id = BLOCK_NUM_TO_NODE_ID(block_num);
+ struct htree_node *node;
+
+ if (!ht)
+ return TEE_ERROR_CORRUPT_OBJECT;
+
+ while (node_id < ht->imeta.max_node_id) {
+ node = find_closest_node(ht, ht->imeta.max_node_id);
+ assert(node && node->id == ht->imeta.max_node_id);
+ assert(!node->child[0] && !node->child[1]);
+ assert(node->parent);
+ assert(node->parent->child[node->id & 1] == node);
+ node->parent->child[node->id & 1] = NULL;
+ free(node);
+ ht->imeta.max_node_id--;
+ ht->dirty = true;
+ }
+
+ return TEE_SUCCESS;
+}