diff options
Diffstat (limited to 'futility/file_type_bios.c')
-rw-r--r-- | futility/file_type_bios.c | 354 |
1 files changed, 202 insertions, 152 deletions
diff --git a/futility/file_type_bios.c b/futility/file_type_bios.c index 13428c14..29ecfd4e 100644 --- a/futility/file_type_bios.c +++ b/futility/file_type_bios.c @@ -5,10 +5,12 @@ #include <errno.h> #include <limits.h> +#include <stdbool.h> #include <stdint.h> #include <stdio.h> #include <string.h> +#include "cbfstool.h" #include "file_type_bios.h" #include "file_type.h" #include "fmap.h" @@ -31,7 +33,7 @@ static void fmap_limit_area(FmapAreaHeader *ah, uint32_t len) { uint32_t sum = ah->area_offset + ah->area_size; if (sum < ah->area_size || sum > len) { - VB2_DEBUG("%s %#x + %#x > %#x\n", + VB2_DEBUG("%.*s %#x + %#x > %#x\n", FMAP_NAMELEN, ah->area_name, ah->area_offset, ah->area_size, len); ah->area_offset = 0; ah->area_size = 0; @@ -237,114 +239,35 @@ int ft_show_bios(const char *name, void *data) /** Sign functions **/ -/* - * This handles FW_MAIN_A and FW_MAIN_B while signing a BIOS image. The data is - * just the RW firmware blob so there's nothing useful to do with it, but we'll - * mark it as valid so that we'll know that this FMAP area exists and can - * be signed. - */ -static int fmap_sign_fw_main(const char *name, uint8_t *buf, uint32_t len, - void *data) -{ - struct bios_state_s *state = (struct bios_state_s *)data; - state->area[state->c].is_valid = 1; - return 0; -} - -/* - * This handles VBLOCK_A and VBLOCK_B while processing a BIOS image. We don't - * do any signing here. We just check to see if the existing FMAP area contains - * a firmware preamble so we can preserve its contents. We do the signing once - * we've looked over all the components. - */ -static int fmap_sign_fw_preamble(const char *name, uint8_t *buf, uint32_t len, - void *data) -{ - static uint8_t workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE] - __attribute__((aligned(VB2_WORKBUF_ALIGN))); - static struct vb2_workbuf wb; - vb2_workbuf_init(&wb, workbuf, sizeof(workbuf)); - - struct vb2_keyblock *keyblock = (struct vb2_keyblock *)buf; - struct bios_state_s *state = (struct bios_state_s *)data; - - /* - * If we have a valid keyblock and fw_preamble, then we can use them to - * determine the size of the firmware body. Otherwise, we'll have to - * just sign the whole region. - */ - if (VB2_SUCCESS != vb2_verify_keyblock_hash(keyblock, len, &wb)) { - fprintf(stderr, "Warning: %s keyblock is invalid. " - "Signing the entire FW FMAP region...\n", name); - goto whatever; - } - - if (vb2_packed_key_looks_ok(&keyblock->data_key, - keyblock->data_key.key_offset + - keyblock->data_key.key_size)) { - fprintf(stderr, "Warning: %s public key is invalid. " - "Signing the entire FW FMAP region...\n", name); - goto whatever; - } - uint32_t more = keyblock->keyblock_size; - struct vb2_fw_preamble *preamble = - (struct vb2_fw_preamble *)(buf + more); - uint32_t fw_size = preamble->body_signature.data_size; - struct bios_area_s *fw_body_area = 0; - - switch (state->c) { - case BIOS_FMAP_VBLOCK_A: - fw_body_area = &state->area[BIOS_FMAP_FW_MAIN_A]; - /* Preserve the flags if they're not specified */ - if (!sign_option.flags_specified) - sign_option.flags = preamble->flags; - break; - case BIOS_FMAP_VBLOCK_B: - fw_body_area = &state->area[BIOS_FMAP_FW_MAIN_B]; - break; - default: - FATAL("Can only handle VBLOCK_A or VBLOCK_B\n"); - } - - if (fw_size > fw_body_area->len) { - fprintf(stderr, - "%s says the firmware is larger than we have\n", - name); - return 1; - } - - /* Update the firmware size */ - fw_body_area->len = fw_size; - -whatever: - state->area[state->c].is_valid = 1; - - return 0; -} - static int write_new_preamble(struct bios_area_s *vblock, struct bios_area_s *fw_body, struct vb2_private_key *signkey, struct vb2_keyblock *keyblock) { - struct vb2_signature *body_sig; - struct vb2_fw_preamble *preamble; + struct vb2_signature *body_sig = NULL; + struct vb2_fw_preamble *preamble = NULL; + int retval = 1; body_sig = vb2_calculate_signature(fw_body->buf, fw_body->len, signkey); if (!body_sig) { - fprintf(stderr, "Error calculating body signature\n"); - return 1; + ERROR("Error calculating body signature\n"); + goto end; } - preamble = vb2_create_fw_preamble(sign_option.version, + preamble = vb2_create_fw_preamble(vblock->version, (struct vb2_packed_key *)sign_option.kernel_subkey, body_sig, signkey, - sign_option.flags); + vblock->flags); if (!preamble) { - fprintf(stderr, "Error creating firmware preamble.\n"); + ERROR("Error creating firmware preamble.\n"); free(body_sig); - return 1; + goto end; + } + + if (keyblock->keyblock_size + preamble->preamble_size > vblock->len) { + ERROR("Keyblock and preamble do not fit in VBLOCK.\n"); + goto end; } /* Write the new keyblock */ @@ -352,11 +275,13 @@ static int write_new_preamble(struct bios_area_s *vblock, memcpy(vblock->buf, keyblock, more); /* and the new preamble */ memcpy(vblock->buf + more, preamble, preamble->preamble_size); + retval = 0; +end: free(preamble); free(body_sig); - return 0; + return retval; } static int write_loem(const char *ab, struct bios_area_s *vblock) @@ -402,8 +327,7 @@ static int sign_bios_at_end(struct bios_state_s *state) struct bios_area_s *fw_b = &state->area[BIOS_FMAP_FW_MAIN_B]; int retval = 0; - if (!vblock_a->is_valid || !vblock_b->is_valid || - !fw_a->is_valid || !fw_b->is_valid) { + if (!vblock_a->is_valid || !fw_a->is_valid) { fprintf(stderr, "Something's wrong. Not changing anything\n"); return 1; } @@ -411,78 +335,197 @@ static int sign_bios_at_end(struct bios_state_s *state) retval |= write_new_preamble(vblock_a, fw_a, sign_option.signprivate, sign_option.keyblock); - retval |= write_new_preamble(vblock_b, fw_b, sign_option.signprivate, - sign_option.keyblock); + if (vblock_b->is_valid && fw_b->is_valid) + retval |= write_new_preamble(vblock_b, fw_b, + sign_option.signprivate, + sign_option.keyblock); + else + INFO("BIOS image does not have %s. Signing only %s\n", + fmap_name[BIOS_FMAP_FW_MAIN_B], + fmap_name[BIOS_FMAP_FW_MAIN_A]); if (sign_option.loemid) { retval |= write_loem("A", vblock_a); - retval |= write_loem("B", vblock_b); + if (vblock_b->is_valid) + retval |= write_loem("B", vblock_b); } return retval; } -/* Functions to call while preparing to sign the bios */ -static int (*fmap_sign_fn[])(const char *name, uint8_t *buf, uint32_t len, - void *data) = { - 0, - fmap_sign_fw_main, - fmap_sign_fw_main, - fmap_sign_fw_preamble, - fmap_sign_fw_preamble, -}; -_Static_assert(ARRAY_SIZE(fmap_sign_fn) == NUM_BIOS_COMPONENTS, - "Size of fmap_sign_fn[] should match NUM_BIOS_COMPONENTS"); +/* Prepare firmware slot for signing. + If fw_size is not zero, then it will be used as new length of signed area, + for zero the length will be taken form FlashMap or preamble. */ +static int prepare_slot(uint8_t *buf, uint32_t len, size_t fw_size, + enum bios_component fw_c, enum bios_component vblock_c, + struct bios_state_s *state) +{ + FmapHeader *fmap; + FmapAreaHeader *ah; + const char *fw_main_name = fmap_name[fw_c]; + const char *vblock_name = fmap_name[vblock_c]; + static uint8_t workbuf[VB2_FIRMWARE_WORKBUF_RECOMMENDED_SIZE] + __attribute__((aligned(VB2_WORKBUF_ALIGN))); + static struct vb2_workbuf wb; + + fmap = fmap_find(buf, len); + vb2_workbuf_init(&wb, workbuf, sizeof(workbuf)); + + VB2_DEBUG("Preparing areas: %s and %s\n", fw_main_name, vblock_name); + + /* FW_MAIN */ + if (!fmap_find_by_name(buf, len, fmap, fw_main_name, &ah)) { + ERROR("%s area not found in FMAP\n", fw_main_name); + return 1; + } + fmap_limit_area(ah, len); + state->area[fw_c].buf = buf + ah->area_offset; + state->area[fw_c].is_valid = 1; + if (fw_size > ah->area_size) { + ERROR("%s size is incorrect.\n", fmap_name[fw_c]); + return 1; + } else if (fw_size) { + state->area[fw_c].len = fw_size; + } else { + WARN("%s does not contain CBFS. Trying to sign entire area.\n", + fmap_name[fw_c]); + state->area[fw_c].len = ah->area_size; + } + + /* Corresponding VBLOCK */ + if (!fmap_find_by_name(buf, len, fmap, vblock_name, &ah)) { + ERROR("%s area not found in FMAP\n", vblock_name); + return 1; + } + fmap_limit_area(ah, len); + state->area[vblock_c].buf = buf + ah->area_offset; + state->area[vblock_c].len = ah->area_size; + + struct vb2_keyblock *keyblock = + (struct vb2_keyblock *)state->area[vblock_c].buf; + int keyblock_valid = 0; + + if (vb2_verify_keyblock_hash(keyblock, state->area[vblock_c].len, + &wb) != VB2_SUCCESS) { + WARN("%s keyblock is invalid.\n", vblock_name); + goto end; + } + + if (vb2_packed_key_looks_ok(&keyblock->data_key, + keyblock->data_key.key_offset + + keyblock->data_key.key_size)) { + WARN("%s public key is invalid.\n", vblock_name); + goto end; + } + + struct vb2_public_key data_key; + if (vb2_unpack_key(&data_key, &keyblock->data_key) != VB2_SUCCESS) { + WARN("%s data key is invalid. Failed to parse.\n", vblock_name); + goto end; + } + + if (keyblock->keyblock_size + sizeof(struct vb2_fw_preamble) > + state->area[vblock_c].len) { + ERROR("%s is invalid. Keyblock and preamble do not fit.\n", + vblock_name); + goto end; + } + + struct vb2_fw_preamble *preamble = + (struct vb2_fw_preamble *)(state->area[vblock_c].buf + + keyblock->keyblock_size); + if (vb2_verify_fw_preamble(preamble, + state->area[vblock_c].len - + keyblock->keyblock_size, + &data_key, &wb)) { + WARN("%s preamble is invalid.\n", vblock_name); + goto end; + } + + if (fw_size == 0) { + if (preamble->body_signature.data_size > + state->area[fw_c].len) { + ERROR("%s says the firmware is larger than we have.\n", + vblock_name); + goto end; + } else { + state->area[fw_c].len = + preamble->body_signature.data_size; + } + } + + keyblock_valid = 1; + +end: + if (sign_option.flags_specified) + state->area[vblock_c].flags = sign_option.flags; + else if (keyblock_valid) + state->area[vblock_c].flags = preamble->flags; + else + state->area[vblock_c].flags = 0; + + if (sign_option.version_specified) + state->area[vblock_c].version = sign_option.version; + else if (keyblock_valid) + state->area[vblock_c].version = preamble->firmware_version; + else + state->area[vblock_c].version = 1; + + state->area[vblock_c].is_valid = 1; + + return 0; +} int ft_sign_bios(const char *name, void *data) { - FmapHeader *fmap; - FmapAreaHeader *ah = 0; - char ah_name[FMAP_NAMELEN + 1]; - enum bios_component c; int retval = 0; struct bios_state_s state; int fd = -1; uint8_t *buf = NULL; uint32_t len = 0; - - retval = futil_open_and_map_file(name, &fd, FILE_MODE_SIGN(sign_option), - &buf, &len); - if (retval) + size_t fw_main_a_size = 0; + size_t fw_main_b_size = 0; + + bool fw_main_a_in_cbfs_mode = + cbfstool_truncate(name, fmap_name[BIOS_FMAP_FW_MAIN_A], + &fw_main_a_size) == VB2_SUCCESS; + + bool fw_main_b_in_cbfs_mode = + cbfstool_truncate(name, fmap_name[BIOS_FMAP_FW_MAIN_B], + &fw_main_b_size) == VB2_SUCCESS; + + if (fw_main_a_in_cbfs_mode) + VB2_DEBUG("CBFS found in area %s\n", + fmap_name[BIOS_FMAP_FW_MAIN_A]); + else + VB2_DEBUG("CBFS not found in area %s\n", + fmap_name[BIOS_FMAP_FW_MAIN_A]); + + if (fw_main_b_in_cbfs_mode) + VB2_DEBUG("CBFS found in area %s\n", + fmap_name[BIOS_FMAP_FW_MAIN_B]); + else + VB2_DEBUG("CBFS not found in area %s\n", + fmap_name[BIOS_FMAP_FW_MAIN_B]); + + if (futil_open_and_map_file(name, &fd, FILE_MODE_SIGN(sign_option), + &buf, &len)) return 1; memset(&state, 0, sizeof(state)); - /* We've already checked, so we know this will work. */ - fmap = fmap_find(buf, len); - for (c = 0; c < NUM_BIOS_COMPONENTS; c++) { - /* We know one of these will work, too */ - if (fmap_find_by_name(buf, len, fmap, fmap_name[c], &ah)) { - /* But the file might be truncated */ - fmap_limit_area(ah, len); - /* The name is not necessarily null-terminated */ - snprintf(ah_name, sizeof(ah_name), "%s", ah->area_name); - - /* Update the state we're passing around */ - state.c = c; - state.area[c].buf = buf + ah->area_offset; - state.area[c].len = ah->area_size; - - VB2_DEBUG("examining FMAP area %d (%s)," - " offset=0x%08x len=0x%08x\n", - c, ah_name, ah->area_offset, ah->area_size); - - /* Go look at it, but abort on error */ - if (fmap_sign_fn[c]) - retval += fmap_sign_fn[c](ah_name, - state.area[c].buf, - state.area[c].len, - &state); - } - } + retval = prepare_slot(buf, len, fw_main_a_size, BIOS_FMAP_FW_MAIN_A, + BIOS_FMAP_VBLOCK_A, &state); + if (retval) + goto done; - retval += sign_bios_at_end(&state); + retval = prepare_slot(buf, len, fw_main_b_size, BIOS_FMAP_FW_MAIN_B, + BIOS_FMAP_VBLOCK_B, &state); + if (retval && state.area[BIOS_FMAP_FW_MAIN_B].is_valid) + goto done; + retval = sign_bios_at_end(&state); +done: futil_unmap_and_close_file(fd, FILE_MODE_SIGN(sign_option), buf, len); return retval; } @@ -490,16 +533,23 @@ int ft_sign_bios(const char *name, void *data) enum futil_file_type ft_recognize_bios_image(uint8_t *buf, uint32_t len) { FmapHeader *fmap; - enum bios_component c; fmap = fmap_find(buf, len); if (!fmap) return FILE_TYPE_UNKNOWN; - for (c = 0; c < NUM_BIOS_COMPONENTS; c++) - if (!fmap_find_by_name(buf, len, fmap, fmap_name[c], 0)) - break; - if (c == NUM_BIOS_COMPONENTS) + /* Correct BIOS image should contain at least GBB, FW_MAIN_A and + VBLOCK_A areas. FW_MAIN_B and VBLOCK_B are optional, but will be + signed or shown if present. */ + const int gbb_and_a_slot_ok = + fmap_find_by_name(buf, len, fmap, fmap_name[BIOS_FMAP_GBB], + 0) != NULL && + fmap_find_by_name(buf, len, fmap, + fmap_name[BIOS_FMAP_FW_MAIN_A], 0) != NULL && + fmap_find_by_name(buf, len, fmap, fmap_name[BIOS_FMAP_VBLOCK_A], + 0) != NULL; + + if (gbb_and_a_slot_ok) return FILE_TYPE_BIOS_IMAGE; return FILE_TYPE_UNKNOWN; |