/* Copyright (c) 2013 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. * * Functions for querying, manipulating and locking rollback indices * stored in the TPM NVRAM. */ #include "2sysincludes.h" #include "2common.h" #include "2crc8.h" #include "sysincludes.h" #include "rollback_index.h" #include "tlcl.h" #include "tss_constants.h" #include "utility.h" #include "vboot_api.h" #ifndef offsetof #define offsetof(A,B) __builtin_offsetof(A,B) #endif /* * Provide protoypes for functions not in the header file. These prototypes * fix -Wmissing-prototypes warnings. */ uint32_t ReadSpaceFirmware(RollbackSpaceFirmware *rsf); uint32_t WriteSpaceFirmware(RollbackSpaceFirmware *rsf); uint32_t ReadSpaceKernel(RollbackSpaceKernel *rsk); uint32_t WriteSpaceKernel(RollbackSpaceKernel *rsk); #ifdef FOR_TEST /* * Compiling for unit test, so we need the real implementations of * rollback functions. The unit test mocks the underlying tlcl * functions, so this is ok to run on the host. */ #undef CHROMEOS_ENVIRONMENT #undef DISABLE_ROLLBACK_TPM #endif #define RETURN_ON_FAILURE(tpm_command) do { \ uint32_t result_; \ if ((result_ = (tpm_command)) != TPM_SUCCESS) { \ VB2_DEBUG("Rollback: %08x returned by " #tpm_command \ "\n", (int)result_); \ return result_; \ } \ } while (0) uint32_t TPMClearAndReenable(void) { VB2_DEBUG("TPM: Clear and re-enable\n"); RETURN_ON_FAILURE(TlclForceClear()); RETURN_ON_FAILURE(TlclSetEnable()); RETURN_ON_FAILURE(TlclSetDeactivated(0)); return TPM_SUCCESS; } uint32_t SafeWrite(uint32_t index, const void *data, uint32_t length) { uint32_t result = TlclWrite(index, data, length); if (result == TPM_E_MAXNVWRITES) { RETURN_ON_FAILURE(TPMClearAndReenable()); return TlclWrite(index, data, length); } else { return result; } } /* Functions to read and write firmware and kernel spaces. */ uint32_t ReadSpaceFirmware(RollbackSpaceFirmware *rsf) { uint32_t r; int attempts = 3; while (attempts--) { r = TlclRead(FIRMWARE_NV_INDEX, rsf, sizeof(RollbackSpaceFirmware)); if (r != TPM_SUCCESS) return r; /* * No CRC in this version, so we'll create one when we write * it. Note that we're marking this as version 2, not * ROLLBACK_SPACE_FIRMWARE_VERSION, because version 2 just * added the CRC. Later versions will need to set default * values for any extra fields explicitly (probably here). */ if (rsf->struct_version < 2) { /* Danger Will Robinson! Danger! */ rsf->struct_version = 2; return TPM_SUCCESS; } /* * If the CRC is good, we're done. If it's bad, try a couple * more times to see if it gets better before we give up. It * could just be noise. */ if (rsf->crc8 == vb2_crc8(rsf, offsetof(RollbackSpaceFirmware, crc8))) return TPM_SUCCESS; VB2_DEBUG("TPM: bad CRC\n"); } VB2_DEBUG("TPM: too many bad CRCs, giving up\n"); return TPM_E_CORRUPTED_STATE; } uint32_t WriteSpaceFirmware(RollbackSpaceFirmware *rsf) { RollbackSpaceFirmware rsf2; uint32_t r; int attempts = 3; /* All writes should use struct_version 2 or greater. */ if (rsf->struct_version < 2) rsf->struct_version = 2; rsf->crc8 = vb2_crc8(rsf, offsetof(RollbackSpaceFirmware, crc8)); while (attempts--) { r = SafeWrite(FIRMWARE_NV_INDEX, rsf, sizeof(RollbackSpaceFirmware)); /* Can't write, not gonna try again */ if (r != TPM_SUCCESS) return r; /* Read it back to be sure it got the right values. */ r = ReadSpaceFirmware(&rsf2); /* This checks the CRC */ if (r == TPM_SUCCESS) return r; VB2_DEBUG("TPM: bad CRC\n"); /* Try writing it again. Maybe it was garbled on the way out. */ } VB2_DEBUG("TPM: too many bad CRCs, giving up\n"); return TPM_E_CORRUPTED_STATE; } uint32_t SetVirtualDevMode(int val) { RollbackSpaceFirmware rsf; VB2_DEBUG("TPM: Entering"); if (TPM_SUCCESS != ReadSpaceFirmware(&rsf)) return VBERROR_TPM_FIRMWARE_SETUP; VB2_DEBUG("TPM: flags were 0x%02x\n", rsf.flags); if (val) rsf.flags |= FLAG_VIRTUAL_DEV_MODE_ON; else rsf.flags &= ~FLAG_VIRTUAL_DEV_MODE_ON; /* * NOTE: This doesn't update the FLAG_LAST_BOOT_DEVELOPER bit. That * will be done on the next boot. */ VB2_DEBUG("TPM: flags are now 0x%02x\n", rsf.flags); if (TPM_SUCCESS != WriteSpaceFirmware(&rsf)) return VBERROR_TPM_SET_BOOT_MODE_STATE; VB2_DEBUG("TPM: Leaving\n"); return VBERROR_SUCCESS; } uint32_t ReadSpaceKernel(RollbackSpaceKernel *rsk) { uint32_t r; int attempts = 3; while (attempts--) { r = TlclRead(KERNEL_NV_INDEX, rsk, sizeof(RollbackSpaceKernel)); if (r != TPM_SUCCESS) return r; /* * No CRC in this version, so we'll create one when we write * it. Note that we're marking this as version 2, not * ROLLBACK_SPACE_KERNEL_VERSION, because version 2 just added * the CRC. Later versions will need to set default values for * any extra fields explicitly (probably here). */ if (rsk->struct_version < 2) { /* Danger Will Robinson! Danger! */ rsk->struct_version = 2; return TPM_SUCCESS; } /* * If the CRC is good, we're done. If it's bad, try a couple * more times to see if it gets better before we give up. It * could just be noise. */ if (rsk->crc8 == vb2_crc8(rsk, offsetof(RollbackSpaceKernel, crc8))) return TPM_SUCCESS; VB2_DEBUG("TPM: bad CRC\n"); } VB2_DEBUG("TPM: too many bad CRCs, giving up\n"); return TPM_E_CORRUPTED_STATE; } uint32_t WriteSpaceKernel(RollbackSpaceKernel *rsk) { RollbackSpaceKernel rsk2; uint32_t r; int attempts = 3; /* All writes should use struct_version 2 or greater. */ if (rsk->struct_version < 2) rsk->struct_version = 2; rsk->crc8 = vb2_crc8(rsk, offsetof(RollbackSpaceKernel, crc8)); while (attempts--) { r = SafeWrite(KERNEL_NV_INDEX, rsk, sizeof(RollbackSpaceKernel)); /* Can't write, not gonna try again */ if (r != TPM_SUCCESS) return r; /* Read it back to be sure it got the right values. */ r = ReadSpaceKernel(&rsk2); /* This checks the CRC */ if (r == TPM_SUCCESS) return r; VB2_DEBUG("TPM: bad CRC\n"); /* Try writing it again. Maybe it was garbled on the way out. */ } VB2_DEBUG("TPM: too many bad CRCs, giving up\n"); return TPM_E_CORRUPTED_STATE; } #ifdef DISABLE_ROLLBACK_TPM /* Dummy implementations which don't support TPM rollback protection */ uint32_t RollbackKernelRead(uint32_t* version) { *version = 0; return TPM_SUCCESS; } uint32_t RollbackKernelWrite(uint32_t version) { return TPM_SUCCESS; } uint32_t RollbackKernelLock(int recovery_mode) { return TPM_SUCCESS; } uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp) { memset(fwmp, 0, sizeof(*fwmp)); return TPM_SUCCESS; } #else uint32_t RollbackKernelRead(uint32_t* version) { RollbackSpaceKernel rsk; /* * Read the kernel space and verify its permissions. If the kernel * space has the wrong permission, or it doesn't contain the right * identifier, we give up. This will need to be fixed by the * recovery kernel. We have to worry about this because at any time * (even with PP turned off) the TPM owner can remove and redefine a * PP-protected space (but not write to it). */ RETURN_ON_FAILURE(ReadSpaceKernel(&rsk)); #ifndef TPM2_MODE /* * TODO(vbendeb): restore this when it is defined how the kernel space * gets protected. */ { uint32_t perms, uid; RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_NV_INDEX, &perms)); memcpy(&uid, &rsk.uid, sizeof(uid)); if (TPM_NV_PER_PPWRITE != perms || ROLLBACK_SPACE_KERNEL_UID != uid) return TPM_E_CORRUPTED_STATE; } #endif memcpy(version, &rsk.kernel_versions, sizeof(*version)); VB2_DEBUG("TPM: RollbackKernelRead %x\n", (int)*version); return TPM_SUCCESS; } uint32_t RollbackKernelWrite(uint32_t version) { RollbackSpaceKernel rsk; uint32_t old_version; RETURN_ON_FAILURE(ReadSpaceKernel(&rsk)); memcpy(&old_version, &rsk.kernel_versions, sizeof(old_version)); VB2_DEBUG("TPM: RollbackKernelWrite %x --> %x\n", (int)old_version, (int)version); memcpy(&rsk.kernel_versions, &version, sizeof(version)); return WriteSpaceKernel(&rsk); } uint32_t RollbackKernelLock(int recovery_mode) { static int kernel_locked = 0; uint32_t r; if (recovery_mode || kernel_locked) return TPM_SUCCESS; r = TlclLockPhysicalPresence(); if (TPM_SUCCESS == r) kernel_locked = 1; return r; } uint32_t RollbackFwmpRead(struct RollbackSpaceFwmp *fwmp) { union { /* * Use a union for buf and bf, rather than making bf a pointer * to a bare uint8_t[] buffer. This ensures bf will be aligned * if necesssary for the target platform. */ uint8_t buf[FWMP_NV_MAX_SIZE]; struct RollbackSpaceFwmp bf; } u; uint32_t r; int attempts = 3; /* Clear destination in case error or FWMP not present */ memset(fwmp, 0, sizeof(*fwmp)); while (attempts--) { /* Try to read entire 1.0 struct */ r = TlclRead(FWMP_NV_INDEX, u.buf, sizeof(u.bf)); if (r == TPM_E_BADINDEX) { /* Missing space is not an error; use defaults */ VB2_DEBUG("TPM: no FWMP space\n"); return TPM_SUCCESS; } else if (r != TPM_SUCCESS) { VB2_DEBUG("TPM: read returned 0x%x\n", r); return r; } /* * Struct must be at least big enough for 1.0, but not bigger * than our buffer size. */ if (u.bf.struct_size < sizeof(u.bf) || u.bf.struct_size > sizeof(u.buf)) return TPM_E_STRUCT_SIZE; /* * If space is bigger than we expect, re-read so we properly * compute the CRC. */ if (u.bf.struct_size > sizeof(u.bf)) { r = TlclRead(FWMP_NV_INDEX, u.buf, u.bf.struct_size); if (r != TPM_SUCCESS) return r; } /* Verify CRC */ if (u.bf.crc != vb2_crc8(u.buf + 2, u.bf.struct_size - 2)) { VB2_DEBUG("TPM: bad CRC\n"); continue; } /* Verify major version is compatible */ if ((u.bf.struct_version >> 4) != (ROLLBACK_SPACE_FWMP_VERSION >> 4)) return TPM_E_STRUCT_VERSION; /* * Copy to destination. Note that if the space is bigger than * we expect (due to a minor version change), we only copy the * part of the FWMP that we know what to do with. * * If this were a 1.1+ reader and the source was a 1.0 struct, * we would need to take care of initializing the extra fields * added in 1.1+. But that's not an issue yet. */ memcpy(fwmp, &u.bf, sizeof(*fwmp)); return TPM_SUCCESS; } VB2_DEBUG("TPM: too many bad CRCs, giving up\n"); return TPM_E_CORRUPTED_STATE; } #endif /* DISABLE_ROLLBACK_TPM */