summaryrefslogtreecommitdiff
path: root/firmware/2lib/2misc.c
diff options
context:
space:
mode:
Diffstat (limited to 'firmware/2lib/2misc.c')
-rw-r--r--firmware/2lib/2misc.c307
1 files changed, 306 insertions, 1 deletions
diff --git a/firmware/2lib/2misc.c b/firmware/2lib/2misc.c
index 9713ffc0..c0692c53 100644
--- a/firmware/2lib/2misc.c
+++ b/firmware/2lib/2misc.c
@@ -14,6 +14,93 @@
#include "2sha.h"
#include "2rsa.h"
+void vb2_workbuf_from_ctx(struct vb2_context *ctx, struct vb2_workbuf *wb)
+{
+ vb2_workbuf_init(wb, ctx->workbuf + ctx->workbuf_used,
+ ctx->workbuf_size - ctx->workbuf_used);
+}
+
+int vb2_read_gbb_header(struct vb2_context *ctx, struct vb2_gbb_header *gbb)
+{
+ static const uint8_t expect_sig[VB2_GBB_SIGNATURE_SIZE] =
+ VB2_GBB_SIGNATURE;
+ int rv;
+
+ /* Read the entire header */
+ rv = vb2ex_read_resource(ctx, VB2_RES_GBB, 0, gbb, sizeof(*gbb));
+ if (rv)
+ return rv;
+
+ /* Make sure it's really a GBB */
+ if (memcmp(gbb->signature, expect_sig, sizeof(expect_sig)))
+ return VB2_ERROR_GBB_MAGIC;
+
+ /* Check for compatible version */
+ if (gbb->major_version != VB2_GBB_MAJOR_VER)
+ return VB2_ERROR_GBB_VERSION;
+
+ /* Current code is not backwards-compatible to 1.0 headers */
+ if (gbb->minor_version < VB2_GBB_MINOR_VER)
+ return VB2_ERROR_GBB_TOO_OLD;
+
+ /*
+ * Header size should be at least as big as we expect. It could be
+ * bigger, if the header has grown.
+ */
+ if (gbb->header_size < sizeof(*gbb))
+ return VB2_ERROR_GBB_HEADER_SIZE;
+
+ return VB2_SUCCESS;
+}
+
+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.
+ */
+ 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 vb2_init_context(struct vb2_context *ctx)
{
struct vb2_shared_data *sd = vb2_get_sd(ctx);
@@ -29,7 +116,7 @@ int vb2_init_context(struct vb2_context *ctx)
*/
if (ctx->workbuf_size < sizeof(*sd))
return VB2_ERROR_INITCTX_WORKBUF_SMALL;
- if (!vb_aligned(ctx->workbuf, sizeof(uint32_t)))
+ if (!vb_aligned(ctx->workbuf, VB2_WORKBUF_ALIGN))
return VB2_ERROR_INITCTX_WORKBUF_ALIGN;
/* Initialize the shared data at the start of the work buffer */
@@ -37,3 +124,221 @@ int vb2_init_context(struct vb2_context *ctx)
ctx->workbuf_used = sizeof(*sd);
return VB2_SUCCESS;
}
+
+void vb2_check_recovery(struct vb2_context *ctx)
+{
+ struct vb2_shared_data *sd = vb2_get_sd(ctx);
+
+ /*
+ * Read the current recovery request, unless there's already been a
+ * failure earlier in the boot process.
+ */
+ if (!sd->recovery_reason)
+ sd->recovery_reason = vb2_nv_get(ctx, VB2_NV_RECOVERY_REQUEST);
+
+ /* Clear the recovery request so we don't get stuck in recovery mode */
+ if (sd->recovery_reason) {
+ vb2_nv_set(ctx, VB2_NV_RECOVERY_REQUEST,
+ VB2_RECOVERY_NOT_REQUESTED);
+ /*
+ * Note that we ignore failures clearing the request. We only
+ * hit this code path if recovery mode has already been
+ * requested, so what more can we do? Don't want to obscure
+ * the original reason for going into recovery mode.
+ */
+ }
+
+ /* If forcing recovery, override recovery reason */
+ if (ctx->flags & VB2_CONTEXT_FORCE_RECOVERY_MODE) {
+ 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;
+}
+
+int vb2_fw_parse_gbb(struct vb2_context *ctx)
+{
+ struct vb2_shared_data *sd = vb2_get_sd(ctx);
+ struct vb2_gbb_header *gbb;
+ struct vb2_workbuf wb;
+ int rv;
+
+ vb2_workbuf_from_ctx(ctx, &wb);
+
+ /* Read GBB into next chunk of work buffer */
+ gbb = vb2_workbuf_alloc(&wb, sizeof(*gbb));
+ if (!gbb)
+ return VB2_ERROR_GBB_WORKBUF;
+
+ rv = vb2_read_gbb_header(ctx, gbb);
+ if (rv)
+ return rv;
+
+ /* Extract the only things we care about at firmware time */
+ sd->gbb_flags = gbb->flags;
+ sd->gbb_rootkey_offset = gbb->rootkey_offset;
+ sd->gbb_rootkey_size = gbb->rootkey_size;
+
+ return VB2_SUCCESS;
+}
+
+int vb2_check_dev_switch(struct vb2_context *ctx)
+{
+ struct vb2_shared_data *sd = vb2_get_sd(ctx);
+ uint32_t flags;
+ uint32_t old_flags;
+ int is_dev = 0;
+ int rv;
+
+ /* Read secure flags */
+ rv = vb2_secdata_get(ctx, VB2_SECDATA_FLAGS, &flags);
+ if (rv)
+ return rv;
+
+ old_flags = flags;
+
+ /* Handle dev disable request */
+ if (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 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);
+ }
+
+ 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.
+ */
+ 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;
+ }
+
+ /* Save new flags */
+ rv = vb2_secdata_set(ctx, VB2_SECDATA_FLAGS, flags);
+ if (rv)
+ return rv;
+ }
+
+ 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;
+}
+
+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);
+
+ /* 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 */
+ vb2_nv_set(ctx, VB2_NV_TRY_COUNT, tries - 1);
+ }
+
+ /* 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;
+}