Fix normal/recovery mode, and RO firmware vs. RW firmware behavior.

Review URL: http://codereview.chromium.org/2792009
This commit is contained in:
Luigi Semenzato
2010-06-15 08:12:32 -07:00
parent 81d696f394
commit 2666f10dec
7 changed files with 153 additions and 101 deletions

View File

@@ -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;
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)));
}
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 */
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*) &current_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;