diff --git a/tests/rollback_index_mock.c b/tests/rollback_index_mock.c index d3da5b0daf..6c629934e0 100644 --- a/tests/rollback_index_mock.c +++ b/tests/rollback_index_mock.c @@ -16,7 +16,7 @@ uint16_t g_firmware_version = 0; uint16_t g_kernel_key_version = 0; uint16_t g_kernel_version = 0; -uint32_t SetupTPM(void) { +uint32_t SetupTPM(int mode, int developer_flag) { #ifndef NDEBUG debug("Rollback Index Library Mock: TPM initialized.\n"); #endif diff --git a/vboot_firmware/include/rollback_index.h b/vboot_firmware/include/rollback_index.h index c5c650e6bb..491814e720 100644 --- a/vboot_firmware/include/rollback_index.h +++ b/vboot_firmware/include/rollback_index.h @@ -20,12 +20,18 @@ extern uint16_t g_kernel_version; #define FIRMWARE_VERSIONS 0 #define KERNEL_VERSIONS 1 +/* Initialization mode */ +#define RO_RECOVERY_MODE 0 +#define RO_NORMAL_MODE 1 +#define RW_NORMAL_MODE 2 + /* TPM NVRAM location indices. */ #define FIRMWARE_VERSIONS_NV_INDEX 0x1001 #define KERNEL_VERSIONS_NV_INDEX 0x1002 #define TPM_IS_INITIALIZED_NV_INDEX 0x1003 #define KERNEL_VERSIONS_BACKUP_NV_INDEX 0x1004 #define KERNEL_MUST_USE_BACKUP_NV_INDEX 0x1005 +#define DEVELOPER_MODE_NV_INDEX 0x1006 /* Unique ID to detect kernel space redefinition */ #define KERNEL_SPACE_UID "GRWL" /* unique ID with secret meaning */ @@ -33,8 +39,23 @@ extern uint16_t g_kernel_version; #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); +/* All functions return TPM_SUCCESS (zero) if successful, non-zero if error */ + +/* SetupTPM is called on boot and on starting the RW firmware, passing the + * appripriate MODE and DEVELOPER_FLAG parameters. MODE can be one of + * RO_RECOVERY_MODE, RO_NORMAL_MODE, RW_NORMAL_MODE. DEVELOPER_FLAG is 1 when + * the developer switch is ON, 0 otherwise. + * + * If SetupTPM returns TPM_SUCCESS, the caller may proceed. If it returns + * TPM_E_MUST_REBOOT, the caller must reboot in normal mode. For all other + * return values, the caller must reboot in recovery mode. + * + * This function has many side effects on the TPM state. In particular, when + * called with mode = RECOVERY_MODE it locks the firmware versions before + * returning. In all other cases, the caller is responsible for locking the + * firmware versions once it decides it doesn't need to update them. + */ +uint32_t SetupTPM(int mode, int developer_flag); uint32_t GetStoredVersions(int type, uint16_t* key_version, uint16_t* version); uint32_t WriteStoredVersions(int type, uint16_t key_version, uint16_t version); uint32_t LockFirmwareVersions(void); diff --git a/vboot_firmware/lib/firmware_image_fw.c b/vboot_firmware/lib/firmware_image_fw.c index 75f4b09f30..827b36f35f 100644 --- a/vboot_firmware/lib/firmware_image_fw.c +++ b/vboot_firmware/lib/firmware_image_fw.c @@ -273,7 +273,7 @@ int VerifyFirmwareDriver_f(uint8_t* root_key_blob, uint16_t version, key_version; /* Temporary variables */ /* Initialize the TPM since we'll be reading the rollback indices. */ - SetupTPM(); + SetupTPM(0, 0); /* We get the key versions by reading directly from the image blobs without * any additional (expensive) sanity checking on the blob since it's faster to diff --git a/vboot_firmware/lib/include/tss_constants.h b/vboot_firmware/lib/include/tss_constants.h index c4c0bfe8f1..ecbdf48fe9 100644 --- a/vboot_firmware/lib/include/tss_constants.h +++ b/vboot_firmware/lib/include/tss_constants.h @@ -28,6 +28,7 @@ #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_E_MUST_REBOOT ((uint32_t)0x00005002) /* 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 3d1553ca0d..b09ea64a6d 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; + 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*) ¤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; diff --git a/vboot_firmware/lib/vboot_firmware.c b/vboot_firmware/lib/vboot_firmware.c index bcfc814dd7..e0cfc6ae01 100644 --- a/vboot_firmware/lib/vboot_firmware.c +++ b/vboot_firmware/lib/vboot_firmware.c @@ -40,7 +40,8 @@ int LoadFirmware2(LoadFirmwareParams* params) { return LOAD_FIRMWARE_RECOVERY; /* Initialize the TPM and read rollback indices. */ - if (0 != SetupTPM() ) + /* TODO: fix SetupTPM parameter */ + if (0 != SetupTPM(0, 0) ) return LOAD_FIRMWARE_RECOVERY; if (0 != GetStoredVersions(FIRMWARE_VERSIONS, &tpm_key_version, &tpm_fw_version)) diff --git a/vboot_firmware/linktest/main.c b/vboot_firmware/linktest/main.c index a3bbbd21f8..212f99de5c 100644 --- a/vboot_firmware/linktest/main.c +++ b/vboot_firmware/linktest/main.c @@ -44,7 +44,7 @@ int main(void) LoadKernel(0); /* rollback_index.h */ - SetupTPM(); + SetupTPM(0, 0); GetStoredVersions(0, &x, &y); WriteStoredVersions(0, 0, 0); LockFirmwareVersions();