iBoot/lib/image/image3/Image3.c

1320 lines
38 KiB
C

/*
* Copyright (C) 2007-2011, 2014 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.
*/
#include "Image3.h"
/* KERNEL is defined when building the AppleMobileFileIntegrityKext */
#ifdef KERNEL
#include <machine/limits.h>
#include <string.h>
#else /* !KERNEL */
#ifdef IMAGE3_DEBUG
#include <fcntl.h>
#endif
#include <string.h>
#include <limits.h>
#include <stdlib.h>
#endif /* !KERNEL */
#include <sys/errno.h>
#ifndef __linux__
#include <machine/endian.h>
#else
#include <endian.h>
#endif
#if (BYTE_ORDER == LITTLE_ENDIAN)
# define HTOLE(x) (x)
# define LETOH(x) (x)
# define HTOLELL(x) (x)
# define LETOHLL(x) (x)
#else
# include <libkern/OSByteOrder.h>
# define HTOLE(x) OSSwapHostToLittleInt32(x)
# define LETOH(x) OSSwapLittleToHostInt32(x)
# define HTOLELL(x) OSSwapHostToLittleInt64(x)
# define LETOHLL(x) OSSwapLittleToHostInt64(x)
#endif
#ifndef offsetof
#if defined(__GNUC__)
#define offsetof(t, d) __builtin_offsetof(t, d)
#else
#error need offsetof definition
#endif
#endif
#ifdef IMAGE3_DEBUG
# ifdef KERNEL
# include <libkern/libkern.h>
# else
# include <stdio.h>
# endif
# define debug(fmt, args...) printf("I3:%s: " fmt "\n", __FUNCTION__ , ##args)
# define UNTAG(x) (((x) >> 24) & 0xff),(((x) >> 16) & 0xff),(((x) >> 8) & 0xff),((x) & 0xff)
#else
# define debug(fmt, args...)
#endif
#include "Image3Format.h"
#define IMAGE3_HASH_SIZE CCSHA1_OUTPUT_SIZE /* SHA-1 hash size */
/*
* Internal handle state
*/
typedef struct _Image3InternalState {
Image3ObjectHeader *image;
u_int32_t flags;
# define kImage3ImageWasInstantiated (1<<0)
# define kImage3ImageWasValidated (1<<1) /* validation has been performed */
# define kImage3ImageIsTrusted (1<<2) /* validation indicates image is trusted */
# define kImage3ImageWasCreated (1<<16)
# define kImage3ImageIsSigned (1<<17) /* signature has been appended */
# define kImage3ImageWasAllocated (1<<18)
size_t allocSize;
#ifdef IMAGE3_CREATOR
int cursor;
int lastTag;
#endif
struct _Image3InternalState *nestedImage;
} Image3InternalState;
/*
* Discards an Image3 working object.
*/
extern void
image3Discard(
Image3ObjectHandle *withHandle)
{
if (withHandle && *withHandle) {
if ((*withHandle)->image && ((*withHandle)->flags & kImage3ImageWasAllocated)) {
debug("discarding image %p", (*withHandle)->image);
image3Free((*withHandle)->image, (*withHandle)->allocSize);
}
debug("discarding handle %p", *withHandle);
image3Free(*withHandle, sizeof(Image3InternalState));
*withHandle = NULL;
}
}
#ifdef IMAGE3_CREATOR
/*
* Create a new Image3 working object.
*
* Returns:
* 0 Success
* ENOMEM Unable to allocate memory
*/
int
image3InstantiateNew(
Image3ObjectHandle *newHandle,
size_t initialAllocation,
u_int32_t imageType)
{
Image3InternalState *state;
int result;
if ((state = image3Malloc(sizeof(*state))) == NULL)
return(ENOMEM);
if (initialAllocation < sizeof(Image3ObjectHeader))
initialAllocation = sizeof(Image3ObjectHeader);
if ((state->image = image3Malloc(initialAllocation)) == NULL) {
image3Free(state, sizeof(*state));
return(ENOMEM);
}
debug("allocated handle %p and initial buffer %p of %u bytes", state, state->image, (unsigned int)initialAllocation);
memset(state->image, 0, sizeof(*state->image));
state->image->ihMagic = HTOLE(kImage3HeaderMagic);
state->image->ihType = HTOLE(imageType);
state->allocSize = initialAllocation;
state->cursor = 0;
state->lastTag = -1;
state->flags = kImage3ImageWasAllocated;
state->nestedImage = NULL;
/*
* Because of the issue described in rdar://problem/6705064, we need to stop embedding a TYPE tag within leaf certificates.
* (Essentially, the TYPE tag in the embbeded cert has precendence over the TYPE tag in the parent image, causing the
* parent image to assume the type of embdded image; i.e., the parent image type "becomes" 'cert'.) As a quick fix, we detect
* whether this new instantiation is for an embedded image by seeing if the requested imageType is for 'cert'. This
* saves plumbing in a new paramter/flag from the caller for now.
*/
if ((imageType != IMAGE_TYPE_EMBEDCERT) && ((result = image3SetTagUnsignedNumber(state, kImage3TagTypeType, imageType)) != 0)) {
image3Free(state->image, initialAllocation);
image3Free(state, sizeof(*state));
return(result);
}
*newHandle = state;
return(0);
}
/*
* Finalises an image.
*
* If signImage is set, hashes and signs the image. Reservation length, if non-zero, indicates that the final signed image
* will include tags not currently present in the image. A partial digest is computed, but the size of the reservation is
* required so that ihSignedLength can be set properly for inclusion in the partial hash state. The partial digest is
* constrained to conclude on a 64 byte boundary by adding the appropriate padding following the last tag in the signed portion
* of a finalised image.
*
* Returns:
* 0 Success
*/
int
image3Finalize(
const Image3ObjectHandle withHandle,
void **objectBuffer,
size_t *objectSize,
bool signImage,
size_t reservationSize)
{
Image3ObjectHeader *hdr;
Image3PartialHash partialHash;
char normalHash[IMAGE3_HASH_SIZE];
void *hashBuffer = NULL;
size_t hashBufferSize;
size_t hashSize;
int result;
void *signedHash;
size_t signedHashSize;
void *certChain;
size_t certChainSize;
size_t paddingSize;
bool createPartialHash = reservationSize != 0;
debug("finalising %p", withHandle);
hdr = withHandle->image;
if (signImage) {
/* pad the image as required to reach a partial hash boundary of 64 bytes */
paddingSize = (64 - ((sizeof(*hdr) - offsetof(Image3ObjectHeader, ihSignedLength) + withHandle->cursor) % 64)) % 64;
if ((result = image3AdvanceCursorWithZeroPad(
withHandle,
paddingSize)) != 0) {
debug("failed to pad image to partial hashing boundary");
return(result);
}
/* update header pointer as the padding above may have reallocated it */
hdr = withHandle->image;
/* sign up to this point, remembering that eventual signed length will cover the reserved area */
hdr->ihSignedLength = HTOLE(withHandle->cursor + reservationSize);
debug("will sign %u bytes for image %p", LETOH(hdr->ihSignedLength), withHandle);
/* generate hash */
hashSize = sizeof(*hdr) - offsetof(Image3ObjectHeader, ihSignedLength) + LETOH(hdr->ihSignedLength) - reservationSize;
if (createPartialHash) {
partialHash.masteredReservationLength = HTOLE(reservationSize);
partialHash.masteredSignedLength = HTOLE(LETOH(hdr->ihSignedLength) - reservationSize);
hashBuffer = &partialHash;
hashBufferSize = sizeof(Image3PartialHash);
image3SHA1Partial(&hdr->ihSignedLength, hashSize, partialHash.sha1_state);
}
else {
hashBuffer = normalHash;
hashBufferSize = IMAGE3_HASH_SIZE;
image3SHA1Generate(&hdr->ihSignedLength, hashSize, hashBuffer);
}
/*
* sign hash & get cert chain; note well that this is the code path for all cases that update the signed length whether
* or not a signature is applied. When computing hashes, for example, execution passes into image3PKISignHash and then
* terminates before returning. Also, in cases supporting authorized installations, the image is finalized, but no actual
* hash or cert tags are appended.
*/
result = image3PKISignHash(
hashBuffer,
hashBufferSize,
&signedHash,
&signedHashSize,
&certChain,
&certChainSize);
if (result) {
debug("hash signing failed");
return(result);
}
debug("signed hash size %u @ %p", (unsigned int)signedHashSize, signedHash);
/* append tags containing signed hash and cert chain if provided */
if (signedHashSize) {
if ((result = image3SetTagStructure(
withHandle,
kImage3TagTypeSignedHash,
signedHash,
signedHashSize,
0)) != 0) {
debug("failed to append signed hash tag");
return(result);
}
}
if (certChainSize) {
if ((result = image3SetTagStructure(
withHandle,
kImage3TagTypeCertificateChain,
certChain,
certChainSize,
0)) != 0) {
debug("failed to append cert chain tag");
return(result);
}
}
/* mark the image as signed and thus immutable */
withHandle->flags |= kImage3ImageIsSigned;
/* update header pointer as the tags above may have reallocated it */
hdr = withHandle->image;
}
/* fill out the remaining header fields */
hdr->ihBufferLength = HTOLE(withHandle->cursor);
hdr->ihSkipDistance = HTOLE(sizeof(*hdr) + withHandle->cursor);
/* hand back the buffer and its final size */
*objectBuffer = hdr;
*objectSize = LETOH(hdr->ihSkipDistance);
return(0);
}
/*
* Set a numeric tag.
*
* Returns:
* 0 Success
* ENOMEM Unable to allocate memory
*/
int
image3SetTagSignedNumber(
const Image3ObjectHandle withHandle,
u_int32_t withTag,
int64_t withValue)
{
Image3TagNumber32 num32;
Image3TagNumber64 num64;
void *num;
int64_t tmp;
size_t size;
debug("setting tag %c%c%c%c to %lld", UNTAG(withTag), withValue);
tmp = (withValue > 0) ? -withValue : withValue;
if ((tmp >> 32) >= -1) {
size = sizeof(num32);
num32.value.s32 = HTOLE(withValue);
num = (void *)&num32;
} else {
size = sizeof(num64);
num64.value.s64 = HTOLELL(withValue);
num = (void *)&num64;
}
return(image3SetTagStructure(withHandle, withTag, num, sizeof(num), 0));
}
int
image3SetTagUnsignedNumber(
const Image3ObjectHandle withHandle,
u_int32_t withTag,
u_int64_t withValue)
{
Image3TagNumber32 num32;
Image3TagNumber64 num64;
void *num;
size_t size;
debug("setting tag %c%c%c%c to %llu", UNTAG(withTag), withValue);
if ((withValue >> 32) == 0) {
size = sizeof(num32);
num32.value.u32 = HTOLE(withValue);
num = (void *)&num32;
} else {
size = sizeof(num64);
num64.value.u64 = HTOLELL(withValue);
num = (void *)&num64;
}
return(image3SetTagStructure(withHandle, withTag, num, size, 0));
}
/*
* Set a string tag.
*
* Returns:
* 0 Success
* ENOMEM Unable to allocate memory
*/
int
image3SetTagString(
const Image3ObjectHandle withHandle,
u_int32_t withTag,
char *withValue)
{
Image3TagString *str;
size_t len;
int result;
debug("setting tag %c%c%c%c to '%s'", UNTAG(withTag), withValue);
len = strlen(withValue);
/* allocate the structure with room for the string */
if ((str = (Image3TagString *)image3Malloc(len + sizeof(*str))) == NULL)
return(ENOMEM);
str->stringLength = len;
memcpy(str->stringBytes, withValue, len);
result = image3SetTagStructure(withHandle, withTag, (void *)str, len + sizeof(*str), 0);
image3Free(str, len + sizeof(*str));
return(result);
}
/*
* Set a structure tag.
*
* Returns:
* 0 Success
* ENOMEM Unable to allocate memory
* EROFS Image is read-only
*/
int
image3SetTagStructure(
const Image3ObjectHandle withHandle,
u_int32_t withTag,
const void *withValue,
size_t withSize,
int withAlignment)
{
Image3ObjectHeader *newHeader;
size_t needAlloc;
Image3TagHeader *tagHeader;
u_int32_t dataCursor;
size_t remainderSize;
debug("setting structure tag %c%c%c%c of %u bytes aligned %d for image %p cursor %u",
UNTAG(withTag), (unsigned int)withSize, withAlignment, withHandle, withHandle->cursor);
/* once an image is signed, it's immutable */
if (withHandle->flags & kImage3ImageIsSigned)
return(EROFS);
/*
* Align the cursor as required. Note that this is fairly hairy, as we have to take the
* image header into account as well, since alignment is relative to the base of the
* image...
*/
if (withAlignment) {
/* forward-align the cursor */
dataCursor = withHandle->cursor + sizeof(*tagHeader) + sizeof(Image3ObjectHeader);
dataCursor += (withAlignment - (withHandle->cursor % withAlignment)) % withAlignment;
withHandle->cursor = dataCursor - sizeof(*tagHeader) - sizeof(Image3ObjectHeader);
/* fix up the skip distance from the previous tag */
if (withHandle->lastTag != -1) {
tagHeader = (Image3TagHeader *)&withHandle->image->ihBuffer[withHandle->lastTag];
tagHeader->itSkipDistance = HTOLE(withHandle->cursor - withHandle->lastTag);
}
debug("doing aligned insertion with tag at %u and data at %u", withHandle->cursor, dataCursor);
}
/* calculate remainder, we want to pad payload size to a multiple of 16 to facilitate AES */
if ((withSize % 16) == 0) {
remainderSize = 0;
}
else {
remainderSize = 16 - (withSize % 16);
}
debug("remainder size is 0x%x", remainderSize, withTag);
/* check to see whether there's room in the buffer and re-allocate to suit */
needAlloc = sizeof(*newHeader) + withHandle->cursor + withSize + remainderSize + sizeof(*tagHeader);
if (needAlloc > withHandle->allocSize) {
debug("need allocation of %u but current allocation only good for %u",
(unsigned int)needAlloc, (unsigned int)withHandle->allocSize);
if ((newHeader = realloc(withHandle->image, needAlloc)) == NULL) {
debug("failed to reallocate buffer to add tag");
return(ENOMEM);
}
debug("image buffer moved %p -> %p", withHandle->image, newHeader);
debug("oldsize was 0x%x, newsize is 0x%x, newsize - oldsize is 0x%x",
withHandle->allocSize, needAlloc, needAlloc - withHandle->allocSize);
withHandle->image = newHeader;
withHandle->allocSize = needAlloc;
}
/* populate the new tag. itBufferLength is intentionally short by remainderSize. */
tagHeader = (Image3TagHeader *)&withHandle->image->ihBuffer[withHandle->cursor];
tagHeader->itTag = HTOLE(withTag);
tagHeader->itBufferLength = HTOLE(withSize);
/* pad payload size to a multiple of 16 to facilitate AES */
tagHeader->itSkipDistance = HTOLE(withSize + remainderSize + sizeof(*tagHeader));
/* and pad skip distance to a multiple of 4 to keep headers aligned */
if (LETOH(tagHeader->itSkipDistance) & 3) {
debug("padding skip distance %u to %u",
LETOH(tagHeader->itSkipDistance),
LETOH(tagHeader->itSkipDistance) + 4 - (LETOH(tagHeader->itSkipDistance) & 3));
tagHeader->itSkipDistance = HTOLE(LETOH(tagHeader->itSkipDistance) + 4 - (LETOH(tagHeader->itSkipDistance) & 3));
}
debug("copying %u bytes of data %p -> %p", (unsigned int)withSize, withValue, tagHeader->itBuffer);
memcpy(tagHeader->itBuffer, withValue, withSize);
debug("padding with %u bytes of zeroes at %p", (unsigned int)remainderSize, tagHeader->itBuffer, tagHeader->itBuffer + withSize);
memset(tagHeader->itBuffer + withSize, 0, remainderSize);
/* move the cursor */
withHandle->lastTag = withHandle->cursor;
withHandle->cursor += LETOH(tagHeader->itSkipDistance);
debug("new cursor is %u", withHandle->cursor);
/* update this so that _image3PrintImage works */
withHandle->image->ihBufferLength = HTOLE(withHandle->cursor);
return(0);
}
/*
* Advances the cursor with zero padding.
*
* Returns:
* 0 Success
* ENOMEM Unable to allocate memory
* EROFS Image is read-only
*/
int
image3AdvanceCursorWithZeroPad(
const Image3ObjectHandle withHandle,
int withSize)
{
Image3ObjectHeader *newHeader;
size_t needAlloc;
Image3TagHeader *tagHeader;
size_t previousCursor;
debug("advancing cursor by %u bytes for image %p cursor %u",
(unsigned int)withSize, withHandle, withHandle->cursor);
/* once an image is signed, it's immutable */
if (withHandle->flags & kImage3ImageIsSigned)
return(EROFS);
/* advance the cursor */
previousCursor = withHandle->cursor;
withHandle->cursor += withSize;
debug("new cursor is %u", withHandle->cursor);
/* fix up the skip distance from the previous tag */
if (withHandle->lastTag != -1) {
tagHeader = (Image3TagHeader *)&withHandle->image->ihBuffer[withHandle->lastTag];
tagHeader->itSkipDistance = HTOLE(withHandle->cursor - withHandle->lastTag);
}
/* check to see whether there's room in the buffer and re-allocate to suit */
needAlloc = sizeof(*newHeader) + withHandle->cursor;
if (needAlloc > withHandle->allocSize) {
debug("need allocation of %u but current allocation only good for %u",
(unsigned int)needAlloc, (unsigned int)withHandle->allocSize);
if ((newHeader = realloc(withHandle->image, needAlloc)) == NULL) {
debug("failed to reallocate buffer to add tag");
return(ENOMEM);
}
debug("image buffer moved %p -> %p", withHandle->image, newHeader);
withHandle->image = newHeader;
withHandle->allocSize = needAlloc;
}
/* zero pad */
bzero(&withHandle->image->ihBuffer[previousCursor], withHandle->cursor - previousCursor);
/* update this so that _image3PrintImage works */
withHandle->image->ihBufferLength = HTOLE(withHandle->cursor);
return(0);
}
#endif /* IMAGE3_CREATOR */
/*
* Take a data buffer that might contain an Image3 object and get a handle to it.
*
* Note that no security validation is performed by this operation.
*
* Returns:
* 0 Success
* ENOMEM Could not allocate handle or buffer
* EINVAL The contents of the buffer are malformed
*/
int
image3InstantiateFromBuffer(
Image3ObjectHandle *newHandle,
const void *fromBuffer,
const size_t bufferSize,
bool copyBuffer)
{
Image3ObjectHeader *hdr;
Image3InternalState *state;
debug("instantiating from buffer %p size %u", fromBuffer, (unsigned int)bufferSize);
/* assume we have a header and do some basic sanity checks */
hdr = (Image3ObjectHeader *)fromBuffer;
if (bufferSize < sizeof(*hdr)) {
debug("buffer size %llu too small for header size %llu", (unsigned long long)bufferSize, (unsigned long long)sizeof(*hdr));
return(EINVAL); /* buffer too small to really contain header */
}
if (LETOH(hdr->ihMagic) != kImage3HeaderMagic) {
debug("bad magic 0x%08x expecting 0x%08x", LETOH(hdr->ihMagic), kImage3HeaderMagic);
return(EINVAL); /* magic must match */
}
if (LETOH(hdr->ihBufferLength) > (bufferSize - sizeof(*hdr))) {
debug("header length %llu too large for buffer length %llu",
(unsigned long long)(LETOH(hdr->ihBufferLength) + sizeof(*hdr)), (unsigned long long)bufferSize);
return(EINVAL); /* container is too big for buffer */
}
if (LETOH(hdr->ihSignedLength) > LETOH(hdr->ihBufferLength)) {
debug("signed legnth %u too large for buffer length %u",
LETOH(hdr->ihSignedLength), LETOH(hdr->ihBufferLength));
return(EINVAL); /* signed length is too large */
}
if ((LETOH(hdr->ihBufferLength) + sizeof(*hdr)) > LETOH(hdr->ihSkipDistance)) {
debug("skip distance %llu too short for buffer length %llu",
(unsigned long long)(LETOH(hdr->ihBufferLength) + sizeof(*hdr)), (unsigned long long)LETOH(hdr->ihSkipDistance));
return(EINVAL); /* skip distance falls into container */
}
/* the buffer looks OK, allocate our state */
if ((state = image3Malloc(sizeof(*state))) == NULL) {
debug("failed to allocate state");
return(ENOMEM);
}
state->flags = kImage3ImageWasInstantiated;
state->nestedImage = NULL;
if (LETOH(hdr->ihSignedLength) > 0) {
debug("image claims to be signed, marking immutable");
state->flags |= kImage3ImageIsSigned;
} else {
#ifdef IMAGE3_CREATOR
Image3TagHeader *tagHeader;
u_int32_t tagCursor;
/* since we have to be able to realloc, copy to an allocated buffer */
copyBuffer = true;
/*
* To get lastTag set correctly, we have to walk the tags in the image.
*
* Note that this is only done in read/write tools and only if the image
* is not signed - in this case we are not concerned with security.
*/
state->cursor = LETOH(hdr->ihBufferLength);
state->lastTag = -1;
if (LETOH(hdr->ihBufferLength) > 0) {
tagCursor = 0;
for (;;) {
tagHeader = (Image3TagHeader *)&hdr->ihBuffer[tagCursor];
/* if the tag points past the end of the image, it's corrupt */
/* if the skip distance is to small, it's corrupt */
if ((tagCursor + LETOH(tagHeader->itSkipDistance)) > LETOH(hdr->ihBufferLength)) {
image3Free(state, sizeof(*state));
debug("tag skip distance %u moves cursor %u outside buffer length %u",
LETOH(tagHeader->itSkipDistance), tagCursor, LETOH(hdr->ihBufferLength));
return(EINVAL);
}
if ((LETOH(tagHeader->itSkipDistance) < sizeof(*tagHeader))) {
image3Free(state, sizeof(*state));
debug("tag skip distance %u outside too small", LETOH(tagHeader->itSkipDistance));
return(EINVAL);
}
/* if the tag points precisely to the end of the image, we're done */
if ((tagCursor + LETOH(tagHeader->itSkipDistance)) == LETOH(hdr->ihBufferLength)) {
state->lastTag = tagCursor;
break;
}
/* skip to the next tag */
tagCursor += LETOH(tagHeader->itSkipDistance);
}
}
#endif
}
/* if we were asked, or forced, copy the buffer */
if (copyBuffer) {
debug("image mutable or copy requested");
state->allocSize = LETOH(hdr->ihBufferLength) + sizeof(*hdr);
if ((state->image = image3Malloc(state->allocSize)) == NULL) {
image3Free(state, sizeof(*state));
debug("failed to allocate memory for image copy");
return(ENOMEM);
}
memcpy(state->image, fromBuffer, state->allocSize);
state->flags |= kImage3ImageWasAllocated;
} else {
state->image = hdr;
state->allocSize = bufferSize;
}
*newHandle = state;
return(0);
}
/*
* Validate the signature on an Image3 object.
*
* Note that images from untrusted sources should be handled with *extreme caution*
* until this operation has reported success.
*
* We assume that the ihBufferLength and ihSignedLength fields in the image header
* have already been sanity-checked.
*
* If the object was loaded from local storage where it was expected that it was
* personalised for the device (normally the case) then the kImage3ValidateLocalStorage
* option should be supplied.
*
* Returns:
* 0 Success.
* EINVAL The contents of the buffer are malformed
* ENOMEM Could not allocate working memory
* EPERM Security validation failed
* Other errors may be returned by the PKI or AES infrastructure.
*
* Note to auditors:
* -----------------
* This code is used on images that have been processed by image3InstantiateFromBuffer.
* As such, it makes the assumption that header fields that are validated there may be
* trusted. In particular, ihBufferLength is trusted to lie within the caller's buffer,
* and ihSignedLength is trusted to be less than or equal to ihBufferLength.
*/
int
image3ValidateSignature(
Image3ObjectHandle withHandle,
u_int32_t expectedType,
u_int32_t validationOptions,
bool *validatedWithEmbeddedSignature)
{
Image3ObjectHeader *hdr;
Image3TagHeader *hashTag;
Image3TagHeader *certTag;
u_int32_t tagCursor;
char hashBuffer[IMAGE3_HASH_SIZE];
size_t hashSize;
void *certCustomData;
size_t certCustomDataLength;
int result;
uint8_t *base;
size_t len;
debug("validating signature on image %p", withHandle);
/* do we already know whether the image is trusted? */
if (withHandle->flags & kImage3ImageWasValidated) {
debug("returning cached result %d", (withHandle->flags & kImage3ImageIsTrusted) ? 0 : EPERM);
return((withHandle->flags & kImage3ImageIsTrusted) ? 0 : EPERM);
}
/* image can be altered below, no second chances */
withHandle->flags |= kImage3ImageWasValidated;
/* if the image is not signed, it's not trusted */
if (!(withHandle->flags & kImage3ImageIsSigned)) {
debug("image is not signed, cannot be considered trusted");
return(EPERM);
}
hdr = withHandle->image;
/* verify that the image is large enough to contain at least one tag header */
if (LETOH(hdr->ihBufferLength) < sizeof(*hashTag)) {
debug("image too small to be signed");
return(EINVAL);
}
/*
* Indirect (ticket) image signature validation.
*/
/*
* If the image has been personalised by the addition of an ECID in
* the signed range, we need to back that out before generating the
* hash for ticket purposes.
*
* This is transitional, and it can eventually be removed out once
* ticketting is the norm.
*
* In the case of ticket validation after backing out an ECID tag, we
* need to zero out the ECID tag after validation to prevent leakage
* of untrusted data. If ticket validation fails and we fall back to
* embedded image signature validation, we have to keep the ECID tag
*/
uint32_t savedLength;
Image3TagHeader *ecidTag;
uint32_t rewindCount;
/* save the signed length - we might overwrite it here, but we'll need
to restore it if ticket validation fails and we fall back to embedded
image signature validation */
savedLength = LETOH(hdr->ihSignedLength);
/* look backwards to see if the last thing in the signed range is an ECID tag */
tagCursor = savedLength;
if (tagCursor > (sizeof(Image3TagHeader) + sizeof(Image3TagNumber64))) {
/* back up to the highest possible address at which we might find the ECID tag */
tagCursor -= sizeof(Image3TagHeader) + sizeof(Image3TagNumber64);
/* due to padding, we may have to go back up to 64 bytes further to find the ECID tag */
rewindCount = 0;
do {
ecidTag = (Image3TagHeader *)&hdr->ihBuffer[tagCursor];
if ((ecidTag->itTag == kImage3TagTypeUniqueID) &&
(ecidTag->itBufferLength == sizeof(Image3TagNumber64))) {
/* this looks like a valid ECID tag - back the signed length up to avoid it */
hdr->ihSignedLength = HTOLE(tagCursor);
debug("backed signed length up from %u to %u\n", savedLength, tagCursor);
break;
}
/* check whether we can safely rewind further */
if (tagCursor < 4)
break;
tagCursor -= 4;
rewindCount += 4;
} while (rewindCount <= 64);
}
/* generate the hash that we expect to find in the ticket */
hashSize = sizeof(*hdr) - offsetof(Image3ObjectHeader, ihSignedLength) + LETOH(hdr->ihSignedLength);
image3SHA1Generate(&hdr->ihSignedLength, hashSize, hashBuffer);
/* ask the ticket validator for an opinion on this image */
result = image3TicketVerifyHash(hashBuffer, sizeof(hashBuffer), LETOH(hdr->ihType), expectedType);
if (result == 0) {
debug("ticket says this image is trusted");
goto out_trusted;
}
if (result == EPERM) {
debug("ticket says this image is untrusted");
return(EPERM);
}
debug("ticket does not like this image");
/*
* Embedded image signature validation.
*/
/* verify that the caller is willing to have us validate with the embedded signature */
if (validationOptions & kImage3ValidateRequireTicket) {
debug("image requires ticket validation");
return(EINVAL);
}
/* restore the signed length to include the ECID tag again */
hdr->ihSignedLength = HTOLE(savedLength);
/* verify that the hash tag lies within the image buffer */
tagCursor = LETOH(hdr->ihSignedLength);
if (tagCursor > (LETOH(hdr->ihBufferLength) - sizeof(*hashTag))) {
debug("hash tag overflows buffer");
return(EINVAL);
}
/* verify that the hash tag's buffer does not overflow the image buffer */
hashTag = (Image3TagHeader *)&hdr->ihBuffer[tagCursor];
if (LETOH(hashTag->itBufferLength) > (LETOH(hdr->ihBufferLength) - tagCursor - sizeof(*hashTag))) {
debug("buffer too small to contain signed hash tag payload");
return(EINVAL);
}
if (LETOH(hashTag->itTag) != kImage3TagTypeSignedHash) {
debug("buffer does not contain signed hash tag at required location");
return(EINVAL);
}
/* move the tag cursor to the location we expect to find the cert chain */
tagCursor += LETOH(hashTag->itSkipDistance);
/* check cursort against lower legal bound */
if (tagCursor < LETOH(hdr->ihSignedLength)) {
debug("skip distance on signed hash tag wraps cursor");
return(EINVAL);
}
/* verify that the cert tag lies within the buffer */
if (tagCursor > (LETOH(hdr->ihBufferLength) - sizeof(*certTag))) {
debug("cert tag overflows buffer");
return(EINVAL);
}
/* verify that the cert tag's buffer does not overflow the image buffer */
certTag = (Image3TagHeader *)&hdr->ihBuffer[tagCursor];
if (LETOH(certTag->itBufferLength) > (LETOH(hdr->ihBufferLength) - tagCursor - sizeof(*certTag))) {
debug("buffer too small to contain cert chain tag payload");
return(EINVAL);
}
if (LETOH(certTag->itTag) != kImage3TagTypeCertificateChain) {
debug("buffer does not contain cert chain tag at required location");
return(EINVAL);
}
/* fix up the buffer length to precisely describe the image including signature and no more */
if ((tagCursor + sizeof(*certTag) + LETOH(certTag->itBufferLength)) != LETOH(hdr->ihBufferLength)) {
debug("correcting buffer length from %u to %u",
LETOH(hdr->ihBufferLength), (unsigned int)(tagCursor + sizeof(*certTag) + LETOH(certTag->itBufferLength)));
hdr->ihBufferLength = HTOLE(tagCursor + sizeof(*certTag) + LETOH(certTag->itBufferLength));
}
/* hash the signed portions of the buffer */
hashSize = sizeof(*hdr) - offsetof(Image3ObjectHeader, ihSignedLength) + LETOH(hdr->ihSignedLength);
image3SHA1Generate(&hdr->ihSignedLength, hashSize, hashBuffer);
/* if the image has come from local storage, the signed hash needs to be decrypted */
if (validationOptions & kImage3ValidateLocalStorage) {
if (0 != (LETOH(hashTag->itBufferLength) % 16)) {
debug("signed hash buffer length invalid for AES decryption");
return(EINVAL);
}
debug("decrypting signed hash");
image3AESDecryptUsingLocalKey(hashTag->itBuffer, LETOH(hashTag->itBufferLength));
}
/* call the PKI interface to validate the signature and compare the hash */
certCustomData = NULL;
certCustomDataLength = 0;
result = image3PKIVerifyHash(
hashBuffer,
IMAGE3_HASH_SIZE,
hashTag->itBuffer,
LETOH(hashTag->itBufferLength),
certTag->itBuffer,
LETOH(certTag->itBufferLength),
&certCustomData,
&certCustomDataLength);
/* clear the possibly decrypted signature from memory */
memset(hashTag->itBuffer, 0, LETOH(hashTag->itBufferLength));
if (result) {
debug("PKI verification failed (%d)", result);
return(result);
}
debug("PKI verification passed...");
/* if the cert validation returned custom data, it should be an image3 image as well */
if (certCustomData) {
result = image3InstantiateFromBuffer(
&withHandle->nestedImage,
certCustomData,
certCustomDataLength,
true /* copyBuffer */);
if (result) {
debug("failed to instantiate image from certificate custom data");
return(result);
}
/* inherit valid/trusted flags from the parent image - not validated separately */
withHandle->nestedImage->flags |=
kImage3ImageWasValidated | kImage3ImageIsTrusted | kImage3ImageIsSigned;
}
if (validatedWithEmbeddedSignature)
*validatedWithEmbeddedSignature = true;
out_trusted:
withHandle->flags |= kImage3ImageIsTrusted;
/*
* Strip the signature and cert chain (and anything that might follow them) out of the buffer
* to prevent any extra data hanging around.
*/
base = (uint8_t *)hdr + sizeof(*hdr) + LETOH(hdr->ihSignedLength);
len = (uint8_t *)hdr + withHandle->allocSize - base;
memset(base, 0, len);
hdr->ihBufferLength = HTOLE(LETOH(hdr->ihSignedLength));
return(0);
}
/*
* Test for the presence of a tag in an image.
*
* Returns:
* 0 Success
* EINVAL The buffer is malformed.
* ENOENT The tag was not found.
*/
int
image3TagIsPresent(
const Image3ObjectHandle withHandle,
const u_int32_t withTag)
{
void *p;
return(image3GetTagStruct(withHandle, withTag, &p, NULL, 0));
}
/*
* Fetch a numeric tag's value.
*
* Returns:
* 0 Success
* EINVAL The buffer is malformed.
* ENOENT The tag was not found.
*/
int
image3GetTagSignedNumber(
const Image3ObjectHandle withHandle,
const u_int32_t withTag,
int64_t *toNumber,
int skipCount)
{
Image3TagNumber *num;
size_t size;
int result;
size = 0;
if ((result = image3GetTagStruct(withHandle, withTag, (void **)&num, &size, skipCount)) == 0) {
switch (size) {
case sizeof(Image3TagNumber32) :
*toNumber = LETOH(num->number.n32.value.s32);
break;
case sizeof(Image3TagNumber64) :
*toNumber = LETOHLL(num->number.n64.value.s64);
break;
default :
result = EINVAL;
break;
}
}
return(result);
}
int
image3GetTagUnsignedNumber(
const Image3ObjectHandle withHandle,
const u_int32_t withTag,
u_int64_t *toNumber,
int skipCount)
{
Image3TagNumber *num;
size_t size;
int result;
size = 0;
if ((result = image3GetTagStruct(withHandle, withTag, (void **)&num, &size, skipCount)) == 0) {
switch (size) {
case sizeof(Image3TagNumber32) :
*toNumber = LETOH(num->number.n32.value.u32);
break;
case sizeof(Image3TagNumber64) :
*toNumber = LETOHLL(num->number.n64.value.u64);
break;
default :
result = EINVAL;
break;
}
}
return(result);
}
/*
* Fetch a string tag's value.
*
* Returns:
* 0 Success. *toBuffer contains an ASCIIZ string that must be freed by the caller.
* EINVAL The buffer is malformed.
* ENOMEM Not enough memory available to allocate a copy of the string.
* ENOENT The tag was not found.
*/
int
image3GetTagString(
const Image3ObjectHandle withHandle,
const u_int32_t withTag,
char **toBuffer,
int skipCount)
{
Image3TagString *str;
size_t size;
char *buf;
int result;
size = 0;
if ((result = image3GetTagStruct(withHandle, withTag, (void **)&str, &size, skipCount)) == 0) {
/* validate string structure */
if (size < sizeof(*str)) /* malformed */
return(EINVAL);
if ((size - sizeof(*str)) != LETOH(str->stringLength)) /* claimed length overflows allocation */
return(EINVAL);
/* make a copy of the string */
if ((buf = image3Malloc(LETOH(str->stringLength) + 1)) == NULL) {
result = ENOMEM;
} else {
/* copy valid bytes, guarantee NUL termination */
memcpy(buf, str->stringBytes, str->stringLength);
buf[str->stringLength] = '\0';
*toBuffer = buf;
}
}
return(result);
}
/*
* Find a tag by name.
*
* The leading (skipCount) matching tags will be ignored.
*
* The tag 0xffffffff is a wildcard with matches any tag.
*
* Returns:
* 0 Success
* ENOENT The tag was not found.
*/
static int
_image3FindTag(
const Image3ObjectHandle withHandle,
const u_int32_t withTag,
Image3TagHeader **forHeader,
size_t *structSize,
int skipCount)
{
Image3TagHeader *thdr;
u_int8_t *cursor, *bound, *next, *tagEnd;
/* our starting point */
cursor = &withHandle->image->ihBuffer[0];
/* find the upper boundary of the tag buffer */
bound = cursor + LETOH(withHandle->image->ihBufferLength);
debug("scanning for tags between %p and %p", cursor, bound);
/* do not permit the buffer to wrap */
if (bound < cursor) {
debug("integer wrap computing upper bound");
return(EINVAL);
}
/* iterate, making sure that the header fits within the upper boundary */
while (cursor < bound) {
/* consider the tag at cursor */
thdr = (Image3TagHeader *)cursor;
/* do not permit the header to wrap */
if ((cursor + sizeof(*thdr)) < cursor) {
debug("integer wrap between cursor and tag header end");
return(EINVAL);
}
/* if any part of the header is outside the boundary, the buffer is malformed */
if ((cursor + sizeof(*thdr)) > bound) {
debug("tag header outside search boundary");
return(EINVAL);
}
/* if the tag data wraps or crosses the boundary, the buffer is malformed */
tagEnd = cursor + sizeof(*thdr) + LETOH(thdr->itBufferLength);
if ((tagEnd < (cursor + sizeof(*thdr))) || (tagEnd > bound)) {
debug("tag data violates search boundaries or wraps");
return(EINVAL);
}
/* compare tags */
if ((withTag == 0xffffffff) || (LETOH(thdr->itTag) == withTag)) {
if (skipCount == 0) {
if (structSize) {
/* caller-specified size does not match tag data size */
if (*structSize && (*structSize != LETOH(thdr->itBufferLength))) {
debug("tag specifies size %u but caller requires %u for tag 0x%x",
(unsigned int)*structSize, LETOH(thdr->itBufferLength), LETOH(thdr->itTag));
return(EINVAL);
}
/* tell caller data size */
*structSize = LETOH(thdr->itBufferLength);
}
debug("found tag at %p", thdr);
*forHeader = thdr;
return(0);
}
skipCount--;
}
/* validate the skip distance */
if (LETOH(thdr->itSkipDistance) < (sizeof(*thdr) + LETOH(thdr->itBufferLength))) {
debug("skip distance %u less than the sum of header size and buffer data %u",
LETOH(thdr->itSkipDistance), (unsigned int)(sizeof(*thdr) + LETOH(thdr->itBufferLength)));
return(EINVAL);
}
/* find the next header */
next = cursor + LETOH(thdr->itSkipDistance);
/* do not permit arithmetic wrap */
if (next < cursor) {
debug("integer wrap advancing cursor");
return(EINVAL);
}
cursor = next;
}
return(ENOENT);
}
/*
* Fetch a direct pointer to a structure tag. Note that this points into the object itself
* and should be used with some caution.
*
* Returns:
* 0 Success.
* EINVAL The buffer is malformed.
* ENOENT The tag was not found.
*/
int
image3GetTagStruct(
const Image3ObjectHandle withHandle,
const u_int32_t withTag,
void **structPtr,
size_t *structSize,
int skipCount)
{
int result;
Image3TagHeader *thdr;
if ((result = _image3FindTag(withHandle, withTag, &thdr, structSize, skipCount)) != 0)
return(result);
*structPtr = (void *)&thdr->itBuffer;
return(0);
}
/*
* Return the nested image's handle (if one exists).
*/
Image3ObjectHandle
image3GetNestedImage(const Image3ObjectHandle withHandle)
{
return(withHandle->nestedImage);
}
#ifdef IMAGE3_DEBUG
void
_image3PrintImage(Image3ObjectHandle withHandle)
{
int result;
int index;
unsigned int i, j;
Image3TagHeader *thdr;
printf("image skip distance: 0x%x\n", LETOH(withHandle->image->ihSkipDistance));
printf("image buffer length: 0x%x\n", LETOH(withHandle->image->ihBufferLength));
printf("image signed length: 0x%x\n\n", LETOH(withHandle->image->ihSignedLength));
for (index = 0; ; index++) {
result = _image3FindTag(withHandle, 0xffffffff, &thdr, NULL, index);
if (result != 0) {
if (result != ENOENT)
debug("image scan terminated unexpectedly");
break;
}
printf("tag: %c%c%c%c\n", UNTAG(LETOH(thdr->itTag)));
printf("skip distance: 0x%x\n", LETOH(thdr->itSkipDistance));
printf("buffer length: 0x%x\n", LETOH(thdr->itBufferLength));
printf("---------------------\n");
for (i = 0; i < LETOH(thdr->itBufferLength); i += 16) {
printf("%08x: ", i);
for (j = 0; (j < 16) && ((i + j) < LETOH(thdr->itBufferLength)); j++)
printf(" %02x", (unsigned int)thdr->itBuffer[i + j]);
printf("\n");
}
}
}
#ifdef IMAGE3_UTILITY
int
_image3WriteData(Image3ObjectHandle withHandle, const char *path)
{
int fd;
int result = 0;
Image3TagHeader *thdr;
fd = open(path, O_CREAT|O_RDWR|O_TRUNC, 0644);
if (fd < 0) {
perror("open failed");
return fd;
}
/*
* Check to see if the image is encrypted. If so, we don't
* decrypt it here, so there is nothing we can do
*/
result = _image3FindTag(withHandle, kImage3TagTypeKeybag, &thdr, NULL, 0);
if (result == 0) {
debug("image is encrypted, no going back");
result = -1;
goto out;
}
result = _image3FindTag(withHandle, kImage3TagTypeData, &thdr, NULL, 0);
if (result != 0) {
debug("couldn't find data section");
goto out;
}
write(fd, thdr->itBuffer, thdr->itBufferLength);
printf("Wrote %s\n", path);
close(fd);
out:
return (result);
}
#endif /* IMAGE3_UTILITY */
#endif /* IMAGE3_DEBUG */