| Index: vboot_firmware/lib/rollback_index.c
|
| diff --git a/vboot_firmware/lib/rollback_index.c b/vboot_firmware/lib/rollback_index.c
|
| index 3d1553ca0d08f6eb5d7181161cfb2d1a61c1ebdb..b09ea64a6d30c7b3cb4f18baa0657238e987e792 100644
|
| --- a/vboot_firmware/lib/rollback_index.c
|
| +++ b/vboot_firmware/lib/rollback_index.c
|
| @@ -34,6 +34,10 @@ static uint32_t InitializeKernelVersionsSpaces(void) {
|
| return TPM_SUCCESS;
|
| }
|
|
|
| +/* When the return value is TPM_SUCCESS, this function sets *|initialized| to 1
|
| + * if the spaces have been fully initialized, to 0 if not. Otherwise
|
| + * *|initialized| is not changed.
|
| + */
|
| static uint32_t GetSpacesInitialized(int* initialized) {
|
| uint32_t space_holder;
|
| uint32_t result;
|
| @@ -51,6 +55,8 @@ static uint32_t GetSpacesInitialized(int* initialized) {
|
| return result;
|
| }
|
|
|
| +/* Creates the NVRAM spaces, and sets their initial values as needed.
|
| + */
|
| static uint32_t InitializeSpaces(void) {
|
| uint32_t zero = 0;
|
| uint32_t firmware_perm = TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_PPWRITE;
|
| @@ -67,9 +73,8 @@ static uint32_t InitializeSpaces(void) {
|
| 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_MUST_USE_BACKUP determines whether the backup value (1) or the
|
| - * regular value (0) should be trusted.
|
| + * versions. The content of space KERNEL_MUST_USE_BACKUP determines whether
|
| + * only the backup value should be trusted.
|
| */
|
| RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_BACKUP_NV_INDEX,
|
| firmware_perm, sizeof(uint32_t)));
|
| @@ -79,6 +84,10 @@ static uint32_t InitializeSpaces(void) {
|
| firmware_perm, sizeof(uint32_t)));
|
| RETURN_ON_FAILURE(TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| (uint8_t*) &zero, sizeof(uint32_t)));
|
| + RETURN_ON_FAILURE(TlclDefineSpace(DEVELOPER_MODE_NV_INDEX,
|
| + firmware_perm, sizeof(uint32_t)));
|
| + RETURN_ON_FAILURE(TlclWrite(DEVELOPER_MODE_NV_INDEX,
|
| + (uint8_t*) &zero, sizeof(uint32_t)));
|
|
|
| /* The space TPM_IS_INITIALIZED_NV_INDEX is used to indicate that the TPM
|
| * initialization has completed. Without it we cannot be sure that the last
|
| @@ -90,68 +99,18 @@ static uint32_t InitializeSpaces(void) {
|
| return TPM_SUCCESS;
|
| }
|
|
|
| -/* Enters the recovery mode. If |unlocked| is true, there is some problem with
|
| - * the TPM, so do not attempt to do any more TPM operations, and particularly
|
| - * do not set bGlobalLock.
|
| - */
|
| -void EnterRecovery(int unlocked) {
|
| - uint32_t combined_versions;
|
| - uint32_t backup_versions;
|
| +static uint32_t SetDistrustKernelSpaceAtNextBoot(uint32_t distrust) {
|
| uint32_t must_use_backup;
|
| - uint32_t result;
|
| -
|
| - if (!unlocked) {
|
| - /* Saves the kernel versions and indicates that we should trust the saved
|
| - * ones.
|
| - */
|
| - 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) {
|
| - 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;
|
| - }
|
| - }
|
| -
|
| - 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. */
|
| - 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;
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| + (uint8_t*) &must_use_backup, sizeof(uint32_t)));
|
| + if (must_use_backup != distrust) {
|
| + RETURN_ON_FAILURE(TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
|
| + (uint8_t*) &distrust, sizeof(uint32_t)));
|
| }
|
| - /* TODO: reboot */
|
| + return TPM_SUCCESS;
|
| }
|
|
|
| -static uint32_t GetTPMRollbackIndices(void) {
|
| +static uint32_t GetTPMRollbackIndices(int type) {
|
| uint32_t firmware_versions;
|
| uint32_t kernel_versions;
|
|
|
| @@ -159,17 +118,22 @@ static uint32_t GetTPMRollbackIndices(void) {
|
| * rollback index locations are missing or somehow messed up. We let the
|
| * caller deal with that.
|
| */
|
| - RETURN_ON_FAILURE(TlclRead(FIRMWARE_VERSIONS_NV_INDEX,
|
| - (uint8_t*) &firmware_versions,
|
| - sizeof(firmware_versions)));
|
| - RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_NV_INDEX,
|
| - (uint8_t*) &kernel_versions,
|
| - sizeof(kernel_versions)));
|
| -
|
| - g_firmware_key_version = firmware_versions >> 16;
|
| - g_firmware_version = firmware_versions && 0xffff;
|
| - g_kernel_key_version = kernel_versions >> 16;
|
| - g_kernel_version = kernel_versions && 0xffff;
|
| + switch (type) {
|
| + case FIRMWARE_VERSIONS:
|
| + RETURN_ON_FAILURE(TlclRead(FIRMWARE_VERSIONS_NV_INDEX,
|
| + (uint8_t*) &firmware_versions,
|
| + sizeof(firmware_versions)));
|
| + g_firmware_key_version = firmware_versions >> 16;
|
| + g_firmware_version = firmware_versions && 0xffff;
|
| + break;
|
| + case KERNEL_VERSIONS:
|
| + RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_NV_INDEX,
|
| + (uint8_t*) &kernel_versions,
|
| + sizeof(kernel_versions)));
|
| + g_kernel_key_version = kernel_versions >> 16;
|
| + g_kernel_version = kernel_versions && 0xffff;
|
| + break;
|
| + }
|
|
|
| return TPM_SUCCESS;
|
| }
|
| @@ -241,7 +205,31 @@ static uint32_t BackupKernelSpace(void) {
|
| return TPM_SUCCESS;
|
| }
|
|
|
| -static uint32_t SetupTPM_(void) {
|
| +/* Checks for transitions between protected mode to developer mode. When going
|
| + * into developer mode, clear the TPM.
|
| + */
|
| +static uint32_t CheckDeveloperModeTransition(uint32_t current_developer) {
|
| + uint32_t past_developer;
|
| + int must_clear;
|
| + RETURN_ON_FAILURE(TlclRead(DEVELOPER_MODE_NV_INDEX,
|
| + (uint8_t*) &past_developer,
|
| + sizeof(past_developer)));
|
| + must_clear = current_developer && !past_developer;
|
| + if (must_clear) {
|
| + RETURN_ON_FAILURE(TlclForceClear());
|
| + }
|
| + if (past_developer != current_developer) {
|
| + /* (Unauthorized) writes to the TPM succeed even when the TPM is disabled
|
| + * and deactivated.
|
| + */
|
| + RETURN_ON_FAILURE(TlclWrite(DEVELOPER_MODE_NV_INDEX,
|
| + (uint8_t*) ¤t_developer,
|
| + sizeof(current_developer)));
|
| + }
|
| + return must_clear ? TPM_E_MUST_REBOOT : TPM_SUCCESS;
|
| +}
|
| +
|
| +static uint32_t SetupTPM_(int mode, int developer_flag) {
|
| uint8_t disable;
|
| uint8_t deactivated;
|
| TlclLibInit();
|
| @@ -253,11 +241,11 @@ static uint32_t SetupTPM_(void) {
|
| if (disable || deactivated) {
|
| RETURN_ON_FAILURE(TlclSetEnable());
|
| RETURN_ON_FAILURE(TlclSetDeactivated(0));
|
| - /* TODO: Reboot */
|
| - return 9999;
|
| + return TPM_E_MUST_REBOOT;
|
| }
|
| - /* We expect this to fail the first time we run on a device, indicating that
|
| - * the TPM has not been initialized yet. */
|
| + /* We expect this to fail the first time we run on a device, because the TPM
|
| + * has not been initialized yet.
|
| + */
|
| if (RecoverKernelSpace() != TPM_SUCCESS) {
|
| int initialized = 0;
|
| RETURN_ON_FAILURE(GetSpacesInitialized(&initialized));
|
| @@ -269,32 +257,73 @@ static uint32_t SetupTPM_(void) {
|
| }
|
| }
|
| RETURN_ON_FAILURE(BackupKernelSpace());
|
| - RETURN_ON_FAILURE(GetTPMRollbackIndices());
|
| + RETURN_ON_FAILURE(SetDistrustKernelSpaceAtNextBoot(mode == RO_RECOVERY_MODE));
|
| + RETURN_ON_FAILURE(GetTPMRollbackIndices(FIRMWARE_VERSIONS));
|
| + RETURN_ON_FAILURE(GetTPMRollbackIndices(KERNEL_VERSIONS));
|
|
|
| + RETURN_ON_FAILURE(CheckDeveloperModeTransition(developer_flag));
|
| +
|
| + /* As a courtesy (I hope) to the caller, lock the firmware versions if we are
|
| + * in recovery mode. The normal mode may need to update the firmware
|
| + * versions, so they cannot be locked here.
|
| + */
|
| + if (mode == RO_RECOVERY_MODE) {
|
| + RETURN_ON_FAILURE(LockFirmwareVersions());
|
| + }
|
| 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;
|
| +/* SetupTPM starts the TPM and establishes the root of trust for the
|
| + * anti-rollback mechanism. SetupTPM can fail for three reasons. 1 A bug. 2 a
|
| + * TPM hardware failure. 3 An unexpected TPM state due to some attack. In
|
| + * general we cannot easily distinguish the kind of failure, so our strategy is
|
| + * to reboot in recovery mode in all cases. The recovery mode calls SetupTPM
|
| + * again, which executes (almost) the same sequence of operations. There is a
|
| + * good chance that, if recovery mode was entered because of a TPM failure, the
|
| + * failure will repeat itself. (In general this is impossible to guarantee
|
| + * because we have no way of creating the exact TPM initial state at the
|
| + * previous boot.) In recovery mode, we ignore the failure and continue, thus
|
| + * giving the recovery kernel a chance to fix things (that's why we don't set
|
| + * bGlobalLock). The choice is between a knowingly insecure device and a
|
| + * bricked device.
|
| + *
|
| + * As a side note, observe that we go through considerable hoops to avoid using
|
| + * the STCLEAR permissions for the index spaces. We do this to avoid writing
|
| + * to the TPM flashram at every reboot or wake-up, because of concerns about
|
| + * the durability of the NVRAM.
|
| + */
|
| +uint32_t SetupTPM(int mode, int developer_flag) {
|
| + switch (mode) {
|
| + case RO_RECOVERY_MODE:
|
| + case RO_NORMAL_MODE: {
|
| + uint32_t result = SetupTPM_(mode, developer_flag);
|
| + if (result == TPM_E_MAXNVWRITES) {
|
| + /* ForceClears and reboots */
|
| + RETURN_ON_FAILURE(TlclForceClear());
|
| + return TPM_E_MUST_REBOOT;
|
| + } else if (mode == RO_NORMAL_MODE) {
|
| + return result;
|
| + } else {
|
| + /* In recovery mode we want to keep going even if there are errors. */
|
| + return TPM_SUCCESS;
|
| + }
|
| + }
|
| + case RW_NORMAL_MODE:
|
| + /* There are no TPM writes here, so no need to check for write limit errors.
|
| + */
|
| + RETURN_ON_FAILURE(GetTPMRollbackIndices(KERNEL_VERSIONS));
|
| + default:
|
| + return TPM_E_INTERNAL_INCONSISTENCY;
|
| }
|
| }
|
|
|
| uint32_t GetStoredVersions(int type, uint16_t* key_version, uint16_t* version) {
|
| -
|
| - /* TODO: should verify that SetupTPM() has been called. Note that
|
| - * SetupTPM() does hardware setup AND sets global variables. When we
|
| - * get down into kernel verification, the hardware setup persists, but
|
| - * we don't have access to the global variables. So I guess we DO need
|
| - * to call SetupTPM() there, and have it be smart enough not to redo the
|
| - * hardware init, but it still needs to re-read the flags... */
|
| -
|
| + /* TODO: should verify that SetupTPM() has been called.
|
| + *
|
| + * Note that SetupTPM() does hardware setup AND sets global variables. When
|
| + * we get down into kernel verification, the hardware setup persists, but we
|
| + * lose the global variables.
|
| + */
|
| switch (type) {
|
| case FIRMWARE_VERSIONS:
|
| *key_version = g_firmware_key_version;
|
|
|