Protect the kernel version space from redefinition.

Review URL: http://codereview.chromium.org/2786005
This commit is contained in:
Luigi Semenzato
2010-06-10 11:01:04 -07:00
parent d183644564
commit 4f11c36ebc
5 changed files with 182 additions and 110 deletions

View File

@@ -25,7 +25,13 @@ extern uint16_t g_kernel_version;
#define KERNEL_VERSIONS_NV_INDEX 0x1002 #define KERNEL_VERSIONS_NV_INDEX 0x1002
#define TPM_IS_INITIALIZED_NV_INDEX 0x1003 #define TPM_IS_INITIALIZED_NV_INDEX 0x1003
#define KERNEL_VERSIONS_BACKUP_NV_INDEX 0x1004 #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 */ /* All functions return 0 if successful, non-zero if error */
uint32_t SetupTPM(void); uint32_t SetupTPM(void);

View File

@@ -110,4 +110,8 @@ uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated);
*/ */
uint32_t TlclSetGlobalLock(void); 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_ */ #endif /* TPM_LITE_TLCL_H_ */

View File

@@ -25,6 +25,9 @@
#define TPM_SUCCESS ((uint32_t)0x00000000) #define TPM_SUCCESS ((uint32_t)0x00000000)
#define TPM_E_BADINDEX ((uint32_t)0x00000002) #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_INDEX0 ((uint32_t)0x00000000)
#define TPM_NV_INDEX_LOCK ((uint32_t)0xffffffff) #define TPM_NV_INDEX_LOCK ((uint32_t)0xffffffff)

View File

