/* 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 #include #include #include #include #include #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; }