diff options
-rw-r--r-- | firmware/include/vboot_nvstorage.h | 24 | ||||
-rw-r--r-- | firmware/lib/include/rollback_index.h | 15 | ||||
-rw-r--r-- | firmware/lib/include/vboot_common.h | 7 | ||||
-rw-r--r-- | firmware/lib/mocked_rollback_index.c | 15 | ||||
-rw-r--r-- | firmware/lib/rollback_index.c | 42 | ||||
-rw-r--r-- | firmware/lib/vboot_api_init.c | 37 | ||||
-rw-r--r-- | firmware/lib/vboot_display.c | 2 | ||||
-rw-r--r-- | firmware/lib/vboot_nvstorage.c | 92 | ||||
-rw-r--r-- | host/lib/crossystem.c | 34 | ||||
-rw-r--r-- | tests/rollback_index2_tests.c | 8 | ||||
-rw-r--r-- | tests/vboot_api_init_tests.c | 246 | ||||
-rw-r--r-- | utility/crossystem.c | 2 |
12 files changed, 511 insertions, 13 deletions
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h index e6b014aa..534fb7e4 100644 --- a/firmware/include/vboot_nvstorage.h +++ b/firmware/include/vboot_nvstorage.h @@ -83,6 +83,8 @@ typedef enum VbNvParam { VBNV_CLEAR_TPM_OWNER_DONE, /* More details on recovery reason */ VBNV_RECOVERY_SUBCODE, + /* Request that NVRAM be backed up at next boot if possible. */ + VBNV_BACKUP_NVRAM_REQUEST, } VbNvParam; /* Recovery reason codes for VBNV_RECOVERY_REQUEST */ @@ -260,4 +262,26 @@ int VbNvGet(VbNvContext *context, VbNvParam param, uint32_t *dest); */ int VbNvSet(VbNvContext *context, VbNvParam param, uint32_t value); +/** + * Attempt to restore some fields of a lost VbNvContext from a backup area. + * The rest of the fields are unchanged, so they'd need to be set to their + * appropriate defaults by calling VbNvSetup() first (which is usually how we + * know the fields have been lost). + * + * Returns 0 if success, non-zero if error. + * + * This may only be called between VbNvSetup() and VbNvTeardown(). + */ +int RestoreNvFromBackup(VbNvContext *vnc); + +/** + * Attempt to save some fields of the VbNvContext to a backup area. + * + * Returns 0 if success, non-zero if error. If it succeeds, it will clear the + * VBNV_BACKUP_NVRAM_REQUEST flag in the VbNvContext. + * + * This may only be called when the backup area is writable. + */ +int SaveNvToBackup(VbNvContext *vnc); + #endif /* VBOOT_REFERENCE_NVSTORAGE_H_ */ diff --git a/firmware/lib/include/rollback_index.h b/firmware/lib/include/rollback_index.h index 7c410eec..dd0de32a 100644 --- a/firmware/lib/include/rollback_index.h +++ b/firmware/lib/include/rollback_index.h @@ -15,6 +15,10 @@ /* TPM NVRAM location indices. */ #define FIRMWARE_NV_INDEX 0x1007 #define KERNEL_NV_INDEX 0x1008 +/* This is just an opaque space for backup purposes */ +#define BACKUP_NV_INDEX 0x1009 +#define BACKUP_NV_SIZE 16 + /* Structure definitions for TPM spaces */ @@ -66,6 +70,7 @@ typedef struct RollbackSpaceFirmware { uint8_t crc8; } __attribute__((packed)) RollbackSpaceFirmware; + /* All functions return TPM_SUCCESS (zero) if successful, non-zero if error */ /* @@ -115,6 +120,16 @@ uint32_t RollbackKernelRead(uint32_t *version); uint32_t RollbackKernelWrite(uint32_t version); /** + * Read backup data. + */ +uint32_t RollbackBackupRead(uint8_t *raw); + +/** + * Write backup data. + */ +uint32_t RollbackBackupWrite(uint8_t *raw); + +/** * Lock must be called. Internally, it's ignored in recovery mode. */ uint32_t RollbackKernelLock(int recovery_mode); diff --git a/firmware/lib/include/vboot_common.h b/firmware/lib/include/vboot_common.h index ca9abd0a..61c7431f 100644 --- a/firmware/lib/include/vboot_common.h +++ b/firmware/lib/include/vboot_common.h @@ -15,6 +15,13 @@ #define ARRAY_SIZE(array) (sizeof(array)/sizeof(array[0])) #endif +/* Test an important condition at compile time, not run time */ +#define _BA1_(cond, line) \ + extern int __build_assertion_ ## line[1 - 2*!(cond)] \ + __attribute__ ((unused)) +#define _BA0_(c, x) _BA1_(c, x) +#define BUILD_ASSERT(cond) _BA0_(cond, __LINE__) + /* Error Codes for all common functions. */ enum { VBOOT_SUCCESS = 0, diff --git a/firmware/lib/mocked_rollback_index.c b/firmware/lib/mocked_rollback_index.c index d2f2bea5..86f223b2 100644 --- a/firmware/lib/mocked_rollback_index.c +++ b/firmware/lib/mocked_rollback_index.c @@ -7,6 +7,7 @@ */ #include "sysincludes.h" +#include "utility.h" #include "rollback_index.h" @@ -67,3 +68,17 @@ uint32_t RollbackKernelWrite(uint32_t version) { uint32_t RollbackKernelLock(int recovery_mode) { return TPM_SUCCESS; } + +static uint8_t rollback_backup[BACKUP_NV_SIZE]; + +uint32_t RollbackBackupRead(uint8_t *raw) +{ + Memcpy(raw, rollback_backup, BACKUP_NV_SIZE); + return TPM_SUCCESS; +} + +uint32_t RollbackBackupWrite(uint8_t *raw) +{ + Memcpy(rollback_backup, raw, BACKUP_NV_SIZE); + return TPM_SUCCESS; +} diff --git a/firmware/lib/rollback_index.c b/firmware/lib/rollback_index.c index e372d9b6..306e9032 100644 --- a/firmware/lib/rollback_index.c +++ b/firmware/lib/rollback_index.c @@ -308,15 +308,22 @@ uint32_t OneTimeInitializeTPM(RollbackSpaceFirmware *rsf, Memcpy(rsf, &rsf_init, sizeof(RollbackSpaceFirmware)); Memcpy(rsk, &rsk_init, sizeof(RollbackSpaceKernel)); - /* Defines and sets firmware and kernel spaces */ + /* Define the backup space. No need to initialize it, though. */ + RETURN_ON_FAILURE(SafeDefineSpace( + BACKUP_NV_INDEX, TPM_NV_PER_PPWRITE, BACKUP_NV_SIZE)); + + /* Define and initialize the kernel space */ RETURN_ON_FAILURE(SafeDefineSpace(KERNEL_NV_INDEX, TPM_NV_PER_PPWRITE, sizeof(RollbackSpaceKernel))); RETURN_ON_FAILURE(WriteSpaceKernel(rsk)); + + /* Do the firmware space last, so we retry if we don't get this far. */ RETURN_ON_FAILURE(SafeDefineSpace( FIRMWARE_NV_INDEX, TPM_NV_PER_GLOBALLOCK | TPM_NV_PER_PPWRITE, sizeof(RollbackSpaceFirmware))); RETURN_ON_FAILURE(WriteSpaceFirmware(rsf)); + return TPM_SUCCESS; } @@ -531,6 +538,16 @@ uint32_t RollbackKernelWrite(uint32_t version) return TPM_SUCCESS; } +uint32_t RollbackBackupRead(uint8_t *raw) +{ + return TPM_SUCCESS; +} + +uint32_t RollbackBackupWrite(uint8_t *raw) +{ + return TPM_SUCCESS; +} + uint32_t RollbackKernelLock(int recovery_mode) { return TPM_SUCCESS; @@ -625,6 +642,29 @@ uint32_t RollbackKernelWrite(uint32_t version) return WriteSpaceKernel(&rsk); } +/* + * We don't really care whether the TPM owner has been messing with this or + * not. We lock it along with the Kernel space just to avoid problems, but it's + * only useful in dev-mode and only when the battery has been drained + * completely. There aren't any security issues. It's just in the TPM because + * we don't have any other place to keep it. + */ +uint32_t RollbackBackupRead(uint8_t *raw) +{ + uint32_t r; + r = TlclRead(BACKUP_NV_INDEX, raw, BACKUP_NV_SIZE); + VBDEBUG(("TPM: %s returning 0x%x\n", __func__, r)); + return r; +} + +uint32_t RollbackBackupWrite(uint8_t *raw) +{ + uint32_t r; + r = TlclWrite(BACKUP_NV_INDEX, raw, BACKUP_NV_SIZE); + VBDEBUG(("TPM: %s returning 0x%x\n", __func__, r)); + return r; +} + uint32_t RollbackKernelLock(int recovery_mode) { if (recovery_mode) diff --git a/firmware/lib/vboot_api_init.c b/firmware/lib/vboot_api_init.c index 58bc215f..b83214f2 100644 --- a/firmware/lib/vboot_api_init.c +++ b/firmware/lib/vboot_api_init.c @@ -36,6 +36,9 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) uint32_t disable_dev_request = 0; uint32_t clear_tpm_owner_request = 0; int is_dev = 0; + uint32_t backup_requested = 0; + uint32_t backup_for_safety = 0; + int lost_nvram; /* Initialize output flags */ iparams->out_flags = 0; @@ -45,11 +48,12 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) return retval; VBDEBUG(("VbInit() input flags 0x%x gbb flags 0x%x\n", iparams->flags, - gbb.flags)); + gbb.flags)); /* Set up NV storage */ VbExNvStorageRead(vnc.raw); VbNvSetup(&vnc); + lost_nvram = vnc.regenerate_crc; /* Initialize shared data structure */ if (0 != VbSharedDataInit(shared, cparams->shared_data_size)) { @@ -184,7 +188,7 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) * dev-switch will be disabled by default) */ VBDEBUG(("TPM: Call RollbackFirmwareSetup(r%d, d%d)\n", - recovery, is_hw_dev)); + recovery, is_hw_dev)); tpm_status = RollbackFirmwareSetup(is_hw_dev, disable_dev_request, clear_tpm_owner_request, @@ -248,6 +252,17 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) } } + /* + * If the nvram state was lost, try to restore the bits we care about + * from the backup in the TPM. It's okay if we can't, though. + * Note: None of the bits that we back up should have been referenced + * before this point. Otherwise, they'll just be overwritten here. + * All the other bits will be unchanged from whatever has happened to + * them since VbNvSetup() reinitialized the VbNvContext. + */ + if (lost_nvram) + RestoreNvFromBackup(&vnc); + /* Allow BIOS to load arbitrary option ROMs? */ if (gbb.flags & GBB_FLAG_LOAD_OPTION_ROMS) iparams->out_flags |= VB_INIT_OUT_ENABLE_OPROM; @@ -295,6 +310,14 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) VbNvSet(&vnc, VBNV_DEV_BOOT_USB, 0); VbNvSet(&vnc, VBNV_DEV_BOOT_LEGACY, 0); VbNvSet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, 0); + /* + * Back up any changes now, so these values can't be forgotten + * by draining the battery. We really only care about these + * three fields, but it's uncommon for any others to change so + * this is an easier test than checking each one. + */ + if (vnc.regenerate_crc) + backup_for_safety = 1; /* * If we don't need the VGA option ROM but got it anyway, stop @@ -309,7 +332,15 @@ VbError_t VbInit(VbCommonParams *cparams, VbInitParams *iparams) } } - VbInit_exit: +VbInit_exit: + /* + * If we successfully backup the NV storage, it will clear the + * VBNV_BACKUP_NVRAM_REQUEST field, so we want to do it before + * calling VbNvTeardown(). It's okay if we can't backup, though. + */ + VbNvGet(&vnc, VBNV_BACKUP_NVRAM_REQUEST, &backup_requested); + if (backup_requested || backup_for_safety) + SaveNvToBackup(&vnc); /* Tear down NV storage */ VbNvTeardown(&vnc); diff --git a/firmware/lib/vboot_display.c b/firmware/lib/vboot_display.c index 6e0c93ac..c66af8df 100644 --- a/firmware/lib/vboot_display.c +++ b/firmware/lib/vboot_display.c @@ -211,6 +211,7 @@ VbError_t VbDisplayScreenFromGBB(VbCommonParams *cparams, uint32_t screen, if (localization >= hdr.number_of_localizations) { localization = 0; VbNvSet(vncptr, VBNV_LOCALIZATION_INDEX, localization); + VbNvSet(vncptr, VBNV_BACKUP_NVRAM_REQUEST, 1); } /* Display all bitmaps for the image */ @@ -641,6 +642,7 @@ VbError_t VbCheckDisplayKey(VbCommonParams *cparams, uint32_t key, VBDEBUG(("VbCheckDisplayKey() - change localization to %d\n", (int)loc)); VbNvSet(vncptr, VBNV_LOCALIZATION_INDEX, loc); + VbNvSet(vncptr, VBNV_BACKUP_NVRAM_REQUEST, 1); #ifdef SAVE_LOCALE_IMMEDIATELY VbNvTeardown(vncptr); /* really only computes checksum */ diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c index 9b2eca1b..258e5aff 100644 --- a/firmware/lib/vboot_nvstorage.c +++ b/firmware/lib/vboot_nvstorage.c @@ -10,6 +10,7 @@ #include "crc8.h" #include "utility.h" +#include "rollback_index.h" #include "vboot_common.h" #include "vboot_nvstorage.h" @@ -27,6 +28,7 @@ #define BOOT_DEBUG_RESET_MODE 0x80 #define BOOT_DISABLE_DEV_REQUEST 0x40 #define BOOT_OPROM_NEEDED 0x20 +#define BOOT_BACKUP_NVRAM 0x10 #define BOOT_TRY_B_COUNT_MASK 0x0F #define RECOVERY_OFFSET 2 @@ -153,6 +155,10 @@ int VbNvGet(VbNvContext *context, VbNvParam param, uint32_t *dest) *dest = (raw[TPM_FLAGS_OFFSET] & TPM_CLEAR_OWNER_DONE ? 1 : 0); return 0; + case VBNV_BACKUP_NVRAM_REQUEST: + *dest = (raw[BOOT_OFFSET] & BOOT_BACKUP_NVRAM ? 1 : 0); + return 0; + default: return 1; } @@ -276,6 +282,14 @@ int VbNvSet(VbNvContext *context, VbNvParam param, uint32_t value) raw[TPM_FLAGS_OFFSET] &= ~TPM_CLEAR_OWNER_DONE; break; + case VBNV_BACKUP_NVRAM_REQUEST: + if (value) + raw[BOOT_OFFSET] |= BOOT_BACKUP_NVRAM; + else + raw[BOOT_OFFSET] &= ~BOOT_BACKUP_NVRAM; + break; + + default: return 1; } @@ -284,3 +298,81 @@ int VbNvSet(VbNvContext *context, VbNvParam param, uint32_t value) context->regenerate_crc = 1; return 0; } + +/* These are the fields of the nvram that we want to back up. */ +static const VbNvParam backup_params[] = { + VBNV_KERNEL_FIELD, + VBNV_LOCALIZATION_INDEX, + VBNV_DEV_BOOT_USB, + VBNV_DEV_BOOT_LEGACY, + VBNV_DEV_BOOT_SIGNED_ONLY, +}; + +/* We can't back things up if there isn't enough storage. */ +BUILD_ASSERT(VBNV_BLOCK_SIZE <= BACKUP_NV_SIZE); + +int RestoreNvFromBackup(VbNvContext *vnc) +{ + VbNvContext bvnc; + uint32_t value; + int i; + + VBDEBUG(("TPM: %s()\n", __func__)); + + if (TPM_SUCCESS != RollbackBackupRead(bvnc.raw)) + return 1; + + VbNvSetup(&bvnc); + if (bvnc.regenerate_crc) { + VBDEBUG(("TPM: Oops, backup is no good.\n")); + return 1; + } + + for (i = 0; i < ARRAY_SIZE(backup_params); i++) { + VbNvGet(&bvnc, backup_params[i], &value); + VbNvSet(vnc, backup_params[i], value); + } + + /* VbNvTeardown(&bvnc); is not needed. We're done with it. */ + return 0; +} + +int SaveNvToBackup(VbNvContext *vnc) +{ + VbNvContext bvnc; + uint32_t value; + int i; + + VBDEBUG(("TPM: %s()\n", __func__)); + + /* Read it first. No point in writing the same data. */ + if (TPM_SUCCESS != RollbackBackupRead(bvnc.raw)) + return 1; + + VbNvSetup(&bvnc); + VBDEBUG(("TPM: existing backup is %s\n", + bvnc.regenerate_crc ? "bad" : "good")); + + for (i = 0; i < ARRAY_SIZE(backup_params); i++) { + VbNvGet(vnc, backup_params[i], &value); + VbNvSet(&bvnc, backup_params[i], value); + } + + VbNvTeardown(&bvnc); + + if (!bvnc.raw_changed) { + VBDEBUG(("TPM: Nothing's changed, not writing backup\n")); + /* Clear the request flag, since we're happy. */ + VbNvSet(vnc, VBNV_BACKUP_NVRAM_REQUEST, 0); + return 0; + } + + if (TPM_SUCCESS == RollbackBackupWrite(bvnc.raw)) { + /* Clear the request flag if we wrote successfully too */ + VbNvSet(vnc, VBNV_BACKUP_NVRAM_REQUEST, 0); + return 0; + } + + VBDEBUG(("TPM: Sorry, couldn't write backup.\n")); + return 1; +} diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c index ba614ccb..5e08c391 100644 --- a/host/lib/crossystem.c +++ b/host/lib/crossystem.c @@ -133,6 +133,19 @@ VbSetNvCleanup: return retval; } +/* + * Set a param value, and try to flag it for persistent backup. + * It's okay if backup isn't supported. It's best-effort only. + */ +static int VbSetNvStorage_WithBackup(VbNvParam param, int value) +{ + int retval; + retval = VbSetNvStorage(param, value); + if (!retval) + VbSetNvStorage(VBNV_BACKUP_NVRAM_REQUEST, 1); + return retval; +} + /* Find what build/debug status is specified on the kernel command * line, if any. */ static VbBuildOption VbScanBuildOption(void) { @@ -458,6 +471,8 @@ int VbGetSystemPropertyInt(const char* name) { } } else if (!strcasecmp(name,"loc_idx")) { value = VbGetNvStorage(VBNV_LOCALIZATION_INDEX); + } else if (!strcasecmp(name,"backup_nvram_request")) { + value = VbGetNvStorage(VBNV_BACKUP_NVRAM_REQUEST); } else if (!strcasecmp(name,"dev_boot_usb")) { value = VbGetNvStorage(VBNV_DEV_BOOT_USB); } else if (!strcasecmp(name,"dev_boot_legacy")) { @@ -558,13 +573,18 @@ int VbSetSystemPropertyInt(const char* name, int value) { return VbSetNvStorage(VBNV_CLEAR_TPM_OWNER_DONE, 0); } else if (!strcasecmp(name,"fwb_tries")) { return VbSetNvStorage(VBNV_TRY_B_COUNT, value); + } else if (!strcasecmp(name,"oprom_needed")) { + return VbSetNvStorage(VBNV_OPROM_NEEDED, value); + } else if (!strcasecmp(name,"backup_nvram_request")) { + /* Best-effort only, since it requires firmware and TPM support. */ + return VbSetNvStorage(VBNV_BACKUP_NVRAM_REQUEST, value); } else if (!strcasecmp(name,"fwupdate_tries")) { int kern_nv = VbGetNvStorage(VBNV_KERNEL_FIELD); if (kern_nv == -1) return -1; kern_nv &= ~KERN_NV_FWUPDATE_TRIES_MASK; kern_nv |= (value & KERN_NV_FWUPDATE_TRIES_MASK); - return VbSetNvStorage(VBNV_KERNEL_FIELD, kern_nv); + return VbSetNvStorage_WithBackup(VBNV_KERNEL_FIELD, kern_nv); } else if (!strcasecmp(name,"block_devmode")) { int kern_nv = VbGetNvStorage(VBNV_KERNEL_FIELD); if (kern_nv == -1) @@ -572,17 +592,15 @@ int VbSetSystemPropertyInt(const char* name, int value) { kern_nv &= ~KERN_NV_BLOCK_DEVMODE_FLAG; if (value) kern_nv |= KERN_NV_BLOCK_DEVMODE_FLAG; - return VbSetNvStorage(VBNV_KERNEL_FIELD, kern_nv); + return VbSetNvStorage_WithBackup(VBNV_KERNEL_FIELD, kern_nv); } else if (!strcasecmp(name,"loc_idx")) { - return VbSetNvStorage(VBNV_LOCALIZATION_INDEX, value); + return VbSetNvStorage_WithBackup(VBNV_LOCALIZATION_INDEX, value); } else if (!strcasecmp(name,"dev_boot_usb")) { - return VbSetNvStorage(VBNV_DEV_BOOT_USB, value); + return VbSetNvStorage_WithBackup(VBNV_DEV_BOOT_USB, value); } else if (!strcasecmp(name,"dev_boot_legacy")) { - return VbSetNvStorage(VBNV_DEV_BOOT_LEGACY, value); + return VbSetNvStorage_WithBackup(VBNV_DEV_BOOT_LEGACY, value); } else if (!strcasecmp(name,"dev_boot_signed_only")) { - return VbSetNvStorage(VBNV_DEV_BOOT_SIGNED_ONLY, value); - } else if (!strcasecmp(name,"oprom_needed")) { - return VbSetNvStorage(VBNV_OPROM_NEEDED, value); + return VbSetNvStorage_WithBackup(VBNV_DEV_BOOT_SIGNED_ONLY, value); } return -1; diff --git a/tests/rollback_index2_tests.c b/tests/rollback_index2_tests.c index 6d28a9c9..853bfc86 100644 --- a/tests/rollback_index2_tests.c +++ b/tests/rollback_index2_tests.c @@ -520,6 +520,8 @@ static void OneTimeInitTest(void) "TlclForceClear()\n" "TlclSetEnable()\n" "TlclSetDeactivated(0)\n" + /* backup space */ + "TlclDefineSpace(0x1009, 0x1, 16)\n" /* kernel space */ "TlclDefineSpace(0x1008, 0x1, 13)\n" "TlclWrite(0x1008, 13)\n" @@ -549,6 +551,8 @@ static void OneTimeInitTest(void) "TlclForceClear()\n" "TlclSetEnable()\n" "TlclSetDeactivated(0)\n" + /* backup space */ + "TlclDefineSpace(0x1009, 0x1, 16)\n" /* kernel space */ "TlclDefineSpace(0x1008, 0x1, 13)\n" "TlclWrite(0x1008, 13)\n" @@ -570,6 +574,8 @@ static void OneTimeInitTest(void) "TlclForceClear()\n" "TlclSetEnable()\n" "TlclSetDeactivated(0)\n" + /* backup space */ + "TlclDefineSpace(0x1009, 0x1, 16)\n" /* kernel space */ "TlclDefineSpace(0x1008, 0x1, 13)\n" "TlclWrite(0x1008, 13)\n" @@ -664,6 +670,8 @@ static void SetupTpmTest(void) "TlclForceClear()\n" "TlclSetEnable()\n" "TlclSetDeactivated(0)\n" + /* backup space */ + "TlclDefineSpace(0x1009, 0x1, 16)\n" "TlclDefineSpace(0x1008, 0x1, 13)\n" "TlclWrite(0x1008, 13)\n" "TlclRead(0x1008, 13)\n" diff --git a/tests/vboot_api_init_tests.c b/tests/vboot_api_init_tests.c index 9aa34b41..96ab9d33 100644 --- a/tests/vboot_api_init_tests.c +++ b/tests/vboot_api_init_tests.c @@ -32,6 +32,9 @@ static uint32_t mock_tpm_version; static uint32_t mock_rfs_retval; static int rfs_clear_tpm_request; static int rfs_disable_dev_request; +static uint8_t backup_space[BACKUP_NV_SIZE]; +static int backup_write_called; +static int backup_read_called; /* Reset mock data (for use before each test) */ static void ResetMocks(void) @@ -53,6 +56,10 @@ static void ResetMocks(void) VbNvSetup(&vnc); VbNvTeardown(&vnc); /* So CRC gets generated */ + Memset(backup_space, 0, sizeof(backup_space)); + backup_write_called = 0; + backup_read_called = 0; + Memset(&shared_data, 0, sizeof(shared_data)); VbSharedDataInit(shared, sizeof(shared_data)); @@ -79,11 +86,25 @@ VbError_t VbExNvStorageRead(uint8_t *buf) VbError_t VbExNvStorageWrite(const uint8_t *buf) { - nv_write_called = 1; + nv_write_called++; Memcpy(vnc.raw, buf, sizeof(vnc.raw)); return VBERROR_SUCCESS; } +uint32_t RollbackBackupRead(uint8_t *raw) +{ + backup_read_called++; + Memcpy(raw, backup_space, sizeof(backup_space)); + return TPM_SUCCESS; +} + +uint32_t RollbackBackupWrite(uint8_t *raw) +{ + backup_write_called++; + Memcpy(backup_space, raw, sizeof(backup_space)); + return TPM_SUCCESS; +} + uint64_t VbExGetTimer(void) { /* @@ -514,10 +535,233 @@ static void VbInitTestTPM(void) TEST_EQ(rfs_clear_tpm_request, 1, "rfs tpm clear request"); } +static void VbInitTestBackup(void) +{ + VbNvContext tmp_vnc; + uint32_t u, nv_w, bu_r; + + ResetMocks(); + /* Normal mode call */ + TestVbInit(0, 0, "normal mode, no backup"); + TEST_EQ(shared->flags, 0, " shared flags"); + TEST_EQ(iparams.out_flags, 0, " out flags"); + TEST_EQ(nv_write_called, 0, + " NV write not called since nothing changed"); + + ResetMocks(); + /* Now set some params that should be backed up. */ + VbNvSet(&vnc, VBNV_KERNEL_FIELD, 0xaabbccdd); + VbNvSet(&vnc, VBNV_LOCALIZATION_INDEX, 0xa5); + VbNvSet(&vnc, VBNV_DEV_BOOT_USB, 1); + VbNvSet(&vnc, VBNV_DEV_BOOT_LEGACY, 1); + VbNvSet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, 1); + /* and some that don't */ + VbNvSet(&vnc, VBNV_OPROM_NEEDED, 1); + VbNvSet(&vnc, VBNV_TRY_B_COUNT, 3); + /* Make sure they're clean */ + VbNvTeardown(&vnc); + /* Normal mode call */ + TestVbInit(0, 0, "normal mode, some backup"); + TEST_EQ(shared->flags, 0, " shared flags"); + TEST_EQ(iparams.out_flags, 0, " out flags"); + TEST_EQ(nv_write_called, 1, + " Write NV because things have changed"); + /* Some fields should be unchanged */ + VbNvGet(&vnc, VBNV_KERNEL_FIELD, &u); + TEST_EQ(u, 0xaabbccdd, " NV kernel field"); + VbNvGet(&vnc, VBNV_LOCALIZATION_INDEX, &u); + TEST_EQ(u, 0xa5, " NV localization index"); + VbNvGet(&vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 1, " NV oprom_needed"); + VbNvGet(&vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 3, " NV try_b_count"); + /* But normal mode should have cleared the DEV_BOOT flags */ + VbNvGet(&vnc, VBNV_DEV_BOOT_USB, &u); + TEST_EQ(u, 0, " NV dev_boot_usb"); + VbNvGet(&vnc, VBNV_DEV_BOOT_LEGACY, &u); + TEST_EQ(u, 0, " NV dev_boot_legacy"); + VbNvGet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &u); + TEST_EQ(u, 0, " NV dev_boot_signed_only"); + /* So we should have written the backup */ + TEST_EQ(backup_write_called, 1, " Backup written once"); + /* And the backup should reflect the persisent flags. */ + Memset(&tmp_vnc, 0, sizeof(tmp_vnc)); + TEST_EQ(0, RestoreNvFromBackup(&tmp_vnc), "read from backup"); + VbNvGet(&tmp_vnc, VBNV_KERNEL_FIELD, &u); + TEST_EQ(u, 0xaabbccdd, " BU kernel field"); + VbNvGet(&tmp_vnc, VBNV_LOCALIZATION_INDEX, &u); + TEST_EQ(u, 0xa5, " BU localization index"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_USB, &u); + TEST_EQ(u, 0, " BU dev_boot_usb"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_LEGACY, &u); + TEST_EQ(u, 0, " BU dev_boot_legacy"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &u); + TEST_EQ(u, 0, " BU dev_boot_signed_only"); + /* but not the others */ + VbNvGet(&tmp_vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 0, " BU oprom_needed"); + VbNvGet(&tmp_vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 0, " BU try_b_count"); + + /* + * If we change one of the non-backed-up NVRAM params and try + * again, we shouldn't need to backup again. + */ + VbNvSet(&vnc, VBNV_OPROM_NEEDED, 0); + VbNvSet(&vnc, VBNV_TRY_B_COUNT, 2); + /* Make sure they're clean */ + VbNvTeardown(&vnc); + /* Normal mode call */ + TestVbInit(0, 0, "normal mode, expect no backup"); + TEST_EQ(shared->flags, 0, " shared flags"); + TEST_EQ(iparams.out_flags, 0, " out flags"); + TEST_EQ(backup_write_called, 1, " Backup still only written once"); + + /* Now switch to dev-mode. */ + iparams.flags = VB_INIT_FLAG_DEV_SWITCH_ON; + TestVbInit(0, 0, "Dev mode on"); + TEST_EQ(shared->recovery_reason, 0, " recovery reason"); + TEST_EQ(iparams.out_flags, + VB_INIT_OUT_CLEAR_RAM | + VB_INIT_OUT_ENABLE_DISPLAY | + VB_INIT_OUT_ENABLE_USB_STORAGE | + VB_INIT_OUT_ENABLE_DEVELOPER | + VB_INIT_OUT_ENABLE_ALTERNATE_OS, " out flags"); + TEST_EQ(shared->flags, VBSD_BOOT_DEV_SWITCH_ON, " shared flags"); + TEST_EQ(backup_write_called, 1, " Still only one backup"); + + /* Now change some params that should be backed up. */ + VbNvSet(&vnc, VBNV_KERNEL_FIELD, 0xdeadbeef); + VbNvSet(&vnc, VBNV_LOCALIZATION_INDEX, 0x5a); + VbNvSet(&vnc, VBNV_DEV_BOOT_USB, 1); + VbNvSet(&vnc, VBNV_DEV_BOOT_LEGACY, 1); + VbNvSet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, 1); + /* and some that don't */ + VbNvSet(&vnc, VBNV_OPROM_NEEDED, 1); + VbNvSet(&vnc, VBNV_TRY_B_COUNT, 4); + /* Make sure they're clean */ + VbNvTeardown(&vnc); + TestVbInit(0, 0, "Dev mode on"); + TEST_EQ(shared->recovery_reason, 0, " recovery reason"); + TEST_EQ(iparams.out_flags, + VB_INIT_OUT_CLEAR_RAM | + VB_INIT_OUT_ENABLE_DISPLAY | + VB_INIT_OUT_ENABLE_USB_STORAGE | + VB_INIT_OUT_ENABLE_DEVELOPER, " out flags"); + TEST_EQ(shared->flags, VBSD_BOOT_DEV_SWITCH_ON, " shared flags"); + TEST_EQ(backup_write_called, 1, " Once more, one backup"); + + /* But if we explictly request a backup, they'll get saved. */ + VbNvSet(&vnc, VBNV_BACKUP_NVRAM_REQUEST, 1); + VbNvTeardown(&vnc); + TestVbInit(0, 0, "Dev mode on"); + TEST_EQ(shared->recovery_reason, 0, " recovery reason"); + TEST_EQ(iparams.out_flags, + VB_INIT_OUT_CLEAR_RAM | + VB_INIT_OUT_ENABLE_DISPLAY | + VB_INIT_OUT_ENABLE_USB_STORAGE | + VB_INIT_OUT_ENABLE_DEVELOPER, " out flags"); + TEST_EQ(shared->flags, VBSD_BOOT_DEV_SWITCH_ON, " shared flags"); + TEST_EQ(backup_write_called, 2, " Two backups now"); + VbNvGet(&vnc, VBNV_BACKUP_NVRAM_REQUEST, &u); + TEST_EQ(u, 0, " backup_request cleared"); + /* Quick check that the non-backed-up stuff is still valid */ + VbNvGet(&vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 1, " NV oprom_needed"); + VbNvGet(&vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 4, " NV try_b_count"); + /* But only the stuff we care about was backed up */ + Memset(&tmp_vnc, 0, sizeof(tmp_vnc)); + TEST_EQ(0, RestoreNvFromBackup(&tmp_vnc), "read from backup"); + VbNvGet(&tmp_vnc, VBNV_KERNEL_FIELD, &u); + TEST_EQ(u, 0xdeadbeef, " BU kernel field"); + VbNvGet(&tmp_vnc, VBNV_LOCALIZATION_INDEX, &u); + TEST_EQ(u, 0x5a, " BU localization index"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_USB, &u); + TEST_EQ(u, 1, " BU dev_boot_usb"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_LEGACY, &u); + TEST_EQ(u, 1, " BU dev_boot_legacy"); + VbNvGet(&tmp_vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &u); + TEST_EQ(u, 1, " BU dev_boot_signed_only"); + /* but not the others */ + VbNvGet(&tmp_vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 0, " BU oprom_needed"); + VbNvGet(&tmp_vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 0, " BU try_b_count"); + + /* If we lose the NV storage, the backup bits will be restored */ + vnc.raw[0] = 0; + bu_r = backup_read_called; + nv_w = nv_write_called; + TestVbInit(0, 0, "Dev mode on"); + TEST_EQ(shared->recovery_reason, 0, " recovery reason"); + TEST_EQ(iparams.out_flags, + VB_INIT_OUT_CLEAR_RAM | + VB_INIT_OUT_ENABLE_DISPLAY | + VB_INIT_OUT_ENABLE_USB_STORAGE | + VB_INIT_OUT_ENABLE_DEVELOPER, " out flags"); + TEST_EQ(shared->flags, VBSD_BOOT_DEV_SWITCH_ON, " shared flags"); + TEST_EQ(backup_write_called, 2, " Still just two backups now"); + TEST_EQ(backup_read_called, bu_r + 1, " One more backup read"); + TEST_EQ(nv_write_called, nv_w + 1, " One more NV write"); + /* The non-backed-up stuff is reset to defaults */ + VbNvGet(&vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 0, " NV oprom_needed"); + VbNvGet(&vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 0, " NV try_b_count"); + /* And the backed up stuff is restored */ + VbNvGet(&vnc, VBNV_KERNEL_FIELD, &u); + TEST_EQ(u, 0xdeadbeef, " BU kernel field"); + VbNvGet(&vnc, VBNV_LOCALIZATION_INDEX, &u); + TEST_EQ(u, 0x5a, " BU localization index"); + VbNvGet(&vnc, VBNV_DEV_BOOT_USB, &u); + TEST_EQ(u, 1, " BU dev_boot_usb"); + VbNvGet(&vnc, VBNV_DEV_BOOT_LEGACY, &u); + TEST_EQ(u, 1, " BU dev_boot_legacy"); + VbNvGet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &u); + TEST_EQ(u, 1, " BU dev_boot_signed_only"); + + /* + * But if we lose the NV storage and go back to normal mode at the same + * time, then the DEV_BOOT_* flags will be cleared. + */ + vnc.raw[0] = 0; + bu_r = backup_read_called; + nv_w = nv_write_called; + iparams.flags = 0; + TestVbInit(0, 0, "Back to normal mode"); + TEST_EQ(shared->recovery_reason, 0, " recovery reason"); + TEST_EQ(iparams.out_flags, 0, " out flags"); + TEST_EQ(shared->flags, 0, " shared flags"); + /* We read twice: once to restore, once for read-prior-to-write */ + TEST_EQ(backup_read_called, bu_r + 2, " Two more backup reads"); + TEST_EQ(backup_write_called, 3, " Backup write due clearing DEV_*"); + TEST_EQ(nv_write_called, nv_w + 1, " One more NV write"); + /* The non-backed-up stuff is reset to defaults */ + VbNvGet(&vnc, VBNV_OPROM_NEEDED, &u); + TEST_EQ(u, 0, " NV oprom_needed"); + VbNvGet(&vnc, VBNV_TRY_B_COUNT, &u); + TEST_EQ(u, 0, " NV try_b_count"); + /* And the backed up stuff is restored */ + VbNvGet(&vnc, VBNV_KERNEL_FIELD, &u); + TEST_EQ(u, 0xdeadbeef, " BU kernel field"); + VbNvGet(&vnc, VBNV_LOCALIZATION_INDEX, &u); + TEST_EQ(u, 0x5a, " BU localization index"); + /* But not the DEV_BOOT_* flags */ + VbNvGet(&vnc, VBNV_DEV_BOOT_USB, &u); + TEST_EQ(u, 0, " BU dev_boot_usb"); + VbNvGet(&vnc, VBNV_DEV_BOOT_LEGACY, &u); + TEST_EQ(u, 0, " BU dev_boot_legacy"); + VbNvGet(&vnc, VBNV_DEV_BOOT_SIGNED_ONLY, &u); + TEST_EQ(u, 0, " BU dev_boot_signed_only"); +} + + int main(int argc, char *argv[]) { VbInitTest(); VbInitTestTPM(); + VbInitTestBackup(); return gTestSuccess ? 0 : 255; } diff --git a/utility/crossystem.c b/utility/crossystem.c index 9a55d17e..5ffb5af1 100644 --- a/utility/crossystem.c +++ b/utility/crossystem.c @@ -32,6 +32,8 @@ typedef struct Param { /* List of parameters, terminated with a param with NULL name */ const Param sys_param_list[] = { {"arch", IS_STRING, "Platform architecture"}, + {"backup_nvram_request", CAN_WRITE, + "Backup the nvram somewhere at the next boot. Cleared on success."}, {"block_devmode", CAN_WRITE, "Block all use of developer mode"}, {"clear_tpm_owner_request", CAN_WRITE, "Clear TPM owner on next boot"}, {"clear_tpm_owner_done", CAN_WRITE, "Clear TPM owner done"}, |