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