blob: 8e70b9ffcbbb0e5162e1bcb0d45357b9bc5a4157 [file] [log] [blame]
/*
* 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