blob: 7baab401bdb9c68783dc8b47a84b2fda06aad57f [file] [log] [blame] [edit]
/*
* Copyright (C) 2015-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 "block_device_tipc.h"
#include <errno.h>
#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <lib/system_state/system_state.h>
#include <lib/tipc/tipc.h>
#include <lk/compiler.h>
#include <trusty_ipc.h>
#include <uapi/err.h>
#include <interface/storage/storage.h>
#include <openssl/mem.h>
#include <openssl/rand.h>
#include "block_cache.h"
#include "client_tipc.h"
#include "fs.h"
#include "ipc.h"
#include "rpmb.h"
#include "tipc_ns.h"
#ifdef APP_STORAGE_RPMB_BLOCK_SIZE
#define BLOCK_SIZE_RPMB (APP_STORAGE_RPMB_BLOCK_SIZE)
#else
#define BLOCK_SIZE_RPMB (512)
#endif
#ifdef APP_STORAGE_RPMB_BLOCK_COUNT
#define BLOCK_COUNT_RPMB (APP_STORAGE_RPMB_BLOCK_COUNT)
#else
#define BLOCK_COUNT_RPMB (0) /* Auto detect */
#endif
#ifdef APP_STORAGE_MAIN_BLOCK_SIZE
#define BLOCK_SIZE_MAIN (APP_STORAGE_MAIN_BLOCK_SIZE)
#else
#define BLOCK_SIZE_MAIN (2048)
#endif
/*
* This is here in case we're using an old storageproxyd that does not have
* support for STORAGE_FILE_GET_MAX_SIZE
*/
#ifdef APP_STORAGE_MAIN_BLOCK_COUNT
#define BLOCK_COUNT_MAIN (APP_STORAGE_MAIN_BLOCK_COUNT)
#else
#define BLOCK_COUNT_MAIN (0x10000000000 / BLOCK_SIZE_MAIN)
#endif
#define BLOCK_SIZE_RPMB_BLOCKS (BLOCK_SIZE_RPMB / RPMB_BUF_SIZE)
STATIC_ASSERT(BLOCK_SIZE_RPMB_BLOCKS == 1 || BLOCK_SIZE_RPMB_BLOCKS == 2);
STATIC_ASSERT((BLOCK_SIZE_RPMB_BLOCKS * RPMB_BUF_SIZE) == BLOCK_SIZE_RPMB);
STATIC_ASSERT(BLOCK_COUNT_RPMB == 0 || BLOCK_COUNT_RPMB >= 8);
STATIC_ASSERT(BLOCK_SIZE_MAIN >= 256);
STATIC_ASSERT(BLOCK_COUNT_MAIN >= 8);
STATIC_ASSERT(BLOCK_SIZE_MAIN >= BLOCK_SIZE_RPMB);
/* Ensure that we can fit a superblock + backup in an RPMB block */
STATIC_ASSERT(BLOCK_SIZE_RPMB >= 256);
#define SS_ERR(args...) fprintf(stderr, "ss: " args)
#define SS_WARN(args...) fprintf(stderr, "ss: " args)
#ifdef SS_DATA_DEBUG_IO
#define SS_DBG_IO(args...) fprintf(stdout, "ss: " args)
#else
#define SS_DBG_IO(args...) \
do { \
} while (0)
#endif
const char file_system_id_td[] = "td";
const char file_system_id_tdea[] = "tdea";
const char file_system_id_tdp[] = "tdp";
const char file_system_id_tp[] = "tp";
const char file_system_id_nsp[] = "nsp";
const char ns_filename[] = "0";
const char ns_alternate_filename[] = "alternate/0";
const char tdp_filename[] = "persist/0";
const char nsp_filename[] = "persist/nsp";
struct rpmb_key_derivation_in {
uint8_t prefix[sizeof(struct key)];
uint8_t block_data[RPMB_BUF_SIZE];
};
struct rpmb_key_derivation_out {
struct rpmb_key rpmb_key;
uint8_t unused[sizeof(struct key)];
};
struct rpmb_span {
uint16_t start;
uint16_t block_count;
};
struct rpmb_spans {
struct rpmb_span key;
struct rpmb_span ns;
struct rpmb_span tdp;
/* Start of the rest of the RPMB, which is used for TP and TDEA */
uint16_t rpmb_start;
};
static int rpmb_check(struct rpmb_state* rpmb_state, uint16_t block) {
int ret;
uint8_t tmp[RPMB_BUF_SIZE];
ret = rpmb_read(rpmb_state, tmp, block, 1);
SS_DBG_IO("%s: check rpmb_block %d, ret %d\n", __func__, block, ret);
return ret;
}
static uint32_t rpmb_search_size(struct rpmb_state* rpmb_state, uint16_t hint) {
int ret;
uint32_t low = 0;
uint16_t high = UINT16_MAX;
uint16_t curr = hint ? hint - 1 : UINT16_MAX;
while (low <= high) {
ret = rpmb_check(rpmb_state, curr);
switch (ret) {
case 0:
low = curr + 1;
break;
case -ENOENT:
high = curr - 1;
break;
default:
return 0;
};
if (ret || curr != hint) {
curr = (low + high) / 2;
hint = curr;
} else {
curr = curr + 1;
}
}
assert((uint32_t)high + 1 == low);
return low;
}
static struct block_device_rpmb* dev_rpmb_to_state(struct block_device* dev) {
assert(dev);
return containerof(dev, struct block_device_rpmb, dev);
}
static void block_device_tipc_rpmb_start_read(struct block_device* dev,
data_block_t block) {
int ret;
uint8_t tmp[BLOCK_SIZE_RPMB]; /* TODO: pass data in? */
uint16_t rpmb_block;
struct block_device_rpmb* dev_rpmb = dev_rpmb_to_state(dev);
assert(block < dev->block_count);
rpmb_block = block + dev_rpmb->base;
ret = rpmb_read(dev_rpmb->rpmb_state, tmp,
rpmb_block * BLOCK_SIZE_RPMB_BLOCKS,
BLOCK_SIZE_RPMB_BLOCKS);
SS_DBG_IO("%s: block %" PRIu64 ", base %d, rpmb_block %d, ret %d\n",
__func__, block, dev_rpmb->base, rpmb_block, ret);
block_cache_complete_read(dev, block, tmp, BLOCK_SIZE_RPMB,
ret ? BLOCK_READ_IO_ERROR : BLOCK_READ_SUCCESS);
}
static inline enum block_write_error translate_write_error(int rc) {
switch (rc) {
case 0:
return BLOCK_WRITE_SUCCESS;
case -EUCLEAN:
return BLOCK_WRITE_FAILED_UNKNOWN_STATE;
case ERR_IO:
return BLOCK_WRITE_SYNC_FAILED;
default:
return BLOCK_WRITE_FAILED;
}
}
static void block_device_tipc_rpmb_start_write(struct block_device* dev,
data_block_t block,
const void* data,
size_t data_size,
bool sync) {
int ret;
uint16_t rpmb_block;
struct block_device_rpmb* dev_rpmb = dev_rpmb_to_state(dev);
/* We currently sync every rpmb write. TODO: can we avoid this? */
(void)sync;
assert(data_size == BLOCK_SIZE_RPMB);
assert(block < dev->block_count);
rpmb_block = block + dev_rpmb->base;
ret = rpmb_write(dev_rpmb->rpmb_state, data,
rpmb_block * BLOCK_SIZE_RPMB_BLOCKS,
BLOCK_SIZE_RPMB_BLOCKS, true, dev_rpmb->is_userdata);
SS_DBG_IO("%s: block %" PRIu64 ", base %d, rpmb_block %d, ret %d\n",
__func__, block, dev_rpmb->base, rpmb_block, ret);
block_cache_complete_write(dev, block, translate_write_error(ret));
}
static void block_device_tipc_rpmb_wait_for_io(struct block_device* dev) {
assert(0); /* TODO: use async read/write */
}
static struct block_device_ns* to_block_device_ns(struct block_device* dev) {
assert(dev);
return containerof(dev, struct block_device_ns, dev);
}
static void block_device_tipc_ns_start_read(struct block_device* dev,
data_block_t block) {
int ret;
enum block_read_error res;
uint8_t tmp[BLOCK_SIZE_MAIN]; /* TODO: pass data in? */
struct block_device_ns* dev_ns = to_block_device_ns(dev);
ret = ns_read_pos(dev_ns->ipc_handle, dev_ns->ns_handle,
block * BLOCK_SIZE_MAIN, tmp, BLOCK_SIZE_MAIN);
SS_DBG_IO("%s: block %" PRIu64 ", ret %d\n", __func__, block, ret);
if (ret == 0) {
res = BLOCK_READ_NO_DATA;
} else if (ret == BLOCK_SIZE_MAIN) {
res = BLOCK_READ_SUCCESS;
} else {
res = BLOCK_READ_IO_ERROR;
}
block_cache_complete_read(dev, block, tmp, BLOCK_SIZE_MAIN, res);
}
static void block_device_tipc_ns_start_write(struct block_device* dev,
data_block_t block,
const void* data,
size_t data_size,
bool sync) {
int ret;
enum block_write_error res = BLOCK_WRITE_FAILED;
struct block_device_ns* dev_ns = to_block_device_ns(dev);
assert(data_size == BLOCK_SIZE_MAIN);
ret = ns_write_pos(dev_ns->ipc_handle, dev_ns->ns_handle,
block * BLOCK_SIZE_MAIN, data, data_size,
dev_ns->is_userdata, sync);
SS_DBG_IO("%s: block %" PRIu64 ", ret %d\n", __func__, block, ret);
if (ret == BLOCK_SIZE_MAIN) {
res = BLOCK_WRITE_SUCCESS;
} else if (ret < 0) {
res = translate_write_error(ret);
}
block_cache_complete_write(dev, block, res);
}
static void block_device_tipc_ns_wait_for_io(struct block_device* dev) {
assert(0); /* TODO: use async read/write */
}
static void block_device_tipc_init_dev_rpmb(struct block_device_rpmb* dev_rpmb,
struct rpmb_state* rpmb_state,
uint16_t base,
uint32_t block_count,
bool is_userdata) {
dev_rpmb->dev.start_read = block_device_tipc_rpmb_start_read;
dev_rpmb->dev.start_write = block_device_tipc_rpmb_start_write;
dev_rpmb->dev.wait_for_io = block_device_tipc_rpmb_wait_for_io;
dev_rpmb->dev.block_count = block_count;
dev_rpmb->dev.block_size = BLOCK_SIZE_RPMB;
dev_rpmb->dev.block_num_size = 2;
dev_rpmb->dev.mac_size = 2;
dev_rpmb->dev.tamper_detecting = true;
list_initialize(&dev_rpmb->dev.io_ops);
dev_rpmb->rpmb_state = rpmb_state;
dev_rpmb->base = base;
dev_rpmb->is_userdata = is_userdata;
}
static void block_device_tipc_init_dev_ns(struct block_device_ns* dev_ns,
handle_t ipc_handle,
bool is_userdata) {
dev_ns->dev.start_read = block_device_tipc_ns_start_read;
dev_ns->dev.start_write = block_device_tipc_ns_start_write;
dev_ns->dev.wait_for_io = block_device_tipc_ns_wait_for_io;
dev_ns->dev.block_size = BLOCK_SIZE_MAIN;
dev_ns->dev.block_num_size = sizeof(data_block_t);
dev_ns->dev.mac_size = sizeof(struct mac);
dev_ns->dev.tamper_detecting = false;
list_initialize(&dev_ns->dev.io_ops);
dev_ns->ipc_handle = ipc_handle;
dev_ns->ns_handle = 0; /* Filled in later */
dev_ns->is_userdata = is_userdata;
}
/**
* hwkey_derive_rpmb_key() - Derive rpmb key through hwkey server.
* @session: The hwkey session handle.
* @in: The input data to derive rpmb key.
* @out: The output data from deriving rpmb key.
*
* Return: NO_ERROR on success, error code less than 0 on error.
*/
static int hwkey_derive_rpmb_key(hwkey_session_t session,
const struct rpmb_key_derivation_in* in,
struct rpmb_key_derivation_out* out) {
uint32_t kdf_version = HWKEY_KDF_VERSION_1;
const void* in_buf = in;
void* out_buf = out;
uint32_t key_size = sizeof(*out);
STATIC_ASSERT(sizeof(*in) >= sizeof(*out));
int ret = hwkey_derive(session, &kdf_version, in_buf, out_buf, key_size);
if (ret < 0) {
SS_ERR("%s: failed to get key: %d\n", __func__, ret);
return ret;
}
return NO_ERROR;
}
/**
* block_device_tipc_program_key() - Program a rpmb key derived through hwkey
* server.
* @state: The rpmb state.
* @rpmb_key_part_base: The base of rpmb_key_part in rpmb partition.
* @in The input rpmb key derivation data.
* @out The output rpmb key derivation data.
* @hwkey_session: The hwkey session handle.
*
* Return: NO_ERROR on success, error code less than 0 on error.
*/
static int block_device_tipc_program_key(struct rpmb_state* state,
uint16_t rpmb_key_part_base,
struct rpmb_key_derivation_in* in,
struct rpmb_key_derivation_out* out,
hwkey_session_t hwkey_session) {
int ret;
if (!system_state_provisioning_allowed()) {
ret = ERR_NOT_ALLOWED;
SS_ERR("%s: rpmb key provisioning is not allowed (%d)\n", __func__,
ret);
return ret;
}
STATIC_ASSERT(sizeof(in->block_data) >= sizeof(out->rpmb_key));
RAND_bytes(in->block_data, sizeof(out->rpmb_key.byte));
ret = hwkey_derive_rpmb_key(hwkey_session, in, out);
if (ret < 0) {
SS_ERR("%s: hwkey_derive_rpmb_key failed (%d)\n", __func__, ret);
return ret;
}
ret = rpmb_program_key(state, &out->rpmb_key);
if (ret < 0) {
SS_ERR("%s: rpmb_program_key failed (%d)\n", __func__, ret);
return ret;
}
rpmb_set_key(state, &out->rpmb_key);
ret = rpmb_write(state, in->block_data,
rpmb_key_part_base * BLOCK_SIZE_RPMB_BLOCKS, 1, false,
false);
if (ret < 0) {
SS_ERR("%s: rpmb_write failed (%d)\n", __func__, ret);
return ret;
}
return 0;
}
static int block_device_tipc_derive_rpmb_key(struct rpmb_state* state,
uint16_t rpmb_key_part_base,
hwkey_session_t hwkey_session) {
int ret;
struct rpmb_key_derivation_in in = {
.prefix = {
0x74, 0x68, 0x43, 0x49, 0x2b, 0xa2, 0x4f, 0x77,
0xb0, 0x8e, 0xd1, 0xd4, 0xb7, 0x01, 0x0e, 0xc6,
0x86, 0x4c, 0xa9, 0xe5, 0x28, 0xf0, 0x20, 0xb1,
0xb8, 0x1e, 0x73, 0x3d, 0x8c, 0x9d, 0xb9, 0x96,
}};
struct rpmb_key_derivation_out out;
ret = rpmb_read_no_mac(state, in.block_data,
rpmb_key_part_base * BLOCK_SIZE_RPMB_BLOCKS, 1);
if (ret < 0) {
ret = block_device_tipc_program_key(state, rpmb_key_part_base, &in,
&out, hwkey_session);
if (ret < 0) {
SS_ERR("%s: program_key failed (%d)\n", __func__, ret);
return ret;
}
return 0;
}
ret = hwkey_derive_rpmb_key(hwkey_session, &in, &out);
if (ret < 0) {
SS_ERR("%s: hwkey_derive_rpmb_key failed (%d)\n", __func__, ret);
return ret;
}
rpmb_set_key(state, &out.rpmb_key);
/*
* Validate that the derived rpmb key is correct as we use it to check
* both mac and content of the block_data.
*/
ret = rpmb_verify(state, in.block_data,
rpmb_key_part_base * BLOCK_SIZE_RPMB_BLOCKS, 1);
if (ret < 0) {
SS_ERR("%s: rpmb_verify failed with the derived rpmb key (%d)\n",
__func__, ret);
return ret;
}
return 0;
}
static int block_device_tipc_init_rpmb_key(struct rpmb_state* state,
const struct rpmb_key* rpmb_key,
uint16_t rpmb_key_part_base,
hwkey_session_t hwkey_session) {
int ret = 0;
if (rpmb_key) {
rpmb_set_key(state, rpmb_key);
} else {
ret = block_device_tipc_derive_rpmb_key(state, rpmb_key_part_base,
hwkey_session);
}
return ret;
}
static int set_storage_size(handle_t handle, struct block_device_ns* dev_ns) {
data_block_t sz;
int ret = ns_get_max_size(handle, dev_ns->ns_handle, &sz);
if (ret < 0) {
/* In case we have an old storageproxyd, use default */
if (ret == ERR_NOT_IMPLEMENTED) {
sz = BLOCK_COUNT_MAIN * dev_ns->dev.block_size;
ret = 0;
} else {
SS_ERR("%s: Could not get max size: %d\n", __func__, ret);
return ret;
}
} else if (sz < (dev_ns->dev.block_size * 8)) {
SS_ERR("%s: max storage file size %" PRIu64 " is too small\n", __func__,
sz);
return -1;
}
dev_ns->dev.block_count = sz / dev_ns->dev.block_size;
return ret;
}
static bool block_device_tipc_has_ns(struct block_device_tipc* self) {
return self->dev_ns.dev.block_count;
}
/**
* init_rpmb_fs() - Initialize @self's RPMB fs and its backing block devices.
* @self: The struct block_device_tipc to modify
* @fs_key: The key to use for the filesystem.
* @partition_start: The first RPMB block in the partition to use for this fs.
*
* Return: NO_ERROR on success, error code less than 0 on error.
*/
static int init_rpmb_fs(struct block_device_tipc* self,
const struct key* fs_key,
uint16_t partition_start) {
int ret;
uint32_t rpmb_block_count;
if (BLOCK_COUNT_RPMB) {
rpmb_block_count = BLOCK_COUNT_RPMB;
ret = rpmb_check(self->rpmb_state,
rpmb_block_count * BLOCK_SIZE_RPMB_BLOCKS - 1);
if (ret < 0) {
SS_ERR("%s: bad static rpmb size, %d\n", __func__,
rpmb_block_count);
goto err_bad_rpmb_size;
}
} else {
rpmb_block_count = rpmb_search_size(self->rpmb_state,
0); /* TODO: get hint from ns */
rpmb_block_count /= BLOCK_SIZE_RPMB_BLOCKS;
}
if (rpmb_block_count < partition_start) {
ret = -1;
SS_ERR("%s: bad rpmb size, %d\n", __func__, rpmb_block_count);
goto err_bad_rpmb_size;
}
block_device_tipc_init_dev_rpmb(&self->dev_rpmb, self->rpmb_state,
partition_start,
rpmb_block_count - partition_start, false);
/* TODO: allow non-rpmb based tamper proof storage */
ret = fs_init(&self->tr_state_rpmb, file_system_id_tp, fs_key,
&self->dev_rpmb.dev, &self->dev_rpmb.dev, FS_INIT_FLAGS_NONE);
if (ret < 0) {
SS_ERR("%s: failed to initialize TP: %d\n", __func__, ret);
goto err_init_tr_state_rpmb;
}
return 0;
err_init_tr_state_rpmb:
block_cache_dev_destroy(&self->dev_rpmb.dev);
err_bad_rpmb_size:
return ret;
}
/**
* destroy_rpmb_fs() - Destroy @self's RPMB fs and its backing block devices.
*/
static void destroy_rpmb_fs(struct block_device_tipc* self) {
fs_destroy(&self->tr_state_rpmb);
block_cache_dev_destroy(&self->dev_rpmb.dev);
}
/**
* block_device_ns_open_file() - Open an ns backing file
*
* @self: The ns block device to use to open the file.
* @name: The name of the file to open.
* @create: Whether the file should be created if it doesn't already exist.
*
* Return: NO_ERROR on success, error code less than 0 if an error was
* encountered during initialization.
*/
static int block_device_ns_open_file(struct block_device_ns* self,
const char* name,
bool create) {
return ns_open_file(self->ipc_handle, name, &self->ns_handle, create);
}
/**
* block_device_ns_open_file_with_alternate() - Open an ns backing file,
* possibly falling back to an alternate if the primary is not available.
*
* @self: The ns block device to use to open the file.
* @name: The name of the primary file to open.
* @alternate_name: The name of the alternate file. Ignored if
* STORAGE_NS_ALTERNATE_SUPERBLOCK_ALLOWED is false.
* @create: Whether the file should be created if it doesn't already
* exist.
* @used_alternate: Out-param, set only on successful return. Will tell whether
* the opened file was the alternate.
*
* Return: NO_ERROR on success, error code less than 0 if an error was
* encountered during initialization.
*/
static int block_device_ns_open_file_with_alternate(
struct block_device_ns* self,
const char* name,
const char* alternate_name,
bool create,
bool* used_alternate) {
int ret = block_device_ns_open_file(self, name, create);
if (ret >= 0) {
*used_alternate = false;
return NO_ERROR;
}
#if STORAGE_NS_ALTERNATE_SUPERBLOCK_ALLOWED
ret = block_device_ns_open_file(self, alternate_name, create);
if (ret >= 0) {
*used_alternate = true;
return NO_ERROR;
}
#endif
return ret;
}
enum ns_init_result {
/* Negative codes reserved for other error values. */
NS_INIT_SUCCESS = 0,
NS_INIT_NOT_READY = 1,
};
/**
* init_ns_fs() - Initialize @self's NS fs and its backing block devices.
* @self: The struct block_device_tipc to modify
* @fs_key: The key to use for the filesystem.
* @partition: The RPMB blocks to use for the filesystem's superblocks.
*
* If no ns filesystems are available, return NS_INIT_NOT_READY and leave the NS
* fs uninitialized. (In that case, block_device_tipc_has_ns() will return
* false.)
*
* Return: NS_INIT_SUCCESS on success, NS_INIT_NOT_READY if ns is unavailable,
* or an error code less than 0 if an error was encountered during
* initialization.
*/
static int init_ns_fs(struct block_device_tipc* self,
const struct key* fs_key,
struct rpmb_span partition) {
block_device_tipc_init_dev_ns(&self->dev_ns, self->ipc_handle, true);
bool alternate_data_partition;
int ret = block_device_ns_open_file_with_alternate(
&self->dev_ns, ns_filename, ns_alternate_filename, true,
&alternate_data_partition);
if (ret < 0) {
/* NS not available; init RPMB fs only */
self->dev_ns.dev.block_count = 0;
return NS_INIT_NOT_READY;
}
ret = set_storage_size(self->ipc_handle, &self->dev_ns);
if (ret < 0) {
goto err_get_td_max_size;
}
/* Request empty file system if file is empty */
uint8_t probe;
uint32_t ns_init_flags = FS_INIT_FLAGS_NONE;
ret = ns_read_pos(self->ipc_handle, self->dev_ns.ns_handle, 0, &probe,
sizeof(probe));
if (ret < (int)sizeof(probe)) {
ns_init_flags |= FS_INIT_FLAGS_DO_CLEAR;
}
block_device_tipc_init_dev_rpmb(&self->dev_ns_rpmb, self->rpmb_state,
partition.start, partition.block_count,
true);
#if STORAGE_NS_RECOVERY_CLEAR_ALLOWED
ns_init_flags |= FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED;
#endif
/*
* This must be false if STORAGE_NS_ALTERNATE_SUPERBLOCK_ALLOWED is
* false.
*/
if (alternate_data_partition) {
ns_init_flags |= FS_INIT_FLAGS_ALTERNATE_DATA;
}
ret = fs_init(&self->tr_state_ns, file_system_id_td, fs_key,
&self->dev_ns.dev, &self->dev_ns_rpmb.dev, ns_init_flags);
if (ret < 0) {
SS_ERR("%s: failed to initialize TD: %d\n", __func__, ret);
goto err_init_fs_ns_tr_state;
}
return NS_INIT_SUCCESS;
err_init_fs_ns_tr_state:
block_cache_dev_destroy(&self->dev_ns.dev);
err_get_td_max_size:
ns_close_file(self->ipc_handle, self->dev_ns.ns_handle);
return ret;
}
/**
* destroy_ns_fs() - Destroy @self's NS fs and its backing block devices.
*/
static void destroy_ns_fs(struct block_device_tipc* self) {
fs_destroy(&self->tr_state_ns);
block_cache_dev_destroy(&self->dev_ns.dev);
}
#if HAS_FS_TDP
/**
* init_tdp_fs() - Initialize @self's TDP fs and its backing block devices.
* @self: The struct block_device_tipc to modify
* @fs_key: The key to use for the filesystem.
* @partition: The RPMB blocks to use for the filesystem's superblocks.
*
* Return: NO_ERROR on success, error code less than 0 on error.
*/
static int init_tdp_fs(struct block_device_tipc* self,
const struct key* fs_key,
struct rpmb_span partition) {
block_device_tipc_init_dev_ns(&self->dev_ns_tdp, self->ipc_handle, false);
int ret = block_device_ns_open_file(&self->dev_ns_tdp, tdp_filename, true);
if (ret < 0) {
SS_ERR("%s: failed to open tdp file (%d)\n", __func__, ret);
goto err_open_tdp;
}
ret = set_storage_size(self->ipc_handle, &self->dev_ns_tdp);
if (ret < 0) {
goto err_get_tdp_max_size;
}
block_device_tipc_init_dev_rpmb(&self->dev_ns_tdp_rpmb, self->rpmb_state,
partition.start, partition.block_count,
false);
uint32_t tdp_init_flags = FS_INIT_FLAGS_NONE;
#if STORAGE_TDP_AUTO_CHECKPOINT_ENABLED
if (!system_state_provisioning_allowed()) {
/*
* Automatically create a checkpoint if we are done provisioning but do
* not already have a checkpoint.
*/
tdp_init_flags |= FS_INIT_FLAGS_AUTO_CHECKPOINT;
}
#endif
ret = fs_init(&self->tr_state_ns_tdp, file_system_id_tdp, fs_key,
&self->dev_ns_tdp.dev, &self->dev_ns_tdp_rpmb.dev,
tdp_init_flags);
if (ret < 0) {
goto err_init_fs_ns_tdp_tr_state;
}
#if STORAGE_TDP_RECOVERY_CHECKPOINT_RESTORE_ALLOWED
if (fs_check(&self->tr_state_ns_tdp) == FS_CHECK_INVALID_BLOCK) {
SS_ERR("%s: TDP filesystem check failed with invalid block, "
"attempting to restore checkpoint\n",
__func__);
fs_destroy(&self->tr_state_ns_tdp);
ret = fs_init(&self->tr_state_ns_tdp, file_system_id_tdp, fs_key,
&self->dev_ns_tdp.dev, &self->dev_ns_tdp_rpmb.dev,
tdp_init_flags | FS_INIT_FLAGS_RESTORE_CHECKPOINT);
if (ret < 0) {
SS_ERR("%s: failed to initialize TDP: %d\n", __func__, ret);
goto err_init_fs_ns_tdp_tr_state;
}
}
#endif
return 0;
err_init_fs_ns_tdp_tr_state:
block_cache_dev_destroy(&self->dev_ns_tdp.dev);
err_get_tdp_max_size:
ns_close_file(self->ipc_handle, self->dev_ns_tdp.ns_handle);
err_open_tdp:
return ret;
}
/**
* destroy_tdp_fs() - Destroy @self's TDP fs and its backing block devices.
*/
static void destroy_tdp_fs(struct block_device_tipc* self) {
fs_destroy(&self->tr_state_ns_tdp);
block_cache_dev_destroy(&self->dev_ns_tdp.dev);
}
#endif
#if HAS_FS_NSP
/**
* init_nsp_fs() - Initialize @self's NSP fs and its backing block devices.
* @self: The struct block_device_tipc to modify
* @fs_key: The key to use for the filesystem.
*
* Return: NO_ERROR on success, error code less than 0 on error.
*/
static int init_nsp_fs(struct block_device_tipc* self,
const struct key* fs_key) {
block_device_tipc_init_dev_ns(&self->dev_ns_nsp, self->ipc_handle, false);
int ret = block_device_ns_open_file(&self->dev_ns_nsp, nsp_filename, true);
if (ret < 0) {
SS_ERR("%s: failed to open NSP file (%d)\n", __func__, ret);
goto err_open_nsp;
}
ret = set_storage_size(self->ipc_handle, &self->dev_ns_nsp);
if (ret < 0) {
goto err_get_nsp_max_size;
}
ret = fs_init(&self->tr_state_ns_nsp, file_system_id_nsp, fs_key,
&self->dev_ns_nsp.dev, &self->dev_ns_nsp.dev,
FS_INIT_FLAGS_RECOVERY_CLEAR_ALLOWED |
FS_INIT_FLAGS_ALLOW_TAMPERING);
if (ret < 0) {
SS_ERR("%s: failed to initialize NSP: %d\n", __func__, ret);
goto err_init_fs_ns_nsp_tr_state;
}
/*
* Check that all files are accessible and attempt to clear the FS if files
* cannot be accessed.
*/
if (fs_check(&self->tr_state_ns_nsp) != FS_CHECK_NO_ERROR) {
SS_ERR("%s: NSP filesystem check failed, attempting to clear\n",
__func__);
fs_destroy(&self->tr_state_ns_nsp);
block_cache_dev_destroy(&self->dev_ns_nsp.dev);
ret = fs_init(&self->tr_state_ns_nsp, file_system_id_nsp, fs_key,
&self->dev_ns_nsp.dev, &self->dev_ns_nsp.dev,
FS_INIT_FLAGS_DO_CLEAR | FS_INIT_FLAGS_ALLOW_TAMPERING);
if (ret < 0) {
SS_ERR("%s: failed to initialize NSP: %d\n", __func__, ret);
goto err_init_fs_ns_nsp_tr_state;
}
}
return 0;
err_init_fs_ns_nsp_tr_state:
block_cache_dev_destroy(&self->dev_ns_nsp.dev);
err_get_nsp_max_size:
ns_close_file(self->ipc_handle, self->dev_ns_nsp.ns_handle);
err_open_nsp:
return ret;
}
/**
* destroy_nsp_fs() - Destroy @self's NSP fs and its backing block devices.
*/
static void destroy_nsp_fs(struct block_device_tipc* self) {
fs_destroy(&self->tr_state_ns_nsp);
block_cache_dev_destroy(&self->dev_ns_nsp.dev);
}
#endif
static void block_device_ns_disconnect(struct block_device_ns* self) {
if (self->ipc_handle != INVALID_IPC_HANDLE) {
ns_close_file(self->ipc_handle, self->ns_handle);
self->ipc_handle = INVALID_IPC_HANDLE;
}
}
static int init_ns_backed_filesystems(struct block_device_tipc* self,
const struct key* fs_key,
struct rpmb_span ns_partition,
struct rpmb_span tdp_partition) {
int ret = init_ns_fs(self, fs_key, ns_partition);
if (ret == NS_INIT_NOT_READY) {
/* If we don't currently have ns access, we didn't actually initialize
* `tr_state_ns`. Trying to init any other ns-dependent fs would fail,
* so skip them. */
assert(!block_device_tipc_has_ns(self));
return 0;
} else if (ret < 0) {
goto err_init_ns_fs;
}
#if HAS_FS_TDP
ret = init_tdp_fs(self, fs_key, tdp_partition);
if (ret < 0) {
goto err_init_tdp_fs;
}
#endif
#if HAS_FS_NSP
ret = init_nsp_fs(self, fs_key);
if (ret < 0) {
goto err_init_nsp_fs;
}
#endif
return 0;
#if HAS_FS_NSP
err_init_nsp_fs:
#endif
#if HAS_FS_TDP
block_device_ns_disconnect(&self->dev_ns_tdp);
destroy_tdp_fs(self);
err_init_tdp_fs:
#endif
block_device_ns_disconnect(&self->dev_ns);
destroy_ns_fs(self);
err_init_ns_fs:
return ret;
}
/**
* rpmb_span_end() - Calculates the first block past the end of @self.
*/
static uint16_t rpmb_span_end(struct rpmb_span self) {
return self.start + self.block_count;
}
/**
* calculate_rpmb_spans() - Determines the starts and sizes of RPMB partitions.
*/
static void calculate_rpmb_spans(struct rpmb_spans* out) {
out->key.block_count = 1;
/* Used to store superblocks */
out->ns.block_count = 2;
#if HAS_FS_TDP
out->tdp.block_count = out->ns.block_count;
#else
out->tdp.block_count = 0;
#endif
out->key.start = 0;
out->ns.start = rpmb_span_end(out->key);
out->tdp.start = rpmb_span_end(out->ns);
out->rpmb_start = rpmb_span_end(out->tdp);
}
int block_device_tipc_init(struct block_device_tipc* state,
handle_t ipc_handle,
const struct key* fs_key,
const struct rpmb_key* rpmb_key,
hwkey_session_t hwkey_session) {
int ret;
struct rpmb_spans partitions;
calculate_rpmb_spans(&partitions);
state->ipc_handle = ipc_handle;
/* init rpmb */
ret = rpmb_init(&state->rpmb_state, &state->ipc_handle);
if (ret < 0) {
SS_ERR("%s: rpmb_init failed (%d)\n", __func__, ret);
goto err_rpmb_init;
}
ret = block_device_tipc_init_rpmb_key(state->rpmb_state, rpmb_key,
partitions.key.start, hwkey_session);
if (ret < 0) {
SS_ERR("%s: block_device_tipc_init_rpmb_key failed (%d)\n", __func__,
ret);
goto err_init_rpmb_key;
}
ret = init_rpmb_fs(state, fs_key, partitions.rpmb_start);
if (ret < 0) {
goto err_init_rpmb_fs;
}
ret = init_ns_backed_filesystems(state, fs_key, partitions.ns,
partitions.tdp);
if (ret < 0) {
goto err_init_ns_fs;
}
return 0;
err_init_ns_fs:
destroy_rpmb_fs(state);
err_init_rpmb_fs:
err_init_rpmb_key:
rpmb_uninit(state->rpmb_state);
err_rpmb_init:
return ret;
}
void block_device_tipc_destroy(struct block_device_tipc* state) {
if (block_device_tipc_has_ns(state)) {
#if HAS_FS_NSP
destroy_nsp_fs(state);
#endif
#if HAS_FS_TDP
destroy_tdp_fs(state);
#endif
destroy_ns_fs(state);
}
destroy_rpmb_fs(state);
rpmb_uninit(state->rpmb_state);
}
bool block_device_tipc_fs_connected(struct block_device_tipc* self,
enum storage_filesystem_type fs_type) {
switch (fs_type) {
case STORAGE_TP:
return self->ipc_handle != INVALID_IPC_HANDLE;
case STORAGE_TDEA:
return self->ipc_handle != INVALID_IPC_HANDLE;
case STORAGE_TD:
return block_device_tipc_has_ns(self) &&
self->dev_ns.ipc_handle != INVALID_IPC_HANDLE;
case STORAGE_TDP:
#if HAS_FS_TDP
return block_device_tipc_has_ns(self) &&
self->dev_ns_tdp.ipc_handle != INVALID_IPC_HANDLE;
#else
return block_device_tipc_fs_connected(self, STORAGE_TP);
#endif
case STORAGE_NSP:
#if HAS_FS_NSP
return block_device_tipc_has_ns(self) &&
self->dev_ns_nsp.ipc_handle != INVALID_IPC_HANDLE;
#else
return block_device_tipc_fs_connected(self, STORAGE_TDP);
#endif
case STORAGE_FILESYSTEMS_COUNT:
default:
SS_ERR("%s: Tried to check fs of unrecognized storage_filesystem type: (%d)\n",
__func__, fs_type);
return false;
}
}
struct fs* block_device_tipc_get_fs(struct block_device_tipc* self,
enum storage_filesystem_type fs_type) {
assert(block_device_tipc_fs_connected(self, fs_type));
switch (fs_type) {
case STORAGE_TP:
return &self->tr_state_rpmb;
case STORAGE_TDEA:
return &self->tr_state_rpmb;
case STORAGE_TD:
return &self->tr_state_ns;
case STORAGE_TDP:
#if HAS_FS_TDP
return &self->tr_state_ns_tdp;
#else
return block_device_tipc_get_fs(self, STORAGE_TP);
#endif
case STORAGE_NSP:
#if HAS_FS_NSP
return &self->tr_state_ns_nsp;
#else
return block_device_tipc_get_fs(self, STORAGE_TDP);
#endif
case STORAGE_FILESYSTEMS_COUNT:
default:
SS_ERR("%s: Tried to init fs of unrecognized storage_filesystem type: (%d)\n",
__func__, fs_type);
return NULL;
}
}
int block_device_tipc_reconnect(struct block_device_tipc* self,
handle_t ipc_handle,
const struct key* fs_key) {
int ret;
assert(self->ipc_handle == INVALID_IPC_HANDLE);
/* rpmb_state keeps a pointer to this handle, so updating here will cause
* all the rpmb connections to use the new handle. */
self->ipc_handle = ipc_handle;
bool has_ns = block_device_tipc_has_ns(self);
if (!has_ns) {
struct rpmb_spans partitions;
calculate_rpmb_spans(&partitions);
ret = init_ns_backed_filesystems(self, fs_key, partitions.ns,
partitions.tdp);
if (ret < 0) {
SS_ERR("%s: failed to init NS backed filesystems (%d)\n", __func__,
ret);
return ret;
}
return 0;
}
bool alternate_data_partition;
self->dev_ns.ipc_handle = ipc_handle;
ret = block_device_ns_open_file_with_alternate(&self->dev_ns, ns_filename,
ns_alternate_filename, false,
&alternate_data_partition);
if (ret < 0) {
/* NS not available right now; leave NS filesystems disconnected. */
self->dev_ns.ipc_handle = INVALID_IPC_HANDLE;
SS_ERR("%s: failed to reconnect ns filesystem (%d)\n", __func__, ret);
return 0;
}
assert(alternate_data_partition == self->tr_state_ns.alternate_data);
#if HAS_FS_TDP
self->dev_ns_tdp.ipc_handle = ipc_handle;
ret = block_device_ns_open_file(&self->dev_ns_tdp, tdp_filename, false);
if (ret < 0) {
SS_ERR("%s: failed to reconnect tdp filesystem (%d)\n", __func__, ret);
self->dev_ns_tdp.ipc_handle = INVALID_IPC_HANDLE;
goto err_reconnect_tdp;
}
#endif
#if HAS_FS_NSP
self->dev_ns_nsp.ipc_handle = ipc_handle;
ret = block_device_ns_open_file(&self->dev_ns_nsp, nsp_filename, false);
if (ret < 0) {
SS_ERR("%s: failed to reconnect nsp filesystem (%d)\n", __func__, ret);
self->dev_ns_nsp.ipc_handle = INVALID_IPC_HANDLE;
goto err_reconnect_nsp;
}
#endif
return 0;
#if HAS_FS_NSP
err_reconnect_nsp:
#endif
#if HAS_FS_TDP
block_device_ns_disconnect(&self->dev_ns_tdp);
err_reconnect_tdp:
#endif
block_device_ns_disconnect(&self->dev_ns);
return ret;
}
void block_device_tipc_disconnect(struct block_device_tipc* self) {
/* Must currently be connected to disconnect */
assert(self->ipc_handle != INVALID_IPC_HANDLE);
/* Disconnects rpmb */
self->ipc_handle = INVALID_IPC_HANDLE;
if (block_device_tipc_has_ns(self)) {
block_device_ns_disconnect(&self->dev_ns);
#if HAS_FS_TDP
block_device_ns_disconnect(&self->dev_ns_tdp);
#endif
#if HAS_FS_NSP
block_device_ns_disconnect(&self->dev_ns_nsp);
#endif
}
}