365 lines
9.2 KiB
C
365 lines
9.2 KiB
C
/* Copyright 2013-2017 IBM Corp.
|
|
*
|
|
* 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 pr_fmt
|
|
#define pr_fmt(fmt) "STB: " fmt
|
|
#endif
|
|
|
|
#include <skiboot.h>
|
|
#include <string.h>
|
|
#include <opal-api.h>
|
|
#include <chip.h>
|
|
#include <xscom.h>
|
|
#include <inttypes.h>
|
|
#include "secureboot.h"
|
|
#include "cvc.h"
|
|
#include "mbedtls/sha512.h"
|
|
|
|
/*
|
|
* Assembly interfaces to call into the Container Verification Code.
|
|
* func_ptr: CVC base address + offset
|
|
*/
|
|
ROM_response __cvc_verify_v1(void *func_ptr, ROM_container_raw *container,
|
|
ROM_hw_params *params);
|
|
void __cvc_sha512_v1(void *func_ptr, const uint8_t *data, size_t len,
|
|
uint8_t *digest);
|
|
|
|
struct container_verification_code {
|
|
uint64_t start_addr;
|
|
uint64_t end_addr;
|
|
struct list_head service_list;
|
|
};
|
|
|
|
static struct container_verification_code *cvc = NULL;
|
|
static bool softrom = false;
|
|
static void *secure_rom_mem = NULL;
|
|
|
|
static struct dt_node *cvc_resv_mem = NULL;
|
|
static struct dt_node *cvc_node = NULL;
|
|
|
|
struct cvc_service {
|
|
int id;
|
|
uint64_t addr; /* base_addr + offset */
|
|
uint32_t version;
|
|
struct list_node link;
|
|
};
|
|
|
|
static struct {
|
|
enum cvc_service_id id;
|
|
const char *name;
|
|
} cvc_service_map[] = {
|
|
{ CVC_SHA512_SERVICE, "sha512" },
|
|
{ CVC_VERIFY_SERVICE, "verify" },
|
|
};
|
|
|
|
static struct cvc_service *cvc_find_service(enum cvc_service_id id)
|
|
{
|
|
struct cvc_service *service;
|
|
if (!cvc)
|
|
return NULL;
|
|
|
|
list_for_each(&cvc->service_list, service, link) {
|
|
if (service->id == id)
|
|
return service;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static const char *cvc_service_map_name(enum cvc_service_id id)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(cvc_service_map); i++) {
|
|
if (cvc_service_map[i].id == id)
|
|
return cvc_service_map[i].name;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void cvc_register(uint64_t start_addr, uint64_t end_addr)
|
|
{
|
|
if (cvc)
|
|
return;
|
|
|
|
cvc = malloc(sizeof(struct container_verification_code));
|
|
assert(cvc);
|
|
cvc->start_addr = start_addr;
|
|
cvc->end_addr = end_addr;
|
|
list_head_init(&cvc->service_list);
|
|
prlog(PR_INFO, "Found CVC @ %" PRIx64 "-%" PRIx64 "\n",
|
|
start_addr, end_addr);
|
|
}
|
|
|
|
static void cvc_service_register(uint32_t id, uint32_t offset, uint32_t version)
|
|
{
|
|
struct cvc_service *service;
|
|
const char *name;
|
|
|
|
if (!cvc)
|
|
return;
|
|
|
|
/* Service already registered? */
|
|
if (cvc_find_service(id))
|
|
return;
|
|
|
|
if (cvc->start_addr + offset > cvc->end_addr) {
|
|
prlog(PR_WARNING, "CVC service @ %x out of range, "
|
|
"id=%d\n", offset, id);
|
|
return;
|
|
}
|
|
|
|
name = cvc_service_map_name(id);
|
|
if (!name) {
|
|
prlog(PR_ERR, "CVC service %d not supported\n", id);
|
|
return;
|
|
}
|
|
|
|
service = malloc(sizeof(struct cvc_service));
|
|
assert(service);
|
|
service->id = id;
|
|
service->version = version;
|
|
service->addr = cvc->start_addr + offset;
|
|
list_add_tail(&cvc->service_list, &service->link);
|
|
prlog(PR_INFO, "Found CVC-%s @ %" PRIx64 ", version=%d\n",
|
|
name, service->addr, service->version);
|
|
}
|
|
|
|
static int cvc_reserved_mem_init(struct dt_node *parent) {
|
|
struct dt_node *node, *service;
|
|
struct dt_node *reserved_mem;
|
|
uint32_t phandle;
|
|
uint64_t addr, size;
|
|
|
|
reserved_mem = dt_find_by_path(dt_root, "/ibm,hostboot/reserved-memory");
|
|
if (!reserved_mem) {
|
|
prlog(PR_ERR, "/ibm,hostboot/reserved-memory not found\n");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* The container verification code is stored in a hostboot reserved
|
|
* memory which is pointed by the property
|
|
* /ibm,secureboot/ibm,container-verification-code/memory-region
|
|
*/
|
|
dt_for_each_child(parent, node) {
|
|
if (dt_node_is_compatible(node, "ibm,container-verification-code")) {
|
|
phandle = dt_prop_get_u32(node, "memory-region");
|
|
cvc_resv_mem = dt_find_by_phandle(reserved_mem, phandle);
|
|
cvc_node = node;
|
|
break;
|
|
}
|
|
}
|
|
if (!cvc_resv_mem) {
|
|
prlog(PR_ERR, "CVC not found in /ibm,hostboot/reserved-memory\n");
|
|
return -1;
|
|
}
|
|
addr = dt_get_address(cvc_resv_mem, 0, &size);
|
|
cvc_register(addr, addr + size-1);
|
|
|
|
/*
|
|
* Each child of the CVC node describes a CVC service
|
|
*/
|
|
dt_for_each_child(node, service) {
|
|
uint32_t version, offset;
|
|
|
|
version = dt_prop_get_u32(service, "version");
|
|
offset = dt_prop_get_u32(service, "reg");
|
|
|
|
if (dt_node_is_compatible(service, "ibm,cvc-sha512"))
|
|
cvc_service_register(CVC_SHA512_SERVICE, offset, version);
|
|
else if (dt_node_is_compatible(service, "ibm,cvc-verify"))
|
|
cvc_service_register(CVC_VERIFY_SERVICE, offset, version);
|
|
else
|
|
prlog(PR_DEBUG, "unknown %s\n", service->name);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define SECURE_ROM_MEMORY_SIZE (16 * 1024)
|
|
#define SECURE_ROM_XSCOM_ADDRESS 0x02020017
|
|
|
|
#define SECURE_ROM_SHA512_OFFSET 0x20
|
|
#define SECURE_ROM_VERIFY_OFFSET 0x30
|
|
|
|
static int cvc_secure_rom_init(void) {
|
|
const uint32_t reg_addr = SECURE_ROM_XSCOM_ADDRESS;
|
|
uint64_t reg_data;
|
|
struct proc_chip *chip;
|
|
|
|
if (!secure_rom_mem) {
|
|
secure_rom_mem = malloc(SECURE_ROM_MEMORY_SIZE);
|
|
assert(secure_rom_mem);
|
|
}
|
|
/*
|
|
* The logic that contains the ROM within the processor is implemented
|
|
* in a way that it only responds to CI (cache inhibited) operations.
|
|
* Due to performance issues we copy the verification code from the
|
|
* secure ROM to RAM. We use memcpy_from_ci() to do that.
|
|
*/
|
|
chip = next_chip(NULL);
|
|
xscom_read(chip->id, reg_addr, ®_data);
|
|
memcpy_from_ci(secure_rom_mem, (void*) reg_data,
|
|
SECURE_ROM_MEMORY_SIZE);
|
|
cvc_register((uint64_t)secure_rom_mem,
|
|
(uint64_t)secure_rom_mem + SECURE_ROM_MEMORY_SIZE-1);
|
|
cvc_service_register(CVC_SHA512_SERVICE, SECURE_ROM_SHA512_OFFSET, 1);
|
|
cvc_service_register(CVC_VERIFY_SERVICE, SECURE_ROM_VERIFY_OFFSET, 1);
|
|
return 0;
|
|
}
|
|
|
|
void cvc_update_reserved_memory_phandle(void) {
|
|
struct dt_node *reserved_mem;
|
|
|
|
if (!cvc_resv_mem || !cvc_node)
|
|
return;
|
|
|
|
/*
|
|
* The linux documentation, reserved-memory.txt, says that memory-region
|
|
* is a phandle that pairs to a children of /reserved-memory
|
|
*/
|
|
reserved_mem = dt_find_by_path(dt_root, "/reserved-memory");
|
|
if (!reserved_mem) {
|
|
prlog(PR_ERR, "/reserved-memory not found\n");
|
|
return;
|
|
}
|
|
cvc_resv_mem = dt_find_by_name(reserved_mem, cvc_resv_mem->name);
|
|
if (cvc_resv_mem) {
|
|
dt_check_del_prop(cvc_node, "memory-region");
|
|
dt_add_property_cells(cvc_node, "memory-region", cvc_resv_mem->phandle);
|
|
} else {
|
|
prlog(PR_WARNING, "CVC not found in /reserved-memory\n");
|
|
return;
|
|
}
|
|
|
|
cvc_resv_mem = NULL;
|
|
cvc_node = NULL;
|
|
}
|
|
|
|
int cvc_init(void)
|
|
{
|
|
struct dt_node *node;
|
|
int version;
|
|
int rc = 0;
|
|
|
|
if (cvc)
|
|
return 0;
|
|
|
|
node = dt_find_by_path(dt_root, "/ibm,secureboot");
|
|
if (!node)
|
|
return -1;
|
|
|
|
if (!secureboot_is_compatible(node, &version, NULL)) {
|
|
/**
|
|
* @fwts-label CVCNotCompatible
|
|
* @fwts-advice Compatible CVC driver not found. Probably,
|
|
* hostboot/mambo/skiboot has updated the
|
|
* /ibm,secureboot/compatible without adding a driver that
|
|
* supports it.
|
|
*/
|
|
prlog(PR_ERR, "%s FAILED, /ibm,secureboot not compatible.\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
/* Only in P8 the CVC is stored in a secure ROM */
|
|
if (version == IBM_SECUREBOOT_V1 &&
|
|
proc_gen == proc_gen_p8) {
|
|
rc = cvc_secure_rom_init();
|
|
} else if (version == IBM_SECUREBOOT_SOFTROM) {
|
|
softrom = true;
|
|
} else if (version == IBM_SECUREBOOT_V2) {
|
|
rc = cvc_reserved_mem_init(node);
|
|
} else {
|
|
prlog(PR_ERR, "%s FAILED. /ibm,secureboot not supported\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
return rc;
|
|
}
|
|
|
|
int call_cvc_sha512(const uint8_t *data, size_t data_len, uint8_t *digest,
|
|
size_t digest_size)
|
|
{
|
|
struct cvc_service *service;
|
|
|
|
if (!data || !digest || digest_size < SHA512_DIGEST_LENGTH)
|
|
return OPAL_PARAMETER;
|
|
|
|
if (data_len <= 0)
|
|
return OPAL_SUCCESS;
|
|
|
|
memset(digest, 0, SHA512_DIGEST_LENGTH);
|
|
if (softrom) {
|
|
mbedtls_sha512_context ctx;
|
|
mbedtls_sha512_init(&ctx);
|
|
mbedtls_sha512_starts(&ctx, 0); // SHA512 = 0
|
|
mbedtls_sha512_update(&ctx, data, data_len);
|
|
mbedtls_sha512_finish(&ctx, digest);
|
|
mbedtls_sha512_free(&ctx);
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
service = cvc_find_service(CVC_SHA512_SERVICE);
|
|
|
|
if (!service)
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
if (service->version == 1)
|
|
__cvc_sha512_v1((void*) service->addr, data, data_len, digest);
|
|
else
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|
|
|
|
int call_cvc_verify(void *container, size_t len, const void *hw_key_hash,
|
|
size_t hw_key_hash_size, uint64_t *log)
|
|
{
|
|
ROM_hw_params hw_params;
|
|
ROM_response rc;
|
|
struct cvc_service *service;
|
|
|
|
if (!container || len < SECURE_BOOT_HEADERS_SIZE ||
|
|
!hw_key_hash || hw_key_hash_size <= 0)
|
|
return OPAL_PARAMETER;
|
|
|
|
if (softrom)
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
service = cvc_find_service(CVC_VERIFY_SERVICE);
|
|
|
|
if (!service)
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
memset(&hw_params, 0, sizeof(ROM_hw_params));
|
|
memcpy(&hw_params.hw_key_hash, hw_key_hash, hw_key_hash_size);
|
|
|
|
if (service->version == 1)
|
|
rc = __cvc_verify_v1((void*) service->addr,
|
|
(ROM_container_raw*) container,
|
|
&hw_params);
|
|
else
|
|
return OPAL_UNSUPPORTED;
|
|
|
|
if (log)
|
|
*log = hw_params.log;
|
|
|
|
if (rc != ROM_DONE)
|
|
return OPAL_PARTIAL;
|
|
|
|
return OPAL_SUCCESS;
|
|
}
|