aboutsummaryrefslogtreecommitdiff
path: root/secure_storage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'secure_storage.cpp')
-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