diff options
-rw-r--r-- | futility/cmd_update.c | 131 | ||||
-rwxr-xr-x | tests/futility/test_update.sh | 14 |
2 files changed, 145 insertions, 0 deletions
diff --git a/futility/cmd_update.c b/futility/cmd_update.c index ed0b83f2..964f071e 100644 --- a/futility/cmd_update.c +++ b/futility/cmd_update.c @@ -12,11 +12,14 @@ #include <stdio.h> #include <stdlib.h> +#include "2rsa.h" #include "crossystem.h" #include "fmap.h" #include "futility.h" #include "host_misc.h" #include "utility.h" +#include "util_misc.h" +#include "vb2_common.h" #include "vb2_struct.h" #define COMMAND_BUFFER_SIZE 256 @@ -935,6 +938,22 @@ static int check_compatible_platform(struct updater_config *cfg) } /* + * Returns a valid root key from GBB header, or NULL on failure. + */ +static const struct vb2_packed_key *get_rootkey( + const struct vb2_gbb_header *gbb) +{ + struct vb2_packed_key *key = NULL; + + key = (struct vb2_packed_key *)((uint8_t *)gbb + gbb->rootkey_offset); + if (!packed_key_looks_ok(key, gbb->rootkey_size)) { + Error("%s: Invalid root key.\n", __FUNCTION__); + return NULL; + } + return key; +} + +/* * Returns a key block key from given image section, or NULL on failure. */ static const struct vb2_keyblock *get_keyblock( @@ -954,6 +973,57 @@ static const struct vb2_keyblock *get_keyblock( } /* + * Duplicates a key block and returns the duplicated block. + * The caller must free the returned key block after being used. + */ +static struct vb2_keyblock *dupe_keyblock(const struct vb2_keyblock *block) +{ + struct vb2_keyblock *new_block; + + new_block = (struct vb2_keyblock *)malloc(block->keyblock_size); + assert(new_block); + memcpy(new_block, block, block->keyblock_size); + return new_block; +} + +/* + * Verifies if keyblock is signed with given key. + * Returns 0 on success, otherwise failure. + */ +static int verify_keyblock(const struct vb2_keyblock *block, + const struct vb2_packed_key *sign_key) { + int r; + uint8_t workbuf[VB2_WORKBUF_RECOMMENDED_SIZE]; + struct vb2_workbuf wb; + struct vb2_public_key key; + struct vb2_keyblock *new_block; + + if (block->keyblock_signature.sig_size == 0) { + Error("%s: Keyblock is not signed.\n", __FUNCTION__); + return -1; + } + vb2_workbuf_init(&wb, workbuf, sizeof(workbuf)); + if (VB2_SUCCESS != vb2_unpack_key(&key, sign_key)) { + Error("%s: Invalid signing key,\n", __FUNCTION__); + return -1; + } + + /* + * vb2_verify_keyblock will destroy the signature inside keyblock + * so we have to verify with a local copy. + */ + new_block = dupe_keyblock(block); + r = vb2_verify_keyblock(new_block, new_block->keyblock_size, &key, &wb); + free(new_block); + + if (r != VB2_SUCCESS) { + Error("%s: Error verifying key block.\n", __FUNCTION__); + return -1; + } + return 0; +} + +/* * Gets the data key and firmware version from a section on firmware image. * The section should contain a vb2_keyblock and a vb2_fw_preamble immediately * after key block so we can decode and save the data key and firmware version @@ -981,6 +1051,61 @@ static int get_key_versions(const struct firmware_image *image, } /* + * Checks if the root key in ro_image can verify vblocks in rw_image. + * Returns 0 for success, otherwise failure. + */ +static int check_compatible_root_key(const struct firmware_image *ro_image, + const struct firmware_image *rw_image) +{ + const struct vb2_gbb_header *gbb = find_gbb(ro_image); + const struct vb2_packed_key *rootkey; + const struct vb2_keyblock *keyblock; + + if (!gbb) + return -1; + + rootkey = get_rootkey(gbb); + if (!rootkey) + return -1; + + /* Assume VBLOCK_A and VBLOCK_B are signed in same way. */ + keyblock = get_keyblock(rw_image, FMAP_RW_VBLOCK_A); + if (!keyblock) + return -1; + + if (verify_keyblock(keyblock, rootkey) != 0) { + const struct vb2_gbb_header *gbb_rw = find_gbb(rw_image); + const struct vb2_packed_key *rootkey_rw = NULL; + int is_same_key = 0; + /* + * Try harder to provide more info. + * packed_key_sha1_string uses static buffer so don't call + * it twice in args list of one expression. + */ + if (gbb_rw) + rootkey_rw = get_rootkey(gbb_rw); + if (rootkey_rw) { + if (rootkey->key_offset == rootkey_rw->key_offset && + rootkey->key_size == rootkey_rw->key_size && + memcmp(rootkey, rootkey_rw, rootkey->key_size + + rootkey->key_offset) == 0) + is_same_key = 1; + } + printf("Current (RO) image root key is %s, ", + packed_key_sha1_string(rootkey)); + if (is_same_key) + printf("same with target (RW) image. " + "Maybe RW corrupted?\n"); + else + printf("target (RW) image is signed with rootkey %s.\n", + rootkey_rw ? packed_key_sha1_string(rootkey_rw) : + "<invalid>"); + return -1; + } + return 0; +} + +/* * Checks if the given firmware image is signed with a key that won't be * blocked by TPM's anti-rollback detection. * Returns 0 for success, otherwise failure. @@ -1030,6 +1155,7 @@ enum updater_error_codes { UPDATE_ERR_WRITE_FIRMWARE, UPDATE_ERR_PLATFORM, UPDATE_ERR_TARGET, + UPDATE_ERR_ROOT_KEY, UPDATE_ERR_TPM_ROLLBACK, UPDATE_ERR_UNKNOWN, }; @@ -1044,6 +1170,7 @@ static const char * const updater_error_messages[] = { [UPDATE_ERR_WRITE_FIRMWARE] = "Failed writing firmware.", [UPDATE_ERR_PLATFORM] = "Your system platform is not compatible.", [UPDATE_ERR_TARGET] = "No valid RW target to update. Abort.", + [UPDATE_ERR_ROOT_KEY] = "RW not signed by same RO root key", [UPDATE_ERR_TPM_ROLLBACK] = "RW not usable due to TPM anti-rollback.", [UPDATE_ERR_UNKNOWN] = "Unknown error.", }; @@ -1070,6 +1197,8 @@ static enum updater_error_codes update_try_rw_firmware( return UPDATE_ERR_NEED_RO_UPDATE; printf("Checking compatibility...\n"); + if (check_compatible_root_key(image_from, image_to)) + return UPDATE_ERR_ROOT_KEY; if (check_compatible_tpm_keys(cfg, image_to)) return UPDATE_ERR_TPM_ROLLBACK; @@ -1126,6 +1255,8 @@ static enum updater_error_codes update_rw_firmrware( FMAP_RW_SECTION_A, FMAP_RW_SECTION_B, FMAP_RW_SHARED); printf("Checking compatibility...\n"); + if (check_compatible_root_key(image_from, image_to)) + return UPDATE_ERR_ROOT_KEY; if (check_compatible_tpm_keys(cfg, image_to)) return UPDATE_ERR_TPM_ROLLBACK; /* diff --git a/tests/futility/test_update.sh b/tests/futility/test_update.sh index 26d993d8..d51219ff 100755 --- a/tests/futility/test_update.sh +++ b/tests/futility/test_update.sh @@ -62,12 +62,18 @@ unpack_image() { local image="$2" mkdir -p "${folder}" (cd "${folder}" && ${FUTILITY} dump_fmap -x "../${image}") + ${FUTILITY} gbb -g --rootkey="${folder}/rootkey" "${image}" } # Unpack images so we can prepare expected results by individual sections. unpack_image "to" "${TO_IMAGE}" unpack_image "from" "${FROM_IMAGE}" +# Hack FROM_IMAGE so it has same root key as TO_IMAGE (for RW update). +FROM_DIFFERENT_ROOTKEY_IMAGE="${FROM_IMAGE}2" +cp -f "${FROM_IMAGE}" "${FROM_DIFFERENT_ROOTKEY_IMAGE}" +"${FUTILITY}" gbb -s --rootkey="${TMP}.to/rootkey" "${FROM_IMAGE}" + # Generate expected results. cp -f "${TO_IMAGE}" "${TMP}.expected.full" cp -f "${FROM_IMAGE}" "${TMP}.expected.rw" @@ -136,6 +142,10 @@ test_update "RW update (incompatible platform)" \ "${FROM_IMAGE}" "!platform is not compatible" \ -i "${LINK_BIOS}" --wp=1 --sys_props 0,0x10001,1 +test_update "RW update (incompatible rootkey)" \ + "${FROM_DIFFERENT_ROOTKEY_IMAGE}" "!RW not signed by same RO root key" \ + -i "${TO_IMAGE}" --wp=1 --sys_props 0,0x10001,1 + test_update "RW update (TPM Anti-rollback: data key)" \ "${FROM_IMAGE}" "!Data key version rollback detected (2->1)" \ -i "${TO_IMAGE}" --wp=1 --sys_props 1,0x20001,1 @@ -160,6 +170,10 @@ test_update "RW update (incompatible platform)" \ "${FROM_IMAGE}" "!platform is not compatible" \ -i "${LINK_BIOS}" -t --wp=1 --sys_props 0x10001,1 +test_update "RW update (incompatible rootkey)" \ + "${FROM_DIFFERENT_ROOTKEY_IMAGE}" "!RW not signed by same RO root key" \ + -i "${TO_IMAGE}" -t --wp=1 --sys_props 0,0x10001,1 + test_update "RW update (TPM Anti-rollback: data key)" \ "${FROM_IMAGE}" "!Data key version rollback detected (2->1)" \ -i "${TO_IMAGE}" -t --wp=1 --sys_props 1,0x20001,1 |