static void tpm_clear_tests(void) { /* No clear request */ reset_common_data(); TEST_SUCC(vb2_check_tpm_clear(&cc), "no clear request"); TEST_EQ(mock_tpm_clear_called, 0, "tpm not cleared"); /* Successful request */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 1); TEST_SUCC(vb2_check_tpm_clear(&cc), "clear request"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST), 0, "request cleared"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_DONE), 1, "done set"); TEST_EQ(mock_tpm_clear_called, 1, "tpm cleared"); /* Failed request */ reset_common_data(); mock_tpm_clear_retval = VB2_ERROR_EX_TPM_CLEAR_OWNER; vb2_nv_set(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 1); TEST_EQ(vb2_check_tpm_clear(&cc), VB2_ERROR_EX_TPM_CLEAR_OWNER, "clear failure"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_REQUEST), 0, "request cleared"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_CLEAR_TPM_OWNER_DONE), 0, "done not set"); }
int vb2_select_fw_slot(struct vb2_context *ctx) { struct vb2_shared_data *sd = vb2_get_sd(ctx); uint32_t tries; /* Get result of last boot */ sd->last_fw_slot = vb2_nv_get(ctx, VB2_NV_FW_TRIED); sd->last_fw_result = vb2_nv_get(ctx, VB2_NV_FW_RESULT); /* Save to the previous result fields in NV storage */ vb2_nv_set(ctx, VB2_NV_FW_PREV_TRIED, sd->last_fw_slot); vb2_nv_set(ctx, VB2_NV_FW_PREV_RESULT, sd->last_fw_result); /* Clear result, since we don't know what will happen this boot */ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN); /* Get slot to try */ sd->fw_slot = vb2_nv_get(ctx, VB2_NV_TRY_NEXT); /* Check try count */ tries = vb2_nv_get(ctx, VB2_NV_TRY_COUNT); if (sd->last_fw_result == VB2_FW_RESULT_TRYING && sd->last_fw_slot == sd->fw_slot && tries == 0) { /* * We used up our last try on the previous boot, so fall back * to the other slot this boot. */ sd->fw_slot = 1 - sd->fw_slot; vb2_nv_set(ctx, VB2_NV_TRY_NEXT, sd->fw_slot); } if (tries > 0) { /* Still trying this firmware */ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_TRYING); /* Decrement non-zero try count, unless told not to */ if (!(ctx->flags & VB2_CONTEXT_NOFAIL_BOOT)) vb2_nv_set(ctx, VB2_NV_TRY_COUNT, tries - 1); } /* Store the slot we're trying */ vb2_nv_set(ctx, VB2_NV_FW_TRIED, sd->fw_slot); /* Set context flag if we're using slot B */ if (sd->fw_slot) ctx->flags |= VB2_CONTEXT_FW_SLOT_B; /* Set status flag */ sd->status |= VB2_SD_STATUS_CHOSE_SLOT; return VB2_SUCCESS; }
int vb2_check_tpm_clear(struct vb2_context *ctx) { int rv; /* Check if we've been asked to clear the owner */ if (!vb2_nv_get(ctx, VB2_NV_CLEAR_TPM_OWNER_REQUEST)) return VB2_SUCCESS; /* No need to clear */ /* Request applies one time only */ vb2_nv_set(ctx, VB2_NV_CLEAR_TPM_OWNER_REQUEST, 0); /* Try clearing */ rv = vb2ex_tpm_clear_owner(ctx); if (rv) { /* * Note that this truncates rv to 8 bit. Which is not as * useful as the full error code, but we don't have NVRAM space * to store the full 32-bit code. */ vb2_fail(ctx, VB2_RECOVERY_TPM_CLEAR_OWNER, rv); return rv; } /* Clear successful */ vb2_nv_set(ctx, VB2_NV_CLEAR_TPM_OWNER_DONE, 1); return VB2_SUCCESS; }
void vb2_check_recovery(struct vb2_context *ctx) { struct vb2_shared_data *sd = vb2_get_sd(ctx); uint32_t reason = vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST); uint32_t subcode = vb2_nv_get(ctx, VB2_NV_RECOVERY_SUBCODE); VB2_DEBUG("Recovery reason from previous boot: %#x / %#x\n", reason, subcode); /* * Sets the current recovery request, unless there's already been a * failure earlier in the boot process. */ if (!sd->recovery_reason) sd->recovery_reason = reason; /* Clear request and subcode so we don't get stuck in recovery mode */ vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, VB2_RECOVERY_NOT_REQUESTED); vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, VB2_RECOVERY_NOT_REQUESTED); if (ctx->flags & VB2_CONTEXT_FORCE_RECOVERY_MODE) { VB2_DEBUG("Recovery was requested manually\n"); if (subcode && !sd->recovery_reason) /* * Recovery was requested at 'broken' screen. * Promote subcode to reason. */ sd->recovery_reason = subcode; else /* Recovery was forced. Override recovery reason */ sd->recovery_reason = VB2_RECOVERY_RO_MANUAL; sd->flags |= VB2_SD_FLAG_MANUAL_RECOVERY; } /* If recovery reason is non-zero, tell caller we need recovery mode */ if (sd->recovery_reason) { ctx->flags |= VB2_CONTEXT_RECOVERY_MODE; VB2_DEBUG("We have a recovery request: %#x / %#x\n", sd->recovery_reason, vb2_nv_get(ctx, VB2_NV_RECOVERY_SUBCODE)); } }
static void recovery_tests(void) { /* No recovery */ reset_common_data(); vb2_check_recovery(&cc); TEST_EQ(sd->recovery_reason, 0, "No recovery reason"); TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, 0, "Not manual recovery"); TEST_EQ(cc.flags & VB2_CONTEXT_RECOVERY_MODE, 0, "Not recovery mode"); /* From request */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_RECOVERY_REQUEST, 3); vb2_check_recovery(&cc); TEST_EQ(sd->recovery_reason, 3, "Recovery reason from request"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 0, "NV cleared"); TEST_EQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, 0, "Not manual recovery"); TEST_NEQ(cc.flags & VB2_CONTEXT_RECOVERY_MODE, 0, "Recovery mode"); /* From request, but already failed */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_RECOVERY_REQUEST, 4); sd->recovery_reason = 5; vb2_check_recovery(&cc); TEST_EQ(sd->recovery_reason, 5, "Recovery reason already failed"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 0, "NV still cleared"); /* Override */ reset_common_data(); sd->recovery_reason = 6; cc.flags |= VB2_CONTEXT_FORCE_RECOVERY_MODE; vb2_check_recovery(&cc); TEST_EQ(sd->recovery_reason, VB2_RECOVERY_RO_MANUAL, "Recovery reason forced"); TEST_NEQ(sd->flags & VB2_SD_FLAG_MANUAL_RECOVERY, 0, "SD flag set"); }
void vb2_fail(struct vb2_context *ctx, uint8_t reason, uint8_t subcode) { struct vb2_shared_data *sd = vb2_get_sd(ctx); /* If NV data hasn't been initialized, initialize it now */ if (!(sd->status & VB2_SD_STATUS_NV_INIT)) vb2_nv_init(ctx); /* See if we were far enough in the boot process to choose a slot */ if (sd->status & VB2_SD_STATUS_CHOSE_SLOT) { /* Boot failed */ vb2_nv_set(ctx, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE); /* Use up remaining tries */ vb2_nv_set(ctx, VB2_NV_TRY_COUNT, 0); /* * Try the other slot next time. We'll alternate * between slots, which may help if one or both slots is * flaky. */ vb2_nv_set(ctx, VB2_NV_TRY_NEXT, 1 - sd->fw_slot); /* * If we didn't try the other slot last boot, or we tried it * and it didn't fail, try it next boot. */ if (sd->last_fw_slot != 1 - sd->fw_slot || sd->last_fw_result != VB2_FW_RESULT_FAILURE) return; } /* * If we're still here, we failed before choosing a slot, or both * this slot and the other slot failed in successive boots. So we * need to go to recovery. * * Set a recovery reason and subcode only if they're not already set. * If recovery is already requested, it's a more specific error code * than later code is providing and we shouldn't overwrite it. */ VB2_DEBUG("Need recovery, reason: %#x / %#x\n", reason, subcode); if (!vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST)) { vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST, reason); vb2_nv_set(ctx, VB2_NV_RECOVERY_SUBCODE, subcode); } }
int vb2api_fw_phase2(struct vb2_context *ctx) { int rv; /* * Use the slot from the last boot if this is a resume. Do not set * VB2_SD_STATUS_CHOSE_SLOT so the try counter is not decremented on * failure as we are explicitly not attempting to boot from a new slot. */ if (ctx->flags & VB2_CONTEXT_S3_RESUME) { struct vb2_shared_data *sd = vb2_get_sd(ctx); /* Set the current slot to the last booted slot */ sd->fw_slot = vb2_nv_get(ctx, VB2_NV_FW_TRIED); /* Set context flag if we're using slot B */ if (sd->fw_slot) ctx->flags |= VB2_CONTEXT_FW_SLOT_B; return VB2_SUCCESS; } /* Always clear RAM when entering developer mode */ if (ctx->flags & VB2_CONTEXT_DEVELOPER_MODE) ctx->flags |= VB2_CONTEXT_CLEAR_RAM; /* Check for explicit request to clear TPM */ rv = vb2_check_tpm_clear(ctx); if (rv) { vb2_fail(ctx, VB2_RECOVERY_TPM_CLEAR_OWNER, rv); return rv; } /* Decide which firmware slot to try this boot */ rv = vb2_select_fw_slot(ctx); if (rv) { vb2_fail(ctx, VB2_RECOVERY_FW_SLOT, rv); return rv; } return VB2_SUCCESS; }
/* Test display key checking */ static void DisplayKeyTest(void) { ResetMocks(); VbCheckDisplayKey(&ctx, 'q', NULL); TEST_EQ(*debug_info, '\0', "DisplayKey q = does nothing"); ResetMocks(); VbCheckDisplayKey(&ctx, '\t', NULL); TEST_NEQ(*debug_info, '\0', "DisplayKey tab = display"); /* Toggle localization */ ResetMocks(); vb2_nv_set(&ctx, VB2_NV_LOCALIZATION_INDEX, 0); VbCheckDisplayKey(&ctx, VB_KEY_DOWN, NULL); TEST_EQ(vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX), 2, "DisplayKey up"); VbCheckDisplayKey(&ctx, VB_KEY_LEFT, NULL); vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX); TEST_EQ(vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX), 1, "DisplayKey left"); VbCheckDisplayKey(&ctx, VB_KEY_RIGHT, NULL); vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX); TEST_EQ(vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX), 2, "DisplayKey right"); VbCheckDisplayKey(&ctx, VB_KEY_UP, NULL); vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX); TEST_EQ(vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX), 0, "DisplayKey up"); /* Reset localization if localization count is invalid */ ResetMocks(); vb2_nv_set(&ctx, VB2_NV_LOCALIZATION_INDEX, 1); mock_localization_count = 0xffffffff; VbCheckDisplayKey(&ctx, VB_KEY_UP, NULL); TEST_EQ(vb2_nv_get(&ctx, VB2_NV_LOCALIZATION_INDEX), 0, "DisplayKey invalid"); }
int vb2_check_dev_switch(struct vb2_context *ctx) { struct vb2_shared_data *sd = vb2_get_sd(ctx); uint32_t flags = 0; uint32_t old_flags; int is_dev = 0; int use_secdata = 1; int rv; /* Read secure flags */ rv = vb2_secdata_get(ctx, VB2_SECDATA_FLAGS, &flags); if (rv) { if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE) { /* * Recovery mode needs to check other ways developer * mode can be enabled, so don't give up yet. But * since we can't read secdata, assume dev mode was * disabled. */ use_secdata = 0; flags = 0; } else { /* Normal mode simply fails */ return rv; } } old_flags = flags; /* Handle dev disable request */ if (use_secdata && vb2_nv_get(ctx, VB2_NV_DISABLE_DEV_REQUEST)) { flags &= ~VB2_SECDATA_FLAG_DEV_MODE; /* Clear the request */ vb2_nv_set(ctx, VB2_NV_DISABLE_DEV_REQUEST, 0); } /* * Check if we've been asked by the caller to disable dev mode. Note * that hardware switch and GBB flag will take precedence over this. */ if (ctx->flags & VB2_DISABLE_DEVELOPER_MODE) flags &= ~VB2_SECDATA_FLAG_DEV_MODE; /* Check virtual dev switch */ if (flags & VB2_SECDATA_FLAG_DEV_MODE) is_dev = 1; /* Handle forcing dev mode via physical switch */ if (ctx->flags & VB2_CONTEXT_FORCE_DEVELOPER_MODE) is_dev = 1; /* Check if GBB is forcing dev mode */ if (sd->gbb_flags & VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON) is_dev = 1; /* Handle whichever mode we end up in */ if (is_dev) { /* Developer mode */ sd->flags |= VB2_SD_DEV_MODE_ENABLED; ctx->flags |= VB2_CONTEXT_DEVELOPER_MODE; flags |= VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER; } else { /* Normal mode */ flags &= ~VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER; /* * Disable dev_boot_* flags. This ensures they will be * initially disabled if the user later transitions back into * developer mode. */ vb2_nv_set(ctx, VB2_NV_DEV_BOOT_USB, 0); vb2_nv_set(ctx, VB2_NV_DEV_BOOT_LEGACY, 0); vb2_nv_set(ctx, VB2_NV_DEV_BOOT_SIGNED_ONLY, 0); vb2_nv_set(ctx, VB2_NV_DEV_BOOT_FASTBOOT_FULL_CAP, 0); vb2_nv_set(ctx, VB2_NV_DEV_DEFAULT_BOOT, 0); vb2_nv_set(ctx, VB2_NV_FASTBOOT_UNLOCK_IN_FW, 0); } if (ctx->flags & VB2_CONTEXT_FORCE_WIPEOUT_MODE) vb2_nv_set(ctx, VB2_NV_REQ_WIPEOUT, 1); if (flags != old_flags) { /* * Just changed dev mode state. Clear TPM owner. This must be * done here instead of simply passing a flag to * vb2_check_tpm_clear(), because we don't want to update * last_boot_developer and then fail to clear the TPM owner. * * Note that we do this even if we couldn't read secdata, since * the TPM owner and secdata may be independent, and we want * the owner to be cleared if *this boot* is different than the * last one (perhaps due to GBB or hardware override). */ rv = vb2ex_tpm_clear_owner(ctx); if (use_secdata) { /* Check for failure to clear owner */ if (rv) { /* * Note that this truncates rv to 8 bit. Which * is not as useful as the full error code, but * we don't have NVRAM space to store the full * 32-bit code. */ vb2_fail(ctx, VB2_RECOVERY_TPM_CLEAR_OWNER, rv); return rv; } /* Save new flags */ rv = vb2_secdata_set(ctx, VB2_SECDATA_FLAGS, flags); if (rv) return rv; } } return VB2_SUCCESS; }
int vb2api_fw_phase1(struct vb2_context *ctx) { int rv; struct vb2_shared_data *sd; /* Initialize the vboot context if it hasn't been yet */ vb2_init_context(ctx); sd = vb2_get_sd(ctx); /* Initialize NV context */ vb2_nv_init(ctx); /* * Handle caller-requested reboot due to secdata. Do this before we * even look at secdata. If we fail because of a reboot loop we'll be * the first failure so will get to set the recovery reason. */ if (!(ctx->flags & VB2_CONTEXT_SECDATA_WANTS_REBOOT)) { /* No reboot requested */ vb2_nv_set(ctx, VB2_NV_TPM_REQUESTED_REBOOT, 0); } else if (vb2_nv_get(ctx, VB2_NV_TPM_REQUESTED_REBOOT)) { /* * Reboot requested... again. Fool me once, shame on you. * Fool me twice, shame on me. Fail into recovery to avoid * a reboot loop. */ vb2_fail(ctx, VB2_RECOVERY_RO_TPM_REBOOT, 0); } else { /* Reboot requested for the first time */ vb2_nv_set(ctx, VB2_NV_TPM_REQUESTED_REBOOT, 1); return VB2_ERROR_API_PHASE1_SECDATA_REBOOT; } /* Initialize secure data */ rv = vb2_secdata_init(ctx); if (rv) vb2_fail(ctx, VB2_RECOVERY_SECDATA_INIT, rv); /* Load and parse the GBB header */ rv = vb2_fw_parse_gbb(ctx); if (rv) vb2_fail(ctx, VB2_RECOVERY_GBB_HEADER, rv); /* * Check for recovery. Note that this function returns void, since any * errors result in requesting recovery. That's also why we don't * return error from failures in the preceding two steps; those * failures simply cause us to detect recovery mode here. */ vb2_check_recovery(ctx); /* Check for dev switch */ rv = vb2_check_dev_switch(ctx); if (rv && !(ctx->flags & VB2_CONTEXT_RECOVERY_MODE)) { /* * Error in dev switch processing, and we weren't already * headed for recovery mode. Reboot into recovery mode, since * it's too late to handle those errors this boot, and we need * to take a different path through the dev switch checking * code in that case. */ vb2_fail(ctx, VB2_RECOVERY_DEV_SWITCH, rv); return rv; } /* * Check for possible reasons to ask the firmware to make display * available. sd->recovery_reason may have been set above by * vb2_check_recovery. VB2_SD_FLAG_DEV_MODE_ENABLED may have been set * above by vb2_check_dev_switch. */ if (!(ctx->flags & VB2_CONTEXT_DISPLAY_INIT) && (vb2_nv_get(ctx, VB2_NV_OPROM_NEEDED) || sd->flags & VB2_SD_FLAG_DEV_MODE_ENABLED || sd->recovery_reason)) ctx->flags |= VB2_CONTEXT_DISPLAY_INIT; /* Mark display as available for downstream vboot and vboot callers. */ if (ctx->flags & VB2_CONTEXT_DISPLAY_INIT) sd->flags |= VB2_SD_FLAG_DISPLAY_AVAILABLE; /* Return error if recovery is needed */ if (ctx->flags & VB2_CONTEXT_RECOVERY_MODE) { /* Always clear RAM when entering recovery mode */ ctx->flags |= VB2_CONTEXT_CLEAR_RAM; return VB2_ERROR_API_PHASE1_RECOVERY; } return VB2_SUCCESS; }
static void nv_storage_test(void) { struct nv_field *vnf; uint8_t goodcrc; uint8_t workbuf[VB2_WORKBUF_RECOMMENDED_SIZE] __attribute__ ((aligned (VB2_WORKBUF_ALIGN))); struct vb2_context c = { .flags = 0, .workbuf = workbuf, .workbuf_size = sizeof(workbuf), }; struct vb2_shared_data *sd = vb2_get_sd(&c); memset(c.nvdata, 0xA6, sizeof(c.nvdata)); vb2_init_context(&c); /* Init with invalid data should set defaults and regenerate CRC */ vb2_nv_init(&c); TEST_EQ(c.nvdata[0], 0x70, "vb2_nv_init() reset header byte"); TEST_NEQ(c.nvdata[15], 0, "vb2_nv_init() CRC"); TEST_EQ(sd->status, VB2_SD_STATUS_NV_INIT | VB2_SD_STATUS_NV_REINIT, "vb2_nv_init() status changed"); test_changed(&c, 1, "vb2_nv_init() reset changed"); goodcrc = c.nvdata[15]; TEST_SUCC(vb2_nv_check_crc(&c), "vb2_nv_check_crc() good"); /* Another init should not cause further changes */ c.flags = 0; sd->status = 0; vb2_nv_init(&c); test_changed(&c, 0, "vb2_nv_init() didn't re-reset"); TEST_EQ(c.nvdata[15], goodcrc, "vb2_nv_init() CRC same"); TEST_EQ(sd->status, VB2_SD_STATUS_NV_INIT, "vb2_nv_init() status same"); /* Perturbing the header should force defaults */ c.nvdata[0] ^= 0x40; TEST_EQ(vb2_nv_check_crc(&c), VB2_ERROR_NV_HEADER, "vb2_nv_check_crc() bad header"); vb2_nv_init(&c); TEST_EQ(c.nvdata[0], 0x70, "vb2_nv_init() reset header byte again"); test_changed(&c, 1, "vb2_nv_init() corrupt changed"); TEST_EQ(c.nvdata[15], goodcrc, "vb2_nv_init() CRC same again"); /* So should perturbing some other byte */ TEST_EQ(c.nvdata[11], 0, "Kernel byte starts at 0"); c.nvdata[11] = 12; TEST_EQ(vb2_nv_check_crc(&c), VB2_ERROR_NV_CRC, "vb2_nv_check_crc() bad CRC"); vb2_nv_init(&c); TEST_EQ(c.nvdata[11], 0, "vb2_nv_init() reset kernel byte"); test_changed(&c, 1, "vb2_nv_init() corrupt elsewhere changed"); TEST_EQ(c.nvdata[15], goodcrc, "vb2_nv_init() CRC same again"); /* Clear the kernel and firmware flags */ vb2_nv_init(&c); TEST_EQ(vb2_nv_get(&c, VB2_NV_FIRMWARE_SETTINGS_RESET), 1, "Firmware settings are reset"); vb2_nv_set(&c, VB2_NV_FIRMWARE_SETTINGS_RESET, 0); TEST_EQ(vb2_nv_get(&c, VB2_NV_FIRMWARE_SETTINGS_RESET), 0, "Firmware settings are clear"); TEST_EQ(vb2_nv_get(&c, VB2_NV_KERNEL_SETTINGS_RESET), 1, "Kernel settings are reset"); vb2_nv_set(&c, VB2_NV_KERNEL_SETTINGS_RESET, 0); TEST_EQ(vb2_nv_get(&c, VB2_NV_KERNEL_SETTINGS_RESET), 0, "Kernel settings are clear"); TEST_EQ(c.nvdata[0], 0x40, "Header byte now just has the header bit"); /* That should have changed the CRC */ TEST_NEQ(c.nvdata[15], goodcrc, "vb2_nv_init() CRC changed due to flags clear"); /* Test explicitly setting the reset flags again */ vb2_nv_init(&c); vb2_nv_set(&c, VB2_NV_FIRMWARE_SETTINGS_RESET, 1); TEST_EQ(vb2_nv_get(&c, VB2_NV_FIRMWARE_SETTINGS_RESET), 1, "Firmware settings forced reset"); vb2_nv_set(&c, VB2_NV_FIRMWARE_SETTINGS_RESET, 0); vb2_nv_set(&c, VB2_NV_KERNEL_SETTINGS_RESET, 1); TEST_EQ(vb2_nv_get(&c, VB2_NV_KERNEL_SETTINGS_RESET), 1, "Kernel settings forced reset"); vb2_nv_set(&c, VB2_NV_KERNEL_SETTINGS_RESET, 0); /* Get/set an invalid field */ vb2_nv_init(&c); vb2_nv_set(&c, -1, 1); TEST_EQ(vb2_nv_get(&c, -1), 0, "Get invalid setting"); /* Test other fields */ vb2_nv_init(&c); for (vnf = nvfields; vnf->desc; vnf++) { TEST_EQ(vb2_nv_get(&c, vnf->param), vnf->default_value, vnf->desc); vb2_nv_set(&c, vnf->param, vnf->test_value); TEST_EQ(vb2_nv_get(&c, vnf->param), vnf->test_value, vnf->desc); vb2_nv_set(&c, vnf->param, vnf->test_value2); TEST_EQ(vb2_nv_get(&c, vnf->param), vnf->test_value2, vnf->desc); } /* None of those changes should have caused a reset to defaults */ vb2_nv_init(&c); TEST_EQ(vb2_nv_get(&c, VB2_NV_FIRMWARE_SETTINGS_RESET), 0, "Firmware settings are still clear"); TEST_EQ(vb2_nv_get(&c, VB2_NV_KERNEL_SETTINGS_RESET), 0, "Kernel settings are still clear"); /* Writing identical settings doesn't cause the CRC to regenerate */ c.flags = 0; vb2_nv_init(&c); test_changed(&c, 0, "No regen CRC on open"); for (vnf = nvfields; vnf->desc; vnf++) vb2_nv_set(&c, vnf->param, vnf->test_value2); test_changed(&c, 0, "No regen CRC if data not changed"); /* Test out-of-range fields mapping to defaults or failing */ vb2_nv_init(&c); vb2_nv_set(&c, VB2_NV_TRY_COUNT, 16); TEST_EQ(vb2_nv_get(&c, VB2_NV_TRY_COUNT), 15, "Try b count out of range"); vb2_nv_set(&c, VB2_NV_RECOVERY_REQUEST, 0x101); TEST_EQ(vb2_nv_get(&c, VB2_NV_RECOVERY_REQUEST), VB2_RECOVERY_LEGACY, "Recovery request out of range"); vb2_nv_set(&c, VB2_NV_LOCALIZATION_INDEX, 0x102); TEST_EQ(vb2_nv_get(&c, VB2_NV_LOCALIZATION_INDEX), 0, "Localization index out of range"); vb2_nv_set(&c, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN + 1); vb2_nv_set(&c, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN + 100); TEST_EQ(vb2_nv_get(&c, VB2_NV_FW_RESULT), VB2_FW_RESULT_UNKNOWN, "Firmware result out of range"); } int main(int argc, char* argv[]) { nv_storage_test(); return gTestSuccess ? 0 : 255; }
static void init_context_tests(void) { /* Use our own context struct so we can re-init it */ struct vb2_context c = { .workbuf = workbuf, .workbuf_size = sizeof(workbuf), }; reset_common_data(); TEST_SUCC(vb2_init_context(&c), "Init context good"); TEST_EQ(c.workbuf_used, sizeof(struct vb2_shared_data), "Init vbsd"); /* Don't re-init if used is non-zero */ c.workbuf_used = 200; TEST_SUCC(vb2_init_context(&c), "Re-init context good"); TEST_EQ(c.workbuf_used, 200, "Didn't re-init"); /* Handle workbuf errors */ c.workbuf_used = 0; c.workbuf_size = sizeof(struct vb2_shared_data) - 1; TEST_EQ(vb2_init_context(&c), VB2_ERROR_INITCTX_WORKBUF_SMALL, "Init too small"); c.workbuf_size = sizeof(workbuf); /* Handle workbuf unaligned */ c.workbuf++; TEST_EQ(vb2_init_context(&c), VB2_ERROR_INITCTX_WORKBUF_ALIGN, "Init unaligned"); } static void misc_tests(void) { struct vb2_workbuf wb; reset_common_data(); cc.workbuf_used = 16; vb2_workbuf_from_ctx(&cc, &wb); TEST_PTR_EQ(wb.buf, workbuf + 16, "vb_workbuf_from_ctx() buf"); TEST_EQ(wb.size, cc.workbuf_size - 16, "vb_workbuf_from_ctx() size"); } static void gbb_tests(void) { struct vb2_gbb_header gbb = { .signature = {'$', 'G', 'B', 'B'}, .major_version = VB2_GBB_MAJOR_VER, .minor_version = VB2_GBB_MINOR_VER, .header_size = sizeof(struct vb2_gbb_header), .flags = 0x1234, .rootkey_offset = 240, .rootkey_size = 1040, }; struct vb2_gbb_header gbbdest; TEST_EQ(sizeof(struct vb2_gbb_header), EXPECTED_VB2_GBB_HEADER_SIZE, "sizeof(struct vb2_gbb_header)"); reset_common_data(); /* Good contents */ mock_resource_index = VB2_RES_GBB; mock_resource_ptr = &gbb; mock_resource_size = sizeof(gbb); TEST_SUCC(vb2_read_gbb_header(&cc, &gbbdest), "read gbb header good"); TEST_SUCC(memcmp(&gbb, &gbbdest, sizeof(gbb)), "read gbb contents"); mock_resource_index = VB2_RES_GBB + 1; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_EX_READ_RESOURCE_INDEX, "read gbb header missing"); mock_resource_index = VB2_RES_GBB; gbb.signature[0]++; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_GBB_MAGIC, "read gbb header bad magic"); gbb.signature[0]--; gbb.major_version = VB2_GBB_MAJOR_VER + 1; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_GBB_VERSION, "read gbb header major version"); gbb.major_version = VB2_GBB_MAJOR_VER; gbb.minor_version = VB2_GBB_MINOR_VER + 1; TEST_SUCC(vb2_read_gbb_header(&cc, &gbbdest), "read gbb header minor++"); gbb.minor_version = 1; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_GBB_TOO_OLD, "read gbb header 1.1 fails"); gbb.minor_version = 0; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_GBB_TOO_OLD, "read gbb header 1.0 fails"); gbb.minor_version = VB2_GBB_MINOR_VER; gbb.header_size--; TEST_EQ(vb2_read_gbb_header(&cc, &gbbdest), VB2_ERROR_GBB_HEADER_SIZE, "read gbb header size"); TEST_EQ(vb2_fw_parse_gbb(&cc), VB2_ERROR_GBB_HEADER_SIZE, "parse gbb failure"); gbb.header_size++; /* Parse GBB */ TEST_SUCC(vb2_fw_parse_gbb(&cc), "parse gbb"); TEST_EQ(sd->gbb_flags, gbb.flags, "gbb flags"); TEST_EQ(sd->gbb_rootkey_offset, gbb.rootkey_offset, "rootkey offset"); TEST_EQ(sd->gbb_rootkey_size, gbb.rootkey_size, "rootkey size"); /* Workbuf failure */ reset_common_data(); cc.workbuf_used = cc.workbuf_size - 4; TEST_EQ(vb2_fw_parse_gbb(&cc), VB2_ERROR_GBB_WORKBUF, "parse gbb no workbuf"); } static void fail_tests(void) { /* Early fail (before even NV init) */ reset_common_data(); sd->status &= ~VB2_SD_STATUS_NV_INIT; vb2_fail(&cc, 1, 2); TEST_NEQ(sd->status & VB2_SD_STATUS_NV_INIT, 0, "vb2_fail inits NV"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 1, "vb2_fail request"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), 2, "vb2_fail subcode"); /* Repeated fail doesn't overwrite the error code */ vb2_fail(&cc, 3, 4); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 1, "vb2_fail repeat"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), 2, "vb2_fail repeat2"); /* Fail with other slot good doesn't trigger recovery */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_TRY_COUNT, 3); vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_UNKNOWN); sd->status |= VB2_SD_STATUS_CHOSE_SLOT; sd->fw_slot = 0; sd->last_fw_slot = 1; sd->last_fw_result = VB2_FW_RESULT_UNKNOWN; vb2_fail(&cc, 5, 6); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 0, "vb2_failover"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_FAILURE, "vb2_fail this fw"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 0, "vb2_fail use up tries"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 1, "vb2_fail try other slot"); /* Fail with other slot already failing triggers recovery */ reset_common_data(); sd->status |= VB2_SD_STATUS_CHOSE_SLOT; sd->fw_slot = 1; sd->last_fw_slot = 0; sd->last_fw_result = VB2_FW_RESULT_FAILURE; vb2_fail(&cc, 7, 8); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), 7, "vb2_fail both slots bad"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_FAILURE, "vb2_fail this fw"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 0, "vb2_fail try other slot"); }
static void select_slot_tests(void) { /* Slot A */ reset_common_data(); TEST_SUCC(vb2_select_fw_slot(&cc), "select slot A"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_UNKNOWN, "result unknown"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 0, "tried A"); TEST_EQ(sd->fw_slot, 0, "selected A"); TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B"); /* Slot B */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_TRY_NEXT, 1); TEST_SUCC(vb2_select_fw_slot(&cc), "select slot B"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_UNKNOWN, "result unknown"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 1, "tried B"); TEST_EQ(sd->fw_slot, 1, "selected B"); TEST_NEQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "ctx says choose B"); /* Slot A ran out of tries */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_TRYING); TEST_SUCC(vb2_select_fw_slot(&cc), "select slot A out of tries"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 1, "try B next"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 1, "tried B"); TEST_EQ(sd->fw_slot, 1, "selected B"); TEST_NEQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "ctx says choose B"); /* Slot A ran out of tries, even with nofail active */ reset_common_data(); cc.flags |= VB2_CONTEXT_NOFAIL_BOOT; vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_TRYING); TEST_SUCC(vb2_select_fw_slot(&cc), "select slot A out of tries"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_NEXT), 1, "try B next"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 1, "tried B"); TEST_EQ(sd->fw_slot, 1, "selected B"); TEST_NEQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "ctx says choose B"); /* Slot A used up a try */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_TRY_COUNT, 3); TEST_SUCC(vb2_select_fw_slot(&cc), "try slot A"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_TRYING, "result trying"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 0, "tried A"); TEST_EQ(sd->fw_slot, 0, "selected A"); TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 2, "tries decremented"); /* Slot A failed, but nofail active */ reset_common_data(); cc.flags |= VB2_CONTEXT_NOFAIL_BOOT; vb2_nv_set(&cc, VB2_NV_TRY_COUNT, 3); TEST_SUCC(vb2_select_fw_slot(&cc), "try slot A"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_RESULT), VB2_FW_RESULT_TRYING, "result trying"); TEST_NEQ(sd->status & VB2_SD_STATUS_CHOSE_SLOT, 0, "chose slot"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_TRIED), 0, "tried A"); TEST_EQ(sd->fw_slot, 0, "selected A"); TEST_EQ(cc.flags & VB2_CONTEXT_FW_SLOT_B, 0, "didn't choose B"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_TRY_COUNT), 3, "tries not decremented"); /* Tried/result get copied to the previous fields */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_FW_TRIED, 0); vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_SUCCESS); vb2_select_fw_slot(&cc); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_TRIED), 0, "prev A"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_RESULT), VB2_FW_RESULT_SUCCESS, "prev success"); reset_common_data(); vb2_nv_set(&cc, VB2_NV_FW_TRIED, 1); vb2_nv_set(&cc, VB2_NV_FW_RESULT, VB2_FW_RESULT_FAILURE); vb2_select_fw_slot(&cc); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_TRIED), 1, "prev B"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FW_PREV_RESULT), VB2_FW_RESULT_FAILURE, "prev failure"); }
static void dev_switch_tests(void) { uint32_t v; /* Normal mode */ reset_common_data(); TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode off"); TEST_EQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd not in dev"); TEST_EQ(cc.flags & VB2_CONTEXT_DEVELOPER_MODE, 0, "ctx not in dev"); TEST_EQ(mock_tpm_clear_called, 0, "no tpm clear"); /* Dev mode */ reset_common_data(); vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, (VB2_SECDATA_FLAG_DEV_MODE | VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER)); TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode on"); TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); TEST_NEQ(cc.flags & VB2_CONTEXT_DEVELOPER_MODE, 0, "ctx in dev"); TEST_EQ(mock_tpm_clear_called, 0, "no tpm clear"); /* Any normal mode boot clears dev boot flags */ reset_common_data(); vb2_nv_set(&cc, VB2_NV_DEV_BOOT_USB, 1); vb2_nv_set(&cc, VB2_NV_DEV_BOOT_LEGACY, 1); vb2_nv_set(&cc, VB2_NV_DEV_BOOT_SIGNED_ONLY, 1); vb2_nv_set(&cc, VB2_NV_DEV_BOOT_FASTBOOT_FULL_CAP, 1); vb2_nv_set(&cc, VB2_NV_FASTBOOT_UNLOCK_IN_FW, 1); TEST_SUCC(vb2_check_dev_switch(&cc), "dev mode off"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_USB), 0, "cleared dev boot usb"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_LEGACY), 0, "cleared dev boot legacy"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_SIGNED_ONLY), 0, "cleared dev boot signed only"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_DEV_BOOT_FASTBOOT_FULL_CAP), 0, "cleared dev boot fastboot full cap"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_FASTBOOT_UNLOCK_IN_FW), 0, "cleared dev boot fastboot unlock in fw"); /* Normal-dev transition clears TPM */ reset_common_data(); vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, VB2_SECDATA_FLAG_DEV_MODE); TEST_SUCC(vb2_check_dev_switch(&cc), "to dev mode"); TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); TEST_EQ(v, (VB2_SECDATA_FLAG_DEV_MODE | VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER), "last boot developer now"); /* Dev-normal transition clears TPM too */ reset_common_data(); vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER); TEST_SUCC(vb2_check_dev_switch(&cc), "from dev mode"); TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); TEST_EQ(v, 0, "last boot not developer now"); /* Disable dev mode */ reset_common_data(); vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, (VB2_SECDATA_FLAG_DEV_MODE | VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER)); vb2_nv_set(&cc, VB2_NV_DISABLE_DEV_REQUEST, 1); TEST_SUCC(vb2_check_dev_switch(&cc), "disable dev request"); TEST_EQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd not in dev"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_DISABLE_DEV_REQUEST), 0, "request cleared"); /* Force enabled by gbb */ reset_common_data(); sd->gbb_flags |= VB2_GBB_FLAG_FORCE_DEV_SWITCH_ON; TEST_SUCC(vb2_check_dev_switch(&cc), "dev on via gbb"); TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, "doesn't set dev on in secdata but does set last boot dev"); TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); /* Force enabled by ctx flag */ reset_common_data(); cc.flags |= VB2_CONTEXT_FORCE_DEVELOPER_MODE; TEST_SUCC(vb2_check_dev_switch(&cc), "dev on via ctx flag"); TEST_NEQ(sd->flags & VB2_SD_DEV_MODE_ENABLED, 0, "sd in dev"); vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, "doesn't set dev on in secdata but does set last boot dev"); TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); /* Simulate clear owner failure */ reset_common_data(); vb2_secdata_set(&cc, VB2_SECDATA_FLAGS, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER); mock_tpm_clear_retval = VB2_ERROR_EX_TPM_CLEAR_OWNER; TEST_EQ(vb2_check_dev_switch(&cc), VB2_ERROR_EX_TPM_CLEAR_OWNER, "tpm clear fail"); TEST_EQ(mock_tpm_clear_called, 1, "tpm clear"); vb2_secdata_get(&cc, VB2_SECDATA_FLAGS, &v); TEST_EQ(v, VB2_SECDATA_FLAG_LAST_BOOT_DEVELOPER, "last boot still developer"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_REQUEST), VB2_RECOVERY_TPM_CLEAR_OWNER, "requests recovery"); TEST_EQ(vb2_nv_get(&cc, VB2_NV_RECOVERY_SUBCODE), (uint8_t)VB2_ERROR_EX_TPM_CLEAR_OWNER, "recovery subcode"); }