@@ -21,25 +21,42 @@ uint16_t g_kernel_version = 0;
#define RETURN_ON_FAILURE(tpm_command) do { \ #define RETURN_ON_FAILURE(tpm_command) do { \
uint32_t result; \ uint32_t result; \
if ((result = tpm_command) != TPM_SUCCESS) {\ if ((result = (tpm_command)) != TPM_SUCCESS) { \
return result; \ return result; \
} \ } \
} while (0) } 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) { static uint32_t InitializeSpaces(void) {
uint32_t zero = 0; uint32_t zero = 0;
uint32_t space_holder;
uint32_t firmware_perm = TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_PPWRITE; uint32_t firmware_perm = TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_PPWRITE;
uint32_t kernel_perm = TPM_NV_PER_PPWRITE;
debug("Initializing spaces\n"); 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(TlclSetNvLocked());
RETURN_ON_FAILURE(TlclDefineSpace(FIRMWARE_VERSIONS_NV_INDEX, 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, RETURN_ON_FAILURE(TlclWrite(FIRMWARE_VERSIONS_NV_INDEX,
(uint8_t*) &zero, sizeof(uint32_t))); (uint8_t*) &zero, sizeof(uint32_t)));
RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX, RETURN_ON_FAILURE(InitializeKernelVersionsSpaces());
kernel_perm, sizeof(uint32_t)));
RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX, (uint8_t*) &zero,
sizeof(uint32_t)));
/* The space KERNEL_VERSIONS_BACKUP_NV_INDEX is used to protect the kernel /* The space KERNEL_VERSIONS_BACKUP_NV_INDEX is used to protect the kernel
* versions when entering recovery mode. The content of space * 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. * regular value (0) should be trusted.
*/ */
RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_BACKUP_NV_INDEX, RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_BACKUP_NV_INDEX,
firmware_perm, sizeof(uint32_t))); firmware_perm, sizeof(uint32_t)));
RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX, RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
(uint8_t*) &zero, sizeof(uint32_t))); (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))); 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))); (uint8_t*) &zero, sizeof(uint32_t)));
/* The space TPM_IS_INITIALIZED_NV_INDEX is used to indicate that the TPM /* 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 * the TPM, so do not attempt to do any more TPM operations, and particularly
* do not set bGlobalLock. * do not set bGlobalLock.
*/ */
static void EnterRecovery(int unlocked) { void EnterRecovery(int unlocked) {
uint32_t combined_versions; uint32_t combined_versions;
uint32_t backup_versions; uint32_t backup_versions;
uint32_t backup_is_valid; uint32_t must_use_backup;
uint32_t result;
if (!unlocked) { if (!unlocked) {
/* Saves the kernel versions and indicates that we should trust the saved /* Saves the kernel versions and indicates that we should trust the saved
* ones. * ones.
*/ */
TlclRead(KERNEL_VERSIONS_NV_INDEX, if (TlclRead(KERNEL_VERSIONS_NV_INDEX, (uint8_t*) &combined_versions,
(uint8_t*) &combined_versions, sizeof(uint32_t)); sizeof(uint32_t)) != TPM_SUCCESS)
TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX, goto recovery_mode;
(uint8_t*) &backup_versions, sizeof(uint32_t)); if (TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX, (uint8_t*) &backup_versions,
/* We could unconditional writes of both KERNEL_VERSIONS_BACKUP and sizeof(uint32_t)) != TPM_SUCCESS)
* KERNEL_BACKUP_IS_VALID, but this is more robust. goto recovery_mode;
*/ /* Avoids idempotent writes. */
if (combined_versions != backup_versions) { if (combined_versions != backup_versions) {
TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX, result = TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
(uint8_t*) &combined_versions, sizeof(uint32_t)); (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, if (TlclRead(KERNEL_MUST_USE_BACKUP_NV_INDEX, (uint8_t*) &must_use_backup,
(uint8_t*) &backup_is_valid, sizeof(uint32_t)); sizeof(uint32_t)) != TPM_SUCCESS)
if (backup_is_valid != 1) { goto recovery_mode;
backup_is_valid = 1; if (must_use_backup != 1) {
TlclWrite(KERNEL_BACKUP_IS_VALID_NV_INDEX, (uint8_t*) &backup_is_valid, must_use_backup = 1;
sizeof(uint32_t)); 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. */ /* Protects the firmware and backup kernel versions. */
LockFirmwareVersions(); if (LockFirmwareVersions() != TPM_SUCCESS)
goto recovery_mode;
} }
recovery_mode:
debug("entering recovery mode"); debug("entering recovery mode");
/* TODO(nelson): code for 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) { static uint32_t GetTPMRollbackIndices(void) {
uint32_t backup_is_valid;
uint32_t firmware_versions; uint32_t firmware_versions;
uint32_t kernel_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 /* 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 * rollback index locations are missing or somehow messed up. We let the
* caller deal with that. * caller deal with that.
@@ -192,35 +174,118 @@ static uint32_t GetTPMRollbackIndices(void) {
return TPM_SUCCESS; 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 disable;
uint8_t deactivated; uint8_t deactivated;
TlclLibInit(); TlclLibInit();
RETURN_ON_FAILURE(TlclStartup()); RETURN_ON_FAILURE(TlclStartup());
RETURN_ON_FAILURE(TlclContinueSelfTest()); RETURN_ON_FAILURE(TlclContinueSelfTest());
RETURN_ON_FAILURE(TlclAssertPhysicalPresence()); 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)); RETURN_ON_FAILURE(TlclGetFlags(&disable, &deactivated));
if (disable || deactivated) { if (disable || deactivated) {
RETURN_ON_FAILURE(TlclSetEnable()); RETURN_ON_FAILURE(TlclSetEnable());
RETURN_ON_FAILURE(TlclSetDeactivated(0)); RETURN_ON_FAILURE(TlclSetDeactivated(0));
/* TODO: Reboot now */ /* TODO: Reboot */
return 9999; return 9999;
} }
/* We expect this to fail the first time we run on a device, indicating that /* We expect this to fail the first time we run on a device, indicating that
* the TPM has not been initialized yet. */ * the TPM has not been initialized yet. */
if (GetTPMRollbackIndices() != TPM_SUCCESS) { if (RecoverKernelSpace() != TPM_SUCCESS) {
/* If InitializeSpaces() fails (possibly because it had been executed int initialized = 0;
* already), something is wrong. */ RETURN_ON_FAILURE(GetSpacesInitialized(&initialized));
if (initialized) {
return TPM_E_ALREADY_INITIALIZED;
} else {
RETURN_ON_FAILURE(InitializeSpaces()); RETURN_ON_FAILURE(InitializeSpaces());
/* Try again. */ RETURN_ON_FAILURE(RecoverKernelSpace());
RETURN_ON_FAILURE(GetTPMRollbackIndices());
} }
}
RETURN_ON_FAILURE(BackupKernelSpace());
RETURN_ON_FAILURE(GetTPMRollbackIndices());
return TPM_SUCCESS; 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) { uint32_t GetStoredVersions(int type, uint16_t* key_version, uint16_t* version) {
/* TODO: should verify that SetupTPM() has been called. Note that /* 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, RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
(uint8_t*) &combined_version, (uint8_t*) &combined_version,
sizeof(uint32_t))); 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; return TPM_SUCCESS;
} }

View File

@@ -33,3 +33,6 @@ uint32_t TlclSetGlobalLock(void) { return TPM_SUCCESS; }
uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated) { uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated) {
return TPM_SUCCESS; return TPM_SUCCESS;
} }
uint32_t TlclGetPermissions(uint32_t index, uint32_t* permissions) {
return TPM_SUCCESS;
}