iBoot/lib/image/image4/image4_wrapper.c

1357 lines
42 KiB
C
Raw Permalink Normal View History

2023-07-08 13:03:17 -07:00
/*
* Copyright (C) 2012-2015 Apple Inc. All rights reserved.
*
* This document is the property of Apple Inc.
* It is considered confidential and proprietary.
*
* This document may not be reproduced or transmitted in any form,
* in whole or in part, without the express written permission of
* Apple Inc.
*/
/* Current implementation: Image4 Spec 1.0 */
#include <corecrypto/ccsha1.h>
#include <debug.h>
#include <drivers/aes.h>
#include <Img4Decode.h>
#include <lib/blockdev.h>
#include <lib/cksum.h>
#include <lib/image.h>
#include <lib/nonce.h>
#include <libDER/asn1Types.h>
#include <libDER/DER_Decode.h>
#include <platform.h>
#include <platform/chipid.h>
#include <sys/errno.h>
#include <sys/hash.h>
#include <sys/security.h>
#include <stdlib.h>
#include <string.h>
#include "image4_partial.h"
#include "image4_wrapper.h"
#include <lib/image4_spi.h>
#include <platform/breadcrumbs.h>
#if !WITH_CORECRYPTO
# error "libImg4Decode requires corecrypto integration"
#endif
#define AES_KEY_SIZE_BYTES_256 (32)
#define UNTAG(x) (((x) >> 24) & 0xff),(((x) >> 16) & 0xff),(((x) >> 8) & 0xff),((x) & 0xff)
typedef enum matching_relation {
REL_EQUAL,
REL_GREATER_EQUAL
} matching_relation_t;
struct enviornment_properties {
/* information passed from enviornment to image4 validation code */
uint64_t chip_id;
uint64_t unique_chip_id;
uint64_t chip_epoch;
uint64_t security_domain;
uint64_t board_id;
bool production_status;
bool security_mode;
bool local_boot;
bool verify_manifest_hash;
bool verify_nonce_hash;
uint64_t boot_nonce;
uint8_t boot_manifest_hash[HASH_OUTPUT_SIZE];
bool valid_boot_manifest_hash;
};
struct image_properties {
/* information/actions passed by image4 validation code */
bool demote_production_status;
bool enable_keys;
bool allow_mix_n_match;
bool effective_production_status;
bool valid_effective_production_status;
bool effective_security_mode;
bool valid_effective_security_mode;
uint8_t manifest_hash[HASH_OUTPUT_SIZE];
bool valid_manifest;
bool manifest_hash_verified;
uint8_t object_digest[HASH_OUTPUT_SIZE];
};
struct image4_info {
struct list_node node;
struct blockdev *bdev; /* device the image is on */
off_t devOffset; /* where on the bdev it is */
struct image_info image_info; /* public image_info */
};
struct image4_wrapper_context {
const Img4 *img4; /* parsed image4, from libImage4Decode */
const struct enviornment_properties *env_properties; /* info provided by enviornment */
struct image_properties *img_properties; /* info gathered from parsed image */
};
/*
* image4 callback mechanism used by some targets to copy image4 properties into the
* device tree. See image4_spi.h for additional documentation
*/
static struct {
bool capture_enabled;
image4_start_capture_callback start_cb;
image4_boolean_property_callback bool_cb;
image4_integer_property_callback int_cb;
image4_string_property_callback string_cb;
image4_validity_callback validity_cb;
} image4_callbacks;
struct image4_keybag {
u_int32_t kbSelector;
#define kImage4KeybagSelectorNoKey (0)
#define kImage4KeybagSelectorChipUnique (1)
#define kImage4KeybagSelectorChipUniqueDev (2)
u_int32_t kbKeySize;
u_int8_t kbIVBytes[AES_BLOCK_SIZE];
u_int8_t kbKeyBytes[AES_KEY_SIZE_BYTES_256];
};
static struct list_node images = LIST_INITIAL_VALUE(images);
static int image4_load_copyobject(struct image_info *image_info, void *objectBuffer, size_t objectSize);
static int image4_validate_property_callback_interposer(uint32_t tag, const Img4Property *value, uint32_t propertyType, void *context);
static int image4_validate_property_callback(uint32_t tag, const Img4Property *value, uint32_t propertyType, void *context);
static int image4_load_decrypt_payload(Img4 *img4, void *payloadBuffer, size_t payloadSize);
static const Img4DecodeImplementation *image4_hash_init(void);
// chain_validation in libImg4Decode expects this to be declared by client
const struct ccdigest_info *sha1_digest_info;
const struct ccdigest_info *sha384_digest_info;
/*
* image4_process_superblock
*
* Searches the given bdev looking for image4 images. Returns a count of found
* images.
*
* Image4 objects are in DER format. They are simply concatenated in
* the blockdev, so we need to partially decode each object's header,
* and repeatedly skip over them until we hit an EOC (end-of-content)
* tag or fail to parse.
*/
int
image4_process_superblock(void *sblock, struct blockdev *bdev, off_t offset, uint32_t imageOptions)
{
uint8_t *buf = malloc(IMAGE4_ID_BYTES);
int count;
dprintf(DEBUG_SPEW, "image4_process_superblock\n");
for (count = 0; count < IMAGE_MAX_COUNT; ++count) {
struct image4_info *info;
uint32_t type, size;
off_t residual;
uint32_t buf_bytes;
// Truncate read size to the end of bdev.
if ((uint64_t) offset >= bdev->total_len) {
dprintf(DEBUG_SPEW, "End of bdev\n");
break;
}
residual = bdev->total_len - offset;
// Don't read partial headers
if (residual < IMAGE4_ID_BYTES) {
dprintf(DEBUG_SPEW, "End of bdev\n");
break;
}
buf_bytes = residual;
// Truncate read size to IMAGE4_ID_BYTES.
if (buf_bytes > IMAGE4_ID_BYTES)
buf_bytes = IMAGE4_ID_BYTES;
if (sblock != NULL) {
// If this is the first loop, grab bytes from the
// block already provided by the caller.
memcpy(buf, sblock, buf_bytes);
sblock = NULL;
} else {
// Otherwise, fetch the next header from bdev.
// Rely on bdev to detect out of range reads.
int err = blockdev_read(bdev, buf, offset, buf_bytes);
if (err != IMAGE4_ID_BYTES) {
dprintf(DEBUG_SPEW,
"Error reading image @0x%llx: %d\n",
offset, err);
break;
}
}
// Check if it's an Image4 header and get the type.
if (image4_get_partial(buf, buf_bytes, &type, &size) != 0) {
dprintf(DEBUG_SPEW, "Not Image4 @ 0x%llx\n", offset);
break;
}
// Images claiming to overflow their block device are up to no good
if (size > (uint64_t)residual) {
dprintf(DEBUG_SPEW, "Size overflows block device @ 0x%llx\n", offset);
break;
}
// Prepare image4_info.
info = malloc(sizeof(*info));
info->bdev = bdev;
info->devOffset = offset;
info->image_info.imageLength = size;
info->image_info.imageAllocation = size;
info->image_info.imageType = type;
info->image_info.imagePrivateMagic = IMAGE4_IMAGE_INFO_MAGIC;
info->image_info.imageOptions = imageOptions;
info->image_info.imagePrivate = info;
// Append to the list
list_add_tail(&images, &info->node);
// Advance to the next image.
offset += size;
}
free(buf);
return count;
}
/*
* image4_free_bdev
*
* Frees resources for any previously found images on the specified bdev.
* Useful for starting afresh with a new image superblock.
*/
void
image4_free_bdev(struct blockdev *bdev)
{
struct image4_info *img, *tmp;
list_for_every_entry_safe(&images, img, tmp, struct image4_info, node) {
if (img->image_info.imagePrivateMagic == IMAGE4_IMAGE_INFO_MAGIC &&
img->bdev == bdev) {
list_delete(&img->node);
free(img);
}
}
}
/*
* image4_find
*
* Returns the image_info handle to the first image of the specified type.
*/
struct image_info *
image4_find(u_int32_t image_type)
{
struct image4_info *image;
list_for_every_entry(&images, image, struct image4_info, node) {
if (image->image_info.imageType == image_type)
return(&image->image_info);
}
return(NULL);
}
/*
* image4_load
*
* Loads the image, with the payload ultimately placed at *load_addr and its
* size in *load_len.
*
* It is the caller's responsibility to ensure that the load address is specified
* and that the buffer is large enough (image_info.imageAllocation).
*/
int
image4_load(struct image_info *image_info, const u_int32_t *types, u_int32_t count, u_int32_t *actual, void **load_addr, size_t *load_len)
{
int result = -1;
void *objectBuffer = NULL;
size_t originalObjectSize = 0;
size_t correctedObjectSize = 0;
Img4 img4;
DERReturn ret;
DERItem item;
void *payloadBuffer;
size_t payloadSize;
bool trustedImage = false; /* treat image as untrusted to start with */
bool matchedType;
u_int32_t actualType;
u_int32_t cnt;
struct image4_wrapper_context image4_wrapper_context;
struct enviornment_properties env_properties;
struct image_properties img_properties;
const Img4DecodeImplementation *implementation;
/* basic sanity on arguments */
RELEASE_ASSERT(NULL != image_info);
RELEASE_ASSERT(NULL != load_addr);
RELEASE_ASSERT(NULL != load_len);
/* refuse to operate on an object that is larger than the load buffer */
if (image_info->imageAllocation > *load_len) {
dprintf(DEBUG_INFO, "loaded image too large\n");
platform_record_breadcrumb_int("image4_load_fail", 1);
goto out;
}
/* refuse to operate on an object buffer at zero */
if (NULL == *load_addr) {
dprintf(DEBUG_INFO, "cannot load image with buffer at zero\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4NullPtr);
goto out;
}
/* initialize locals */
bzero((void *)&img_properties, sizeof(img_properties));
/* initialize enviornment object */
bzero((void *)&env_properties, sizeof(env_properties));
env_properties.chip_epoch = (uint64_t)platform_get_hardware_epoch();
env_properties.chip_id = (uint64_t)platform_get_chip_id();
env_properties.board_id = (uint64_t)platform_get_board_id();
env_properties.unique_chip_id = platform_get_ecid_id();
env_properties.security_domain = (uint64_t)platform_get_security_domain();
env_properties.production_status = platform_get_raw_production_mode();
env_properties.security_mode = platform_get_secure_mode();
env_properties.local_boot = ((image_info->imageOptions & IMAGE_OPTION_LOCAL_STORAGE) == IMAGE_OPTION_LOCAL_STORAGE);
env_properties.verify_nonce_hash = ((image_info->imageOptions & IMAGE_OPTION_NEW_TRUST_CHAIN) == IMAGE_OPTION_NEW_TRUST_CHAIN);
env_properties.verify_manifest_hash = !env_properties.verify_nonce_hash && platform_get_mix_n_match_prevention_status();
env_properties.valid_boot_manifest_hash = (platform_get_boot_manifest_hash(env_properties.boot_manifest_hash) == 0);
if (env_properties.verify_nonce_hash && !env_properties.local_boot)
env_properties.boot_nonce = platform_get_nonce();
/* setup for image4 library */
implementation = image4_hash_init();
/*
* Fetch the object into the supplied (presumed safe) buffer.
*/
objectBuffer = *load_addr;
originalObjectSize = image_info->imageAllocation;
if (image4_load_copyobject(image_info, objectBuffer, originalObjectSize))
goto out;
/* if we are just being asked to load the image whole, we're done here */
if ((image_info->imageOptions & IMAGE_OPTION_JUST_LOAD) == IMAGE_OPTION_JUST_LOAD) {
result = 0;
goto out;
}
/* Correct image object length, libImg4Decode expects exact length passed from caller.
* If there are extra bytes at the end, erase them.
*/
if (image4_get_partial((const void *)objectBuffer, IMAGE4_ID_BYTES, NULL, (uint32_t *)&correctedObjectSize) != 0) {
dprintf(DEBUG_INFO, "Error parsing Image4 header\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4NullPtr);
goto out;
}
if (correctedObjectSize > originalObjectSize) {
dprintf(DEBUG_INFO, "Truncated Image4: %u vs %u\n", (unsigned) correctedObjectSize, (unsigned) originalObjectSize);
platform_record_breadcrumb_int("image4_load_fail", kBCImg4Truncated);
goto out;
}
bzero((void *)(objectBuffer + correctedObjectSize), (originalObjectSize - correctedObjectSize));
/*
* Instantiate the Image4 object.
*/
dprintf(DEBUG_SPEW, "instantiating image\n");
ret = Img4DecodeInit((const DERByte *) objectBuffer,
(DERSize) correctedObjectSize,
&img4);
if (ret != DR_Success) {
dprintf(DEBUG_INFO, "image instantiation failed, ret: %d\n", (int)ret);
platform_record_breadcrumb_int("image4_load_fail", kBCImg4DecodeInitFail);
goto out;
}
/*
* Assume that the object's payload type is the actual type.
*/
Img4DecodeGetPayloadType(&img4, &actualType);
dprintf(DEBUG_SPEW, "actualType = %x\n", actualType);
if (image4_callbacks.start_cb && image4_callbacks.start_cb(actualType)) {
// Start a capture -- clear out any existing captured state / validity
if (!image4_callbacks.validity_cb)
panic("image4_callbacks.validity_cb is NULL");
image4_callbacks.validity_cb(false);
image4_callbacks.capture_enabled = true;
} else {
image4_callbacks.capture_enabled = false;
}
/*
* If the caller provided a list of types,
* check if the actual type matches one of
* the types the caller will accept.
*/
if (count != 0) {
matchedType = false;
for (cnt = 0; cnt < count; cnt++) {
dprintf(DEBUG_SPEW, "types[%x] = %x\n", cnt, types[cnt]);
if (types[cnt] == actualType) {
matchedType = true;
break;
}
}
if (!matchedType) {
dprintf(DEBUG_INFO, "image type not accepted by caller\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4ImageTypeMismatch);
goto out;
}
}
/*
* Check if image contains a manifest
*/
ret = Img4DecodeManifestExists(&img4, &img_properties.valid_manifest);
if ((ret != DR_Success) || !img_properties.valid_manifest) {
dprintf(DEBUG_INFO, "failed manifest exists check, ret: %d, contain_manifest: %d\n", (int)ret, img_properties.valid_manifest);
platform_record_breadcrumb_int("image4_load_fail", kBCImg4ManifestInvalid);
goto check_untrusted;
}
/* At this point, we expect img_properties to be updated with useful values */
/*
* Save context info to be passed back to the callbacks from the library
*/
image4_wrapper_context.img4 = (const Img4 *) &img4;
image4_wrapper_context.env_properties = (const struct enviornment_properties *) &env_properties;
image4_wrapper_context.img_properties = &img_properties;
/*
* Image 4 object trust evaluation: steps 7 to 16
*/
ret = Img4DecodePerformTrustEvaluatation(actualType,
&img4,
(Img4DecodeValidatePropertyCallback) image4_validate_property_callback_interposer,
implementation,
(void *) &image4_wrapper_context);
if (ret != DR_Success) {
dprintf(DEBUG_INFO, "trust evalulation failed, ret: %d\n", (int)ret);
platform_record_breadcrumb_int("image4_load_fail", kBCImg4TrustEvalFail);
goto check_untrusted;
}
/*
* Save object manifest hash
*/
RELEASE_ASSERT(Img4DecodeCopyManifestDigest((const Img4 *) &img4, img_properties.manifest_hash, HASH_OUTPUT_SIZE, implementation) == DR_Success);
/*
* Except first boot stage, each boot-stage is required to verify boot manifest hash if mix-n-match prevention is enforced by previous stage.
* If AllowMixNMatch tag found true, it will break the chain of enforcing mix-n-match prevention.
* If previous stage didn't passed a valid boot manifest hash, skip verifying manifest hash, and tag image untrusted.
* First stage: ROM, or iBoot when performing restore from recovery mode or LLB in DFU mode.
*/
if (env_properties.verify_manifest_hash && !img_properties.allow_mix_n_match) {
if (!env_properties.valid_boot_manifest_hash || (0 != memcmp((const void *)env_properties.boot_manifest_hash, (const void *)img_properties.manifest_hash, HASH_OUTPUT_SIZE))) {
dprintf(DEBUG_INFO, "invalid boot manifest hash, or boot manifest hash failed to match\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4BootManifestFail);
goto check_untrusted;
}
img_properties.manifest_hash_verified = true;
}
/*
* Check effective production status still matches current state of the device,
* if effective production status is valid and security mode
*/
if (img_properties.valid_effective_production_status) {
if (img_properties.effective_production_status != security_get_effective_production_status(img_properties.demote_production_status)) {
dprintf(DEBUG_INFO, "effective production status failed to match\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4ProdStatusMismatch);
goto check_untrusted;
}
}
/*
* Check effective secure mode still matches current state of the device,
* if effective secure mode is valid
*/
if (img_properties.valid_effective_security_mode) {
if (img_properties.effective_security_mode != platform_get_secure_mode()) {
dprintf(DEBUG_INFO, "effective security mode failed to match\n");
platform_record_breadcrumb_int("image4_load_fail", kBCImg4SecureModeMismatch);
goto check_untrusted;
}
}
/* Mark image as trusted now */
trustedImage = true;
check_untrusted:
/*
* The image is not signed or signature is invalid or a constraint is not met.
*
* If the image was created with 'enhanced security', this is fatal. Otherwise
* tell the security system and see if it's OK for us to use the image.
*
* Note that the security call may adjust the state of the system in order
* to ensure that it is safe to run untrusted code before replying OK.
*/
if (!trustedImage) {
/* Rollback image and enviornment properites updated by image validation callback */
img_properties.enable_keys = false;
img_properties.demote_production_status = false;
img_properties.effective_production_status = false;
img_properties.effective_security_mode = false;
img_properties.allow_mix_n_match = false;
img_properties.valid_effective_production_status = false;
img_properties.valid_effective_security_mode = false;
#if WITH_UNTRUSTED_EXECUTION_ALLOWED
/* Only ROM allows untrusted execution if boot-config with TEST_MODE is selected and part is fused non-secure ('S' bit cleared) */
if (((image_info->imageOptions & IMAGE_OPTION_REQUIRE_TRUST) == 0) && !platform_get_secure_mode()) {
dprintf(DEBUG_INFO, "untrusted execution allowed, continuing\n");
}
else
#endif
{
/* policy says that we aren't allowed to use un-trusted images */
dprintf(DEBUG_INFO, "untrusted execution not allowed, exiting\n");
goto out;
}
}
/*
* Find the payload.
*/
dprintf(DEBUG_SPEW, "looking for payload\n");
ret = Img4DecodeGetPayload(&img4, &item);
if (ret != DR_Success) {
dprintf(DEBUG_INFO, "Img4DecodeGetPayload failed: %d\n", (int) ret);
platform_record_breadcrumb_int("image4_load_fail", kBCImg4PayloadDecodeFail);
goto out;
}
payloadBuffer = item.data;
payloadSize = item.length;
/*
* Verify that the payload will fit within the output buffer.
*/
if (payloadSize > *load_len) {
dprintf(DEBUG_INFO, "payload size exceeds buffer size\n");
goto out;
}
#if WITH_AES
/*
* If keys are requested to be disabled (untrusted image or trusted image with EKEY set to false),
* don't decrypt it.
*/
if (img_properties.enable_keys) {
/*
* Decrypt the payload data
*/
if (0 != image4_load_decrypt_payload(&img4,
payloadBuffer,
payloadSize)) {
/* the image is encrypted but we can't decrypt it */
platform_record_breadcrumb_int("image4_load_fail", kBCImg4PayloadDecryptFail);
goto out;
}
}
#endif /* WITH_AES */
/*
* All done and OK; copy the payload to the base of the buffer and pass the actual
* payload size back to the caller.
*/
dprintf(DEBUG_SPEW, "copying payload %p->%p\n", payloadBuffer, objectBuffer);
memcpy(objectBuffer, payloadBuffer, payloadSize);
*load_len = payloadSize;
result = 0;
/*
* Clear remains of image4 from the memory.
* NOTE: Currently in ROM, iBoot first and second stage, image4 object as whole is loaded
* at final destination address. Its processed there, then payload from image4 is moved to
* final destination. This assertion is added to catch if this changes in future, and a more
* exhaustive checks will be needed to guarantee image4 related information is cleared from the
* memory besides payload.
*/
RELEASE_ASSERT(objectBuffer < payloadBuffer);
RELEASE_ASSERT(payloadSize < correctedObjectSize);
bzero((uint8_t *)objectBuffer + payloadSize, correctedObjectSize - payloadSize);
/*
* Disable keys if object never requested to enable them or image validation failed.
* Also, tell security system to flag GID and UID access.
*/
if (!img_properties.enable_keys)
security_set_untrusted();
/*
* If the image is trusted, and it has asserted that we should clear the
* production status flag, do that now.
*/
security_set_production_override(img_properties.demote_production_status);
/*
* Save current object manifest hash, it will be boot manifest hash for next stage.
* If untrusted (we failed to find a manifest or manifest is not valid), let next stage know manifest is not valid.
*/
if (trustedImage)
security_set_boot_manifest_hash(img_properties.manifest_hash);
else
security_set_boot_manifest_hash(NULL);
/*
* If ANMN is true and image is trusted, stop enforcing MixNMatch prevention.
* Otherwise, keep enforcing MixNMatch prevention:
* - if current enviornment requires nonce hash and image is trusted
* - if manifest hash is verified which will be true if enviornment started with VerifyManifestHash true, AMNM is false, and image is trusted.
*/
if (!img_properties.allow_mix_n_match &&
((trustedImage && env_properties.verify_nonce_hash) || img_properties.manifest_hash_verified))
security_set_mix_n_match_prevention_status(true);
else
security_set_mix_n_match_prevention_status(false);
/*
* If didn't load from local storage, the fuses must be locked
* (For local storage, the platform can delay locking in some rare cases)
*/
if (!env_properties.local_boot)
security_set_lock_fuses();
/*
* Provide the actual type to the caller.
*/
if (actual != NULL) {
*actual = actualType;
}
out:
/*
* If we failed, nuke the caller's pointers and clear the work buffer.
*/
if (result != 0) {
dprintf(DEBUG_SPEW, "load failed, clearing object buffer\n");
*load_addr = 0;
*load_len = 0;
if (objectBuffer != NULL) {
memset(objectBuffer, 0, originalObjectSize);
}
/*
* If capturing properties, alert caller that the image is invalid
*/
if (image4_callbacks.capture_enabled)
image4_callbacks.validity_cb(false);
}
else {
if (image4_callbacks.capture_enabled)
image4_callbacks.validity_cb(true);
}
return (result);
}
/*
* image4_dump_list
*
* Print the list of currently-known images.
*/
void
image4_dump_list(bool detailed)
{
struct image4_info *image;
list_for_every_entry(&images, image, struct image4_info, node) {
printf("image %p: bdev %p type %c%c%c%c offset 0x%llx",
image,
image->bdev,
UNTAG(image->image_info.imageType),
image->devOffset);
if (image->image_info.imageLength > 0) {
printf(" len 0x%x",
image->image_info.imageLength);
}
printf("\n");
}
}
/*
* image4_check_magic
*
* Verifies img4 header
*/
static int
image4_check_magic(void *buf, size_t len)
{
const DERByte *der = (const DERByte *) buf;
uint32_t der_size;
if (len < IMAGE4_ID_BYTES) {
dprintf(DEBUG_INFO, "Runt header size: %u\n", (unsigned) len);
return -1;
}
if (image4_get_partial(der, IMAGE4_ID_BYTES, NULL, &der_size) != 0) {
dprintf(DEBUG_INFO, "Error parsing Image4 header\n");
return -1;
}
if (der_size > len) {
dprintf(DEBUG_INFO, "Truncated Image4: %u vs %u\n",
(unsigned) der_size, (unsigned) len);
return -1;
}
return 0;
}
/*
* image4_load_copyobject
*
* Fetch an image4 object from its current location into the supplied object buffer.
*/
static int
image4_load_copyobject(struct image_info *image_info, void *objectBuffer, size_t objectSize)
{
struct image4_info *image;
int result;
RELEASE_ASSERT(NULL != objectBuffer);
dprintf(DEBUG_SPEW, "loading image %p with buffer %p/%x\n", image_info, objectBuffer, (unsigned)objectSize);
switch (image_info->imagePrivateMagic) {
case IMAGE4_IMAGE_INFO_MAGIC:
/*
* Fetch the image data from the bdev into the caller-supplied
* scratch buffer.
*/
dprintf(DEBUG_SPEW, "reading image from bdev\n");
image = (struct image4_info *)(image_info->imagePrivate);
result = blockdev_read(image->bdev,
objectBuffer,
image->devOffset,
objectSize);
if (result != (int)objectSize) {
dprintf(DEBUG_CRITICAL, "blockdev read failed with %d\n", result);
return(-1);
}
break;
case IMAGE_MEMORY_INFO_MAGIC:
/*
* Copy the image data from the memory buffer into the
* caller-supplied scratch buffer.
*
* If the caller does not supply a buffer, the image is assumed to be in
* 'trusted' memory already and is left where it is.
*/
if (image4_check_magic(image_info->imagePrivate, objectSize) != 0) {
dprintf(DEBUG_INFO, "image not image4\n");
return(-1);
}
if (objectBuffer != image_info->imagePrivate) {
dprintf(DEBUG_SPEW, "reading image from %p/%x\n", image_info->imagePrivate, (unsigned) objectSize);
memmove(objectBuffer, image_info->imagePrivate, objectSize);
}
break;
default:
dprintf(DEBUG_INFO, "unrecognised image source type %x\n", image_info->imagePrivateMagic);
return(-1);
}
return(0);
}
/* image4_verify_number_relation
*
* Verifies equal or greater equal relation for Number properties
* returns:: DR_Success (0): success, -1: doesn't match required_value, DR_xxx: Sequence decode error
*
*/
static int
image4_verify_number_relation(matching_relation_t relation, DERTag tag, const Img4Property *tag_value_item, uint64_t required_value)
{
DERReturn ret = DR_Success;
uint64_t tag_val = 0;
if ((ret = Img4DecodeGetPropertyInteger64(tag_value_item, tag, &tag_val)) != DR_Success) {
dprintf(DEBUG_INFO, "tag type not integer, ret: %d\n", (int)ret);
goto out;
}
switch (relation) {
case REL_EQUAL:
if (tag_val != required_value)
ret = -1;
break;
case REL_GREATER_EQUAL:
if (tag_val < required_value)
ret = -1;
break;
}
out:
return ret;
}
/* image4_verify_bool_relation
*
* Verifies equal relation for Boolean properties
* returns:: DR_Success (0): success, -1: doesn't match required_value, DR_xxx: Sequence decode error
*
*/
static int
image4_verify_boolean_relation(DERTag tag, const Img4Property *tag_value_item, bool required_value)
{
DERReturn ret = DR_Success;
bool tag_val = false;
if ((ret = Img4DecodeGetPropertyBoolean(tag_value_item, tag, &tag_val)) != DR_Success) {
dprintf(DEBUG_INFO, "tag type not boolean, ret: %d\n", (int)ret);
goto out;
}
if (tag_val != required_value)
ret = -1;
out:
return ret;
}
/* image4_verify_matching_bytes
*
* Verifies bytes matches for Data properties
* returns:: DR_Success (0): success, -1: doesn't match required_value, DR_xxx: Sequence decode error
*
*/
static int
image4_verify_matching_bytes(matching_relation_t relation, DERTag tag, const Img4Property *tag_value_item, const void *required_bytes, size_t required_length)
{
DERReturn ret = -1;
uint8_t *data;
uint32_t length;
if (Img4DecodeGetPropertyData(tag_value_item, tag, &data, &length) != DR_Success) {
dprintf(DEBUG_INFO, "tag type not data, ret: %d\n", (int)ret);
goto out;
}
if (length != required_length) {
goto out;
}
switch(relation) {
case REL_EQUAL:
if (memcmp(data, required_bytes, required_length) != 0)
goto out;
break;
case REL_GREATER_EQUAL:
if (memcmp(data, required_bytes, required_length) < 0)
goto out;
break;
}
ret = DR_Success;
out:
return ret;
}
/*
* image4_validate_property_callback_interposer
*
* To support property capturing, libImg4Decode calls this adapter, which either
* directly calls the real image4_validate_property_callback, or captures the
* value and then calls image4_validate_property_callback.
*/
static int
image4_validate_property_callback_interposer(uint32_t tag, const Img4Property *value, uint32_t propertyType, void *context)
{
/* Captures are only enabled on targets that call
* image4_register_property_capture_callbacks, and the registered type is
* equal to the actualType of the image.
*/
if (image4_callbacks.capture_enabled)
{
switch(value->type) {
case ASN1_BOOLEAN:
if (image4_callbacks.bool_cb) {
bool tag_val;
if (Img4DecodeGetPropertyBoolean(value, tag, &tag_val) == DR_Success) {
image4_callbacks.bool_cb(tag, propertyType == kImg4ObjectProperty, tag_val);
}
}
break;
case ASN1_INTEGER:
if (image4_callbacks.int_cb) {
uint64_t tag_val;
if (Img4DecodeGetPropertyInteger64(value, tag, &tag_val) == DR_Success) {
image4_callbacks.int_cb(tag, propertyType == kImg4ObjectProperty, tag_val);
}
}
break;
case ASN1_OCTET_STRING:
if (image4_callbacks.string_cb) {
uint8_t *data;
uint32_t length;
if (Img4DecodeGetPropertyData(value, tag, &data, &length) == DR_Success) {
image4_callbacks.string_cb(tag, propertyType == kImg4ObjectProperty, data, length);
}
}
break;
default:
panic("Unknown ASN1 type %d\n", value->type);
}
}
return image4_validate_property_callback(tag, value, propertyType, context);
}
/*
* image4_validate_property_callback
*
* libImg4Decode will call this function to validate various
* properties in the enviornment
*/
static int
image4_validate_property_callback(uint32_t tag, const Img4Property *value, uint32_t propertyType, void *context)
{
// <rdar://problem/11960612> ValidatePropertyCallback return type should be DERReturn
// Once this is fixed return more precise errors and for malformed images don't bother executing.
int ret = 0;
const struct image4_wrapper_context *image4_wrapper_context = (const struct image4_wrapper_context *)context;
const struct enviornment_properties *env_properties = image4_wrapper_context->env_properties;
struct image_properties *img_properties = image4_wrapper_context->img_properties;
const Img4 *img4 = image4_wrapper_context->img4;
/* setup for image4 library */
const Img4DecodeImplementation *implementation = image4_hash_init();
switch (propertyType) {
case kImg4ManifestProperty:
switch (tag) {
/* Properties require Equal relation */
case kImg4Tag_ECID: /* Unique Chip ID (Number) */
ret = image4_verify_number_relation(REL_EQUAL,
tag,
value,
env_properties->unique_chip_id);
break;
case kImg4Tag_CHIP: /* Chip ID (Number) */
ret = image4_verify_number_relation(REL_EQUAL,
tag,
value,
env_properties->chip_id);
break;
case kImg4Tag_BORD: /* Board ID (Number) */
ret = image4_verify_number_relation(REL_EQUAL,
tag,
value,
env_properties->board_id);
break;
case kImg4Tag_SDOM: /* Security Domain (Number) */
ret = image4_verify_number_relation(REL_EQUAL,
tag,
value,
env_properties->security_domain);
break;
/* Properties require Greater Equal relation */
case kImg4Tag_CEPO: /* Certificate Epoch (Number) */
ret = image4_verify_number_relation(REL_GREATER_EQUAL,
tag,
value,
env_properties->chip_epoch);
break;
/* Properties require an action */
case kImg4Tag_AMNM: /* Allow Mix-n-Match (Boolean) */
ret = image4_verify_boolean_relation(tag, value, true);
if (ret == 0)
img_properties->allow_mix_n_match = true;
else if (ret == -1)
ret = 0;
break;
/* Properties (Boolean) require must match relation */
case kImg4Tag_CPRO: /* Certificate Production Status (Boolean) */
ret = image4_verify_boolean_relation(tag, value, env_properties->production_status);
break;
case kImg4Tag_CSEC: /* Certificate Security Mode (Boolean) */
ret = image4_verify_boolean_relation(tag, value, env_properties->security_mode);
break;
/* Properties (Data) require must match relation */
case kImg4Tag_BNCH: /* Boot Nonce Hash (Data) */
{
/*
* Default say ok for boot-nonce hash, since its in manifest so it will be called for every object.
* Only verify, when a stage requires verification (in ROM, and in iBoot when doing restore from recovery mode.)
*/
if(env_properties->verify_nonce_hash) {
uint64_t nonce;
uint8_t nonce_hash[HASH_OUTPUT_SIZE];
if (env_properties->local_boot) {
DERItem tag_data;
/* Expects a Boot-nonce in RestoreInfo section */
if ((ret = Img4DecodeGetRestoreInfoData(img4,
kImg4Tag_BNCN,
&tag_data.data,
&tag_data.length)) == DR_Success) {
if (tag_data.length != sizeof(nonce)) {
dprintf(DEBUG_INFO, "BNCN tag_data length mismatch\n");
goto out;
}
memcpy((void *)&nonce, (const void *)tag_data.data, (size_t)tag_data.length);
}
else {
dprintf(DEBUG_INFO, "failed to find RestoreInfo, ret: %d\n", (int)ret);
goto out;
}
}
else {
nonce = env_properties->boot_nonce;
}
hash_calculate((const void *)&nonce, sizeof(nonce), (void *)nonce_hash, sizeof(nonce_hash));
// The boot nonce hash (BNCH) is truncated to NONCE_HASH_OUTPUT_SIZE bytes.
ret = image4_verify_matching_bytes(REL_EQUAL, tag, value, (const void *)&nonce_hash, NONCE_HASH_OUTPUT_SIZE);
}
break;
}
/* Ignore rest */
default:
dprintf(DEBUG_INFO, "Ignoring unknown tag 0x%08x\n", tag);
break;
}
break;
case kImg4ObjectProperty:
switch (tag) {
/* Properties require an action */
case kImg4Tag_DPRO: /* Demote Production Status (Boolean) */
{
ret = image4_verify_boolean_relation(tag, value, true);
if(ret == 0)
img_properties->demote_production_status = true;
else if (ret == -1)
ret = 0;
break;
}
case kImg4Tag_EKEY: /* Enable Keys (Boolean) */
ret = image4_verify_boolean_relation(tag, value, true);
if (ret == 0)
img_properties->enable_keys = true;
else if (ret == -1)
ret = 0;
break;
case kImg4Tag_EPRO: /* Effective Production Status (Boolean) */
img_properties->valid_effective_production_status = true;
ret = image4_verify_boolean_relation(tag, value, true);
/* true: production, false: development */
if (ret == 0)
img_properties->effective_production_status = true;
else if (ret == -1)
ret = 0;
break;
case kImg4Tag_ESEC: /* Effective Security Mode (Boolean) */
img_properties->valid_effective_security_mode = true;
ret = image4_verify_boolean_relation(tag, value, true);
/* true: secure, false: non-secure */
if (ret == 0)
img_properties->effective_security_mode = true;
else if (ret == -1)
ret = 0;
break;
/* Properties require must match relation */
case kImg4Tag_DGST: /* Object Digest (Data) */
{
if ((ret = Img4DecodeCopyPayloadDigest(img4, img_properties->object_digest, HASH_OUTPUT_SIZE, implementation)) != DR_Success) {
dprintf(DEBUG_INFO, "failed to find PayloadHash, ret: %d\n", (int)ret);
goto out;
}
ret = image4_verify_matching_bytes(REL_EQUAL, tag, value, (const void *)img_properties->object_digest, HASH_OUTPUT_SIZE);
break;
}
/* Ignore rest */
default:
dprintf(DEBUG_INFO, "Ignoring unknown tag 0x%08x\n", tag);
break;
}
break;
}
out:
if (ret != 0)
dprintf(DEBUG_INFO, "failed to validate tag 0x%08x, ret:%d\n", tag, ret);
return ret;
}
void image4_register_property_capture_callbacks(image4_start_capture_callback start_cb, image4_boolean_property_callback bool_cb,
image4_integer_property_callback int_cb, image4_string_property_callback string_cb,
image4_validity_callback validity)
{
if (start_cb && !validity)
{
// For security reasons, if the caller is interested in any callbacks for an image type, a validity callback must be provided.
panic("image4_register_property_capture_callbacks: When capturing properties, you must provide a validity callback\n");
}
else
{
image4_callbacks.start_cb = start_cb;
image4_callbacks.bool_cb = bool_cb;
image4_callbacks.int_cb = int_cb;
image4_callbacks.string_cb = string_cb;
image4_callbacks.validity_cb = validity;
}
image4_callbacks.capture_enabled = false;
if (image4_callbacks.validity_cb)
image4_callbacks.validity_cb(false);
dprintf(DEBUG_SPEW, "image4 callbacks registered.\n");
}
static const Img4DecodeImplementation *image4_hash_init(void)
{
#if WITH_SHA2_384
sha384_digest_info = ccsha384_di();
return &kImg4DecodeSecureBootRsa4kSha384;
#else // SHA1
sha1_digest_info = sha1_get_ccsha1_ccdigest_info();
return &kImg4DecodeSecureBootRsa1kSha1;
#endif
}
#if WITH_AES
/*
* image4_load_decrypt_payload
*
* Decrypt the payload contents if they are encrypted and we can find a key
* that matches.
*/
/* Reference Keybag Schema from image4 DER object :
65 116: OCTET STRING, encapsulates {
<30 72>
67 114: SEQUENCE {
<30 37>
69 55: SEQUENCE {
<02 01>
71 1: INTEGER 1
<04 10>
74 16: OCTET STRING
: 48 BA A6 12 92 44 61 67 03 72 2D 59 FA 8D 59 3B
<04 20>
92 32: OCTET STRING
: 30 44 52 34 7F 48 8E BF 7A 63 95 BA 54 6A EE 43
: 87 4A 2B 99 8C 1F 24 7F 64 51 4C E3 0C 6C 4C 52
: }
<30 37>
126 55: SEQUENCE {
<02 01>
128 1: INTEGER 2
<04 10>
131 16: OCTET STRING
: 90 F0 C5 19 3D 08 EA 7D 9B 5E F5 8B 31 EE 7F 9D
<04 20>
149 32: OCTET STRING
: 06 5F 6B 72 92 56 ED FF A2 B5 07 9C 9B E3 A6 40
: D1 B8 FD 87 90 80 BA 89 5C E6 84 84 03 9E DF 24
: }
: }
: }
: }
*/
struct ivkey {
u_int8_t IV[AES_BLOCK_SIZE];
u_int8_t Key[AES_KEY_SIZE_BYTES_256];
} __packed;
static int
image4_load_decrypt_payload(Img4 *img4, void *payloadBuffer, size_t payloadSize)
{
int result;
DERReturn ret;
DERItem keybagDER;
DERDecodedInfo keybagSequenceInfo;
DERSequence keybagSequence;
u_int32_t platformKeyOpts;
u_int32_t keybagKeyOpts;
size_t keybagKeySize;
struct ivkey keybagIVKey;
struct image4_keybag keyBag;
DERDecodedInfo keyDER;
DERSequence keySequence;
DERDecodedInfo info;
/* basic sanity on arguments */
RELEASE_ASSERT(NULL != img4);
/* initialize locals */
result = -1;
memset(&keybagIVKey, 0, sizeof(keybagIVKey));
/*
* Look for payload encryption keybags, see if there is one for us.
*
* If there is, unpack the key and use it and the decryption to move
* the payload to the head of the buffer. If there are keybags but not one
* for us, that's an error.
*
* If there are no keybags or the image was untrusted, just copy the image directly.
*/
dprintf(DEBUG_INFO, "checking for encrypted payload\n");
ret = Img4DecodeGetPayloadKeybag(img4, &keybagDER);
if ((keybagDER.data == NULL) || (keybagDER.length == 0) || (ret == DR_ParamErr)) {
/* there are no keybags, payload is not encrypted */
result = 0;
dprintf(DEBUG_INFO, "Unencrypted payload\n");
goto out;
}
else if (ret != DR_Success) {
dprintf(DEBUG_INFO, "failed to find a valid keybag for this system, ret: %d\n", (int) ret);
goto out;
}
if ((DERDecodeItem(&keybagDER, &keybagSequenceInfo) != DR_Success) ||
(DERDecodeSeqContentInit(&keybagSequenceInfo.content, &keybagSequence) != DR_Success)) {
dprintf(DEBUG_INFO, "PayloadKeybag SEQUENCE header malformed\n");
goto out;
}
for (;;) {
if ((DERDecodeSeqNext(&keybagSequence, &keyDER) != DR_Success) ||
(DERDecodeSeqContentInit(&keyDER.content, &keySequence) != DR_Success)) {
dprintf(DEBUG_INFO, "PayloadKeybag key SEQUENCE header malformed\n");
goto out;
}
if ((DERDecodeSeqNext(&keySequence, &info) != DR_Success) ||
(DERParseInteger(&info.content, &keyBag.kbSelector) != DR_Success)) {
dprintf(DEBUG_INFO, "PayloadKeybag key bad KeySelector\n");
goto out;
}
if (DERDecodeSeqNext(&keySequence, &info) != DR_Success) {
dprintf(DEBUG_INFO, "PayloadKeybag key bad IV\n");
goto out;
}
if (info.content.length != AES_BLOCK_SIZE) {
dprintf(DEBUG_INFO, "IV wrong size: %u but expected %u\n",
(unsigned) info.content.length, (unsigned) AES_BLOCK_SIZE);
goto out;
}
memcpy(&keyBag.kbIVBytes, info.content.data, sizeof(keyBag.kbIVBytes));
if (DERDecodeSeqNext(&keySequence, &info) != DR_Success) {
dprintf(DEBUG_INFO, "PayloadKeybag key bad key\n");
goto out;
}
keyBag.kbKeySize = info.content.length * 8;
if ((keyBag.kbKeySize != 128) && (keyBag.kbKeySize != 192) && (keyBag.kbKeySize != 256)) {
dprintf(DEBUG_INFO, "Unsupported key size: %d\n", keyBag.kbKeySize);
goto out;
}
memcpy((void *)&keyBag.kbKeyBytes, (const void *)info.content.data, sizeof(keyBag.kbKeyBytes));
/* work out how big the key is going to be */
keybagKeyOpts = AES_KEY_TYPE_USER;
switch (keyBag.kbKeySize) {
case 128 : keybagKeyOpts |= AES_KEY_SIZE_128; break;
case 192 : keybagKeyOpts |= AES_KEY_SIZE_192; break;
case 256 : keybagKeyOpts |= AES_KEY_SIZE_256; break;
default:
/* not a valid AES key size, bail */
dprintf(DEBUG_INFO, "AES key size %d not supported/valid\n", keyBag.kbKeySize);
goto out;
}
keybagKeySize = keyBag.kbKeySize / 8;
if (!(keybagKeySize <= sizeof(keybagIVKey.Key)))
goto out;
/* copy the IV from the keybag */
memcpy(&keybagIVKey.IV, keyBag.kbIVBytes, sizeof(keybagIVKey.IV));
/* copy the key from the keybag */
memcpy(&keybagIVKey.Key, keyBag.kbKeyBytes, keybagKeySize);
switch (keyBag.kbSelector) {
case kImage4KeybagSelectorChipUnique:
case kImage4KeybagSelectorChipUniqueDev:
/* ask platform for key details */
platformKeyOpts = 0;
if (platform_translate_key_selector(keyBag.kbSelector, &platformKeyOpts) != 0) {
/* we don't recognise this key, spin and try again */
dprintf(DEBUG_INFO, "key selector %d not recognised\n", keyBag.kbSelector);
continue;
}
dprintf(DEBUG_INFO, "using key selector %d\n", keyBag.kbSelector);
if (aes_cbc_decrypt(&keybagIVKey,
&keybagIVKey,
sizeof(keybagIVKey.IV) + keybagKeySize,
platformKeyOpts,
0,
0)) {
dprintf(DEBUG_INFO, "cannot decrypt image - hardware AES keys disabled\n");
goto out;
}
break;
default:
/*
* Unknown keybag-selector, bail
*/
dprintf(DEBUG_INFO, "unknown keybag-selector\n");
goto out;
}
/*
* Decrypt the payload.
*
* Pad payload size to multiple of 16 bytes (AES_BLOCK_SIZE), if its not padded already.
*/
RELEASE_ASSERT((payloadSize % AES_BLOCK_SIZE) == 0);
dprintf(DEBUG_INFO, "AES operation 0x%zx bytes\n", payloadSize);
result = aes_cbc_decrypt(payloadBuffer,
payloadBuffer,
payloadSize,
keybagKeyOpts,
&keybagIVKey.Key,
&keybagIVKey.IV);
/* clear the iv & key from memory */
memset(&keybagIVKey, 0, sizeof(keybagIVKey));
if (result)
dprintf(DEBUG_INFO, "cannot decrypt image - unexpected AES failure");
goto out;
}
out:
return (result);
}
#endif