/* Copyright 2016 The Chromium OS Authors. All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ #include "2sysincludes.h" #include "2hmac.h" #include "2sha.h" #include "bdb_api.h" #include "bdb_struct.h" #include "bdb.h" #include "nvm.h" #include "secrets.h" static int nvmrw_validate(const void *buf, uint32_t size) { const struct nvmrw *nvm = buf; if (nvm->struct_magic != NVM_RW_MAGIC) return BDB_ERROR_NVM_RW_MAGIC; if (nvm->struct_major_version != NVM_HEADER_VERSION_MAJOR) return BDB_ERROR_NVM_STRUCT_VERSION; if (size < nvm->struct_size) return BDB_ERROR_NVM_STRUCT_SIZE; /* * We allow any sizes between min and max so that we can handle minor * version mismatches. Reader can be older than data or the other way * around. FW in slot B can upgrade NVM-RW but fails to qualify as a * stable boot path. Then, FW in slot A is invoked which is older than * the NVM-RW written by FW in slot B. */ if (nvm->struct_size < NVM_RW_MIN_STRUCT_SIZE || NVM_RW_MAX_STRUCT_SIZE < nvm->struct_size) return BDB_ERROR_NVM_STRUCT_SIZE; return BDB_SUCCESS; } static int nvmrw_verify(const struct bdb_secrets *secrets, const struct nvmrw *nvm, uint32_t size) { uint8_t mac[NVM_HMAC_SIZE]; int rv; if (!secrets || !nvm) return BDB_ERROR_NVM_INVALID_PARAMETER; rv = nvmrw_validate(nvm, size); if (rv) return rv; /* Compute and verify HMAC */ if (hmac(VB2_HASH_SHA256, secrets->nvm_rw, BDB_SECRET_SIZE, nvm, nvm->struct_size - sizeof(mac), mac, sizeof(mac))) return BDB_ERROR_NVM_RW_HMAC; /* TODO: Use safe_memcmp */ if (memcmp(mac, nvm->hmac, sizeof(mac))) return BDB_ERROR_NVM_RW_INVALID_HMAC; return BDB_SUCCESS; } int nvmrw_write(struct vba_context *ctx, enum nvm_type type) { struct nvmrw *nvm = &ctx->nvmrw; int retry = NVM_MAX_WRITE_RETRY; int rv; if (!ctx) return BDB_ERROR_NVM_INVALID_PARAMETER; if (!ctx->secrets) return BDB_ERROR_NVM_INVALID_SECRET; rv = nvmrw_validate(nvm, sizeof(*nvm)); if (rv) return rv; /* Update HMAC */ hmac(VB2_HASH_SHA256, ctx->secrets->nvm_rw, BDB_SECRET_SIZE, nvm, nvm->struct_size - sizeof(nvm->hmac), nvm->hmac, sizeof(nvm->hmac)); while (retry--) { uint8_t buf[sizeof(struct nvmrw)]; if (vbe_write_nvm(type, nvm, nvm->struct_size)) continue; if (vbe_read_nvm(type, buf, sizeof(buf))) continue; if (memcmp(buf, nvm, sizeof(buf))) continue; /* Write success */ return BDB_SUCCESS; } /* NVM seems corrupted. Go to chip recovery mode */ return BDB_ERROR_NVM_WRITE; } static int read_verify_nvmrw(enum nvm_type type, const struct bdb_secrets *secrets, uint8_t *buf, uint32_t buf_size) { struct nvmrw *nvm = (struct nvmrw *)buf; int rv; /* Read minimum amount */ if (vbe_read_nvm(type, buf, NVM_MIN_STRUCT_SIZE)) return BDB_ERROR_NVM_VBE_READ; /* Validate the content */ rv = nvmrw_validate(buf, buf_size); if (rv) return rv; /* Read full body */ if (vbe_read_nvm(type, buf, nvm->struct_size)) return BDB_ERROR_NVM_VBE_READ; /* Verify the content */ rv = nvmrw_verify(secrets, nvm, sizeof(*nvm)); return rv; return BDB_SUCCESS; } int nvmrw_read(struct vba_context *ctx) { uint8_t buf1[NVM_RW_MAX_STRUCT_SIZE]; uint8_t buf2[NVM_RW_MAX_STRUCT_SIZE]; struct nvmrw *nvm1 = (struct nvmrw *)buf1; struct nvmrw *nvm2 = (struct nvmrw *)buf2; int rv1, rv2; /* Read and verify the 1st copy */ rv1 = read_verify_nvmrw(NVM_TYPE_RW_PRIMARY, ctx->secrets, buf1, sizeof(buf1)); /* Read and verify the 2nd copy */ rv2 = read_verify_nvmrw(NVM_TYPE_RW_SECONDARY, ctx->secrets, buf2, sizeof(buf2)); if (rv1 == BDB_SUCCESS && rv2 == BDB_SUCCESS) { /* Sync primary and secondary based on update_count. */ if (nvm1->update_count > nvm2->update_count) rv2 = !BDB_SUCCESS; else if (nvm1->update_count < nvm2->update_count) rv1 = !BDB_SUCCESS; } else if (rv1 != BDB_SUCCESS && rv2 != BDB_SUCCESS){ /* Abort. Neither was successful. */ return rv1; } if (rv1 == BDB_SUCCESS) /* both copies are good. use primary copy */ memcpy(&ctx->nvmrw, buf1, sizeof(ctx->nvmrw)); else /* primary is bad but secondary is good. */ memcpy(&ctx->nvmrw, buf2, sizeof(ctx->nvmrw)); if (ctx->nvmrw.struct_minor_version != NVM_HEADER_VERSION_MINOR) { /* * Upgrade or downgrade is required. So, we need to write both. * When upgrading, this is the place where new fields should be * initialized. We don't increment update_count. */ ctx->nvmrw.struct_minor_version = NVM_HEADER_VERSION_MINOR; ctx->nvmrw.struct_size = sizeof(ctx->nvmrw); /* We don't worry about calculating hmac twice because * this is a corner case. */ rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); } else if (rv1 != BDB_SUCCESS) { /* primary copy is bad. sync it with secondary copy */ rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); } else if (rv2 != BDB_SUCCESS){ /* secondary copy is bad. sync it with primary copy */ rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); } else { /* Both copies are good and versions are same as the reader. * Skip writing. This should be the common case. */ } if (rv1 || rv2) return rv1 ? rv1 : rv2; return BDB_SUCCESS; } static int nvmrw_init(struct vba_context *ctx) { if (nvmrw_read(ctx)) return BDB_ERROR_NVM_INIT; return BDB_SUCCESS; } int vba_update_kernel_version(struct vba_context *ctx, uint32_t kernel_data_key_version, uint32_t kernel_version) { struct nvmrw *nvm = &ctx->nvmrw; if (nvmrw_verify(ctx->secrets, nvm, sizeof(*nvm))) { if (nvmrw_init(ctx)) return BDB_ERROR_NVM_INIT; } if (nvm->min_kernel_data_key_version < kernel_data_key_version || nvm->min_kernel_version < kernel_version) { int rv1, rv2; /* Roll forward versions */ nvm->min_kernel_data_key_version = kernel_data_key_version; nvm->min_kernel_version = kernel_version; /* Increment update counter */ nvm->update_count++; /* Update both copies */ rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); if (rv1 || rv2) return BDB_ERROR_RECOVERY_REQUEST; } return BDB_SUCCESS; } int vba_update_buc(struct vba_context *ctx, uint8_t *new_buc) { struct nvmrw *nvm = &ctx->nvmrw; uint8_t buc[BUC_ENC_DIGEST_SIZE]; int rv1, rv2; if (nvmrw_verify(ctx->secrets, nvm, sizeof(*nvm))) { if (nvmrw_init(ctx)) return BDB_ERROR_NVM_INIT; } /* Encrypt new BUC * Note that we do not need to decide whether we should use hardware * crypto or not because this is supposed to be running in RW code. */ if (vbe_aes256_encrypt(new_buc, BUC_ENC_DIGEST_SIZE, ctx->secrets->buc, buc)) return BDB_ERROR_ENCRYPT_BUC; /* Return if new BUC is same as current one. */ if (!memcmp(buc, nvm->buc_enc_digest, sizeof(buc))) return BDB_SUCCESS; memcpy(nvm->buc_enc_digest, buc, sizeof(buc)); /* Increment update counter */ nvm->update_count++; /* Write new BUC */ rv1 = nvmrw_write(ctx, NVM_TYPE_RW_PRIMARY); rv2 = nvmrw_write(ctx, NVM_TYPE_RW_SECONDARY); if (rv1 || rv2) return BDB_ERROR_WRITE_BUC; return BDB_SUCCESS; } int nvmrw_get(struct vba_context *ctx, enum nvmrw_var var, uint32_t *val) { struct nvmrw *nvm = &ctx->nvmrw; /* No init or verify so that this can be called from futility. * Callers are responsible for init and verify. */ switch (var) { case NVMRW_VAR_UPDATE_COUNT: *val = nvm->update_count; break; case NVMRW_VAR_MIN_KERNEL_DATA_KEY_VERSION: *val = nvm->min_kernel_data_key_version; break; case NVMRW_VAR_MIN_KERNEL_VERSION: *val = nvm->min_kernel_version; break; case NVMRW_VAR_BUC_TYPE: *val = nvm->buc_type; break; case NVMRW_VAR_FLAG_BUC_PRESENT: *val = nvm->flags & NVM_RW_FLAG_BUC_PRESENT; break; case NVMRW_VAR_FLAG_DFM_DISABLE: *val = nvm->flags & NVM_RW_FLAG_DFM_DISABLE; break; case NVMRW_VAR_FLAG_DOSM: *val = nvm->flags & NVM_RW_FLAG_DOSM; break; default: return BDB_ERROR_NVM_INVALID_PARAMETER; } return BDB_SUCCESS; } #define MAX_8BIT_UINT ((((uint64_t)1) << 8) - 1) int nvmrw_set(struct vba_context *ctx, enum nvmrw_var var, uint32_t val) { struct nvmrw *nvm = &ctx->nvmrw; /* No init or verify so that this can be called from futility. * Callers are responsible for init and verify. */ switch (var) { case NVMRW_VAR_UPDATE_COUNT: nvm->update_count = val; break; case NVMRW_VAR_MIN_KERNEL_DATA_KEY_VERSION: nvm->min_kernel_data_key_version = val; break; case NVMRW_VAR_MIN_KERNEL_VERSION: nvm->min_kernel_version = val; break; case NVMRW_VAR_BUC_TYPE: if (val > MAX_8BIT_UINT) return BDB_ERROR_NVM_INVALID_PARAMETER; nvm->buc_type = val; break; case NVMRW_VAR_FLAG_BUC_PRESENT: nvm->flags &= ~NVM_RW_FLAG_BUC_PRESENT; nvm->flags |= val ? NVM_RW_FLAG_BUC_PRESENT : 0; break; case NVMRW_VAR_FLAG_DFM_DISABLE: nvm->flags &= ~NVM_RW_FLAG_DFM_DISABLE; nvm->flags |= val ? NVM_RW_FLAG_DFM_DISABLE : 0; break; case NVMRW_VAR_FLAG_DOSM: nvm->flags &= ~NVM_RW_FLAG_DOSM; nvm->flags |= val ? NVM_RW_FLAG_DOSM : 0; break; default: return BDB_ERROR_NVM_INVALID_PARAMETER; } return BDB_SUCCESS; }