commit | 572453e7ffa01897b39e43de9d0c6729d27a5f7e | [log] [tgz] |
---|---|---|
author | Stephen Crane <cranes@google.com> | Mon Nov 22 13:43:03 2021 -0800 |
committer | Stephen Crane <cranes@google.com> | Thu Jan 20 14:26:16 2022 -0800 |
tree | 6eea896a5bf080abbebcd6f1327e1eb8004a6483 | |
parent | cbe59bc679610c95ee3ff8661ba8cb9efde6e183 [diff] |
storage: Support switching between two superblocks Swaps the current superblock with a backup when the storage TA detects that the normal backing file location for a filesystem is inaccessible but an alternate data file is available. This indicates that the device is in an "alternate data" mode (i.e. a dynamic system update image is running). We store the current alternate data state in the superblock, which allows us to detect when the state changes and therefore when the superblock should be swapped with its backup. When we detect that the current superblock does not match the expected mode we either swap the current superblock roots with those in the backup slot, or we back up and clear the roots (depending on whether we detect that we need to clear the fs). This functionality is gated on the STORAGE_NS_ALTERNATE_SUPERBLOCK_ALLOWED build flag. This backup and restore allows the TD filesystem to be temporarily cleared, e.g. during a dynamic system update (DSU) using a Generic System Image (GSI). A DSU temporarily mounts a new userdata partition, thus clearing the TD filesystem. However, the DSU is temporary and when the device reverts back to its original installation and userdata partition, Trusty storage would previously detect this as a rollback if any writes occurred while in DSU mode. With this new functionality, storage will back up the superblock upon entering DSU mode and detecting a missing TD backing file. When DSU mode is turned off, storage will attempt to swap the backup (which corresponds to the original TD backing file) with the current superblock (which was for GSI) if it matches the expected backing file state. We back up rather than clear in this case because DSU allows rebooting into and out of the same GSI with the same userdata partition multiple times. Test: build.py generic-arm64-test-debug --test storage_test Bug: 203719297 Change-Id: I45137264bb1cdc17d92aefeb3f56864b6c028fb8
The secure storage service provides encrypted and tamper proof storage to secure apps. All operations that modify the file system state are transactional. Files can be opened, create or deleted by name (where the name is local to the app). Open files support read, write, get-size and set-size operations. There is currently no support for sparse files, permissions, quotas or directory operations.
The server provides three client ports that provide various minimum capabilities.
The STORAGE_CLIENT_TD_PORT port offers tamper and rollback detection once the non-secure OS has booted. This port should be used by most apps as it can offer more storage and better performance than the other choices.
The STORAGE_CLIENT_TDEA_PORT port also offers tamper and rollback detection but can be available before the non-secure OS has booted if the boot loader supports it.
The STORAGE_CLIENT_TP_PORT port offers tamper proof storage. An app can for instance use this port to store data needed by the boot loader that the non-secure OS cannot erase. Note that non-secure code can prevent read and write operations from succeeding, but it cannot modify on-disk data.
In the current code STORAGE_CLIENT_TDEA_PORT and STORAGE_CLIENT_TP_PORT map to the same file system. Apps should not create files with the same name using different ports, as it is device specific which ports share file systems.
The file system stores two super-blocks on a device that has tamper detection. The rest of the data can be stored in a non-secure partition or file. The default implementation has two file systems. One file system stores the super-blocks at the start of the mmc rpmb partition and all other blocks in a file in the file system of the non-secure OS. The other file system stores all data in the rpmb partition.
Both file systems use the same basic storage format but are configured to use different block, block number and mac sizes. Two super blocks are used to allow devices that don't provide atomic block write operations. Two version bits are used to identify the most recent super block version. The main purpose of the super block is to locate and validate the root of the free and file B+ trees.
Every block in the file system starts with a 16 byte iv struct. Each time the data in the block changes, this is assigned a new random value. The rest of the block is encrypted using this value and a device specific key.
The free set B+ tree list every free block in the file system as a set of ranges. The key value in the B+ tree is the start of a free range and the data value is the first block number not in the free range. Overlapping or adjacent ranges are not allowed, so the data value is also the start of an allocated range or, for the last free range, the number of blocks in the file system.
The file tree stores all the files in the file system. The key value is a hash of the file name and is the same size and the block number size for the file system. The data value is a block-mac that points to a file-entry block. The file-entry block stores the full file name, file size and the root of a block map where the file data can be found.
The block map tree is similar to the file tree except the key is the file block plus one (0 keys are not supported), and the data points to file data.
The super block always points to a valid file system. To make changes to the file system, any block that needs to change is copied to a new location. These changes are tracked by in-memory transaction objects. Before a transaction is complete it uses three block sets (the same storage format as the free set described above) to keep track of allocated and freed blocks. Blocks that will not be needed after the transaction completes are stored in the tmp_allocated set. Blocks that will be needed are stored in allocated, and blocks that should be added to the free set are stored in freed. To allow concurrent transactions to update independent files without conflict, files modified by a transaction are stored in temporary trees until the transaction is complete.
Example file system states of a file system using a 2k block size, 64 bit block numbers and 16 byte mac values:
Empty file system example.
Active Transaction state after creating a new file and writing one byte to it. Note that at this point all the added nodes may only be in the block cache.
State after completing transaction. The second super block is now the most recent one. The first super block still points to a valid file system, but as soon as the next transaction starts, those blocks may be reused. Block 3072 and above may not be on disk and their cache entries are invalid.