diff --git a/core/include/tee/fs_dirfile.h b/core/include/tee/fs_dirfile.h index bd066d66a2d..f216d558c2d 100644 --- a/core/include/tee/fs_dirfile.h +++ b/core/include/tee/fs_dirfile.h @@ -56,7 +56,7 @@ struct tee_fs_dirfile_fileh { * @commit_writes: commits changes since the file was opened */ struct tee_fs_dirfile_operations { - TEE_Result (*open)(bool create, const TEE_UUID *uuid, + TEE_Result (*open)(bool create, uint8_t *hash, const TEE_UUID *uuid, struct tee_fs_dirfile_fileh *dfh, struct tee_file_handle **fh); void (*close)(struct tee_file_handle *fh); @@ -69,10 +69,14 @@ struct tee_fs_dirfile_operations { /** * tee_fs_dirfile_open() - opens a dirfile handle + * @create: true if a new dirfile is to be created, else the dirfile + * is read opened and verified + * @hash: hash of underlying file * @fops: file interface * @dirh: returned dirfile handle */ -TEE_Result tee_fs_dirfile_open(const struct tee_fs_dirfile_operations *fops, +TEE_Result tee_fs_dirfile_open(bool create, uint8_t *hash, + const struct tee_fs_dirfile_operations *fops, struct tee_fs_dirfile_dirh **dirh); /** * tee_fs_dirfile_close() - closes a dirfile handle @@ -86,8 +90,10 @@ void tee_fs_dirfile_close(struct tee_fs_dirfile_dirh *dirh); /** * tee_fs_dirfile_commit_writes() - commit updates of dirfile * @dirh: dirfile handle + * @hash: hash of underlying file is copied here if not NULL */ -TEE_Result tee_fs_dirfile_commit_writes(struct tee_fs_dirfile_dirh *dirh); +TEE_Result tee_fs_dirfile_commit_writes(struct tee_fs_dirfile_dirh *dirh, + uint8_t *hash); /** * tee_fs_dirfile_get_tmp() - get a temporary file handle diff --git a/core/include/tee/tee_fs.h b/core/include/tee/tee_fs.h index 3c982671e83..2a9ae60d6a5 100644 --- a/core/include/tee/tee_fs.h +++ b/core/include/tee/tee_fs.h @@ -77,6 +77,9 @@ extern const struct tee_file_operations ree_fs_ops; #endif #ifdef CFG_RPMB_FS extern const struct tee_file_operations rpmb_fs_ops; + +TEE_Result tee_rpmb_fs_raw_open(const char *fname, bool create, + struct tee_file_handle **fh); #endif #endif /*TEE_FS_H*/ diff --git a/core/tee/fs_dirfile.c b/core/tee/fs_dirfile.c index 752875e5dbb..92b729a16dd 100644 --- a/core/tee/fs_dirfile.c +++ b/core/tee/fs_dirfile.c @@ -129,7 +129,8 @@ static TEE_Result write_dent(struct tee_fs_dirfile_dirh *dirh, size_t n, return res; } -TEE_Result tee_fs_dirfile_open(const struct tee_fs_dirfile_operations *fops, +TEE_Result tee_fs_dirfile_open(bool create, uint8_t *hash, + const struct tee_fs_dirfile_operations *fops, struct tee_fs_dirfile_dirh **dirh_ret) { TEE_Result res; @@ -140,12 +141,9 @@ TEE_Result tee_fs_dirfile_open(const struct tee_fs_dirfile_operations *fops, return TEE_ERROR_OUT_OF_MEMORY; dirh->fops = fops; - res = fops->open(false, NULL, NULL, &dirh->fh); - if (res) { - res = fops->open(true, NULL, NULL, &dirh->fh); - if (res) - goto out; - } + res = fops->open(create, hash, NULL, NULL, &dirh->fh); + if (res) + goto out; for (n = 0;; n++) { struct dirfile_entry dent; @@ -193,9 +191,10 @@ void tee_fs_dirfile_close(struct tee_fs_dirfile_dirh *dirh) } } -TEE_Result tee_fs_dirfile_commit_writes(struct tee_fs_dirfile_dirh *dirh) +TEE_Result tee_fs_dirfile_commit_writes(struct tee_fs_dirfile_dirh *dirh, + uint8_t *hash) { - return dirh->fops->commit_writes(dirh->fh, NULL); + return dirh->fops->commit_writes(dirh->fh, hash); } TEE_Result tee_fs_dirfile_get_tmp(struct tee_fs_dirfile_dirh *dirh, diff --git a/core/tee/tee_ree_fs.c b/core/tee/tee_ree_fs.c index 275ba107a0d..9ad3dbf7ed0 100644 --- a/core/tee/tee_ree_fs.c +++ b/core/tee/tee_ree_fs.c @@ -381,13 +381,13 @@ static TEE_Result ree_fs_write_primitive(struct tee_file_handle *fh, size_t pos, return out_of_place_write(fdp, pos, buf, len); } -static TEE_Result ree_fs_open_primitive(bool create, const TEE_UUID *uuid, +static TEE_Result ree_fs_open_primitive(bool create, uint8_t *hash, + const TEE_UUID *uuid, struct tee_fs_dirfile_fileh *dfh, struct tee_file_handle **fh) { TEE_Result res; struct tee_fs_fd *fdp; - uint8_t *hash = NULL; fdp = calloc(1, sizeof(struct tee_fs_fd)); if (!fdp) @@ -404,8 +404,6 @@ static TEE_Result ree_fs_open_primitive(bool create, const TEE_UUID *uuid, if (res != TEE_SUCCESS) goto out; - if (dfh) - hash = dfh->hash; res = tee_fs_htree_open(create, hash, uuid, &ree_fs_storage_ops, fdp, &fdp->ht); out: @@ -462,39 +460,110 @@ static const struct tee_fs_dirfile_operations ree_dirf_ops = { static struct tee_fs_dirfile_dirh *ree_fs_dirh; static size_t ree_fs_dirh_refcount; -static TEE_Result get_dirh(struct tee_fs_dirfile_dirh **dirh) +#ifdef CFG_RPMB_FS +static struct tee_file_handle *ree_fs_rpmb_fh; + +static TEE_Result open_dirh(struct tee_fs_dirfile_dirh **dirh) { - if (!ree_fs_dirh_refcount) { - TEE_Result res; + TEE_Result res; + uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; + uint8_t *hashp = NULL; + const char fname[] = "dirfile.db.hash"; - assert(!ree_fs_dirh); - res = tee_fs_dirfile_open(&ree_dirf_ops, &ree_fs_dirh); + res = tee_rpmb_fs_raw_open(fname, false, &ree_fs_rpmb_fh); + if (!res) { + size_t l = sizeof(hash); + + res = rpmb_fs_ops.read(ree_fs_rpmb_fh, 0, hash, &l); if (res) return res; + if (l == sizeof(hash)) + hashp = hash; + } else if (res == TEE_ERROR_ITEM_NOT_FOUND) { + res = tee_rpmb_fs_raw_open(fname, true, &ree_fs_rpmb_fh); + } + if (res) + return res; + + if (!tee_fs_dirfile_open(false, hashp, &ree_dirf_ops, dirh)) + return TEE_SUCCESS; + + res = tee_fs_dirfile_open(true, NULL, &ree_dirf_ops, dirh); + if (res) + rpmb_fs_ops.close(&ree_fs_rpmb_fh); + return res; +} + +static TEE_Result commit_dirh_writes(struct tee_fs_dirfile_dirh *dirh) +{ + TEE_Result res; + uint8_t hash[TEE_FS_HTREE_HASH_SIZE]; + + res = tee_fs_dirfile_commit_writes(dirh, hash); + if (res) + return res; + return rpmb_fs_ops.write(ree_fs_rpmb_fh, 0, hash, sizeof(hash)); +} + +static void close_dirh(struct tee_fs_dirfile_dirh **dirh) +{ + tee_fs_dirfile_close(*dirh); + *dirh = NULL; + rpmb_fs_ops.close(&ree_fs_rpmb_fh); +} + +#else /*!CFG_RPMB_FS*/ +static TEE_Result open_dirh(struct tee_fs_dirfile_dirh **dirh) +{ + if (!tee_fs_dirfile_open(false, NULL, &ree_dirf_ops, dirh)) + return TEE_SUCCESS; + return tee_fs_dirfile_open(true, NULL, &ree_dirf_ops, dirh); +} + +static TEE_Result commit_dirh_writes(struct tee_fs_dirfile_dirh *dirh) +{ + return tee_fs_dirfile_commit_writes(dirh, NULL); +} + +static void close_dirh(struct tee_fs_dirfile_dirh **dirh) +{ + tee_fs_dirfile_close(*dirh); + *dirh = NULL; +} +#endif /*!CFG_RPMB_FS*/ + +static TEE_Result get_dirh(struct tee_fs_dirfile_dirh **dirh) +{ + if (!ree_fs_dirh) { + TEE_Result res = open_dirh(&ree_fs_dirh); + + if (res) { + *dirh = NULL; + return res; + } } - assert(ree_fs_dirh); ree_fs_dirh_refcount++; + assert(ree_fs_dirh); + assert(ree_fs_dirh_refcount); *dirh = ree_fs_dirh; return TEE_SUCCESS; } -static void put_dirh_primitive(void) +static void put_dirh_primitive(bool close) { assert(ree_fs_dirh_refcount); assert(ree_fs_dirh); ree_fs_dirh_refcount--; - if (!ree_fs_dirh_refcount) { - tee_fs_dirfile_close(ree_fs_dirh); - ree_fs_dirh = NULL; - } + if (!ree_fs_dirh_refcount || close) + close_dirh(&ree_fs_dirh); } -static void put_dirh(struct tee_fs_dirfile_dirh *dirh) +static void put_dirh(struct tee_fs_dirfile_dirh *dirh, bool close) { if (dirh) { assert(dirh == ree_fs_dirh); - put_dirh_primitive(); + put_dirh_primitive(close); } } @@ -516,7 +585,7 @@ static TEE_Result ree_fs_open(struct tee_pobj *po, size_t *size, if (res != TEE_SUCCESS) goto out; - res = ree_fs_open_primitive(false, &po->uuid, &dfh, fh); + res = ree_fs_open_primitive(false, dfh.hash, &po->uuid, &dfh, fh); if (res == TEE_ERROR_ITEM_NOT_FOUND) { /* * If the object isn't found someone has tampered with it, @@ -531,7 +600,7 @@ static TEE_Result ree_fs_open(struct tee_pobj *po, size_t *size, out: if (res) - put_dirh(dirh); + put_dirh(dirh, false); mutex_unlock(&ree_fs_mutex); return res; @@ -564,7 +633,7 @@ static TEE_Result set_name(struct tee_fs_dirfile_dirh *dirh, if (res) return res; - res = tee_fs_dirfile_commit_writes(dirh); + res = commit_dirh_writes(dirh); if (res) return res; @@ -578,7 +647,7 @@ static void ree_fs_close(struct tee_file_handle **fh) { if (*fh) { mutex_lock(&ree_fs_mutex); - put_dirh_primitive(); + put_dirh_primitive(false); mutex_unlock(&ree_fs_mutex); ree_fs_close_primitive(*fh); @@ -609,7 +678,7 @@ static TEE_Result ree_fs_create(struct tee_pobj *po, bool overwrite, if (res) goto out; - res = ree_fs_open_primitive(true, &po->uuid, &dfh, fh); + res = ree_fs_open_primitive(true, dfh.hash, &po->uuid, &dfh, fh); if (res) goto out; @@ -641,7 +710,7 @@ static TEE_Result ree_fs_create(struct tee_pobj *po, bool overwrite, res = set_name(dirh, fdp, po, overwrite); out: if (res) { - put_dirh(dirh); + put_dirh(dirh, true); if (*fh) { ree_fs_close_primitive(*fh); *fh = NULL; @@ -677,9 +746,9 @@ static TEE_Result ree_fs_write(struct tee_file_handle *fh, size_t pos, res = tee_fs_dirfile_update_hash(dirh, &fdp->dfh); if (res) goto out; - res = tee_fs_dirfile_commit_writes(dirh); + res = commit_dirh_writes(dirh); out: - put_dirh(dirh); + put_dirh(dirh, res); mutex_unlock(&ree_fs_mutex); return res; @@ -724,7 +793,7 @@ static TEE_Result ree_fs_rename(struct tee_pobj *old, struct tee_pobj *new, goto out; } - res = tee_fs_dirfile_commit_writes(dirh); + res = commit_dirh_writes(dirh); if (res) goto out; @@ -732,7 +801,7 @@ static TEE_Result ree_fs_rename(struct tee_pobj *old, struct tee_pobj *new, tee_fs_rpc_remove_dfh(OPTEE_MSG_RPC_CMD_FS, &remove_dfh); out: - put_dirh(dirh); + put_dirh(dirh, res); mutex_unlock(&ree_fs_mutex); return res; @@ -759,7 +828,7 @@ static TEE_Result ree_fs_remove(struct tee_pobj *po) if (res) goto out; - res = tee_fs_dirfile_commit_writes(dirh); + res = commit_dirh_writes(dirh); if (res) goto out; @@ -768,7 +837,7 @@ static TEE_Result ree_fs_remove(struct tee_pobj *po) assert(tee_fs_dirfile_find(dirh, &po->uuid, po->obj_id, po->obj_id_len, &dfh)); out: - put_dirh(dirh); + put_dirh(dirh, res); mutex_unlock(&ree_fs_mutex); return res; @@ -797,7 +866,7 @@ static TEE_Result ree_fs_truncate(struct tee_file_handle *fh, size_t len) res = tee_fs_dirfile_update_hash(dirh, &fdp->dfh); out: - put_dirh(dirh); + put_dirh(dirh, res); mutex_unlock(&ree_fs_mutex); return res; @@ -833,7 +902,7 @@ static TEE_Result ree_fs_opendir_rpc(const TEE_UUID *uuid, *dir = d; } else { if (d) - put_dirh(d->dirh); + put_dirh(d->dirh, false); free(d); } mutex_unlock(&ree_fs_mutex); @@ -846,7 +915,7 @@ static void ree_fs_closedir_rpc(struct tee_fs_dir *d) if (d) { mutex_lock(&ree_fs_mutex); - put_dirh(d->dirh); + put_dirh(d->dirh, false); free(d); mutex_unlock(&ree_fs_mutex); diff --git a/core/tee/tee_rpmb_fs.c b/core/tee/tee_rpmb_fs.c index 8dd2d3e750c..d27ace60d80 100644 --- a/core/tee/tee_rpmb_fs.c +++ b/core/tee/tee_rpmb_fs.c @@ -1941,20 +1941,13 @@ static TEE_Result generate_fek(struct rpmb_fat_entry *fe, const TEE_UUID *uuid) return res; } -static TEE_Result rpmb_fs_open_internal(struct tee_pobj *po, bool create, - struct tee_file_handle **ret_fh) +static TEE_Result rpmb_fs_open_internal(struct rpmb_file_handle *fh, + const TEE_UUID *uuid, bool create) { - struct rpmb_file_handle *fh = NULL; tee_mm_pool_t p; bool pool_result; TEE_Result res = TEE_ERROR_GENERIC; - fh = alloc_file_handle(po, po->temporary); - if (!fh) { - res = TEE_ERROR_OUT_OF_MEMORY; - goto out; - } - /* We need to do setup in order to make sure fs_par is filled in */ res = rpmb_fs_setup(); if (res != TEE_SUCCESS) @@ -1996,7 +1989,7 @@ static TEE_Result rpmb_fs_open_internal(struct tee_pobj *po, bool create, /* Start address and size are 0 */ fh->fat_entry.flags = FILE_IS_ACTIVE; - res = generate_fek(&fh->fat_entry, &po->uuid); + res = generate_fek(&fh->fat_entry, uuid); if (res != TEE_SUCCESS) goto out; DMSG("GENERATE FEK key: %p", @@ -2012,11 +2005,6 @@ static TEE_Result rpmb_fs_open_internal(struct tee_pobj *po, bool create, res = TEE_SUCCESS; out: - if (res == TEE_SUCCESS) - *ret_fh = (struct tee_file_handle *)fh; - else - free(fh); - return res; } @@ -2066,12 +2054,11 @@ static TEE_Result rpmb_fs_read(struct tee_file_handle *tfh, size_t pos, return res; } -static TEE_Result rpmb_fs_write_primitive(struct tee_file_handle *tfh, +static TEE_Result rpmb_fs_write_primitive(struct rpmb_file_handle *fh, size_t pos, const void *buf, size_t size) { TEE_Result res; - struct rpmb_file_handle *fh = (struct rpmb_file_handle *)tfh; tee_mm_pool_t p; bool pool_result = false; tee_mm_entry_t *mm; @@ -2174,44 +2161,41 @@ static TEE_Result rpmb_fs_write(struct tee_file_handle *tfh, size_t pos, TEE_Result res; mutex_lock(&rpmb_mutex); - res = rpmb_fs_write_primitive(tfh, pos, buf, size); + res = rpmb_fs_write_primitive((struct rpmb_file_handle *)tfh, pos, + buf, size); mutex_unlock(&rpmb_mutex); return res; } -static TEE_Result rpmb_fs_remove_internal(struct tee_pobj *po) +static TEE_Result rpmb_fs_remove_internal(struct rpmb_file_handle *fh) { - TEE_Result res = TEE_ERROR_GENERIC; - struct rpmb_file_handle *fh = NULL; - - fh = alloc_file_handle(po, po->temporary); - if (!fh) { - res = TEE_ERROR_OUT_OF_MEMORY; - goto out; - } + TEE_Result res; res = read_fat(fh, NULL); - if (res != TEE_SUCCESS) - goto out; + if (res) + return res; /* Clear this file entry. */ memset(&fh->fat_entry, 0, sizeof(struct rpmb_fat_entry)); - res = write_fat_entry(fh, false); - -out: - free(fh); - return res; + return write_fat_entry(fh, false); } static TEE_Result rpmb_fs_remove(struct tee_pobj *po) { TEE_Result res; + struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); + + if (!fh) + return TEE_ERROR_OUT_OF_MEMORY; mutex_lock(&rpmb_mutex); - res = rpmb_fs_remove_internal(po); + + res = rpmb_fs_remove_internal(fh); + mutex_unlock(&rpmb_mutex); + free(fh); return res; } @@ -2560,19 +2544,27 @@ static void rpmb_fs_closedir(struct tee_fs_dir *dir) } static TEE_Result rpmb_fs_open(struct tee_pobj *po, size_t *size, - struct tee_file_handle **fh) + struct tee_file_handle **ret_fh) { TEE_Result res; + struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); + + if (!fh) + return TEE_ERROR_OUT_OF_MEMORY; mutex_lock(&rpmb_mutex); - res = rpmb_fs_open_internal(po, false, fh); - if (!res && size) { - struct rpmb_file_handle *f = (struct rpmb_file_handle *)*fh; - *size = f->fat_entry.data_size; - } + res = rpmb_fs_open_internal(fh, &po->uuid, false); + if (!res && size) + *size = fh->fat_entry.data_size; + mutex_unlock(&rpmb_mutex); + if (res) + free(fh); + else + *ret_fh = (struct tee_file_handle *)fh; + return res; } @@ -2580,40 +2572,41 @@ static TEE_Result rpmb_fs_create(struct tee_pobj *po, bool overwrite, const void *head, size_t head_size, const void *attr, size_t attr_size, const void *data, size_t data_size, - struct tee_file_handle **fh) + struct tee_file_handle **ret_fh) { TEE_Result res; size_t pos = 0; + struct rpmb_file_handle *fh = alloc_file_handle(po, po->temporary); + + if (!fh) + return TEE_ERROR_OUT_OF_MEMORY; - *fh = NULL; mutex_lock(&rpmb_mutex); - res = rpmb_fs_open_internal(po, true, fh); + res = rpmb_fs_open_internal(fh, &po->uuid, true); if (res) goto out; if (head && head_size) { - res = rpmb_fs_write_primitive(*fh, pos, head, head_size); + res = rpmb_fs_write_primitive(fh, pos, head, head_size); if (res) goto out; pos += head_size; } if (attr && attr_size) { - res = rpmb_fs_write_primitive(*fh, pos, attr, attr_size); + res = rpmb_fs_write_primitive(fh, pos, attr, attr_size); if (res) goto out; pos += attr_size; } if (data && data_size) { - res = rpmb_fs_write_primitive(*fh, pos, data, data_size); + res = rpmb_fs_write_primitive(fh, pos, data, data_size); if (res) goto out; } if (po->temporary) { - struct rpmb_file_handle *f = (struct rpmb_file_handle *)*fh; - /* * If it's a temporary filename (which it normally is) * rename into the final filename now that the file is @@ -2626,14 +2619,17 @@ static TEE_Result rpmb_fs_create(struct tee_pobj *po, bool overwrite, goto out; } /* Update file handle after rename. */ - tee_svc_storage_create_filename(f->filename, - sizeof(f->filename), po, false); + tee_svc_storage_create_filename(fh->filename, + sizeof(fh->filename), + po, false); } out: - if (res && *fh) { - rpmb_fs_close(fh); - rpmb_fs_remove_internal(po); + if (res) { + rpmb_fs_remove_internal(fh); + free(fh); + } else { + *ret_fh = (struct tee_file_handle *)fh; } mutex_unlock(&rpmb_mutex); @@ -2653,3 +2649,32 @@ const struct tee_file_operations rpmb_fs_ops = { .closedir = rpmb_fs_closedir, .readdir = rpmb_fs_readdir, }; + +TEE_Result tee_rpmb_fs_raw_open(const char *fname, bool create, + struct tee_file_handle **ret_fh) +{ + TEE_Result res; + struct rpmb_file_handle *fh = calloc(1, sizeof(*fh)); + const TEE_UUID uuid = { 0 }; + + if (!fh) + return TEE_ERROR_OUT_OF_MEMORY; + + snprintf(fh->filename, sizeof(fh->filename), "/%s", fname); + + mutex_lock(&rpmb_mutex); + + res = rpmb_fs_open_internal(fh, &uuid, create); + + mutex_unlock(&rpmb_mutex); + + if (res) { + if (create) + rpmb_fs_remove_internal(fh); + free(fh); + } else { + *ret_fh = (struct tee_file_handle *)fh; + } + + return res; +} diff --git a/core/tee/tee_svc.c b/core/tee/tee_svc.c index 08fcd15b185..d064241bf16 100644 --- a/core/tee/tee_svc.c +++ b/core/tee/tee_svc.c @@ -103,7 +103,11 @@ static const uint32_t crypto_ecc_en; * 100: Antirollback enforced at REE level * 1000: Antirollback TEE-controlled hardware */ +#ifdef CFG_RPMB_FS +static const uint32_t ts_antiroll_prot_lvl = 1000; +#else static const uint32_t ts_antiroll_prot_lvl; +#endif /* Trusted OS implementation version */ static const char trustedos_impl_version[] = TO_STR(TEE_IMPL_VERSION); diff --git a/documentation/secure_storage.md b/documentation/secure_storage.md index 78893c2d624..1c84f86c550 100644 --- a/documentation/secure_storage.md +++ b/documentation/secure_storage.md @@ -74,8 +74,9 @@ Below is an excerpt from the specification listing the most vital requirements: Typically, an implementation may rely on the REE for that purpose (protection level 100) or on hardware assets controlled by the TEE (protection level 1000). - The current implementation does *not* provide any protection against - rollback, and therefore the protection level is set to 0. + If configured with CFG_RPMB_FS=y the protection against rollback is is + controlled by the TEE and is set to 1000. If CFG_RPMB_FS=n, there's no + protection against rollback, and the protection level is set to 0. ### TEE File Structure In Linux File System @@ -223,17 +224,6 @@ implementations in your platform code for: These implementations should fetch the key data from your SoC-specific e-fuses, or crypto unit according to the method defined by your SoC vendor. -## Future Work - -- **Rollback attack detection** - -An attacker can backup the whole `/data/tee` folder and restore it at later -time. - -The basic idea of detecting rollback attack is to store information -representing the state of `/data/tee/dirf.db` into another storage which has -anti-rollback capability such as the eMMC RPMB partition. - ## Reference * [Secure Storage Presentation](http://www.slideshare.net/linaroorg/sfo15503-secure-storage-in-optee) diff --git a/documentation/secure_storage_rpmb.md b/documentation/secure_storage_rpmb.md index 7ac2304a122..60f6305c8e5 100644 --- a/documentation/secure_storage_rpmb.md +++ b/documentation/secure_storage_rpmb.md @@ -132,6 +132,12 @@ CBC block encryption is used only for RPMB (the REE implementation uses GCM). The FAT is not encrypted. +## REE FS + +If configured with both CFG_REE_FS=y and CFG_RPMB_FS=y the REE FS will +create a special file, "dirfile.db.hash" in RPMB which hold a hash +representing the state of REE FS. + ## References - [1] _Embedded Multi-Media Card (e•MMC) Electrical Standard (5.1)_, JEDEC JESD84-B51, February 2015