diff options
author | Mattias Nissler <mnissler@google.com> | 2016-06-08 15:04:29 +0200 |
---|---|---|
committer | Mattias Nissler <mnissler@google.com> | 2016-08-08 14:00:21 +0200 |
commit | 38173e3002fe487b5cb414a48fddb545a18f7a6c (patch) | |
tree | 71860d27d3b8d6909cf6f067620b0a8afc80b3b2 | |
parent | 84821232f805a4137eeee59c5739eb78bb8a7e9b (diff) | |
download | nvram-38173e3002fe487b5cb414a48fddb545a18f7a6c.tar.gz |
Update secure storage glue code to use RBMP FS.
This changes the NVRAM storage layer to store data in the RPMB file
system. This gives us good tamper-resistance behavior.
BUG: 29147036
Change-Id: I1011b0881186ac936d2981244f717bb1f6b9a6ae
-rw-r--r-- | secure_storage.cpp | 167 |
1 files changed, 91 insertions, 76 deletions
diff --git a/secure_storage.cpp b/secure_storage.cpp index ee744c1..8e70b9f 100644 --- a/secure_storage.cpp +++ b/secure_storage.cpp @@ -29,70 +29,95 @@ extern "C" { #include <nvram/core/logger.h> // This file implements the NVRAM storage layer on top of Trusty's secure -// storage service. -// TODO: Make sure to use RPMB-backed storage here in order to get -// tamper-resistant semantics. +// storage service. It keeps header and space data in files stored in the RPMB +// file system. namespace nvram { namespace storage { namespace { -// Name of the storage object holding the header. +// Name of the file holding the header. const char kHeaderFileName[] = "header"; -// Pattern for space data storage object names. +// Pattern for space data file names. const char kSpaceDataFileNamePattern[] = "space_%08x"; -// Maximum size of objects we're willing to read and write. +// Maximum file size we're willing to read and write. const size_t kMaxFileSize = 2048; // Buffer size for formatting names. using NameBuffer = char[16]; -// Formats the storage object name for the given space index. +// Formats the storage files name for the given space index. bool FormatSpaceFileName(NameBuffer name, uint32_t index) { int ret = snprintf(name, sizeof(NameBuffer), kSpaceDataFileNamePattern, index); return ret >= 0 && ret < static_cast<int>(sizeof(NameBuffer)); }; -// Translates an Trusty error code returned by secure storage (as defined in -// lk/include/errno.h, mostly following POSIX) to the corresponding NVRAM -// storage status. Most error conditions are subsumed into the generic -// Status::kStorageError. More granular status codes can be added as needed. -Status ErrorToStatus(int error) { - switch (error) { - case 0: - return Status::kSuccess; - case -ENOENT: - return Status::kNotFound; - default: - break; - } - - return Status::kStorageError; -} - // An RAII wrapper for storage_session_t. class StorageSession { public: - StorageSession() { error_ = storage_open_session(&session_); } + StorageSession() { + // The Access-Controlled NVRAM HAL specification states that NVRAM spaces + // which are subject to persistent write lock must be prevented from being + // written until the next 'full device reset’, which is defined to be an + // event that 'clears device state including all user data'. In other words, + // a factory reset that wipes user data must clear NVRAM data as well, and + // vice versa, losing NVRAM data isn't acceptable unless all other device + // state gets wiped as well. + // + // Trusty's secure storage subsystem offers storage modes that either have + // tampering detection (tampering can be detected but not prevented, i.e. + // the non-secure OS can delete all data) or tamper-proof behavior (data is + // stored in RPMB, which can only be written by the secure OS). + // + // Given these storage modes, there are two ways to achieve the semantics + // required by the HAL spec: + // 1. Use tamper-evident storage. Upon discovering tampering, force a full + // device reset. Unfortunately, secure storage currently doesn't surface + // an indication of detected tampering, so it's currently impossible to + // distinguish the situation of first initialization (i.e. no data + // present) from detected tampering (i.e. full data deletion by + // non-secure OS). + // 2. Use tamper-proof storage. This requires further integration work with + // the bootloader / factory reset / recovery flow to make sure NVRAM + // data gets wiped during full device reset as mandated by the HAL + // specification. + // + // Either way, there's further work required to meet the HAL specification + // requirements. The code below passes STORAGE_CLIENT_TDEA_PORT to request + // tamper-evident semantics. Since we can't reliably detect tampering right + // now, this DOES NOT satisfy the specification requirements. However, going + // with tamper-evident behavior at least gives us the correct device reset + // behavior for now as long as we don't have bootloader / factory reset / + // recovery integration. + // + // TODO(mnissler): Revisit once tampering can be reliably detected and/or + // bootloader and recovery flows can handle wiping NVRAM state. + error_ = storage_open_session(&handle_, STORAGE_CLIENT_TDEA_PORT); + if (error_ != 0) { + NVRAM_LOG_ERR("Failed to open storage session: %d", error_); + } + } ~StorageSession() { if (error_ != 0) { return; } - storage_close_session(session_); + // Close the storage session. Note that this will discard any non-committed + // transactions. + storage_close_session(handle_); error_ = -EINVAL; } int error() const { return error_; } - storage_session_t session() { return session_; } + storage_session_t handle() { return handle_; } private: int error_ = -EINVAL; - storage_session_t session_ = 0; + storage_session_t handle_ = 0; }; // An RAII wrapper for file_handle_t. @@ -100,8 +125,9 @@ class FileHandle { public: FileHandle(const char* name, uint32_t flags) { if (session_.error() == 0) { - error_ = storage_open_file(session(), &handle_, const_cast<char*>(name), - flags); + error_ = storage_open_file(session_.handle(), &handle_, + const_cast<char*>(name), flags, + 0 /* don't commit */); } else { error_ = session_.error(); } @@ -111,12 +137,12 @@ class FileHandle { return; } - storage_close_file(session(), handle_); + storage_close_file(handle_); error_ = -EINVAL; } + StorageSession* session() { return &session_; } int error() const { return error_; } - storage_session_t session() { return session_.session(); } file_handle_t handle() { return handle_; } private: @@ -125,19 +151,24 @@ class FileHandle { file_handle_t handle_ = 0; }; -// Loads the storage object identified by |name|. +// Loads the file identified by |name|. Status LoadFile(const char* name, Blob* blob) { - FileHandle file(name, 0); - if (file.error()) { - NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error()); - return ErrorToStatus(file.error()); + FileHandle file(name, 0 /* no open flags, just read */); + switch (file.error()) { + case 0: + break; + case -ENOENT: + return Status::kNotFound; + default: + NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error()); + return Status::kStorageError; } storage_off_t size = 0; - int error = storage_get_file_size(file.session(), file.handle(), &size); + int error = storage_get_file_size(file.handle(), &size); if (error != 0) { NVRAM_LOG_ERR("Failed to get size for %s: %d" , name, error); - return ErrorToStatus(error); + return Status::kStorageError; } if (size > kMaxFileSize) { @@ -145,24 +176,15 @@ Status LoadFile(const char* name, Blob* blob) { return Status::kStorageError; } - // File creation and write are separate, i.e. the write path does not perform - // them as a single atomic operation. Hence there's a chance that an object - // has been created, but the write didn't go through. Just report the object - // as absent in this case. - if (size == 0) { - return Status::kNotFound; - } - if (!blob->Resize(size)) { NVRAM_LOG_ERR("Failed to allocate read buffer for %s" , name); return Status::kStorageError; } - int size_read = storage_read(file.session(), file.handle(), 0, blob->data(), - blob->size()); + int size_read = storage_read(file.handle(), 0, blob->data(), blob->size()); if (size_read < 0) { NVRAM_LOG_ERR("Failed to read %s: %d" , name, size_read); - return ErrorToStatus(size_read); + return Status::kStorageError; } if (static_cast<size_t>(size_read) != blob->size()) { @@ -174,25 +196,27 @@ Status LoadFile(const char* name, Blob* blob) { return Status::kSuccess; } -// Writes blob to the storage object indicated by |name|. +// Writes blob to the file indicated by |name|. Status StoreFile(const char* name, const Blob& blob) { if (blob.size() > kMaxFileSize) { - NVRAM_LOG_ERR("Bad object size for %s: %u" , name, blob.size()); + NVRAM_LOG_ERR("Bad file size for %s: %u" , name, blob.size()); return Status::kStorageError; } - FileHandle file(name, STORAGE_FILE_OPEN_CREATE); + // Note that it's OK to truncate on open, since the changes to the file will + // only become visible when the transaction gets committed. + FileHandle file(name, STORAGE_FILE_OPEN_CREATE | STORAGE_FILE_OPEN_TRUNCATE); if (file.error()) { NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error()); - return ErrorToStatus(file.error()); + return Status::kStorageError; } int size_written = - storage_write(file.session(), file.handle(), 0, - const_cast<uint8_t*>(blob.data()), blob.size()); + storage_write(file.handle(), 0, const_cast<uint8_t*>(blob.data()), + blob.size(), 0 /* don't commit */); if (size_written < 0) { NVRAM_LOG_ERR("Failed to write %s: %d" , name, size_written); - return ErrorToStatus(size_written); + return Status::kStorageError; } if (static_cast<size_t>(size_written) != blob.size()) { @@ -201,22 +225,12 @@ Status StoreFile(const char* name, const Blob& blob) { return Status::kStorageError; } - // Adjust the file size in case the data shrunk. Note that this is a separate - // operation, so may not go through if we crash.. However, the higher layers - // can deal with trailing data safely, so it is OK if we crash before the size - // update hits disk. - // - // Note that the alternative of opening the file with the truncate flag is not - // OK - this would result in the truncation getting committed to disk - // separately from the write, thus creating the risk of data loss when - // crashing after the truncate, but before the write. - // - // TODO(mnissler): Revisit this when switching to the RPMB file system, which - // is going to provide more powerful transactional semantics. - int error = storage_set_file_size(file.session(), file.handle(), blob.size()); + // File updated successful, so commit the storage transaction now. + int error = + storage_end_transaction(file.session()->handle(), true /* commit */); if (error) { - NVRAM_LOG_ERR("Failed to adjust file size for %s: %d\n", name, error); - // Return success below. + NVRAM_LOG_ERR("Failed to commit %s after write: %d", name, error); + return Status::kStorageError; } return Status::kSuccess; @@ -256,15 +270,16 @@ Status DeleteSpace(uint32_t index) { StorageSession session; if (session.error() != 0) { - return ErrorToStatus(session.error()); + return Status::kStorageError; } - int error = storage_delete_file(session.session(), name); - if (error == -ENOENT) { + int error = storage_delete_file(session.handle(), name, STORAGE_OP_COMPLETE); + if (error == 0 || error == -ENOENT) { return Status::kSuccess; } - return ErrorToStatus(error); + NVRAM_LOG_ERR("Failed to delete %s: %d", name, error); + return Status::kStorageError; } } // namespace strorage |