| Index: vboot_firmware/lib/rollback_index.c
|
| diff --git a/vboot_firmware/lib/rollback_index.c b/vboot_firmware/lib/rollback_index.c
|
| index e15b07ed700fbc5881fe9a4743e4e999a2ebd727..3d1553ca0d08f6eb5d7181161cfb2d1a61c1ebdb 100644
|
| --- a/vboot_firmware/lib/rollback_index.c
|
| +++ b/vboot_firmware/lib/rollback_index.c
|
| @@ -19,27 +19,44 @@ uint16_t g_firmware_version = 0;
|
| uint16_t g_kernel_key_version = 0;
|
| uint16_t g_kernel_version = 0;
|
|
|
| -#define RETURN_ON_FAILURE(tpm_command) do { \
|
| - uint32_t result; \
|
| - if ((result = tpm_command) != TPM_SUCCESS) {\
|
| - return result; \
|
| - } \
|
| +#define RETURN_ON_FAILURE(tpm_command) do { \
|
| + uint32_t result; \
|
| + if ((result = (tpm_command)) != TPM_SUCCESS) { \
|
| + return result; \
|
| + } \
|
| } while (0)
|
|
|
| +static uint32_t InitializeKernelVersionsSpaces(void) {
|
| + RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
|
| + TPM_NV_PER_PPWRITE, KERNEL_SPACE_SIZE));
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX, KERNEL_SPACE_INIT_DATA,
|
| + KERNEL_SPACE_SIZE));
|
| + return TPM_SUCCESS;
|
| +}
|
| +
|
| +static uint32_t GetSpacesInitialized(int* initialized) {
|
| + uint32_t space_holder;
|
| + uint32_t result;
|
| + result = TlclRead(TPM_IS_INITIALIZED_NV_INDEX,
|
| + (uint8_t*) &space_holder, sizeof(space_holder));
|
| + switch (result) {
|
| + case TPM_SUCCESS:
|
| + *initialized = 1;
|
| + break;
|
| + case TPM_E_BADINDEX:
|
| + *initialized = 0;
|
| + result = TPM_SUCCESS;
|
| + break;
|
| + }
|
| + return result;
|
| +}
|
| +
|
| static uint32_t InitializeSpaces(void) {
|
| uint32_t zero = 0;
|
| - uint32_t space_holder;
|
| uint32_t firmware_perm = TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_PPWRITE;
|
| - uint32_t kernel_perm = TPM_NV_PER_PPWRITE;
|
|
|
| debug("Initializing spaces\n");
|
|
|
| - if (TlclRead(TPM_IS_INITIALIZED_NV_INDEX,
|
| - (uint8_t*) &space_holder, sizeof(space_holder)) == TPM_SUCCESS) {
|
| - /* Spaces are already initialized, so this is an error */
|
| - return 0;
|
| - }
|
| -
|
| RETURN_ON_FAILURE(TlclSetNvLocked());
|
|
|
| RETURN_ON_FAILURE(TlclDefineSpace(FIRMWARE_VERSIONS_NV_INDEX,
|
| @@ -47,23 +64,20 @@ static uint32_t InitializeSpaces(void) {
|
| RETURN_ON_FAILURE(TlclWrite(FIRMWARE_VERSIONS_NV_INDEX,
|
| (uint8_t*) &zero, sizeof(uint32_t)));
|
|
|
| - RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
|
| - kernel_perm, sizeof(uint32_t)));
|
| - RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX, (uint8_t*) &zero,
|
| - sizeof(uint32_t)));
|
| + RETURN_ON_FAILURE(InitializeKernelVersionsSpaces());
|
|
|
| /* The space KERNEL_VERSIONS_BACKUP_NV_INDEX is used to protect the kernel
|
| * versions when entering recovery mode. The content of space
|
| - * KERNEL_BACKUP_IS_VALID determines whether the backup value (1) or the
|
| + * KERNEL_MUST_USE_BACKUP determines whether the backup value (1) or the
|
| * regular value (0) should be trusted.
|
| */
|
| RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| firmware_perm, sizeof(uint32_t)));
|
| RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| (uint8_t*) &zero, sizeof(uint32_t)));
|
| - RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_BACKUP_IS_VALID_NV_INDEX,
|
| + RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| firmware_perm, sizeof(uint32_t)));
|
| - RETURN_ON_FAILURE(TlclWrite(KERNEL_BACKUP_IS_VALID_NV_INDEX,
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| (uint8_t*) &zero, sizeof(uint32_t)));
|
|
|
| /* The space TPM_IS_INITIALIZED_NV_INDEX is used to indicate that the TPM
|
| @@ -80,99 +94,67 @@ static uint32_t InitializeSpaces(void) {
|
| * the TPM, so do not attempt to do any more TPM operations, and particularly
|
| * do not set bGlobalLock.
|
| */
|
| -static void EnterRecovery(int unlocked) {
|
| +void EnterRecovery(int unlocked) {
|
| uint32_t combined_versions;
|
| uint32_t backup_versions;
|
| - uint32_t backup_is_valid;
|
| + uint32_t must_use_backup;
|
| + uint32_t result;
|
|
|
| if (!unlocked) {
|
| /* Saves the kernel versions and indicates that we should trust the saved
|
| * ones.
|
| */
|
| - TlclRead(KERNEL_VERSIONS_NV_INDEX,
|
| - (uint8_t*) &combined_versions, sizeof(uint32_t));
|
| - TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| - (uint8_t*) &backup_versions, sizeof(uint32_t));
|
| - /* We could unconditional writes of both KERNEL_VERSIONS_BACKUP and
|
| - * KERNEL_BACKUP_IS_VALID, but this is more robust.
|
| - */
|
| + if (TlclRead(KERNEL_VERSIONS_NV_INDEX, (uint8_t*) &combined_versions,
|
| + sizeof(uint32_t)) != TPM_SUCCESS)
|
| + goto recovery_mode;
|
| + if (TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX, (uint8_t*) &backup_versions,
|
| + sizeof(uint32_t)) != TPM_SUCCESS)
|
| + goto recovery_mode;
|
| + /* Avoids idempotent writes. */
|
| if (combined_versions != backup_versions) {
|
| - TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| - (uint8_t*) &combined_versions, sizeof(uint32_t));
|
| + result = TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| + (uint8_t*) &combined_versions, sizeof(uint32_t));
|
| + if (result == TPM_E_MAXNVWRITES) {
|
| + goto forceclear_and_reboot;
|
| + } else if (result != TPM_SUCCESS) {
|
| + goto recovery_mode;
|
| + }
|
| }
|
|
|
| - TlclRead(KERNEL_BACKUP_IS_VALID_NV_INDEX,
|
| - (uint8_t*) &backup_is_valid, sizeof(uint32_t));
|
| - if (backup_is_valid != 1) {
|
| - backup_is_valid = 1;
|
| - TlclWrite(KERNEL_BACKUP_IS_VALID_NV_INDEX, (uint8_t*) &backup_is_valid,
|
| - sizeof(uint32_t));
|
| + if (TlclRead(KERNEL_MUST_USE_BACKUP_NV_INDEX, (uint8_t*) &must_use_backup,
|
| + sizeof(uint32_t)) != TPM_SUCCESS)
|
| + goto recovery_mode;
|
| + if (must_use_backup != 1) {
|
| + must_use_backup = 1;
|
| + result = TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| + (uint8_t*) &must_use_backup, sizeof(uint32_t));
|
| + if (result == TPM_E_MAXNVWRITES) {
|
| + goto forceclear_and_reboot;
|
| + } else if (result != TPM_SUCCESS) {
|
| + goto recovery_mode;
|
| + }
|
| }
|
| /* Protects the firmware and backup kernel versions. */
|
| - LockFirmwareVersions();
|
| + if (LockFirmwareVersions() != TPM_SUCCESS)
|
| + goto recovery_mode;
|
| }
|
| +
|
| + recovery_mode:
|
| debug("entering recovery mode");
|
|
|
| /* TODO(nelson): code for entering recovery mode. */
|
| +
|
| + forceclear_and_reboot:
|
| + if (TlclForceClear() != TPM_SUCCESS) {
|
| + goto recovery_mode;
|
| + }
|
| + /* TODO: reboot */
|
| }
|
|
|
| static uint32_t GetTPMRollbackIndices(void) {
|
| - uint32_t backup_is_valid;
|
| uint32_t firmware_versions;
|
| uint32_t kernel_versions;
|
|
|
| - if (TlclRead(KERNEL_BACKUP_IS_VALID_NV_INDEX, (uint8_t*) &backup_is_valid,
|
| - sizeof(uint32_t)) != TPM_SUCCESS) {
|
| - EnterRecovery(1);
|
| - }
|
| - if (backup_is_valid) {
|
| - /* We reach this path if the previous boot went into recovery mode and we
|
| - * made a copy of the kernel versions to protect them.
|
| - */
|
| - uint32_t protected_combined_versions;
|
| - uint32_t unsafe_combined_versions;
|
| - uint32_t result;
|
| - uint32_t zero = 0;
|
| - if (TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| - (uint8_t*) &protected_combined_versions,
|
| - sizeof(uint32_t)) != TPM_SUCCESS) {
|
| - EnterRecovery(1);
|
| - }
|
| - result = TlclRead(KERNEL_VERSIONS_NV_INDEX,
|
| - (uint8_t*) &unsafe_combined_versions, sizeof(uint32_t));
|
| - if (result == TPM_E_BADINDEX) {
|
| - /* Jeez, someone removed the space. This is either hostile or extremely
|
| - * incompetent. Foo to them. Politeness and lack of an adequate
|
| - * character set prevent me from expressing my true feelings.
|
| - */
|
| - RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
|
| - TPM_NV_PER_PPWRITE,
|
| - sizeof(uint32_t)));
|
| - } else if (result != TPM_SUCCESS) {
|
| - EnterRecovery(1);
|
| - }
|
| - if (result == TPM_E_BADINDEX ||
|
| - protected_combined_versions != unsafe_combined_versions) {
|
| - RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
|
| - (uint8_t*) &protected_combined_versions,
|
| - sizeof(uint32_t)));
|
| - }
|
| - /* We recovered the backed-up versions and now we can reset the
|
| - * BACKUP_IS_VALID flag.
|
| - */
|
| - RETURN_ON_FAILURE(TlclWrite(KERNEL_BACKUP_IS_VALID_NV_INDEX,
|
| - (uint8_t*) &zero, 0));
|
| -
|
| - if (!TlclIsOwned()) {
|
| - /* Must ForceClear and reboot to prevent from running into the 64-write
|
| - * limit.
|
| - */
|
| - RETURN_ON_FAILURE(TlclForceClear());
|
| - /* Reboot! No return */
|
| - return 9999;
|
| - }
|
| - }
|
| -
|
| /* We perform the reads, making sure they succeed. A failure means that the
|
| * rollback index locations are missing or somehow messed up. We let the
|
| * caller deal with that.
|
| @@ -192,35 +174,118 @@ static uint32_t GetTPMRollbackIndices(void) {
|
| return TPM_SUCCESS;
|
| }
|
|
|
| +/* Checks if the kernel version space has been mucked with. If it has,
|
| + * reconstructs it using the backup value.
|
| + */
|
| +uint32_t RecoverKernelSpace(void) {
|
| + uint32_t perms = 0;
|
| + uint8_t buffer[KERNEL_SPACE_SIZE];
|
| + int read_OK = 0;
|
| + int perms_OK = 0;
|
| + uint32_t backup_combined_versions;
|
| + uint32_t must_use_backup;
|
| +
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| + (uint8_t*) &must_use_backup, sizeof(uint32_t)));
|
| + /* must_use_backup is true if the previous boot entered recovery mode. */
|
| +
|
| + read_OK = TlclRead(KERNEL_VERSIONS_NV_INDEX, (uint8_t*) &buffer,
|
| + KERNEL_SPACE_SIZE) == TPM_SUCCESS;
|
| + if (read_OK) {
|
| + RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_VERSIONS_NV_INDEX, &perms));
|
| + perms_OK = perms == TPM_NV_PER_PPWRITE;
|
| + }
|
| + if (!must_use_backup && read_OK && perms_OK &&
|
| + !Memcmp(buffer + sizeof(uint32_t), KERNEL_SPACE_UID,
|
| + KERNEL_SPACE_UID_SIZE)) {
|
| + /* Everything is fine. This is the normal, frequent path. */
|
| + return TPM_SUCCESS;
|
| + }
|
|
|
| -uint32_t SetupTPM(void) {
|
| + /* Either we detected that something went wrong, or we cannot trust the
|
| + * PP-protected kernel space. Attempts to fix. It is not always necessary
|
| + * to redefine the space, but we might as well, since this path should be
|
| + * taken quite seldom (after recovery mode and after an attack).
|
| + */
|
| + RETURN_ON_FAILURE(InitializeKernelVersionsSpaces());
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| + (uint8_t*) &backup_combined_versions,
|
| + sizeof(uint32_t)));
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
|
| + (uint8_t*) &backup_combined_versions,
|
| + sizeof(uint32_t)));
|
| + if (must_use_backup) {
|
| + uint32_t zero = 0;
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| + (uint8_t*) &zero, 0));
|
| +
|
| + }
|
| + return TPM_SUCCESS;
|
| +}
|
| +
|
| +static uint32_t BackupKernelSpace(void) {
|
| + uint32_t kernel_versions;
|
| + uint32_t backup_versions;
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_NV_INDEX,
|
| + (uint8_t*) &kernel_versions, sizeof(uint32_t)));
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| + (uint8_t*) &backup_versions, sizeof(uint32_t)));
|
| + if (kernel_versions == backup_versions) {
|
| + return TPM_SUCCESS;
|
| + } else if (kernel_versions < backup_versions) {
|
| + /* This cannot happen. We're screwed. */
|
| + return TPM_E_INTERNAL_INCONSISTENCY;
|
| + }
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| + (uint8_t*) &kernel_versions, sizeof(uint32_t)));
|
| + return TPM_SUCCESS;
|
| +}
|
| +
|
| +static uint32_t SetupTPM_(void) {
|
| uint8_t disable;
|
| uint8_t deactivated;
|
| TlclLibInit();
|
| RETURN_ON_FAILURE(TlclStartup());
|
| RETURN_ON_FAILURE(TlclContinueSelfTest());
|
| RETURN_ON_FAILURE(TlclAssertPhysicalPresence());
|
| - /* Check that the TPM is enabled and activated. */
|
| + /* Checks that the TPM is enabled and activated. */
|
| RETURN_ON_FAILURE(TlclGetFlags(&disable, &deactivated));
|
| if (disable || deactivated) {
|
| RETURN_ON_FAILURE(TlclSetEnable());
|
| RETURN_ON_FAILURE(TlclSetDeactivated(0));
|
| - /* TODO: Reboot now */
|
| + /* TODO: Reboot */
|
| return 9999;
|
| }
|
| /* We expect this to fail the first time we run on a device, indicating that
|
| * the TPM has not been initialized yet. */
|
| - if (GetTPMRollbackIndices() != TPM_SUCCESS) {
|
| - /* If InitializeSpaces() fails (possibly because it had been executed
|
| - * already), something is wrong. */
|
| - RETURN_ON_FAILURE(InitializeSpaces());
|
| - /* Try again. */
|
| - RETURN_ON_FAILURE(GetTPMRollbackIndices());
|
| + if (RecoverKernelSpace() != TPM_SUCCESS) {
|
| + int initialized = 0;
|
| + RETURN_ON_FAILURE(GetSpacesInitialized(&initialized));
|
| + if (initialized) {
|
| + return TPM_E_ALREADY_INITIALIZED;
|
| + } else {
|
| + RETURN_ON_FAILURE(InitializeSpaces());
|
| + RETURN_ON_FAILURE(RecoverKernelSpace());
|
| + }
|
| }
|
| + RETURN_ON_FAILURE(BackupKernelSpace());
|
| + RETURN_ON_FAILURE(GetTPMRollbackIndices());
|
|
|
| return TPM_SUCCESS;
|
| }
|
|
|
| +uint32_t SetupTPM(void) {
|
| + uint32_t result = SetupTPM_();
|
| + if (result == TPM_E_MAXNVWRITES) {
|
| + /* ForceClears and reboots */
|
| + RETURN_ON_FAILURE(TlclForceClear());
|
| + /* TODO: reboot */
|
| + return 9999;
|
| + } else {
|
| + return result;
|
| + }
|
| +}
|
| +
|
| uint32_t GetStoredVersions(int type, uint16_t* key_version, uint16_t* version) {
|
|
|
| /* TODO: should verify that SetupTPM() has been called. Note that
|
| @@ -257,15 +322,6 @@ uint32_t WriteStoredVersions(int type, uint16_t key_version, uint16_t version) {
|
| RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
|
| (uint8_t*) &combined_version,
|
| sizeof(uint32_t)));
|
| - break;
|
| - }
|
| - /* TODO(semenzato): change TlclIsOwned to return a TPM status directly and
|
| - * the "owned" value by reference.
|
| - */
|
| - if (!TlclIsOwned()) {
|
| - RETURN_ON_FAILURE(TlclForceClear());
|
| - /* TODO: Reboot here. No return. */
|
| - return 9999;
|
| }
|
| return TPM_SUCCESS;
|
| }
|
|
|