aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMattias Nissler <mnissler@google.com>2016-06-08 15:04:29 +0200
committerMattias Nissler <mnissler@google.com>2016-08-08 14:00:21 +0200
commit38173e3002fe487b5cb414a48fddb545a18f7a6c (patch)
tree71860d27d3b8d6909cf6f067620b0a8afc80b3b2
parent84821232f805a4137eeee59c5739eb78bb8a7e9b (diff)
downloadnvram-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.cpp167
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