You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
855 lines
30 KiB
855 lines
30 KiB
/* |
|
* 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/nvram_manager.h" |
|
|
|
extern "C" { |
|
#include <inttypes.h> |
|
#include <string.h> |
|
} // extern "C" |
|
|
|
#include <nvram/core/logger.h> |
|
|
|
#include "crypto.h" |
|
|
|
using namespace nvram::storage; |
|
|
|
namespace nvram { |
|
|
|
namespace { |
|
|
|
// Maximum size of a single space's contents. |
|
constexpr size_t kMaxSpaceSize = 1024; |
|
|
|
// Maximum authorization blob size; |
|
constexpr size_t kMaxAuthSize = 32; |
|
|
|
// The bitmask of all supported control flags. |
|
constexpr uint32_t kSupportedControlsMask = |
|
(1 << NV_CONTROL_PERSISTENT_WRITE_LOCK) | |
|
(1 << NV_CONTROL_BOOT_WRITE_LOCK) | |
|
(1 << NV_CONTROL_BOOT_READ_LOCK) | |
|
(1 << NV_CONTROL_WRITE_AUTHORIZATION) | |
|
(1 << NV_CONTROL_READ_AUTHORIZATION) | |
|
(1 << NV_CONTROL_WRITE_EXTEND); |
|
|
|
// Convert the |space.controls| bitmask to vector representation. |
|
nvram_result_t GetControlsVector(const NvramSpace& space, |
|
Vector<nvram_control_t>* controls) { |
|
for (size_t control = 0; control < sizeof(uint32_t) * 8; ++control) { |
|
if (space.HasControl(control)) { |
|
if (!controls->Resize(controls->size() + 1)) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
(*controls)[controls->size() - 1] = static_cast<nvram_control_t>(control); |
|
} |
|
} |
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
// Constant time memory block comparison. |
|
bool ConstantTimeEquals(const Blob& a, const Blob& b) { |
|
if (a.size() != b.size()) |
|
return false; |
|
|
|
// The volatile qualifiers prevent the compiler from making assumptions that |
|
// allow shortcuts: |
|
// * The entire array data must be read from memory. |
|
// * Marking |result| volatile ensures the subsequent loop iterations must |
|
// still store to |result|, thus avoiding the loop to exit early. |
|
// This achieves the desired constant-time behavior. |
|
volatile const uint8_t* data_a = a.data(); |
|
volatile const uint8_t* data_b = b.data(); |
|
volatile uint8_t result = 0; |
|
for (size_t i = 0; i < a.size(); ++i) { |
|
result |= data_a[i] ^ data_b[i]; |
|
} |
|
|
|
return result == 0; |
|
} |
|
|
|
// A standard minimum function. |
|
template <typename Type> |
|
const Type& min(const Type& a, const Type& b) { |
|
return (a < b) ? a : b; |
|
} |
|
|
|
// Filter status codes from the storage layer to only include known values. |
|
// Anything outside the range will be mapped to the generic |kStorageError|. |
|
storage::Status SanitizeStorageStatus(storage::Status status) { |
|
switch (status) { |
|
case storage::Status::kSuccess: |
|
return storage::Status::kSuccess; |
|
case storage::Status::kNotFound: |
|
return storage::Status::kNotFound; |
|
case storage::Status::kStorageError: |
|
return storage::Status::kStorageError; |
|
} |
|
NVRAM_LOG_ERR("Unknown status code %u!", status); |
|
return storage::Status::kStorageError; |
|
} |
|
|
|
} // namespace |
|
|
|
// Looks at |request| to determine the command to execute, then invokes |
|
// the appropriate handler. |
|
void NvramManager::Dispatch(const nvram::Request& request, |
|
nvram::Response* response) { |
|
nvram_result_t result = NV_RESULT_INVALID_PARAMETER; |
|
const nvram::RequestUnion& input = request.payload; |
|
nvram::ResponseUnion* output = &response->payload; |
|
|
|
switch (input.which()) { |
|
case nvram::COMMAND_GET_INFO: |
|
result = GetInfo(*input.get<COMMAND_GET_INFO>(), |
|
&output->Activate<COMMAND_GET_INFO>()); |
|
break; |
|
case nvram::COMMAND_CREATE_SPACE: |
|
result = CreateSpace(*input.get<COMMAND_CREATE_SPACE>(), |
|
&output->Activate<COMMAND_CREATE_SPACE>()); |
|
break; |
|
case nvram::COMMAND_GET_SPACE_INFO: |
|
result = GetSpaceInfo(*input.get<COMMAND_GET_SPACE_INFO>(), |
|
&output->Activate<COMMAND_GET_SPACE_INFO>()); |
|
break; |
|
case nvram::COMMAND_DELETE_SPACE: |
|
result = DeleteSpace(*input.get<COMMAND_DELETE_SPACE>(), |
|
&output->Activate<COMMAND_DELETE_SPACE>()); |
|
break; |
|
case nvram::COMMAND_DISABLE_CREATE: |
|
result = DisableCreate(*input.get<COMMAND_DISABLE_CREATE>(), |
|
&output->Activate<COMMAND_DISABLE_CREATE>()); |
|
break; |
|
case nvram::COMMAND_WRITE_SPACE: |
|
result = WriteSpace(*input.get<COMMAND_WRITE_SPACE>(), |
|
&output->Activate<COMMAND_WRITE_SPACE>()); |
|
break; |
|
case nvram::COMMAND_READ_SPACE: |
|
result = ReadSpace(*input.get<COMMAND_READ_SPACE>(), |
|
&output->Activate<COMMAND_READ_SPACE>()); |
|
break; |
|
case nvram::COMMAND_LOCK_SPACE_WRITE: |
|
result = LockSpaceWrite(*input.get<COMMAND_LOCK_SPACE_WRITE>(), |
|
&output->Activate<COMMAND_LOCK_SPACE_WRITE>()); |
|
break; |
|
case nvram::COMMAND_LOCK_SPACE_READ: |
|
result = LockSpaceRead(*input.get<COMMAND_LOCK_SPACE_READ>(), |
|
&output->Activate<COMMAND_LOCK_SPACE_READ>()); |
|
break; |
|
case nvram::COMMAND_WIPE_STORAGE: |
|
result = WipeStorage(*input.get<COMMAND_WIPE_STORAGE>(), |
|
&output->Activate<COMMAND_WIPE_STORAGE>()); |
|
break; |
|
case nvram::COMMAND_DISABLE_WIPE: |
|
result = DisableWipe(*input.get<COMMAND_DISABLE_WIPE>(), |
|
&output->Activate<COMMAND_DISABLE_WIPE>()); |
|
break; |
|
} |
|
|
|
response->result = result; |
|
} |
|
|
|
nvram_result_t NvramManager::GetInfo(const GetInfoRequest& /* request */, |
|
GetInfoResponse* response) { |
|
NVRAM_LOG_INFO("GetInfo"); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
// TODO: Get better values for total and available size from the storage |
|
// layer. |
|
response->total_size = kMaxSpaceSize * kMaxSpaces; |
|
response->available_size = kMaxSpaceSize * (kMaxSpaces - num_spaces_); |
|
response->max_space_size = kMaxSpaceSize; |
|
response->max_spaces = kMaxSpaces; |
|
Vector<uint32_t>& space_list = response->space_list; |
|
if (!space_list.Resize(num_spaces_)) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
for (size_t i = 0; i < num_spaces_; ++i) { |
|
space_list[i] = spaces_[i].index; |
|
} |
|
response->wipe_disabled = disable_wipe_; |
|
|
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
nvram_result_t NvramManager::CreateSpace(const CreateSpaceRequest& request, |
|
CreateSpaceResponse* /* response */) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("CreateSpace Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
if (disable_create_) { |
|
NVRAM_LOG_INFO("Creation of further spaces is disabled."); |
|
return NV_RESULT_OPERATION_DISABLED; |
|
} |
|
|
|
if (FindSpace(index) != kMaxSpaces) { |
|
NVRAM_LOG_INFO("Space 0x%" PRIx32 " already exists.", index); |
|
return NV_RESULT_SPACE_ALREADY_EXISTS; |
|
} |
|
|
|
if (num_spaces_ + 1 > kMaxSpaces) { |
|
NVRAM_LOG_INFO("Too many spaces."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
if (request.size > kMaxSpaceSize) { |
|
NVRAM_LOG_INFO("Create request exceeds max space size."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
if (request.authorization_value.size() > kMaxAuthSize) { |
|
NVRAM_LOG_INFO("Authorization blob too large."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
uint32_t controls = 0; |
|
for (uint32_t control : request.controls) { |
|
controls |= (1 << control); |
|
} |
|
if ((controls & ~kSupportedControlsMask) != 0) { |
|
NVRAM_LOG_INFO("Bad controls."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
if ((controls & (1 << NV_CONTROL_PERSISTENT_WRITE_LOCK)) != 0 && |
|
(controls & (1 << NV_CONTROL_BOOT_WRITE_LOCK)) != 0) { |
|
NVRAM_LOG_INFO("Write lock controls are exclusive."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
if ((controls & (1 << NV_CONTROL_WRITE_EXTEND)) != 0 && |
|
request.size != crypto::kSHA256DigestSize) { |
|
NVRAM_LOG_INFO("Write-extended space size must be %zu.", |
|
crypto::kSHA256DigestSize); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
// Mark the index as allocated. |
|
spaces_[num_spaces_].index = index; |
|
spaces_[num_spaces_].write_locked = false; |
|
spaces_[num_spaces_].read_locked = false; |
|
++num_spaces_; |
|
|
|
// Create a space record. |
|
NvramSpace space; |
|
space.flags = 0; |
|
space.controls = controls; |
|
|
|
// Copy the auth blob. |
|
if (space.HasControl(NV_CONTROL_WRITE_AUTHORIZATION) || |
|
space.HasControl(NV_CONTROL_READ_AUTHORIZATION)) { |
|
if (!space.authorization_value.Assign(request.authorization_value.data(), |
|
request.authorization_value.size())) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
} |
|
|
|
// Initialize the space content. |
|
if (!space.contents.Resize(request.size)) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
memset(space.contents.data(), 0, request.size); |
|
|
|
// Write the header before the space data. This ensures that all space |
|
// definitions present in storage are also recorded in the header. Thus, the |
|
// set of spaces present in the header is always a superset of the set of |
|
// spaces that have state in storage. If there's a crash after writing the |
|
// header but before writing the space information, the space data will be |
|
// missing in storage. The initialization code handles this by checking the |
|
// for the space data corresponding to the index marked as provisional in the |
|
// header. |
|
nvram_result_t result; |
|
if ((result = WriteHeader(Optional<uint32_t>(index))) != NV_RESULT_SUCCESS || |
|
(result = WriteSpace(index, space)) != NV_RESULT_SUCCESS) { |
|
--num_spaces_; |
|
} |
|
return result; |
|
} |
|
|
|
nvram_result_t NvramManager::GetSpaceInfo(const GetSpaceInfoRequest& request, |
|
GetSpaceInfoResponse* response) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("GetSpaceInfo Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
response->size = space_record.persistent.contents.size(); |
|
|
|
result = GetControlsVector(space_record.persistent, &response->controls); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
|
|
if (space_record.persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) { |
|
response->read_locked = space_record.transient->read_locked; |
|
} |
|
|
|
if (space_record.persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) { |
|
response->write_locked = |
|
space_record.persistent.HasFlag(NvramSpace::kFlagWriteLocked); |
|
} else if (space_record.persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) { |
|
response->write_locked = space_record.transient->write_locked; |
|
} |
|
|
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
nvram_result_t NvramManager::DeleteSpace(const DeleteSpaceRequest& request, |
|
DeleteSpaceResponse* /* response */) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("DeleteSpace Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
result = space_record.CheckWriteAccess(request.authorization_value); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return result; |
|
} |
|
|
|
// Delete the space. First mark the space as provisionally removed in the |
|
// header. Then, delete the space data from storage. This allows orphaned |
|
// space data be cleaned up after a crash. |
|
SpaceListEntry tmp = spaces_[space_record.array_index]; |
|
spaces_[space_record.array_index] = spaces_[num_spaces_ - 1]; |
|
--num_spaces_; |
|
result = WriteHeader(Optional<uint32_t>(index)); |
|
if (result == NV_RESULT_SUCCESS) { |
|
switch (SanitizeStorageStatus(persistence::DeleteSpace(index))) { |
|
case storage::Status::kStorageError: |
|
NVRAM_LOG_ERR("Failed to delete space 0x%" PRIx32 " data.", index); |
|
result = NV_RESULT_INTERNAL_ERROR; |
|
break; |
|
case storage::Status::kNotFound: |
|
// The space was missing even if it shouldn't have been. Log an error, |
|
// but return success as we're in the desired state. |
|
NVRAM_LOG_ERR("Space 0x%" PRIx32 " data missing on deletion.", index); |
|
return NV_RESULT_SUCCESS; |
|
case storage::Status::kSuccess: |
|
return NV_RESULT_SUCCESS; |
|
} |
|
} |
|
|
|
// Failed to delete, re-add the transient state to |spaces_|. |
|
spaces_[num_spaces_] = tmp; |
|
++num_spaces_; |
|
return result; |
|
} |
|
|
|
nvram_result_t NvramManager::DisableCreate( |
|
const DisableCreateRequest& /* request */, |
|
DisableCreateResponse* /* response */) { |
|
NVRAM_LOG_INFO("DisableCreate"); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
// Set the |disable_create_| flag and call |WriteHeader| to persist the flag |
|
// such that it remains effective after a reboot. Make sure to restore the |
|
// current value of |disable_create_| if the write call fails, as we return an |
|
// error in that case and client code would not expect state changes. |
|
bool disable_create_previous = disable_create_; |
|
disable_create_ = true; |
|
nvram_result_t result = WriteHeader(Optional<uint32_t>()); |
|
if (result != NV_RESULT_SUCCESS) { |
|
disable_create_ = disable_create_previous; |
|
} |
|
return result; |
|
} |
|
|
|
nvram_result_t NvramManager::WriteSpace(const WriteSpaceRequest& request, |
|
WriteSpaceResponse* /* response */) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("WriteSpace Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
result = space_record.CheckWriteAccess(request.authorization_value); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return result; |
|
} |
|
|
|
Blob& contents = space_record.persistent.contents; |
|
if (space_record.persistent.HasControl(NV_CONTROL_WRITE_EXTEND)) { |
|
// Concatenate the current space |contents| with the input data. |
|
Blob sha256_input; |
|
if (!sha256_input.Resize(contents.size() + request.buffer.size())) { |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
memcpy(sha256_input.data(), contents.data(), contents.size()); |
|
memcpy(sha256_input.data() + contents.size(), request.buffer.data(), |
|
request.buffer.size()); |
|
|
|
// Compute the SHA-256 digest and write it back to |contents|. |
|
crypto::SHA256(sha256_input.data(), sha256_input.size(), contents.data(), |
|
contents.size()); |
|
} else { |
|
if (contents.size() < request.buffer.size()) { |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
memcpy(contents.data(), request.buffer.data(), request.buffer.size()); |
|
memset(contents.data() + request.buffer.size(), 0x0, |
|
contents.size() - request.buffer.size()); |
|
} |
|
|
|
return WriteSpace(index, space_record.persistent); |
|
} |
|
|
|
nvram_result_t NvramManager::ReadSpace(const ReadSpaceRequest& request, |
|
ReadSpaceResponse* response) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("ReadSpace Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
result = space_record.CheckReadAccess(request.authorization_value); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return result; |
|
} |
|
|
|
if (!response->buffer.Assign(space_record.persistent.contents.data(), |
|
space_record.persistent.contents.size())) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
|
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
nvram_result_t NvramManager::LockSpaceWrite( |
|
const LockSpaceWriteRequest& request, |
|
LockSpaceWriteResponse* /* response */) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("LockSpaceWrite Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
result = space_record.CheckWriteAccess(request.authorization_value); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return result; |
|
} |
|
|
|
if (space_record.persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) { |
|
space_record.persistent.SetFlag(NvramSpace::kFlagWriteLocked); |
|
return WriteSpace(index, space_record.persistent); |
|
} else if (space_record.persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) { |
|
space_record.transient->write_locked = true; |
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
NVRAM_LOG_ERR("Space not configured for write locking."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
nvram_result_t NvramManager::LockSpaceRead( |
|
const LockSpaceReadRequest& request, |
|
LockSpaceReadResponse* /* response */) { |
|
const uint32_t index = request.index; |
|
NVRAM_LOG_INFO("LockSpaceRead Ox%" PRIx32, index); |
|
|
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
SpaceRecord space_record; |
|
nvram_result_t result; |
|
if (!LoadSpaceRecord(index, &space_record, &result)) { |
|
return result; |
|
} |
|
|
|
result = space_record.CheckReadAccess(request.authorization_value); |
|
if (result != NV_RESULT_SUCCESS) { |
|
return result; |
|
} |
|
|
|
if (space_record.persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) { |
|
space_record.transient->read_locked = true; |
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
NVRAM_LOG_ERR("Space not configured for read locking."); |
|
return NV_RESULT_INVALID_PARAMETER; |
|
} |
|
|
|
nvram_result_t NvramManager::WipeStorage( |
|
const WipeStorageRequest& /* request */, |
|
WipeStorageResponse* /* response */) { |
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
#ifdef NVRAM_WIPE_STORAGE_SUPPORT |
|
if (disable_wipe_) { |
|
return NV_RESULT_OPERATION_DISABLED; |
|
} |
|
|
|
// Go through all spaces and wipe the corresponding data. Note that the header |
|
// is only updated once all space data is gone. This will "break" all spaces |
|
// that are left declared but don't have data. This situation can be observed |
|
// if we crash somewhere during the wiping process before clearing the header. |
|
// |
|
// Note that we deliberately choose this wiping sequence so we can never end |
|
// up in a state where the header appears clean but existing space data |
|
// remains. |
|
// |
|
// As a final note, the ideal solution would be to atomically clear the header |
|
// and delete all space data. While more desirable from an operational point |
|
// of view, this would drastically complicate storage layer requirements to |
|
// support cross-object atomicity instead of per-object atomicity. |
|
for (size_t i = 0; i < num_spaces_; ++i) { |
|
const uint32_t index = spaces_[i].index; |
|
switch (SanitizeStorageStatus(persistence::DeleteSpace(index))) { |
|
case storage::Status::kStorageError: |
|
NVRAM_LOG_ERR("Failed to wipe space 0x%" PRIx32 " data.", index); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
case storage::Status::kNotFound: |
|
// The space was missing even if it shouldn't have been. This may occur |
|
// if a previous wiping attempt was aborted half-way. Log an error, but |
|
// return success as we're in the desired state. |
|
NVRAM_LOG_WARN("Space 0x%" PRIx32 " data missing on wipe.", index); |
|
break; |
|
case storage::Status::kSuccess: |
|
break; |
|
} |
|
} |
|
|
|
// All spaces are gone, clear the header. |
|
num_spaces_ = 0; |
|
return WriteHeader(Optional<uint32_t>()); |
|
#else // NVRAM_WIPE_STORAGE_SUPPORT |
|
// We're not accessing the flag member, so prevent a compiler warning. The |
|
// alternative of conditionally including the member in the class declaration |
|
// looks cleaner at first sight, but comes with the risk of |
|
// NVRAM_WIPE_STORAGE_SUPPORT polarity mismatches between compilation units, |
|
// which is more subtly dangerous, so we rather keep the member even for the |
|
// case in which it is not used. |
|
(void)disable_wipe_; |
|
return NV_RESULT_OPERATION_DISABLED; |
|
#endif // NVRAM_WIPE_STORAGE_SUPPORT |
|
} |
|
|
|
nvram_result_t NvramManager::DisableWipe( |
|
const DisableWipeRequest& /* request */, |
|
DisableWipeResponse* /* response */) { |
|
if (!Initialize()) |
|
return NV_RESULT_INTERNAL_ERROR; |
|
|
|
#ifdef NVRAM_WIPE_STORAGE_SUPPORT |
|
disable_wipe_ = true; |
|
return NV_RESULT_SUCCESS; |
|
#else // NVRAM_WIPE_STORAGE_SUPPORT |
|
return NV_RESULT_OPERATION_DISABLED; |
|
#endif // NVRAM_WIPE_STORAGE_SUPPORT |
|
} |
|
|
|
nvram_result_t NvramManager::SpaceRecord::CheckWriteAccess( |
|
const Blob& authorization_value) { |
|
if (persistent.HasControl(NV_CONTROL_PERSISTENT_WRITE_LOCK)) { |
|
if (persistent.HasFlag(NvramSpace::kFlagWriteLocked)) { |
|
NVRAM_LOG_INFO("Attempt to write persistently locked space 0x%" PRIx32 |
|
".", |
|
transient->index); |
|
return NV_RESULT_OPERATION_DISABLED; |
|
} |
|
} else if (persistent.HasControl(NV_CONTROL_BOOT_WRITE_LOCK)) { |
|
if (transient->write_locked) { |
|
NVRAM_LOG_INFO("Attempt to write per-boot locked space 0x%" PRIx32 ".", |
|
transient->index); |
|
return NV_RESULT_OPERATION_DISABLED; |
|
} |
|
} |
|
|
|
if (persistent.HasControl(NV_CONTROL_WRITE_AUTHORIZATION) && |
|
!ConstantTimeEquals(persistent.authorization_value, |
|
authorization_value)) { |
|
NVRAM_LOG_INFO( |
|
"Authorization value mismatch for write access to space 0x%" PRIx32 ".", |
|
transient->index); |
|
return NV_RESULT_ACCESS_DENIED; |
|
} |
|
|
|
// All checks passed, allow the write. |
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
nvram_result_t NvramManager::SpaceRecord::CheckReadAccess( |
|
const Blob& authorization_value) { |
|
if (persistent.HasControl(NV_CONTROL_BOOT_READ_LOCK)) { |
|
if (transient->read_locked) { |
|
NVRAM_LOG_INFO("Attempt to read per-boot locked space 0x%" PRIx32 ".", |
|
transient->index); |
|
return NV_RESULT_OPERATION_DISABLED; |
|
} |
|
} |
|
|
|
if (persistent.HasControl(NV_CONTROL_READ_AUTHORIZATION) && |
|
!ConstantTimeEquals(persistent.authorization_value, |
|
authorization_value)) { |
|
NVRAM_LOG_INFO( |
|
"Authorization value mismatch for read access to space 0x%" PRIx32 ".", |
|
transient->index); |
|
return NV_RESULT_ACCESS_DENIED; |
|
} |
|
|
|
// All checks passed, allow the read. |
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
bool NvramManager::Initialize() { |
|
if (initialized_) |
|
return true; |
|
|
|
NvramHeader header; |
|
switch (SanitizeStorageStatus(persistence::LoadHeader(&header))) { |
|
case storage::Status::kStorageError: |
|
NVRAM_LOG_ERR("Init failed to load header."); |
|
return false; |
|
case storage::Status::kNotFound: |
|
// No header in storage. This happens the very first time we initialize |
|
// on a fresh device where the header isn't present yet. The first write |
|
// will flush the fresh header to storage. |
|
initialized_ = true; |
|
return true; |
|
case storage::Status::kSuccess: |
|
if (header.version > NvramHeader::kVersion) { |
|
NVRAM_LOG_ERR("Storage format %" PRIu32 " is more recent than %" PRIu32 |
|
", aborting.", |
|
header.version, NvramHeader::kVersion); |
|
return false; |
|
} |
|
break; |
|
} |
|
|
|
// Check the state of the provisional space if applicable. |
|
const Optional<uint32_t>& provisional_index = header.provisional_index; |
|
bool provisional_space_in_storage = false; |
|
if (provisional_index.valid()) { |
|
NvramSpace space; |
|
switch (SanitizeStorageStatus( |
|
persistence::LoadSpace(provisional_index.value(), &space))) { |
|
case storage::Status::kStorageError: |
|
// Log an error but leave the space marked as allocated. This will allow |
|
// initialization to complete, so other spaces can be accessed. |
|
// Operations on the bad space will fail however. The choice of keeping |
|
// the bad space around (as opposed to dropping it) is intentional: |
|
// * Failing noisily reduces the chances of bugs going undetected. |
|
// * Keeping the index allocated prevents it from being accidentally |
|
// clobbered due to appearing absent after transient storage errors. |
|
NVRAM_LOG_ERR("Failed to load provisional space 0x%" PRIx32 ".", |
|
provisional_index.value()); |
|
provisional_space_in_storage = true; |
|
break; |
|
case storage::Status::kNotFound: |
|
break; |
|
case storage::Status::kSuccess: |
|
provisional_space_in_storage = true; |
|
break; |
|
} |
|
} |
|
|
|
// If there are more spaces allocated than this build supports, fail |
|
// initialization. This may seem a bit drastic, but the alternatives aren't |
|
// acceptable: |
|
// * If we continued with just a subset of the spaces, that may lead to wrong |
|
// conclusions about the system state in consumers. Furthermore, consumers |
|
// might delete a space to make room and then create a space that appears |
|
// free but is present in storage. This would clobber the existing space |
|
// data and potentially violate its access control rules. |
|
// * We could just try to allocate more memory to hold the larger number of |
|
// spaces. That'd render the memory footprint of the NVRAM implementation |
|
// unpredictable. One variation that may work is to allow a maximum number |
|
// of existing spaces larger than kMaxSpaces, but still within sane limits. |
|
if (header.allocated_indices.size() > kMaxSpaces) { |
|
NVRAM_LOG_ERR("Excess spaces %zu in header.", |
|
header.allocated_indices.size()); |
|
return false; |
|
} |
|
|
|
// Initialize the transient space bookkeeping data. |
|
bool delete_provisional_space = provisional_index.valid(); |
|
for (uint32_t index : header.allocated_indices) { |
|
if (provisional_index.valid() && provisional_index.value() == index) { |
|
// The provisional space index refers to a created space. If it isn't |
|
// valid, pretend it was never created. |
|
if (!provisional_space_in_storage) { |
|
continue; |
|
} |
|
|
|
// The provisional space index corresponds to a created space that is |
|
// present in storage. Retain the space. |
|
delete_provisional_space = false; |
|
} |
|
|
|
spaces_[num_spaces_].index = index; |
|
spaces_[num_spaces_].write_locked = false; |
|
spaces_[num_spaces_].read_locked = false; |
|
++num_spaces_; |
|
} |
|
|
|
// If the provisional space data is present in storage, but the index wasn't |
|
// in |header.allocated_indices|, it refers to half-deleted space. Destroy the |
|
// space in that case. |
|
if (delete_provisional_space) { |
|
switch (SanitizeStorageStatus( |
|
persistence::DeleteSpace(provisional_index.value()))) { |
|
case storage::Status::kStorageError: |
|
NVRAM_LOG_ERR("Failed to delete provisional space 0x%" PRIx32 " data.", |
|
provisional_index.value()); |
|
return false; |
|
case storage::Status::kNotFound: |
|
// The space isn't present in storage. This may happen if the space |
|
// deletion succeeded, but the header wasn't written subsequently. |
|
break; |
|
case storage::Status::kSuccess: |
|
break; |
|
} |
|
} |
|
|
|
disable_create_ = header.HasFlag(NvramHeader::kFlagDisableCreate); |
|
initialized_ = true; |
|
|
|
// Write the header to clear the provisional index if necessary. It's actually |
|
// not a problem if this fails, because the state is consistent regardless. We |
|
// still do this opportunistically in order to avoid loading the provisional |
|
// space data for each reboot after a crash. |
|
if (provisional_index.valid()) { |
|
WriteHeader(Optional<uint32_t>()); |
|
} |
|
|
|
return true; |
|
} |
|
|
|
size_t NvramManager::FindSpace(uint32_t space_index) { |
|
for (size_t i = 0; i < num_spaces_; ++i) { |
|
if (spaces_[i].index == space_index) { |
|
return i; |
|
} |
|
} |
|
|
|
return kMaxSpaces; |
|
} |
|
|
|
bool NvramManager::LoadSpaceRecord(uint32_t index, |
|
SpaceRecord* space_record, |
|
nvram_result_t* result) { |
|
space_record->array_index = FindSpace(index); |
|
if (space_record->array_index == kMaxSpaces) { |
|
*result = NV_RESULT_SPACE_DOES_NOT_EXIST; |
|
return false; |
|
} |
|
|
|
space_record->transient = &spaces_[space_record->array_index]; |
|
|
|
switch (SanitizeStorageStatus( |
|
persistence::LoadSpace(index, &space_record->persistent))) { |
|
case storage::Status::kStorageError: |
|
NVRAM_LOG_ERR("Failed to load space 0x%" PRIx32 " data.", index); |
|
*result = NV_RESULT_INTERNAL_ERROR; |
|
return false; |
|
case storage::Status::kNotFound: |
|
// This should never happen if the header contains the index. |
|
NVRAM_LOG_ERR("Space index 0x%" PRIx32 |
|
" present in header, but data missing.", |
|
index); |
|
*result = NV_RESULT_INTERNAL_ERROR; |
|
return false; |
|
case storage::Status::kSuccess: |
|
*result = NV_RESULT_SUCCESS; |
|
return true; |
|
} |
|
|
|
*result = NV_RESULT_INTERNAL_ERROR; |
|
return false; |
|
} |
|
|
|
nvram_result_t NvramManager::WriteHeader(Optional<uint32_t> provisional_index) { |
|
NvramHeader header; |
|
header.version = NvramHeader::kVersion; |
|
if (disable_create_) { |
|
header.SetFlag(NvramHeader::kFlagDisableCreate); |
|
} |
|
|
|
if (!header.allocated_indices.Resize(num_spaces_)) { |
|
NVRAM_LOG_ERR("Allocation failure."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
for (size_t i = 0; i < num_spaces_; ++i) { |
|
header.allocated_indices[i] = spaces_[i].index; |
|
} |
|
|
|
header.provisional_index = provisional_index; |
|
|
|
if (SanitizeStorageStatus(persistence::StoreHeader(header)) != |
|
storage::Status::kSuccess) { |
|
NVRAM_LOG_ERR("Failed to store header."); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
|
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
nvram_result_t NvramManager::WriteSpace(uint32_t index, |
|
const NvramSpace& space) { |
|
if (SanitizeStorageStatus(persistence::StoreSpace(index, space)) != |
|
storage::Status::kSuccess) { |
|
NVRAM_LOG_ERR("Failed to store space 0x%" PRIx32 ".", index); |
|
return NV_RESULT_INTERNAL_ERROR; |
|
} |
|
|
|
return NV_RESULT_SUCCESS; |
|
} |
|
|
|
} // namespace nvram
|
|
|