From 4f11c36ebcc42a8f875ce6ea7cdc36f5c4e965de Mon Sep 17 00:00:00 2001 From: Luigi Semenzato Date: Thu, 10 Jun 2010 11:01:04 -0700 Subject: [PATCH] Protect the kernel version space from redefinition. Review URL: http://codereview.chromium.org/2786005 --- vboot_firmware/include/rollback_index.h | 8 +- vboot_firmware/include/tlcl.h | 4 + vboot_firmware/lib/include/tss_constants.h | 3 + vboot_firmware/lib/rollback_index.c | 274 +++++++++++++-------- vboot_firmware/stub/tlcl.c | 3 + 5 files changed, 182 insertions(+), 110 deletions(-) diff --git a/vboot_firmware/include/rollback_index.h b/vboot_firmware/include/rollback_index.h index 9be97b9737..c5c650e6bb 100644 --- a/vboot_firmware/include/rollback_index.h +++ b/vboot_firmware/include/rollback_index.h @@ -25,7 +25,13 @@ extern uint16_t g_kernel_version; #define KERNEL_VERSIONS_NV_INDEX 0x1002 #define TPM_IS_INITIALIZED_NV_INDEX 0x1003 #define KERNEL_VERSIONS_BACKUP_NV_INDEX 0x1004 -#define KERNEL_BACKUP_IS_VALID_NV_INDEX 0x1005 +#define KERNEL_MUST_USE_BACKUP_NV_INDEX 0x1005 + +/* Unique ID to detect kernel space redefinition */ +#define KERNEL_SPACE_UID "GRWL" /* unique ID with secret meaning */ +#define KERNEL_SPACE_UID_SIZE (sizeof(KERNEL_SPACE_UID) - 1) +#define KERNEL_SPACE_INIT_DATA ((uint8_t*) "\0\0\0\0" KERNEL_SPACE_UID) +#define KERNEL_SPACE_SIZE (sizeof(uint32_t) + KERNEL_SPACE_UID_SIZE) /* All functions return 0 if successful, non-zero if error */ uint32_t SetupTPM(void); diff --git a/vboot_firmware/include/tlcl.h b/vboot_firmware/include/tlcl.h index 8a01928a25..ba1647c8fe 100644 --- a/vboot_firmware/include/tlcl.h +++ b/vboot_firmware/include/tlcl.h @@ -110,4 +110,8 @@ uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated); */ uint32_t TlclSetGlobalLock(void); +/* Gets the permission bits for the NVRAM space with |index|. + */ +uint32_t TlclGetPermissions(uint32_t index, uint32_t* permissions); + #endif /* TPM_LITE_TLCL_H_ */ diff --git a/vboot_firmware/lib/include/tss_constants.h b/vboot_firmware/lib/include/tss_constants.h index 69873a64b2..c4c0bfe8f1 100644 --- a/vboot_firmware/lib/include/tss_constants.h +++ b/vboot_firmware/lib/include/tss_constants.h @@ -25,6 +25,9 @@ #define TPM_SUCCESS ((uint32_t)0x00000000) #define TPM_E_BADINDEX ((uint32_t)0x00000002) +#define TPM_E_MAXNVWRITES ((uint32_t)0x00000048) +#define TPM_E_ALREADY_INITIALIZED ((uint32_t)0x00005000) /* vboot local */ +#define TPM_E_INTERNAL_INCONSISTENCY ((uint32_t)0x00005001) /* vboot local */ #define TPM_NV_INDEX0 ((uint32_t)0x00000000) #define TPM_NV_INDEX_LOCK ((uint32_t)0xffffffff) diff --git a/vboot_firmware/lib/rollback_index.c b/vboot_firmware/lib/rollback_index.c index e15b07ed70..3d1553ca0d 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; -uint32_t SetupTPM(void) { + 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; + } + + /* 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; } diff --git a/vboot_firmware/stub/tlcl.c b/vboot_firmware/stub/tlcl.c index 6d1cf5b18e..23f0f09ad3 100644 --- a/vboot_firmware/stub/tlcl.c +++ b/vboot_firmware/stub/tlcl.c @@ -33,3 +33,6 @@ uint32_t TlclSetGlobalLock(void) { return TPM_SUCCESS; } uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated) { return TPM_SUCCESS; } +uint32_t TlclGetPermissions(uint32_t index, uint32_t* permissions) { + return TPM_SUCCESS; +}