// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2022 Google LLC */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define BCC_CONFIG_DESC_SIZE 64 struct boot_measurement { const uint8_t *public_key; size_t public_key_size; uint8_t digest[AVB_SHA256_DIGEST_SIZE]; }; static uint8_t *bcc_handover_buffer; static size_t bcc_handover_buffer_size; static const DiceMode bcc_to_dice_mode[] = { [BCC_MODE_NORMAL] = kDiceModeNormal, [BCC_MODE_MAINTENANCE] = kDiceModeMaintenance, [BCC_MODE_DEBUG] = kDiceModeDebug, }; static void *find_bcc_handover(size_t *size) { struct udevice *dev; struct resource res; /* Probe drivers that provide a BCC handover buffer. */ for (uclass_first_device(UCLASS_DICE, &dev); dev; uclass_next_device(&dev)) { if (!dev_read_resource(dev, 0, &res)) { *size = resource_size(&res); return (void *)res.start; } } return NULL; } int bcc_init(void) { /* Idempotent initialization to allow indepedent client modules. */ if (bcc_handover_buffer && bcc_handover_buffer_size) return 0; /* If a BCC handover wasn't already set, look for a driver. */ bcc_handover_buffer = find_bcc_handover(&bcc_handover_buffer_size); if (!bcc_handover_buffer || !bcc_handover_buffer_size) return -ENOENT; return 0; } void bcc_set_handover(void *handover, size_t handover_size) { bcc_handover_buffer = handover; bcc_handover_buffer_size = handover_size; } void bcc_clear_memory(void *data, size_t size) { DiceClearMemory(NULL, size, data); } static void sha256(const void *data, size_t data_size, uint8_t digest[AVB_SHA256_DIGEST_SIZE]) { sha256_context ctx; sha256_starts(&ctx); sha256_update(&ctx, data, data_size); sha256_finish(&ctx, digest); } /** * Format the VM instance data into a CBOR record. * * The salt is left uninitialized and a pointer to it is returned. * * Measurement = [ * 1: bstr, // public key * 2: bstr, // digest * ] * * InstanceData = { * 1: bstr .size 64, // salt * 2: Measurement, // code * ? 3: Measurement, // config * } */ static int vm_instance_format(const struct boot_measurement *code, const struct boot_measurement *config, size_t buffer_size, uint8_t *buffer, size_t *formatted_size, uint8_t **salt) { const int64_t salt_label = 1; const int64_t code_label = 2; const int64_t config_label = 3; const int64_t public_key_label = 1; const int64_t digest_label = 2; struct CborOut out; if (!code || !formatted_size) return -EINVAL; CborOutInit(buffer, buffer_size, &out); CborWriteMap(/*num_pairs=*/config ? 3 : 1, &out); CborWriteInt(salt_label, &out); *salt = CborAllocBstr(VM_INSTANCE_SALT_SIZE, &out); CborWriteInt(code_label, &out); CborWriteMap(/*num_pairs=*/2, &out); CborWriteInt(public_key_label, &out); CborWriteBstr(code->public_key_size, code->public_key, &out); CborWriteInt(digest_label, &out); CborWriteBstr(AVB_SHA256_DIGEST_SIZE, code->digest, &out); if (config) { CborWriteInt(config_label, &out); CborWriteMap(/*num_pairs=*/2, &out); CborWriteInt(public_key_label, &out); CborWriteBstr(config->public_key_size, config->public_key, &out); CborWriteInt(digest_label, &out); CborWriteBstr(AVB_SHA256_DIGEST_SIZE, config->digest, &out); } *formatted_size = CborOutSize(&out); return CborOutOverflowed(&out) ? -E2BIG : 0; } /** * Verify the measurements are the same as previously recorded for the VM * instance or create a new VM instance with the measurements. * * The salt that is saved with the instance data is added as a hidden input to * bcc_ctx if the measurements don't conflict with the previously recorded * measurements. */ static int vm_instance_verify(const char *iface_str, int devnum, const char *instance_uuid, bool must_exist, const struct boot_measurement *code, const struct boot_measurement *config, uint8_t out_hidden[VM_INSTANCE_SALT_SIZE]) { const uint8_t key_info[] = { 'v', 'm', '-', 'i', 'n', 's', 't', 'a', 'n', 'c', 'e' }; uint8_t *record; uint8_t *record_salt; uint8_t *saved_record; size_t record_size; struct AvbOps *ops; char devnum_str[3]; struct uuid uuid; uint8_t sealing_key[32]; int ret; if (uuid_str_to_bin(instance_uuid, (unsigned char *)&uuid, UUID_STR_FORMAT_STD)) return -EINVAL; /* Measure the formatted record to allocate large enough buffers. */ ret = vm_instance_format(code, config, /*buffer_size=*/0, /*buffer=*/NULL, &record_size, &record_salt); if (ret && ret != -E2BIG) return ret; record = malloc(record_size); if (!record) return -ENOMEM; saved_record = malloc(record_size); if (!saved_record) { free(record); return -ENOMEM; } /* Format the record into the buffer. */ ret = vm_instance_format(code, config, record_size, record, &record_size, &record_salt); if (ret) return ret; snprintf(devnum_str, sizeof(devnum_str), "%d", devnum); ops = avb_ops_alloc(iface_str, devnum_str); if (!ops) { ret = -EINVAL; goto out; } ret = bcc_get_sealing_key(key_info, sizeof(key_info), sealing_key, sizeof(sealing_key)); if (ret) goto out; /* Load any previous data, failing if it's a different size. */ ret = vm_instance_load_entry(ops, &uuid, sealing_key, sizeof(sealing_key), saved_record, record_size); if (ret == 0) { ptrdiff_t salt_offset = record_salt - record; /* * Copy the assumed saved salt so the records will be * byte-for-byte identical if they match. */ memcpy(record_salt, saved_record + salt_offset, VM_INSTANCE_SALT_SIZE); if (memcmp(record, saved_record, record_size) != 0) { log_err("VM instance data mismatch.\n"); ret = -EINVAL; goto out; } ret = BCC_VM_INSTANCE_FOUND; } else if (ret == -ENOENT) { if (must_exist) { log_err("VM instance data not found.\n"); goto out; } /* No previous entry so create a fresh one. */ printf("Creating new VM instance.\n"); ret = vm_instance_new_salt(record_salt); if (ret) goto out; ret = vm_instance_save_entry(ops, &uuid, sealing_key, sizeof(sealing_key), record, record_size); if (ret) { log_err("Failed to create VM instance.\n"); goto out; } ret = BCC_VM_INSTANCE_CREATED; } memcpy(out_hidden, record_salt, VM_INSTANCE_SALT_SIZE); out: avb_ops_free(ops); bcc_clear_memory(sealing_key, sizeof(sealing_key)); bcc_clear_memory(record, record_size); bcc_clear_memory(saved_record, record_size); free(record); free(saved_record); return ret; } static int boot_measurement_from_avb_data(const AvbSlotVerifyData *data, struct boot_measurement *measurement) { const char *partition_name; size_t n; /* * Use the public key of the top-level vbmeta as the authority as this * cannot change. The authority of any chained partitions will be * captured in the vbmeta digest. */ if (avb_find_main_pubkey(data, &measurement->public_key, &measurement->public_key_size) == CMD_RET_FAILURE) return -EINVAL; /* * Assuming we're only trying to load one thing, a top-level vbmeta with * no chained partitions or a single partition that was chained from a * top-level vbmeta. * * Calculate a hash that captures just the code we're interested in * loading and will match: * * avbtool calculate_vbmeta_digest --image .img */ if (data->num_vbmeta_images == 1) partition_name = data->vbmeta_images[0].partition_name; else if (data->num_loaded_partitions == 1) partition_name = data->loaded_partitions[0].partition_name; else return -EINVAL; for (n = 0; n < data->num_vbmeta_images; ++n) { AvbVBMetaData *vbmeta = &data->vbmeta_images[n]; if (strcmp(partition_name, vbmeta->partition_name) == 0) { sha256(vbmeta->vbmeta_data, vbmeta->vbmeta_size, measurement->digest); return 0; } } return -EINVAL; } /* * Format a config descriptor conformaing to the BCC specification. * * BccConfigDescriptor = { * ? -70002 : tstr, ; component name * ? -70003 : int, ; component version * ? -70004 : null, ; resettable * ; Extension fields * -71000: bstr .size 32; ; config digest * } */ DiceResult format_config_descriptor(const char *component_name, const uint8_t config_digest[AVB_SHA256_DIGEST_SIZE], size_t buffer_size, uint8_t *buffer, size_t *actual_size) { const int64_t component_name_label = -70002; const int64_t config_digest_label = -71000; struct CborOut out; CborOutInit(buffer, buffer_size, &out); CborWriteMap(config_digest ? 2 : 1, &out); CborWriteInt(component_name_label, &out); CborWriteTstr(component_name, &out); if (config_digest) { CborWriteInt(config_digest_label, &out); CborWriteBstr(AVB_SHA256_DIGEST_SIZE, config_digest, &out); } *actual_size = CborOutSize(&out); return CborOutOverflowed(&out) ? -E2BIG : 0; } int bcc_vm_instance_handover(const char *iface_str, int devnum, const char *instance_uuid, bool must_exist, const char *component_name, enum bcc_mode bcc_mode, const AvbSlotVerifyData *code_data, const AvbSlotVerifyData *config_data, const void *unverified_config, size_t unverified_config_size) { uint8_t config_desc[BCC_CONFIG_DESC_SIZE]; struct boot_measurement code; struct boot_measurement config = {}; uint8_t *new_handover = NULL; DiceInputValues input_vals; DiceResult res; int instance_ret, ret; /* Continue without the BCC if it wasn't found. */ ret = bcc_init(); if (ret == -ENOENT) return 0; if (ret) return ret; /* For now, only allow one config source. */ if (config_data && unverified_config) return -EINVAL; ret = boot_measurement_from_avb_data(code_data, &code); if (ret) return ret; if (config_data) { ret = boot_measurement_from_avb_data(config_data, &config); if (ret) return ret; /* For now, require the same authority. */ if (config.public_key_size != code.public_key_size || memcmp(config.public_key, code.public_key, code.public_key_size) != 0) return -EINVAL; } else if (unverified_config) { sha256(unverified_config, unverified_config_size, config.digest); } input_vals = (DiceInputValues){ .config_type = kDiceConfigTypeDescriptor, .config_descriptor = config_desc, .mode = bcc_to_dice_mode[bcc_mode], }; memcpy(input_vals.code_hash, code.digest, AVB_SHA256_DIGEST_SIZE); sha256(code.public_key, code.public_key_size, input_vals.authority_hash); ret = format_config_descriptor(component_name, config.digest, sizeof(config_desc), config_desc, &input_vals.config_descriptor_size); if (ret) return ret; instance_ret = vm_instance_verify(iface_str, devnum, instance_uuid, must_exist, &code, config_data ? &config : NULL, input_vals.hidden); if (instance_ret < 0) { ret = instance_ret; log_err("Failed to validate instance.\n"); goto out; } printf("Booting VM instance.\n"); new_handover = calloc(1, bcc_handover_buffer_size); if (!new_handover) return -ENOMEM; res = BccHandoverMainFlow(/*context=*/NULL, bcc_handover_buffer, bcc_handover_buffer_size, &input_vals, bcc_handover_buffer_size, new_handover, NULL); if (res != kDiceResultOk) { ret = -EINVAL; goto out; } /* Update the handover buffer with the new data. */ memcpy(bcc_handover_buffer, new_handover, bcc_handover_buffer_size); out: if (new_handover) { bcc_clear_memory(new_handover, bcc_handover_buffer_size); free(new_handover); } bcc_clear_memory(&input_vals, sizeof(input_vals)); return ret ? ret : instance_ret; } int bcc_vm_instance_avf_boot_state(bool *strict_boot, bool *new_instance) { const void *fdt; int chosen, len; if (!CONFIG_IS_ENABLED(ARM64)) { *strict_boot = false; *new_instance = false; return 0; } fdt = (const void *)env_get_hex("fdtaddr", 0); if (!fdt) return -EINVAL; chosen = fdt_path_offset(fdt, "/chosen"); if (chosen == -FDT_ERR_NOTFOUND) { *strict_boot = false; *new_instance = false; return 0; } if (chosen < 0) return -EINVAL; fdt_getprop(fdt, chosen, "avf,strict-boot", &len); if (len >= 0) *strict_boot = true; else if (len == -FDT_ERR_NOTFOUND) *strict_boot = false; else return -EINVAL; fdt_getprop(fdt, chosen, "avf,new-instance", &len); if (len >= 0) *new_instance = true; else if (len == -FDT_ERR_NOTFOUND) *new_instance = false; else return -EINVAL; return 0; } int bcc_get_sealing_key(const uint8_t *info, size_t info_size, uint8_t *out_key, size_t out_key_size) { const uint64_t cdi_attest_label = 1; const uint64_t cdi_seal_label = 2; struct CborIn in; int64_t label; size_t item_size; const uint8_t *ptr; int ret; /* Make sure initialization is complete. */ ret = bcc_init(); if (ret) return ret; CborInInit(bcc_handover_buffer, bcc_handover_buffer_size, &in); if (CborReadMap(&in, &item_size) != CBOR_READ_RESULT_OK || item_size < 3 || // Read the attestation CDI. CborReadInt(&in, &label) != CBOR_READ_RESULT_OK || label != cdi_attest_label || CborReadBstr(&in, &item_size, &ptr) != CBOR_READ_RESULT_OK || item_size != DICE_CDI_SIZE || // Read the sealing CDI. CborReadInt(&in, &label) != CBOR_READ_RESULT_OK || label != cdi_seal_label || CborReadBstr(&in, &item_size, &ptr) != CBOR_READ_RESULT_OK || item_size != DICE_CDI_SIZE) return -EINVAL; if (!HKDF(out_key, out_key_size, EVP_sha512(), ptr, DICE_CDI_SIZE, /*salt=*/NULL, /*salt_len=*/0, info, info_size)) { return -EIO; } return 0; }