aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMattias Nissler <mnissler@google.com>2016-08-08 14:00:14 +0200
committerMattias Nissler <mnissler@google.com>2016-08-08 14:00:14 +0200
commit2d956d8584bef62dabe7ea1b66434094f511246f (patch)
tree4c008eb7652123960e823c78ddef3076be1d582c
parente26b4baabffb0d06997cc49ec2e70d9fa9b4c911 (diff)
downloadnvram-2d956d8584bef62dabe7ea1b66434094f511246f.tar.gz
Implement NVRAM storage on top of secure storage.
This implements the storage layer required by the NVRAM implementation on top of secure storage, i.e. storing NVRAM objects in secure storage files. Note this implementation currently doesn't meet the tamper evidence requirements, but allows functional end-to-end testing of the NVRAM implementation. BUG: 27194378 Change-Id: Icf6587ab4fa2b2d58db8dd0c6c7ed95963ee6286
-rw-r--r--secure_storage.cpp271
1 files changed, 271 insertions, 0 deletions
diff --git a/secure_storage.cpp b/secure_storage.cpp
new file mode 100644
index 0000000..ee744c1
--- /dev/null
+++ b/secure_storage.cpp
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nvram/core/storage.h>
+
+extern "C" {
+
+#include <errno.h>
+#include <stdio.h>
+
+#include <interface/storage/storage.h>
+#include <lib/storage/storage.h>
+
+} // 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.
+
+namespace nvram {
+namespace storage {
+
+namespace {
+
+// Name of the storage object holding the header.
+const char kHeaderFileName[] = "header";
+
+// Pattern for space data storage object names.
+const char kSpaceDataFileNamePattern[] = "space_%08x";
+
+// Maximum size of objects 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.
+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() {
+ if (error_ != 0) {
+ return;
+ }
+
+ storage_close_session(session_);
+ error_ = -EINVAL;
+ }
+
+ int error() const { return error_; }
+ storage_session_t session() { return session_; }
+
+ private:
+ int error_ = -EINVAL;
+ storage_session_t session_ = 0;
+};
+
+// An RAII wrapper for file_handle_t.
+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);
+ } else {
+ error_ = session_.error();
+ }
+ }
+ ~FileHandle() {
+ if (error_ != 0) {
+ return;
+ }
+
+ storage_close_file(session(), handle_);
+ error_ = -EINVAL;
+ }
+
+ int error() const { return error_; }
+ storage_session_t session() { return session_.session(); }
+ file_handle_t handle() { return handle_; }
+
+ private:
+ StorageSession session_;
+ int error_ = -EINVAL;
+ file_handle_t handle_ = 0;
+};
+
+// Loads the storage object 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());
+ }
+
+ storage_off_t size = 0;
+ int error = storage_get_file_size(file.session(), file.handle(), &size);
+ if (error != 0) {
+ NVRAM_LOG_ERR("Failed to get size for %s: %d" , name, error);
+ return ErrorToStatus(error);
+ }
+
+ if (size > kMaxFileSize) {
+ NVRAM_LOG_ERR("Bad size for %s: %llu", name, size);
+ 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());
+ if (size_read < 0) {
+ NVRAM_LOG_ERR("Failed to read %s: %d" , name, size_read);
+ return ErrorToStatus(size_read);
+ }
+
+ if (static_cast<size_t>(size_read) != blob->size()) {
+ NVRAM_LOG_ERR("Size mismatch for %s: %d vs %u", name, size_read,
+ blob->size());
+ return Status::kStorageError;
+ }
+
+ return Status::kSuccess;
+}
+
+// Writes blob to the storage object 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());
+ return Status::kStorageError;
+ }
+
+ FileHandle file(name, STORAGE_FILE_OPEN_CREATE);
+ if (file.error()) {
+ NVRAM_LOG_ERR("Failed to open %s: %d" , name, file.error());
+ return ErrorToStatus(file.error());
+ }
+
+ int size_written =
+ storage_write(file.session(), file.handle(), 0,
+ const_cast<uint8_t*>(blob.data()), blob.size());
+ if (size_written < 0) {
+ NVRAM_LOG_ERR("Failed to write %s: %d" , name, size_written);
+ return ErrorToStatus(size_written);
+ }
+
+ if (static_cast<size_t>(size_written) != blob.size()) {
+ NVRAM_LOG_ERR("Size mismatch for %s: %d vs %d", name, size_written,
+ blob.size());
+ 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());
+ if (error) {
+ NVRAM_LOG_ERR("Failed to adjust file size for %s: %d\n", name, error);
+ // Return success below.
+ }
+
+ return Status::kSuccess;
+}
+
+} // namespace
+
+Status LoadHeader(Blob* blob) {
+ return LoadFile(kHeaderFileName, blob);
+}
+
+Status StoreHeader(const Blob& blob) {
+ return StoreFile(kHeaderFileName, blob);
+}
+
+Status LoadSpace(uint32_t index, Blob* blob) {
+ NameBuffer name;
+ if (!FormatSpaceFileName(name, index)) {
+ return Status::kStorageError;
+ }
+ return LoadFile(name, blob);
+}
+
+Status StoreSpace(uint32_t index, const Blob& blob) {
+ NameBuffer name;
+ if (!FormatSpaceFileName(name, index)) {
+ return Status::kStorageError;
+ }
+ return StoreFile(name, blob);
+}
+
+Status DeleteSpace(uint32_t index) {
+ NameBuffer name;
+ if (!FormatSpaceFileName(name, index)) {
+ return Status::kStorageError;
+ }
+
+ StorageSession session;
+ if (session.error() != 0) {
+ return ErrorToStatus(session.error());
+ }
+
+ int error = storage_delete_file(session.session(), name);
+ if (error == -ENOENT) {
+ return Status::kSuccess;
+ }
+
+ return ErrorToStatus(error);
+}
+
+} // namespace strorage
+} // namespace nvram