blob: b9f5b720c5c587b45823abbbea164a096a23cbc6 [file] [log] [blame]
/*
* 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 <lib/system_state/system_state.h>
#include <lk/compiler.h>
#include <stdint.h>
#include <string.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 "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
#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);
#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
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)];
};
static int rpmb_check(struct block_device_tipc* state, uint16_t block) {
int ret;
uint8_t tmp[RPMB_BUF_SIZE];
ret = rpmb_read(state->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 block_device_tipc* 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(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->state->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);
}
static void block_device_tipc_rpmb_start_write(struct block_device* dev,
data_block_t block,
const void* data,
size_t data_size) {
int ret;
uint16_t rpmb_block;
struct block_device_rpmb* dev_rpmb = dev_rpmb_to_state(dev);
assert(data_size == BLOCK_SIZE_RPMB);
assert(block < dev->block_count);
rpmb_block = block + dev_rpmb->base;
ret = rpmb_write(dev_rpmb->state->rpmb_state, data,
rpmb_block * BLOCK_SIZE_RPMB_BLOCKS,
BLOCK_SIZE_RPMB_BLOCKS, true);
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, !!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;
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->state->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);
block_cache_complete_read(dev, block, tmp, BLOCK_SIZE_MAIN,
ret != BLOCK_SIZE_MAIN);
}
static void block_device_tipc_ns_start_write(struct block_device* dev,
data_block_t block,
const void* data,
size_t data_size) {
int ret;
struct block_device_ns* dev_ns = to_block_device_ns(dev);
assert(data_size == BLOCK_SIZE_MAIN);
ret = ns_write_pos(dev_ns->state->ipc_handle, dev_ns->ns_handle,
block * BLOCK_SIZE_MAIN, data, data_size);
SS_DBG_IO("%s: block %" PRIu64 ", ret %d\n", __func__, block, ret);
block_cache_complete_write(dev, block, ret != BLOCK_SIZE_MAIN);
}
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 block_device_tipc* state,
uint16_t base,
uint32_t block_count) {
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->state = state;
dev_rpmb->base = base;
}
static void block_device_tipc_init_dev_ns(struct block_device_ns* dev_ns,
struct block_device_tipc* state) {
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_count = BLOCK_COUNT_MAIN;
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->state = state;
dev_ns->ns_handle = 0; /* Filled in later */
}
/**
* 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);
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;
}
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;
bool new_ns_fs;
uint8_t probe;
uint16_t rpmb_key_part_base = 0;
uint32_t rpmb_block_count;
uint32_t rpmb_part_sb_ns_block_count = 2;
/*
* First block is reserved for rpmb key derivation data, whose base is
* rpmb_key_part_base
*/
uint16_t rpmb_part1_base = 1;
uint16_t rpmb_part2_base = rpmb_part1_base + rpmb_part_sb_ns_block_count;
#if HAS_FS_TDP
uint16_t rpmb_part_sb_tdp_base = rpmb_part2_base;
rpmb_part2_base += rpmb_part_sb_ns_block_count;
#endif
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,
rpmb_key_part_base, hwkey_session);
if (ret < 0) {
SS_ERR("%s: block_device_tipc_init_rpmb_key failed (%d)\n", __func__,
ret);
goto err_init_rpmb_key;
}
if (BLOCK_COUNT_RPMB) {
rpmb_block_count = BLOCK_COUNT_RPMB;
ret = rpmb_check(state, rpmb_block_count * BLOCK_SIZE_RPMB_BLOCKS - 1);
if (ret) {
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(state, 0); /* TODO: get hint from ns */
rpmb_block_count /= BLOCK_SIZE_RPMB_BLOCKS;
}
if (rpmb_block_count < rpmb_part2_base) {
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(&state->dev_rpmb, state, rpmb_part2_base,
rpmb_block_count - rpmb_part2_base);
/* TODO: allow non-rpmb based tamper proof storage */
ret = fs_init(&state->tr_state_rpmb, fs_key, &state->dev_rpmb.dev,
&state->dev_rpmb.dev, false);
if (ret < 0) {
goto err_init_tr_state_rpmb;
}
state->fs_rpmb.tr_state = &state->tr_state_rpmb;
ret = client_create_port(&state->fs_rpmb.client_ctx,
STORAGE_CLIENT_TP_PORT);
if (ret < 0) {
goto err_fs_rpmb_create_port;
}
state->fs_rpmb_boot.tr_state = &state->tr_state_rpmb;
ret = client_create_port(&state->fs_rpmb_boot.client_ctx,
STORAGE_CLIENT_TDEA_PORT);
if (ret < 0) {
goto err_fs_rpmb_boot_create_port;
}
block_device_tipc_init_dev_ns(&state->dev_ns, state);
ret = ns_open_file(state->ipc_handle, "0", &state->dev_ns.ns_handle, true);
if (ret < 0) {
/* RPMB fs only */
state->dev_ns.dev.block_count = 0;
return 0;
}
#if HAS_FS_TDP
block_device_tipc_init_dev_ns(&state->dev_ns_tdp, state);
ret = ns_open_file(state->ipc_handle, "persist/0",
&state->dev_ns_tdp.ns_handle, true);
if (ret < 0) {
SS_ERR("%s: failed to open tdp file (%d)\n", __func__, ret);
goto err_open_tdp;
}
state->fs_tdp.tr_state = &state->tr_state_ns_tdp;
block_device_tipc_init_dev_rpmb(&state->dev_ns_tdp_rpmb, state,
rpmb_part_sb_tdp_base,
rpmb_part_sb_ns_block_count);
ret = fs_init(&state->tr_state_ns_tdp, fs_key, &state->dev_ns_tdp.dev,
&state->dev_ns_tdp_rpmb.dev, false /* Don't allow wiping */);
if (ret < 0) {
goto err_init_fs_ns_tdp_tr_state;
}
#else
/*
* Create STORAGE_CLIENT_TDP_PORT alias after we know the backing file for
* STORAGE_CLIENT_TD_PORT is available. On future devices, using HAS_FS_TDP,
* STORAGE_CLIENT_TDP_PORT will not be available when the bootloader is
* running, so we limit access to this alias as well to prevent apps
* developed on old devices from relying on STORAGE_CLIENT_TDP_PORT being
* available early.
*/
state->fs_tdp.tr_state = &state->tr_state_rpmb;
#endif
ret = client_create_port(&state->fs_tdp.client_ctx,
STORAGE_CLIENT_TDP_PORT);
if (ret < 0) {
goto err_fs_rpmb_tdp_create_port;
}
/* Request empty file system if file is empty */
ret = ns_read_pos(state->ipc_handle, state->dev_ns.ns_handle, 0, &probe,
sizeof(probe));
new_ns_fs = ret < (int)sizeof(probe);
state->fs_ns.tr_state = &state->tr_state_ns;
block_device_tipc_init_dev_rpmb(&state->dev_ns_rpmb, state, rpmb_part1_base,
rpmb_part_sb_ns_block_count);
ret = fs_init(&state->tr_state_ns, fs_key, &state->dev_ns.dev,
&state->dev_ns_rpmb.dev, new_ns_fs);
if (ret < 0) {
goto err_init_fs_ns_tr_state;
}
ret = client_create_port(&state->fs_ns.client_ctx, STORAGE_CLIENT_TD_PORT);
if (ret < 0) {
goto err_fs_ns_create_port;
}
return 0;
err_fs_ns_create_port:
fs_destroy(&state->tr_state_ns);
err_init_fs_ns_tr_state:
block_cache_dev_destroy(&state->dev_ns.dev);
ipc_port_destroy(&state->fs_tdp.client_ctx);
err_fs_rpmb_tdp_create_port:
#if HAS_FS_TDP
fs_destroy(&state->tr_state_ns_tdp);
err_init_fs_ns_tdp_tr_state:
block_cache_dev_destroy(&state->dev_ns_tdp.dev);
ns_close_file(state->ipc_handle, state->dev_ns_tdp.ns_handle);
err_open_tdp:
#endif
ns_close_file(state->ipc_handle, state->dev_ns.ns_handle);
ipc_port_destroy(&state->fs_rpmb_boot.client_ctx);
err_fs_rpmb_boot_create_port:
ipc_port_destroy(&state->fs_rpmb.client_ctx);
err_fs_rpmb_create_port:
fs_destroy(&state->tr_state_rpmb);
err_init_tr_state_rpmb:
block_cache_dev_destroy(&state->dev_rpmb.dev);
err_bad_rpmb_size:
err_init_rpmb_key:
rpmb_uninit(state->rpmb_state);
err_rpmb_init:
return ret;
}
void block_device_tipc_uninit(struct block_device_tipc* state) {
if (state->dev_ns.dev.block_count) {
ipc_port_destroy(&state->fs_ns.client_ctx);
fs_destroy(&state->tr_state_ns);
block_cache_dev_destroy(&state->dev_ns.dev);
ns_close_file(state->ipc_handle, state->dev_ns.ns_handle);
ipc_port_destroy(&state->fs_tdp.client_ctx);
#if HAS_FS_TDP
fs_destroy(&state->tr_state_ns_tdp);
block_cache_dev_destroy(&state->dev_ns_tdp.dev);
ns_close_file(state->ipc_handle, state->dev_ns_tdp.ns_handle);
#endif
}
ipc_port_destroy(&state->fs_rpmb_boot.client_ctx);
ipc_port_destroy(&state->fs_rpmb.client_ctx);
fs_destroy(&state->tr_state_rpmb);
block_cache_dev_destroy(&state->dev_rpmb.dev);
rpmb_uninit(state->rpmb_state);
}