| /* |
| * 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. It keeps header and space data in files stored in the RPMB |
| // file system. |
| |
| namespace nvram { |
| namespace storage { |
| |
| namespace { |
| |
| // Name of the file holding the header. |
| const char kHeaderFileName[] = "header"; |
| |
| // Pattern for space data file names. |
| const char kSpaceDataFileNamePattern[] = "space_%08x"; |
| |
| // 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 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)); |
| }; |
| |
| // An RAII wrapper for storage_session_t. |
| class StorageSession { |
| public: |
| 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; |
| } |
| |
| // 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 handle() { return handle_; } |
| |
| private: |
| int error_ = -EINVAL; |
| storage_session_t handle_ = 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(), &handle_, |
| const_cast<char*>(name), flags, |
| 0 /* don't commit */); |
| } else { |
| error_ = session_.error(); |
| } |
| } |
| ~FileHandle() { |
| if (error_ != 0) { |
| return; |
| } |
| |
| storage_close_file(handle_); |
| error_ = -EINVAL; |
| } |
| |
| StorageSession* session() { return &session_; } |
| int error() const { return error_; } |
| file_handle_t handle() { return handle_; } |
| |
| private: |
| StorageSession session_; |
| int error_ = -EINVAL; |
| file_handle_t handle_ = 0; |
| }; |
| |
| // Loads the file identified by |name|. |
| Status LoadFile(const char* name, Blob* blob) { |
| 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.handle(), &size); |
| if (error != 0) { |
| NVRAM_LOG_ERR("Failed to get size for %s: %d" , name, error); |
| return Status::kStorageError; |
| } |
| |
| if (size > kMaxFileSize) { |
| NVRAM_LOG_ERR("Bad size for %s: %llu", name, size); |
| return Status::kStorageError; |
| } |
| |
| if (!blob->Resize(size)) { |
| NVRAM_LOG_ERR("Failed to allocate read buffer for %s" , name); |
| return Status::kStorageError; |
| } |
| |
| 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 Status::kStorageError; |
| } |
| |
| 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 file indicated by |name|. |
| Status StoreFile(const char* name, const Blob& blob) { |
| if (blob.size() > kMaxFileSize) { |
| NVRAM_LOG_ERR("Bad file size for %s: %u" , name, blob.size()); |
| return Status::kStorageError; |
| } |
| |
| // 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 Status::kStorageError; |
| } |
| |
| int size_written = |
| 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 Status::kStorageError; |
| } |
| |
| 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; |
| } |
| |
| // 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 commit %s after write: %d", name, error); |
| return Status::kStorageError; |
| } |
| |
| 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 Status::kStorageError; |
| } |
| |
| int error = storage_delete_file(session.handle(), name, STORAGE_OP_COMPLETE); |
| if (error == 0 || error == -ENOENT) { |
| return Status::kSuccess; |
| } |
| |
| NVRAM_LOG_ERR("Failed to delete %s: %d", name, error); |
| return Status::kStorageError; |
| } |
| |
| } // namespace strorage |
| } // namespace nvram |