diff options
Diffstat (limited to 'secure_storage.cpp')
-rw-r--r-- | secure_storage.cpp | 271 |
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 |