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; |