upload android base code part6

This commit is contained in:
August 2018-08-08 17:48:24 +08:00
parent 421e214c7d
commit 4e516ec6ed
35396 changed files with 9188716 additions and 0 deletions

View file

@ -0,0 +1,45 @@
#
# 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libnvram-core
LOCAL_SRC_FILES := \
crypto_boringssl.cpp \
nvram_manager.cpp \
persistence.cpp
LOCAL_CFLAGS := -Wall -Werror -Wextra
LOCAL_CLANG := true
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := libnvram-messages libcrypto
include $(BUILD_STATIC_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := libnvram-core-host
LOCAL_SRC_FILES := \
crypto_boringssl.cpp \
nvram_manager.cpp \
persistence.cpp
LOCAL_CFLAGS := -Wall -Werror -Wextra -DNVRAM_WIPE_STORAGE_SUPPORT
LOCAL_CLANG := true
LOCAL_C_INCLUDES := $(LOCAL_PATH)/include
LOCAL_EXPORT_C_INCLUDE_DIRS := $(LOCAL_PATH)/include
LOCAL_SHARED_LIBRARIES := libnvram-messages libcrypto
include $(BUILD_HOST_STATIC_LIBRARY)
include $(call all-makefiles-under,$(LOCAL_PATH))

View file

@ -0,0 +1,45 @@
/*
* 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.
*/
#ifndef NVRAM_CORE_CRYPTO_H_
#define NVRAM_CORE_CRYPTO_H_
extern "C" {
#include <stddef.h>
#include <stdint.h>
} // extern "C"
namespace nvram {
namespace crypto {
// Size of a SHA-256 digest in bytes.
constexpr size_t kSHA256DigestSize = 32;
// Computes the SHA-256 digest of the |data_size| input bytes stored at |data|.
// The digest is written to |digest|, which is a buffer of size |digest_size|.
// Note that |digest_size| doesn't have to match SHA-256's output size of 32
// bytes. If it doesn't the digest is truncated or zero-padded as necessary.
//
// Returns true if the digest was computed successfully, false otherwise.
void SHA256(const uint8_t* data,
size_t data_size,
uint8_t* digest,
size_t digest_size);
} // namespace crypto
} // namespace nvram
#endif // NVRAM_CORE_CRYPTO_H_

View file

@ -0,0 +1,48 @@
/*
* 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 "crypto.h"
extern "C" {
#include <string.h>
#include <openssl/mem.h>
#include <openssl/sha.h>
} // extern "C"
namespace nvram {
namespace crypto {
void SHA256(const uint8_t* data,
size_t data_size,
uint8_t* digest,
size_t digest_size) {
// SHA256 requires an output buffer of at least SHA256_DIGEST_LENGTH.
// |digest_size| might be less, so store the digest in a local buffer.
uint8_t buffer[SHA256_DIGEST_LENGTH];
::SHA256(data, data_size, buffer);
// Copy the result to |digest|.
if (digest_size < sizeof(buffer)) {
memcpy(digest, buffer, digest_size);
} else {
memcpy(digest, buffer, sizeof(buffer));
memset(digest + sizeof(buffer), 0, digest_size - sizeof(buffer));
}
}
} // namespace crypto
} // namespace nvram

View file

@ -0,0 +1,77 @@
/*
* 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.
*/
#ifndef NVRAM_CORE_LOGGER_H_
#define NVRAM_CORE_LOGGER_H_
// Numeric constants for log levels.
#define NVRAM_LOG_LEVEL_ERR 1
#define NVRAM_LOG_LEVEL_WARN 2
#define NVRAM_LOG_LEVEL_INFO 3
#define NVRAM_LOG_LEVEL_DEBUG 4
#if !defined(NVRAM_LOG_LEVEL)
// By default, log only warnings and errors.
#define NVRAM_LOG_LEVEL NVRAM_LOG_LEVEL_WARN
#endif
#ifdef __ANDROID__
#define LOG_TAG "NVRAM"
#include <log/log.h>
// Maps NVRAM log levels to Android log priorities.
#define NVRAM_ANDROID_LOG_PRI_ERR ANDROID_LOG_ERROR
#define NVRAM_ANDROID_LOG_PRI_WARN ANDROID_LOG_WARN
#define NVRAM_ANDROID_LOG_PRI_INFO ANDROID_LOG_INFO
#define NVRAM_ANDROID_LOG_PRI_DEBUG ANDROID_LOG_DEBUG
// Send log output to Android's logging system.
#define NVRAM_LOG_EMIT(level, fmt, ...) \
LOG_PRI(NVRAM_ANDROID_LOG_PRI_##level, LOG_TAG, fmt, ##__VA_ARGS__)
#else // __ANDROID__
extern "C" {
#include <stdio.h>
}
// By default, send log output to stderr.
#define NVRAM_LOG_EMIT(level, fmt, ...) \
fprintf(stderr, "NVRAM: " fmt "\n", ##__VA_ARGS__)
#endif // !__ANDROID__
// NVRAM_LOG is the central log macro. It checks whether the log level is
// effective, adds file and line information and calls the platform-specific
// NVRAM_LOG_EMIT.
#define NVRAM_STR(arg) #arg
#define NVRAM_STRINGIFY(arg) NVRAM_STR(arg)
#define NVRAM_LOG(level, fmt, ...) \
do { \
if (NVRAM_LOG_LEVEL_##level <= NVRAM_LOG_LEVEL) { \
NVRAM_LOG_EMIT(level, __FILE__ ":" NVRAM_STRINGIFY(__LINE__) ": " fmt, \
##__VA_ARGS__); \
} \
} while (0)
// Convenience logging macros.
#define NVRAM_LOG_ERR(fmt, ...) NVRAM_LOG(ERR, fmt, ##__VA_ARGS__)
#define NVRAM_LOG_WARN(fmt, ...) NVRAM_LOG(WARN, fmt, ##__VA_ARGS__)
#define NVRAM_LOG_INFO(fmt, ...) NVRAM_LOG(INFO, fmt, ##__VA_ARGS__)
#define NVRAM_LOG_DEBUG(fmt, ...) NVRAM_LOG(DEBUG, fmt, ##__VA_ARGS__)
#endif // NVRAM_CORE_LOGGER_H_

View file

@ -0,0 +1,155 @@
/*
* 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.
*/
#ifndef NVRAM_CORE_NVRAM_MANAGER_H_
#define NVRAM_CORE_NVRAM_MANAGER_H_
#include <nvram/messages/nvram_messages.h>
#include <nvram/core/persistence.h>
namespace nvram {
// |NvramManager| implements the core functionality of the access-controlled
// NVRAM HAL backend. It keeps track of the allocated spaces and their state,
// including the transient state that is held per boot. It provides operations
// for querying, creating, deleting, reading and writing spaces. It deals with
// persistent storage objects in the form of |NvramHeader| and |NvramSpace|
// objects and uses the persistence layer to read and write them from persistent
// storage.
class NvramManager {
public:
// Looks at |request| to determine the command to execute, extracts the
// request parameters and invokes the correct handler function. Stores status
// and output parameters in |response|.
void Dispatch(const Request& request, Response* response);
nvram_result_t GetInfo(const GetInfoRequest& request,
GetInfoResponse* response);
nvram_result_t CreateSpace(const CreateSpaceRequest& request,
CreateSpaceResponse* response);
nvram_result_t GetSpaceInfo(const GetSpaceInfoRequest& request,
GetSpaceInfoResponse* response);
nvram_result_t DeleteSpace(const DeleteSpaceRequest& request,
DeleteSpaceResponse* response);
nvram_result_t DisableCreate(const DisableCreateRequest& request,
DisableCreateResponse* response);
nvram_result_t WriteSpace(const WriteSpaceRequest& request,
WriteSpaceResponse* response);
nvram_result_t ReadSpace(const ReadSpaceRequest& request,
ReadSpaceResponse* response);
nvram_result_t LockSpaceWrite(const LockSpaceWriteRequest& request,
LockSpaceWriteResponse* response);
nvram_result_t LockSpaceRead(const LockSpaceReadRequest& request,
LockSpaceReadResponse* response);
// The wipe functions are meant for use by firmware after determining the
// device's mode of operation. These can be used to clear access-controlled
// NVRAM when a user invokes a full hardware reset. Note that in regular
// operation, the user *MUST BE PREVENTED* from wiping access-controlled
// NVRAM.
//
// If a full hardware reset can conveniently clear the access-controlled NVRAM
// storage area out of band, it's fine to do so. In this case, the
// wiping-related commands should not be exposed at all. Note that this is the
// default behavior - the reference implementation will ignore all wipe
// requests unless compiled with NVRAM_WIPE_STORAGE_SUPPORT=1.
//
// For devices where firmware doesn't have direct control over the storage
// area used by access-controlled NVRAM, the wiping commands are provided to
// facilitate clearing storage:
// 1. Determine boot mode.
// 2. If not in recovery mode, call DisableWipe(). All further wipe requests
// will be rejected. A reboot (or TEE restart for that matter) is
// required before a new decision can be made.
// 3. If operating in recovery mode, forgo calling DisableWipe(). The
// recovery process will then be able to invoke WipeStorage() later as
// needed.
nvram_result_t WipeStorage(const WipeStorageRequest& request,
WipeStorageResponse* response);
nvram_result_t DisableWipe(const DisableWipeRequest& request,
DisableWipeResponse* response);
private:
// Holds transient state corresponding to an allocated NVRAM space, i.e. meta
// data valid for a single boot. One instance of this struct is kept in memory
// in the |spaces_| array for each of the spaces that are currently allocated.
struct SpaceListEntry {
uint32_t index;
bool write_locked = false;
bool read_locked = false;
};
// |SpaceRecord| holds all information known about a space. It includes both
// an index and pointer to the transient information held in the
// |SpaceListEntry| in the |spaces_| array and the persistent |NvramSpace|
// state held in permanent storage. We only load the persistent space data
// from storage when it is needed for an operation, such as reading and
// writing space contents.
struct SpaceRecord {
// Access control check for write access to the space. The
// |authorization_value| is only relevant if the space was configured to
// require authorization. Returns RESULT_SUCCESS if write access is
// permitted and a suitable result code to return to the client on failure.
nvram_result_t CheckWriteAccess(const Blob& authorization_value);
// Access control check for read access to the space. The
// |authorization_value| is only relevant if the space was configured to
// require authorization. Returns RESULT_SUCCESS if write access is
// permitted and a suitable result code to return the client on failure.
nvram_result_t CheckReadAccess(const Blob& authorization_value);
size_t array_index = 0;
SpaceListEntry* transient = nullptr;
NvramSpace persistent;
};
// Initializes |header_| from storage if that hasn't happened already. Returns
// true if NvramManager object is initialized and ready to serve requests. May
// be called again after failure to attempt initialization again.
bool Initialize();
// Finds the array index in |spaces_| that corresponds to |space_index|.
// Returns |kMaxSpaces| if there is no matching space.
size_t FindSpace(uint32_t space_index);
// Loads space data for |index|. Fills in |space_record| and returns true if
// successful. Returns false and sets |result| on error.
bool LoadSpaceRecord(uint32_t index,
SpaceRecord* space_record,
nvram_result_t* result);
// Writes the header to storage and returns a suitable status code.
nvram_result_t WriteHeader(Optional<uint32_t> provisional_index);
// Write |space| data for |index|.
nvram_result_t WriteSpace(uint32_t index, const NvramSpace& space);
// Maximum number of NVRAM spaces we're willing to allocate.
static constexpr size_t kMaxSpaces = 32;
bool initialized_ = false;
bool disable_create_ = false;
bool disable_wipe_ = false;
// Bookkeeping information for allocated spaces.
size_t num_spaces_ = 0;
SpaceListEntry spaces_[kMaxSpaces];
};
} // namespace nvram
#endif // NVRAM_CORE_NVRAM_MANAGER_H_

View file

@ -0,0 +1,149 @@
/*
* 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.
*/
#ifndef NVRAM_CORE_PERSISTENCE_H_
#define NVRAM_CORE_PERSISTENCE_H_
extern "C" {
#include <stdint.h>
} // extern "C"
#include <nvram/messages/optional.h>
#include <nvram/messages/struct.h>
#include <nvram/messages/vector.h>
#include <nvram/core/storage.h>
namespace nvram {
// The NVRAM header data structure, which holds global information used by the
// NVRAM service, such as version and a list of defined spaces.
struct NvramHeader {
// State flags affecting all spaces.
enum Flags {
kFlagDisableCreate = 1 << 0,
};
// Check whether a flag is present.
bool HasFlag(Flags flag) const {
return (flags & flag) != 0;
}
// Set a flag.
void SetFlag(Flags flag) {
flags |= flag;
}
// The current major header version. Bump this upon making
// forward-incompatible changes to the storage format. Old versions will
// reject the header on load and refuse to operate when they encounter a
// version that is larger than the compile-time one.
static constexpr uint32_t kVersion = 1;
// The header version, indicating the data format revision used when the
// header was last written. On load, if the version is more recent then what
// the code can handle, we bail out. This allows making forward-incompatible
// changes to the data format with the guarantee that old code versions won't
// clobber new data. This is a last resort kill switch for old code, in
// general we should aim for maximum compatibility between versions.
uint32_t version = kVersion;
// Current header flags. Bitwise OR of |NvramHeader::Flags| values.
uint32_t flags = 0;
// A list of allocated indices, in no particular order.
Vector<uint32_t> allocated_indices;
// An index that is in the process of being created or deleted. This field is
// used as follows:
// * On space creation, we add the new space's index both to
// |allocated_indices| and set it as the |provisional_index|. Then, the
// header is written, followed by the space data. If we crash in between,
// the next load will find |provisional_index| present and will check
// whether that space is present on disk or not. If not, it'll clear the
// index from |allocated_indices| and reset |provisional_index|, hence the
// space is as good as never created.
// * On space deletion, the deleted space's index is removed from
// |allocated_indices|, but stored in |provisional_index|. The, the header
// gets written, followed by the space deletion. If we crash in between ,
// the next load will find the provisional index set, but the space is
// absent in |allocated_indices|. If this is the case, the initialization
// code will make sure to delete the space data if it's still around and
// clear |provisional_index| afterwards.
Optional<uint32_t> provisional_index;
};
// All data corresponding to a single NVRAM space is held in an NvramSpace
// structure. There is one structure per allocated index.
struct NvramSpace {
// Flags indicating internal status in effect for a space.
enum Flags {
kFlagWriteLocked = 1 << 0,
};
// Check whether a given flag is set.
bool HasFlag(Flags flag) const {
return (flags & flag) != 0;
}
// Set a flag.
void SetFlag(Flags flag) {
flags |= flag;
}
// A helper to simplify checking control flags.
bool HasControl(uint32_t control) const {
return (controls & (1 << control)) != 0;
}
// Persistent space flags. Bitwise OR of |NvramSpace::Flags| values.
uint32_t flags = 0;
// A bitmask of CONTROL_XYZ values in effect for the space. These are set at
// space creation time and generally not touched afterwards.
uint32_t controls = 0;
// The authorization value for the space. This is a shared secret that must be
// provided to read and write the space as specified by the appropriate
// |controls| flags.
Blob authorization_value;
// The space payload data.
Blob contents;
};
namespace persistence {
// Load NVRAM header from storage.
storage::Status LoadHeader(NvramHeader* header);
// Write the NVRAM header to storage.
storage::Status StoreHeader(const NvramHeader& header);
// Load NVRAM space data for a given index from storage.
storage::Status LoadSpace(uint32_t index, NvramSpace* space);
// Write the NVRAM space data for the given index to storage.
storage::Status StoreSpace(uint32_t index, const NvramSpace& space);
// Delete the stored NVRAM space data for the given index.
storage::Status DeleteSpace(uint32_t index);
} // namespace persistence
} // namespace nvram
#endif // NVRAM_CORE_PERSISTENCE_H_

View file

@ -0,0 +1,96 @@
/*
* 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.
*/
#ifndef NVRAM_CORE_STORAGE_H_
#define NVRAM_CORE_STORAGE_H_
extern "C" {
#include <stdint.h>
} // extern "C"
#include <nvram/messages/blob.h>
namespace nvram {
namespace storage {
// Indicates the result of a storage operation.
enum class Status {
kSuccess, // Operation successful.
kNotFound, // The element to be read could not be found.
kStorageError, // Failure on the underlying storage layer.
};
// Load NVRAM header from storage. See the comment on LoadSpace() for details on
// semantics and return values.
Status LoadHeader(Blob* blob);
// Write the binary-encoded NVRAM header |blob| to storage. See the comment on
// StoreSpace() for details on semantics and return values.
Status StoreHeader(const Blob& blob);
// Load NVRAM space data for a given index from storage.
//
// This must place the data in |blob| that was provided by the last store
// operation If there is evidence that no header data is present in the storage
// system, this function must return Status::kNotFound. For all other error
// conditions, implementations should return Status::kStorageError.
//
// It's OK if the data placed in |blob| exceeds the size of the original data,
// i.e. contain trailing bytes that haven't actually been written. This allows
// implementations to write at block granularity of the underlying storage
// system, which may be simpler instead of having to track sizes accurately.
Status LoadSpace(uint32_t index, Blob* blob);
// Write the NVRAM space data for the given index to storage.
//
// Implementations must atomically replace the current data with the data
// provided in |blob|. This must be robust against crashes, i.e. reloading the
// data after the crash should either return the previous version of the data,
// or the updated data provided in |blob|. In particular, crashes must not
// result in any form of data corruption or loss.
//
// It's OK for the implementation to allocate a larger storage area than
// required, i.e. not match blob.size() accurately. This allows implementations
// to perform I/O at block granularity of the underlying storage subsystem in
// case this simplifies things. There is no requirement as to the values of any
// additional trailing bytes.
//
// This function must make sure that the data actually hits disk before
// returning. The return value must be Status::kSuccess if and only if data was
// stored successfully, the function should return kStorageError otherwise.
Status StoreSpace(uint32_t index, const Blob& blob);
// Delete the stored NVRAM space data for the given index.
//
// This function must atomically delete the storage corresponding to the NVRAM
// space data associated with index. A subsequent read operation for the same
// index should return Status::kNotFound.
//
// This function must be robust in the event of crashes, i.e. a crash should
// leave the system with either the previously existing data still intact and
// accessible, or the space data entirely removed.
//
// This function must return Status::kSuccess upon deleting the space data
// successfully and having committed that operation to the underlying storage
// medium. Status::kNotFound should be returned in case the space data
// positively doesn't exist on the medium, and Status::kStorageError should be
// returned for all other error conditions.
Status DeleteSpace(uint32_t index);
} // namespace storage
} // namespace nvram
#endif // NVRAM_CORE_STORAGE_H

View file

@ -0,0 +1,855 @@
/*
* 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

View file

@ -0,0 +1,140 @@
/*
* 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/persistence.h"
#include <nvram/messages/io.h>
#include <nvram/messages/proto.hpp>
#include <nvram/core/logger.h>
namespace nvram {
namespace {
// Magic constants that identify encoded |NvramHeader| vs. |NvramSpace| objects.
const uint32_t kHeaderMagic = 0x4e5648; // "NVH" in hex
const uint32_t kSpaceMagic = 0x4e5653; // "NVS" in hex
// Encodes an |object| as a protobuf message and writes it to |blob|. Note that
// standard protobuf encoding doesn't include information about the overall size
// of the encoded object. This is not good enough here, as encoding should
// gracefully handle trailing data on decode, e.g. to allow underlying storage
// systems that only provide block-granular I/O.
//
// Not that the code uses |proto::detail::MessageEncoder<Object>::Encode()|
// instead of the regular |proto::Encode()| to encode the message. This results
// in the message being wrapped in a length-delimited proto field record, so the
// length field can be used to determine the actual length of the message. Also,
// this gives us the opportunity to encode a magic constant in the field number
// bits of the wire tag, thus allowing us to detect situations where we're
// attempting to decode a message of wrong type.
template <uint32_t magic, typename Object>
storage::Status EncodeObject(const Object& object, Blob* blob) {
BlobOutputStreamBuffer stream(blob);
ProtoWriter writer(&stream);
writer.set_field_number(magic);
if (!proto::detail::MessageEncoder<Object>::Encode(object, &writer) ||
!stream.Truncate()) {
NVRAM_LOG_ERR("Failed to encode object.");
return storage::Status::kStorageError;
}
return storage::Status::kSuccess;
}
// Decodes a protobuf-encoded |object| from |blob|. It is OK if the provided
// |blob| includes trailing data that doesn't belong to the encoded object.
//
// Note that the code below reads the wire tag to strip the wrapping proto field
// record produced by |EncodeObject|. It then checks the magic field number to
// make sure we're decoding a message of correct type. Finally,
// |proto::detail::MessageDecoder<Object>::Decode()| takes care of reading the
// message payload from the proto field record.
template <uint32_t magic, typename Object>
storage::Status DecodeObject(const Blob& blob, Object* object) {
InputStreamBuffer stream(blob.data(), blob.size());
ProtoReader reader(&stream);
if (!reader.ReadWireTag() || reader.field_number() != magic ||
reader.wire_type() != WireType::kLengthDelimited ||
!proto::detail::MessageDecoder<Object>::Decode(*object, &reader)) {
NVRAM_LOG_ERR("Failed to decode object of size %zu.", blob.size());
return storage::Status::kStorageError;
}
return storage::Status::kSuccess;
}
} // namespace
template <> struct DescriptorForType<NvramHeader> {
static constexpr auto kFields =
MakeFieldList(MakeField(1, &NvramHeader::version),
MakeField(2, &NvramHeader::flags),
MakeField(3, &NvramHeader::allocated_indices),
MakeField(4, &NvramHeader::provisional_index));
};
template <> struct DescriptorForType<NvramSpace> {
static constexpr auto kFields =
MakeFieldList(MakeField(1, &NvramSpace::flags),
MakeField(2, &NvramSpace::controls),
MakeField(3, &NvramSpace::authorization_value),
MakeField(4, &NvramSpace::contents));
};
namespace persistence {
storage::Status LoadHeader(NvramHeader* header) {
Blob blob;
storage::Status status = storage::LoadHeader(&blob);
if (status != storage::Status::kSuccess) {
return status;
}
return DecodeObject<kHeaderMagic>(blob, header);
}
storage::Status StoreHeader(const NvramHeader& header) {
Blob blob;
storage::Status status = EncodeObject<kHeaderMagic>(header, &blob);
if (status != storage::Status::kSuccess) {
return status;
}
return storage::StoreHeader(blob);
}
storage::Status LoadSpace(uint32_t index, NvramSpace* space) {
Blob blob;
storage::Status status = storage::LoadSpace(index, &blob);
if (status != storage::Status::kSuccess) {
return status;
}
return DecodeObject<kSpaceMagic>(blob, space);
}
storage::Status StoreSpace(uint32_t index, const NvramSpace& space) {
Blob blob;
storage::Status status = EncodeObject<kSpaceMagic>(space, &blob);
if (status != storage::Status::kSuccess) {
return status;
}
return storage::StoreSpace(index, blob);
}
storage::Status DeleteSpace(uint32_t index) {
return storage::DeleteSpace(index);
}
} // namespace persistence
} // namespace nvram

View file

@ -0,0 +1,47 @@
#
# 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.
#
LOCAL_DIR := $(GET_LOCAL_DIR)
# A library target that contains the trusty NVRAM implementation, without any
# glue to build the app or dependencies to other services. This allows to link
# this module into the unittest app with dependencies mocked out.
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/crypto_boringssl.cpp \
$(LOCAL_DIR)/nvram_manager.cpp \
$(LOCAL_DIR)/persistence.cpp
MODULE_CPPFLAGS := -Wall -Werror -Wextra
MODULE_DEPS += \
external/openssl \
lib/libc-trusty \
lib/libstdc++-trusty \
system/nvram/messages
ifneq ($(NVRAM_LOG_LEVEL),)
GLOBAL_DEFINES += NVRAM_LOG_LEVEL=$(NVRAM_LOG_LEVEL)
endif
ifneq ($(NVRAM_WIPE_STORAGE_SUPPORT),)
GLOBAL_DEFINES += NVRAM_WIPE_STORAGE_SUPPORT=$(NVRAM_WIPE_STORAGE_SUPPORT)
endif
GLOBAL_INCLUDES += $(LOCAL_DIR)/include/
include make/module.mk

View file

@ -0,0 +1,29 @@
#
# 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.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libnvram-core-tests
LOCAL_MODULE_TAGS := debug
LOCAL_SRC_FILES := \
fake_storage.cpp \
nvram_manager_test.cpp
LOCAL_CFLAGS := -Wall -Werror -Wextra -DHAS_GTEST
LOCAL_CLANG := true
LOCAL_STATIC_LIBRARIES := libnvram-core-host
LOCAL_SHARED_LIBRARIES := libnvram-messages libcrypto
include $(BUILD_HOST_NATIVE_TEST)

View file

@ -0,0 +1,180 @@
/*
* 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 "fake_storage.h"
#include <nvram/messages/blob.h>
#include <nvram/messages/compiler.h>
#define countof(a) (sizeof(a) / sizeof((a)[0]))
namespace nvram {
namespace storage {
namespace {
class StorageSlot {
public:
Status Load(Blob* blob) {
if (read_error_) {
return Status::kStorageError;
}
if (!present_) {
return Status::kNotFound;
}
NVRAM_CHECK(blob->Assign(blob_.data(), blob_.size()));
return Status::kSuccess;
}
Status Store(const Blob& blob) {
if (write_error_) {
return Status::kStorageError;
}
NVRAM_CHECK(blob_.Assign(blob.data(), blob.size()));
present_ = true;
return Status::kSuccess;
}
Status Delete() {
if (write_error_) {
return Status::kStorageError;
}
NVRAM_CHECK(blob_.Resize(0));
present_ = false;
return Status::kSuccess;
}
void Clear() {
present_ = false;
read_error_ = false;
write_error_ = false;
NVRAM_CHECK(blob_.Resize(0));
}
bool present() const { return present_; }
void set_present(bool present) { present_ = present; }
void set_read_error(bool error) { read_error_ = error; }
void set_write_error(bool error) { write_error_ = error; }
private:
bool present_ = false;
bool read_error_ = false;
bool write_error_ = false;
Blob blob_;
};
// Header storage.
StorageSlot g_header;
// Space blob storage.
struct SpaceStorageSlot {
uint32_t index;
StorageSlot slot;
};
SpaceStorageSlot g_spaces[256];
// Find the position in |g_spaces| corresponding to a given space |index|.
// Returns the slot pointer or |nullptr| if not found.
StorageSlot* FindSlotForIndex(uint32_t index) {
for (size_t i = 0; i < countof(g_spaces); ++i) {
if (g_spaces[i].slot.present() && g_spaces[i].index == index) {
return &g_spaces[i].slot;
}
}
return nullptr;
}
// Finds or creates the slot for |index|. Returns the slot pointer or |nullptr|
// if not found.
StorageSlot* FindOrCreateSlotForIndex(uint32_t index) {
StorageSlot* slot = FindSlotForIndex(index);
if (slot) {
return slot;
}
for (size_t i = 0; i < countof(g_spaces); ++i) {
if (!g_spaces[i].slot.present()) {
g_spaces[i].index = index;
return &g_spaces[i].slot;
}
}
return nullptr;
}
} // namespace
Status LoadHeader(Blob* blob) {
return g_header.Load(blob);
}
Status StoreHeader(const Blob& blob) {
return g_header.Store(blob);
}
void SetHeaderReadError(bool error) {
g_header.set_read_error(error);
}
void SetHeaderWriteError(bool error) {
g_header.set_write_error(error);
}
Status LoadSpace(uint32_t index, Blob* blob) {
StorageSlot* slot = FindSlotForIndex(index);
return slot ? slot->Load(blob) : Status::kNotFound;
}
Status StoreSpace(uint32_t index, const Blob& blob) {
StorageSlot* slot = FindOrCreateSlotForIndex(index);
return slot ? slot->Store(blob) : Status::kStorageError;
}
Status DeleteSpace(uint32_t index) {
StorageSlot* slot = FindSlotForIndex(index);
return slot ? slot->Delete() : Status::kNotFound;
}
void Clear() {
g_header.Clear();
for (size_t i = 0; i < countof(g_spaces); ++i) {
g_spaces[i].slot.Clear();
}
}
void SetSpaceReadError(uint32_t index, bool error) {
StorageSlot* slot = FindOrCreateSlotForIndex(index);
if (slot) {
slot->set_read_error(error);
}
}
void SetSpaceWriteError(uint32_t index, bool error) {
StorageSlot* slot = FindOrCreateSlotForIndex(index);
if (slot) {
slot->set_write_error(error);
}
}
} // namespace storage
} // namespace nvram

View file

@ -0,0 +1,45 @@
/*
* 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.
*/
#ifndef NVRAM_TEST_FAKE_STORAGE_H_
#define NVRAM_TEST_FAKE_STORAGE_H_
#include <nvram/core/storage.h>
namespace nvram {
namespace storage {
// Setup the header storage read functions to return Status::kStorageError.
void SetHeaderReadError(bool error);
// Setup the header storage write functions to return Status::kStorageError.
void SetHeaderWriteError(bool error);
// Setup the storage read calls for space |index| to return
// Status::kStorageError.
void SetSpaceReadError(uint32_t index, bool error);
// Setup the storage write calls for space |index| to return
// Status::kStorageError.
void SetSpaceWriteError(uint32_t index, bool error);
// Clears all storage.
void Clear();
} // namespace storage
} // namespace nvram
#endif // NVRAM_TEST_FAKE_STORAGE_H_

View file

@ -0,0 +1,54 @@
/*
* 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 "gtest_stubs.h"
namespace testing {
bool g_test_status = false;
namespace detail {
void TestRegistry::RunAllTests() {
int test_count = 0;
int test_failures = 0;
for (TestDeclarationBase* decl = tests_; decl; decl = decl->next) {
TestInstanceBase* instance = decl->create_function();
fprintf(stderr, "[ %s ] Starting...\n", decl->name);
g_test_status = true;
instance->Run();
test_failures += g_test_status ? 0 : 1;
++test_count;
fprintf(stderr, "[ %s ] %s\n", decl->name, g_test_status ? "PASS" : "FAIL");
delete instance;
}
fprintf(stderr, "Ran %d tests, %d failures.\n", test_count, test_failures);
}
void TestRegistry::Register(TestDeclarationBase* test_declaration) {
test_declaration->next = tests_;
tests_ = test_declaration;
}
TestRegistry TestRegistry::g_instance;
} // namespace detail
} // namespace testing
int main() {
testing::detail::TestRegistry::instance()->RunAllTests();
}

View file

@ -0,0 +1,129 @@
/*
* 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.
*/
/*
* This is a minimal set of stubs to compile nvram_manager_test.cpp in
* environments where googletest is not available. The test status output isn't
* as pretty, but good enough to follow test progress and pinpoint test
* failures.
*/
extern "C" {
#include <stdio.h>
} // extern "C"
namespace testing {
// Global test status.
extern bool g_test_status;
#define ASSERT_MSG(cond) \
if (!(cond)) { \
testing::g_test_status = false; \
fprintf(stderr, "Assertion failed: " #cond "\n"); \
return; \
}
#define ASSERT_TRUE(cond) ASSERT_MSG(cond)
#define ASSERT_EQ(expected, actual) ASSERT_MSG(expected == actual)
#define EXPECT_MSG(cond) \
if (!(cond)) { \
testing::g_test_status = false; \
fprintf(stderr, "Expectation failed: " #cond "\n"); \
}
#define EXPECT_TRUE(cond) EXPECTED_MSG(cond)
#define EXPECT_EQ(expected, actual) EXPECT_MSG((expected) == (actual))
#define EXPECT_NE(expected, actual) EXPECT_MSG((expected) != (actual))
// Test fixture base class.
class Test {};
namespace detail {
// A polymorphic wrapper around test instances. This is the base class that
// defines the common interface.
class TestInstanceBase {
public:
virtual ~TestInstanceBase() = default;
virtual void Run() = 0;
};
// Test-specific subclass that holds an instance of the test.
template<typename TestCase>
class TestInstance : public TestInstanceBase {
public:
~TestInstance() override = default;
static TestInstanceBase* Create() {
return new TestInstance<TestCase>;
}
private:
void Run() override {
test_.Run();
}
TestCase test_;
};
struct TestDeclarationBase;
using CreateTestInstanceFunction = TestInstanceBase*(void);
// |TestRegistry| keeps track of all registered tests.
class TestRegistry {
public:
static TestRegistry* instance() { return &g_instance; }
void RunAllTests();
void Register(TestDeclarationBase* test_declaration);
private:
TestDeclarationBase* tests_ = nullptr;
static TestRegistry g_instance;
};
struct TestDeclarationBase {
TestDeclarationBase(const char* name,
CreateTestInstanceFunction* create_function)
: name(name), create_function(create_function) {
TestRegistry::instance()->Register(this);
}
const char* name;
CreateTestInstanceFunction* create_function;
TestDeclarationBase* next;
};
} // namespace detail
// Registers |TestCase| with |TestRegistry|.
template <typename TestCase>
struct TestDeclaration : public detail::TestDeclarationBase {
TestDeclaration(const char* name)
: TestDeclarationBase(name, &detail::TestInstance<TestCase>::Create) {}
};
#define TEST_F(fixture, name) \
class fixture##_##name : public fixture { \
public: \
void Run(); \
}; \
static testing::TestDeclaration<fixture##_##name> \
g_##fixture##_##name##_declaration(#name); \
void fixture##_##name::Run()
} // namespace testing

View file

@ -0,0 +1,25 @@
/*
* 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 <trusty_app_manifest.h>
trusty_app_manifest_t TRUSTY_APP_MANIFEST_ATTRS trusty_app_manifest = {
// UUID: {879419fa-37a8-44a1-b719-504d03b7502a}
{0x879419fa,
0x37a8,
0x44a1,
{0xb7, 0x19, 0x50, 0x4d, 0x03, 0xb7, 0x50, 0x2a}},
};

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,35 @@
#
# 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.
#
LOCAL_DIR := $(GET_LOCAL_DIR)
MODULE := $(LOCAL_DIR)
MODULE_SRCS += \
$(LOCAL_DIR)/nvram_manager_test.cpp \
$(LOCAL_DIR)/fake_storage.cpp \
$(LOCAL_DIR)/gtest_stubs.cpp \
$(LOCAL_DIR)/manifest.c
MODULE_CPPFLAGS := -Wall -Werror -Wextra
MODULE_DEPS += \
app/trusty \
lib/libc-trusty \
system/nvram/core
include make/module.mk