This test sets the TPM to a each of a large amount of "interesting" initial states, and runs the firmware code at user level.

This code compiles and installs using a modified ebuild (which needs to be committed after this change).

Review URL: http://codereview.chromium.org/2857030
diff --git a/Makefile b/Makefile
index b4fce9d..638d512 100644
--- a/Makefile
+++ b/Makefile
@@ -35,6 +35,7 @@
 install:
 	$(MAKE) -C utility install
 	$(MAKE) -C cgpt install
+	$(MAKE) -C tests install
 
 runtests:
 	$(MAKE) -C tests runtests
diff --git a/firmware/Makefile b/firmware/Makefile
index 73ed231..38eb24f 100644
--- a/firmware/Makefile
+++ b/firmware/Makefile
@@ -51,10 +51,10 @@
 		| sort | xargs cat | md5sum | cut -c 25-32 > \
 	${BUILD_ROOT}/x.tmp && \
 	echo "char* VbootVersion = \"VBOOv=$$(cat ${BUILD_ROOT}/x.tmp)\";" > \
-		${BUILD_ROOT}\version.tmp && \
-	(cmp -s ${BUILD_ROOT}\version.tmp version.c || \
+		${BUILD_ROOT}/version.tmp && \
+	(cmp -s ${BUILD_ROOT}/version.tmp version.c || \
 		( echo "** Updating version.c **" &&  \
-		  cp ${BUILD_ROOT}\version.tmp version.c))
+		  cp ${BUILD_ROOT}/version.tmp version.c))
 
 include ../common.mk
 
diff --git a/firmware/include/tlcl.h b/firmware/include/tlcl.h
index 82947f6..5bdbaa3 100644
--- a/firmware/include/tlcl.h
+++ b/firmware/include/tlcl.h
@@ -30,6 +30,13 @@
  */
 void TlclLibInit(void);
 
+/* Close and open the device.  This is needed for running more complex commands
+ * at user level, such as TPM_TakeOwnership, since the TPM device can be opened
+ * only by one process at a time.
+ */
+void TlclCloseDevice(void);
+void TlclOpenDevice(void);
+
 /* Sends a TPM_Startup(ST_CLEAR).  Note that this is a no-op for the emulator,
  * because it runs this command during initialization.  The TPM error code is
  * returned (0 for success).
@@ -89,10 +96,14 @@
  */
 uint32_t TlclForceClear(void);
 
-/* Issues a SetEnable.  The TPM error code is returned.
+/* Issues a PhysicalEnable.  The TPM error code is returned.
  */
 uint32_t TlclSetEnable(void);
 
+/* Issues a PhysicalDisable.  The TPM error code is returned.
+ */
+uint32_t TlclClearEnable(void);
+
 /* Issues a SetDeactivated.  Pass 0 to activate.  Returns result code.
  */
 uint32_t TlclSetDeactivated(uint8_t flag);
diff --git a/firmware/lib/include/rollback_index.h b/firmware/lib/include/rollback_index.h
index e339912..fd5f72a 100644
--- a/firmware/lib/include/rollback_index.h
+++ b/firmware/lib/include/rollback_index.h
@@ -95,4 +95,13 @@
 /* Lock must be called.  Internally, it's ignored in recovery mode. */
 uint32_t RollbackKernelLock(void);
 
+/* The following functions are here for testing only. */
+
+/* Store 1 in *|initialized| if the TPM NVRAM spaces have been initialized, 0
+ * otherwise.  Return TPM errors. */
+uint32_t GetSpacesInitialized(int* initialized);
+
+/* Issue a TPM_Clear and reenable/reactivate the TPM. */
+uint32_t TPMClearAndReenable(void);
+
 #endif  /* VBOOT_REFERENCE_ROLLBACK_INDEX_H_ */
diff --git a/firmware/lib/include/tss_constants.h b/firmware/lib/include/tss_constants.h
index 9e60529..b035ebd 100644
--- a/firmware/lib/include/tss_constants.h
+++ b/firmware/lib/include/tss_constants.h
@@ -26,7 +26,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_OWNER_SET ((uint32_t)0x00000014)
+
+#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_E_CORRUPTED_STATE        ((uint32_t)0x00005003)  /* vboot local */
diff --git a/firmware/lib/rollback_index.c b/firmware/lib/rollback_index.c
index a81bb9b..944104b 100644
--- a/firmware/lib/rollback_index.c
+++ b/firmware/lib/rollback_index.c
@@ -24,7 +24,7 @@
     }                                                   \
   } while (0)
 
-static uint32_t TPMClearAndReenable() {
+uint32_t TPMClearAndReenable(void) {
   RETURN_ON_FAILURE(TlclForceClear());
   RETURN_ON_FAILURE(TlclSetEnable());
   RETURN_ON_FAILURE(TlclSetDeactivated(0));
@@ -58,7 +58,7 @@
  * if the spaces have been fully initialized, to 0 if not.  Otherwise
  * *|initialized| is not changed.
  */
-static uint32_t GetSpacesInitialized(int* initialized) {
+uint32_t GetSpacesInitialized(int* initialized) {
   uint32_t space_holder;
   uint32_t result;
   result = TlclRead(TPM_IS_INITIALIZED_NV_INDEX,
@@ -154,8 +154,8 @@
                              KERNEL_SPACE_SIZE));
   RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_VERSIONS_NV_INDEX, &perms));
   if (perms != TPM_NV_PER_PPWRITE ||
-      !Memcmp(buffer + sizeof(uint32_t), KERNEL_SPACE_UID,
-              KERNEL_SPACE_UID_SIZE)) {
+      Memcmp(buffer + sizeof(uint32_t), KERNEL_SPACE_UID,
+              KERNEL_SPACE_UID_SIZE) != 0) {
     return TPM_E_CORRUPTED_STATE;
   }
 
@@ -233,6 +233,7 @@
                          int developer_mode) {
   uint8_t disable;
   uint8_t deactivated;
+  uint32_t result;
 
   TlclLibInit();
   RETURN_ON_FAILURE(TlclStartup());
@@ -245,14 +246,15 @@
     RETURN_ON_FAILURE(TlclSetDeactivated(0));
     return TPM_E_MUST_REBOOT;
   }
-  /* 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) {
+  result = RecoverKernelSpace();
+  if (result != TPM_SUCCESS) {
+     /* Check if this is the first time we run and the TPM has not been
+      * initialized yet.
+      */
     int initialized = 0;
     RETURN_ON_FAILURE(GetSpacesInitialized(&initialized));
     if (initialized) {
-      return TPM_E_ALREADY_INITIALIZED;
+      return result;
     } else {
       RETURN_ON_FAILURE(InitializeSpaces());
       RETURN_ON_FAILURE(RecoverKernelSpace());
@@ -299,7 +301,7 @@
 }
 
 uint32_t RollbackKernelRecovery(int developer_mode) {
-  (void) SetupTPM(1, developer_mode);
+  uint32_t result = SetupTPM(1, developer_mode);
   /* In recovery mode we ignore TPM malfunctions or corruptions, and leave the
    * TPM completely unlocked if and only if the dev mode switch is ON.  The
    * recovery kernel will fix the TPM (if needed) and lock it ASAP.  We leave
@@ -308,7 +310,10 @@
   if (!developer_mode) {
     RETURN_ON_FAILURE(TlclSetGlobalLock());
   }
-  return TPM_SUCCESS;
+  /* We still return the result of SetupTPM even though we expect the caller to
+   * ignore it.  It's useful in unit testing.
+   */
+  return result;
 }
 
 uint32_t RollbackKernelRead(uint16_t* key_version, uint16_t* version) {
diff --git a/firmware/linktest/main.c b/firmware/linktest/main.c
index 4696c2e..5e900a9 100644
--- a/firmware/linktest/main.c
+++ b/firmware/linktest/main.c
@@ -35,6 +35,8 @@
 
   /* tlcl.h */
   TlclLibInit();
+  TlclCloseDevice();
+  TlclOpenDevice();
   TlclStartup();
   TlclSelftestfull();
   TlclContinueSelfTest();
@@ -48,6 +50,7 @@
   TlclIsOwned();
   TlclForceClear();
   TlclSetEnable();
+  TlclClearEnable();
   TlclSetDeactivated(0);
   TlclGetFlags(0, 0);
 
diff --git a/firmware/stub/tlcl.c b/firmware/stub/tlcl.c
index cad286a..1a44ea1 100644
--- a/firmware/stub/tlcl.c
+++ b/firmware/stub/tlcl.c
@@ -11,6 +11,8 @@
 __pragma(warning (disable: 4100))
 
 void TlclLibInit(void) { return; }
+void TlclCloseDevice(void) { return; }
+void TlclOpenDevice(void) { return; }
 uint32_t TlclStartup(void) { return TPM_SUCCESS; }
 uint32_t TlclSelftestfull(void) { return TPM_SUCCESS; }
 uint32_t TlclContinueSelfTest(void) { return TPM_SUCCESS; }
@@ -31,6 +33,7 @@
 int TlclIsOwned(void) { return TPM_SUCCESS; }
 uint32_t TlclForceClear(void) { return TPM_SUCCESS; }
 uint32_t TlclSetEnable(void) { return TPM_SUCCESS; }
+uint32_t TlclClearEnable(void) { return TPM_SUCCESS; }
 uint32_t TlclSetDeactivated(int deactivated) { return TPM_SUCCESS; }
 uint32_t TlclSetGlobalLock(void) { return TPM_SUCCESS; }
 uint32_t TlclGetFlags(uint8_t* disable, uint8_t* deactivated) {
diff --git a/firmware/version.c b/firmware/version.c
index 3e5b6e7..2d4dc14 100644
--- a/firmware/version.c
+++ b/firmware/version.c
@@ -1 +1 @@
-char* VbootVersion = "VBOOv=c79b5eca";
+char* VbootVersion = "VBOOv=983af25b";
diff --git a/tests/Makefile b/tests/Makefile
index 3259dfe..ade73c9 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -10,13 +10,14 @@
 BUILD_ROOT = ${BUILD}/tests
 
 TEST_NAMES = cgptlib_test \
-		rsa_padding_test \
-		rsa_verify_benchmark \
-		sha_benchmark \
-		sha_tests \
-		vboot_common_tests \
-		vboot_common2_tests \
-		vboot_common3_tests
+	     rsa_padding_test \
+	     rsa_verify_benchmark \
+	     sha_benchmark \
+	     sha_tests \
+	     vboot_common_tests \
+	     vboot_common2_tests \
+	     vboot_common3_tests \
+
 TEST_BINS = $(addprefix ${BUILD_ROOT}/,$(TEST_NAMES))
 
 TEST_LIB = ${BUILD_ROOT}/test.a
@@ -31,12 +32,19 @@
 EXTRA_TARGET = runtests
 endif
 
-all: $(TEST_BINS) ${EXTRA_TARGET}
+all: $(TEST_BINS) ${EXTRA_TARGET} $(BUILD_ROOT)/rollback_index_test
 
 ${TEST_LIB}: ${TEST_LIB_OBJS}
 	rm -f $@
 	ar qc $@ $^
 
+${BUILD_ROOT}/rollback_index_test.o : rollback_index_test.c
+	$(CC) $(CFLAGS) -I/usr/include $(INCLUDES) -MMD -MF $@.d -c -o $@ $<
+
+${BUILD_ROOT}/rollback_index_test: rollback_index_test.c ${HOSTLIB} ${FWLIB}
+	$(CC) $(CFLAGS) -I/usr/include $(INCLUDES) $< -o $@ \
+        -ltlcl ${HOSTLIB} ${FWLIB} -lcrypto -lrt
+
 ${BUILD_ROOT}/%.o : %.c
 	$(CC) $(CFLAGS) $(INCLUDES) -MMD -MF $@.d -c -o $@ $<
 
@@ -54,10 +62,10 @@
 #		verify_firmware_fuzz_driver
 #
 #		big_kernel_tests
-#		kernel_image_tests 
-#		kernel_rollback_tests 
-#		kernel_splicing_tests 
-#		kernel_verify_benchmark 
+#		kernel_image_tests
+#		kernel_rollback_tests
+#		kernel_splicing_tests
+#		kernel_verify_benchmark
 #		verify_kernel_fuzz_driver
 
 # Generate test keys
@@ -91,4 +99,8 @@
 #	${BUILD_ROOT}/firmware_rollback_tests
 #	${BUILD_ROOT}/kernel_rollback_tests
 
+install: $(BUILD_ROOT)/rollback_index_test
+	mkdir -p $(DESTDIR)
+	cp -f $(BUILD_ROOT)/rollback_index_test $(DESTDIR)
+
 -include ${ALL_DEPS}
diff --git a/tests/rbtest.conf b/tests/rbtest.conf
new file mode 100644
index 0000000..e2e2e9f
--- /dev/null
+++ b/tests/rbtest.conf
@@ -0,0 +1,34 @@
+# Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+# Rollback code exhaustive test.
+#
+# INSTRUCTIONS.  Put this file in /etc/init.  Move /etc/init/tcsd.conf to
+# /etc/init/tcsd.confxxx to disable it.  Then boot with the device connected by
+# wired ethernet.  The test will start and reboot the host after every cycle.
+# Unplug the ethernet cable to stop testing.  If left alone, the test will stop
+# at the first failure or when all the states have been tested.
+#
+# Reminder: rollback_index_test only works with TPM-agnostic firmware.
+
+# Connecting to tcsd requires that "localhost" be reachable, so we wait for
+# flimflam to start, but that's not enough, and in the while loop below we also
+# wait for pinging to localhost to succeed.
+
+start on started flimflam
+
+script
+  cable=""
+  while [ "$cable" != "yes" ]; do
+    cable=$(/usr/sbin/ethtool eth0 | grep Link | cut -f 3 -d ' ')
+    logger "rbtest: cable is $cable"
+    ping -c 1 localhost || cable=""
+    sleep 2
+  done
+  # ideally we would like to issue a "stop tcsd", but this doesn't work
+  # (upstart race?) so we must manually disable tcsd.conf
+  ### stop tcsd
+  logger "starting rbtest"
+  /usr/bin/rollback_index_test > /tmp/rbtest.out 2>&1
+end script
diff --git a/tests/rollback_index_test.c b/tests/rollback_index_test.c
new file mode 100644
index 0000000..1fcbcc8
--- /dev/null
+++ b/tests/rollback_index_test.c
@@ -0,0 +1,749 @@
+/* Copyright (c) 2010 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+/* Exhaustive testing for correctness and integrity of TPM locking code from
+ * all interesting initial conditions.
+ *
+ * This program iterates through a large number of initial states of the TPM at
+ * power on, and executes the code related to initialing the TPM and managing
+ * the anti-rollback indices.
+ *
+ * This program must be run on a system with "TPM-agnostic" BIOS: that is, the
+ * system must have a TPM (as of this date, the emulator isn't good enough,
+ * because it doesn't support bGlobalLock), but the firmware should not issue a
+ * TPM_Startup.  In addition, the TPM drivers must be loaded (tpm_tis, tpm, and
+ * tpm_bios) but tcsd should NOT be running.  However, tcsd must be installed,
+ * as well as the command tpm_takeownership from the TPM tools, and tpm-nvtool
+ * from third-party/tpm.
+ *
+ * This program must be run as root.  It issues multiple reboots, saving and
+ * restoring the state from a file.  Typically it works in two phases: on one
+ * reboot it sets the TPM to a certain state, and in the next reboot it runs
+ * the test.
+ *
+ * This program may take a long time to complete.
+ *
+ * A companion upstart file rbtest.conf contains test setup instructions.  Look
+ * around for it.
+ */
+
+#include "rollback_index.h"
+#include "tlcl.h"
+#include "tss_constants.h"
+#include "utility.h"
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <syslog.h>
+#include <unistd.h>
+
+#define RETURN_ON_FAILURE(tpm_command) do {             \
+    uint32_t result;                                    \
+    if ((result = (tpm_command)) != TPM_SUCCESS) {      \
+      return result;                                    \
+    }                                                   \
+  } while (0)
+
+#define WRITE_BUCKET_NV_INDEX 0x1050
+#define STATEPATH "/mnt/stateful_partition/var/spool/rbtest.state"
+#define TPM_ANY_FAILURE (-1)
+
+#define TPM_MAX_NV_WRITE_NOOWNER 64
+#define MAX_NV_WRITES_AT_BOOT 18         /* see comment below */
+#define INITIALIZATION_NV_WRITES 11      /* see below */
+
+const int high_writecount = TPM_MAX_NV_WRITE_NOOWNER;
+/* The -1 below is to make sure there is at least one case when we don't hit
+ * the write limit.  It is probably unnecessary, but we pay this cost to avoid
+ * an off-by-one error.
+ */
+const int low_writecount = TPM_MAX_NV_WRITE_NOOWNER - MAX_NV_WRITES_AT_BOOT - 1;
+const int low_writecount_when_initialized = TPM_MAX_NV_WRITE_NOOWNER
+  - MAX_NV_WRITES_AT_BOOT + INITIALIZATION_NV_WRITES;
+
+/*
+ * This structure contains all TPM states of interest, and other testing
+ * states.  It is saved and restored from a file across all reboots.
+ *
+ * OWNED/UNOWNED
+ * ACTIVATED/DEACTIVATED
+ * ENABLED/DISABLED
+ * WRITE COUNT
+ *
+ * The write count tests hitting the write limit with an unowned TPM.  After
+ * resetting the TPM we reset the write count to zero, then we perform
+ * |writecount| writes to bring the count to the desired number.
+ *
+ * Low write counts are not interesting, because we know they cannot cause the
+ * code to hit the limit during a single boot.  There are a total of N
+ * SafeWrite and SafeDefineSpace call sites, where N = MAX_NV_WRITES_AT_BOOT.
+ * Every call site can be reached at most once at every boot (there are no
+ * loops or multiple nested calls).  So we only need to test N + 1 different
+ * initial values of the NVRAM write count (between 64 - (N + 1)) and 64).
+ *
+ * A number of calls happen at initialization, so when the TPM_IS_INITIALIZED
+ * space exists, we only need to start checking at TPM_MAX_NV_WRITE_NOOWNER -
+ * MAX_NV_WRITES_AT_BOOT + INITIALIZATION_NV_WRITES.
+ *
+ * TPM_IS_INITIALIZED space exists/does not exist
+ * KERNEL_MUST_USE_BACKUP = 0 or 1
+ * KERNEL_VERSIONS exists/does not
+ * KERNEL_VERSIONS space has wrong permissions
+ * KERNEL_VERSIONS does not contain the replacement-prevention value
+ * KERNEL_VERSIONS and KERNEL_VERSIONS_BACKUP are the same/are not
+ * DEVELOPER_MODE_NV_INDEX = 0 or 1
+ *
+ * developer switch on/off
+ * recovery switch on/off
+ */
+
+typedef struct RBTState {
+  /* Internal testing state */
+  int advancing;  /* this is 1 if we are setting the TPM to the next initial
+                     state, 0 if we are running the test. */
+
+  /* TPM state */
+  int writecount;
+  int owned;
+  int disable;
+  int deactivated;
+  int TPM_IS_INITIALIZED_exists;
+  int KERNEL_MUST_USE_BACKUP;
+  int KERNEL_VERSIONS_exists;
+  int KERNEL_VERSIONS_wrong_permissions;
+  int KERNEL_VERSIONS_wrong_value;
+  int KERNEL_VERSIONS_same_as_backup;
+  int DEVELOPER_MODE;   /* content of DEVELOPER_MODE space */
+  int developer;        /* setting of developer mode switch */
+  int recovery;         /* booting in recovery mode */
+} RBTState;
+
+RBTState RBTS;
+
+/* Set to 1 if the TPM was cleared in this run, to avoid clearing it again
+ * before we set the write count.
+ */
+int tpm_was_just_cleared = 0;
+
+const char* RBTS_format =
+  "advancing=%d, owned=%d, disable=%d, activated=%d, "
+  "writecount=%d, TII_exists=%d, KMUB=%d, "
+  "KV_exists=%d, KV_wp=%d, KV_wv=%d, KV_sab=%d, DM=%d, dm=%d, rm=%d";
+
+static void Log(const char* format, ...) {
+  va_list ap;
+  va_start(ap, format);
+  vsyslog(LOG_INFO, format, ap);
+  va_end(ap);
+  va_start(ap, format);
+  vfprintf(stderr, format, ap);
+  va_end(ap);
+  fprintf(stderr, "\n");
+}
+
+static void reboot(void) {
+  int status;
+  Log("requesting reboot");
+  status = system("/sbin/reboot");
+  if (status != 0) {
+    Log("reboot failed with status %d", status);
+    exit(1);
+  }
+}
+
+static void RollbackTest_SaveState(FILE* file) {
+  rewind(file);
+  fprintf(file, RBTS_format,
+          RBTS.advancing,
+          RBTS.owned,
+          RBTS.disable,
+          RBTS.deactivated,
+          RBTS.writecount,
+          RBTS.TPM_IS_INITIALIZED_exists,
+          RBTS.KERNEL_MUST_USE_BACKUP,
+          RBTS.KERNEL_VERSIONS_exists,
+          RBTS.KERNEL_VERSIONS_wrong_permissions,
+          RBTS.KERNEL_VERSIONS_wrong_value,
+          RBTS.KERNEL_VERSIONS_same_as_backup,
+          RBTS.DEVELOPER_MODE,
+          RBTS.developer,
+          RBTS.recovery);
+}
+
+static void RollbackTest_RestoreState(FILE* file) {
+  if (fscanf(file, RBTS_format,
+             &RBTS.advancing,
+             &RBTS.owned,
+             &RBTS.disable,
+             &RBTS.deactivated,
+             &RBTS.writecount,
+             &RBTS.TPM_IS_INITIALIZED_exists,
+             &RBTS.KERNEL_MUST_USE_BACKUP,
+             &RBTS.KERNEL_VERSIONS_exists,
+             &RBTS.KERNEL_VERSIONS_wrong_permissions,
+             &RBTS.KERNEL_VERSIONS_wrong_value,
+             &RBTS.KERNEL_VERSIONS_same_as_backup,
+             &RBTS.DEVELOPER_MODE,
+             &RBTS.developer,
+             &RBTS.recovery) != sizeof(RBTS)/sizeof(int)) {
+    Log("failed to restore state");
+    exit(1);
+  }
+}
+
+static void RollbackTest_LogState(void) {
+  Log(RBTS_format,
+      RBTS.advancing,
+      RBTS.owned,
+      RBTS.disable,
+      RBTS.deactivated,
+      RBTS.writecount,
+      RBTS.TPM_IS_INITIALIZED_exists,
+      RBTS.KERNEL_MUST_USE_BACKUP,
+      RBTS.KERNEL_VERSIONS_exists,
+      RBTS.KERNEL_VERSIONS_wrong_permissions,
+      RBTS.KERNEL_VERSIONS_wrong_value,
+      RBTS.KERNEL_VERSIONS_same_as_backup,
+      RBTS.DEVELOPER_MODE,
+      RBTS.developer,
+      RBTS.recovery);
+}
+
+/* Executes a TPM command from the shell.
+ */
+static void RollbackTest_TPMShellCommand(char* command) {
+  int status;
+  TlclCloseDevice();
+  status = system("/usr/sbin/tcsd");
+  if (status != 0) {
+    Log("could not start tcsd");
+    exit(1);
+  }
+  status = system("/usr/bin/sleep 0.1");
+  status = system(command);
+  if (status != 0) {
+    Log("command %s returned 0x%x", command, status);
+    exit(1);
+  }
+  status = system("/usr/bin/pkill tcsd");
+  if (status != 0) {
+    Log("could not kill tcsd, status 0x%x", status);
+    exit(1);
+  }
+  status = system("/usr/bin/sleep 0.1");
+  TlclOpenDevice();
+}
+
+/* Sets or clears ownership.
+ */
+static uint32_t RollbackTest_SetOwnership(int ownership) {
+  if (ownership) {
+    /* Requesting owned state */
+    int owned = TlclIsOwned();
+    if (!owned) {
+      Log("acquiring ownership");
+      RollbackTest_TPMShellCommand("/usr/sbin/tpm_takeownership -y -z");
+      Log("ownership acquired");
+    }
+  } else {
+    /* Requesting unowned state */
+    Log("clearing TPM");
+    RETURN_ON_FAILURE(TPMClearAndReenable());
+    tpm_was_just_cleared = 1;
+  }
+  return TPM_SUCCESS;
+}
+
+/* Removes a space.  This is a huge pain, because spaces can be removed only
+ * when the TPM is owned.
+ */
+static uint32_t RollbackTest_RemoveSpace(uint32_t index) {
+  char command[1024];
+  RollbackTest_SetOwnership(1);
+  snprintf(command, sizeof(command),
+           "/usr/bin/tpm-nvtool --release --index 0x%x --owner_password \"\"",
+           index);
+  Log("releasing space %x with command: %s", index, command);
+  RollbackTest_TPMShellCommand(command);
+  Log("space %x released", index);
+  return TPM_SUCCESS;
+}
+
+/* Checks if the TPM is disabled/deactivated, and optionally enables/activates.
+ * Does not disable/deactivate here because it might interfere with other
+ * operations.
+ */
+static uint32_t RollbackTest_PartiallyAdjustFlags(uint8_t* disable,
+                                                  uint8_t* deactivated) {
+  RETURN_ON_FAILURE(TlclGetFlags(disable, deactivated));
+
+  if (*deactivated && !RBTS.deactivated) {
+    /* Needs to enable before we can activate. */
+    RETURN_ON_FAILURE(TlclSetEnable());
+    *disable = 0;
+    /* Needs to reboot after activating. */
+    RETURN_ON_FAILURE(TlclSetDeactivated(0));
+    reboot();
+  }
+  /* We disable and deactivate at the end, if needed. */
+
+  if (*disable && !RBTS.disable) {
+    RETURN_ON_FAILURE(TlclSetEnable());
+  }
+  return TPM_SUCCESS;
+}
+
+/* Removes or creates the TPM_IS_INITIALIZED space.
+ */
+static uint32_t RollbackTest_AdjustIsInitialized(void) {
+  int initialized;
+  RETURN_ON_FAILURE(GetSpacesInitialized(&initialized));
+  if (RBTS.TPM_IS_INITIALIZED_exists && !initialized) {
+    RETURN_ON_FAILURE(TlclDefineSpace(TPM_IS_INITIALIZED_NV_INDEX,
+                                      TPM_NV_PER_PPWRITE, sizeof(uint32_t)));
+  }
+  if (!RBTS.TPM_IS_INITIALIZED_exists && initialized) {
+    RETURN_ON_FAILURE(RollbackTest_RemoveSpace(TPM_IS_INITIALIZED_NV_INDEX));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Sets or clears KERNEL_MUST_USE_BACKUP.
+ */
+static uint32_t RollbackTest_AdjustMustUseBackup(void) {
+  uint32_t must_use_backup;
+  RETURN_ON_FAILURE(TlclRead(KERNEL_MUST_USE_BACKUP_NV_INDEX,
+                             (uint8_t*) &must_use_backup,
+                             sizeof(must_use_backup)));
+  if (RBTS.KERNEL_MUST_USE_BACKUP != must_use_backup) {
+    RETURN_ON_FAILURE(TlclWrite(KERNEL_MUST_USE_BACKUP_NV_INDEX,
+                                (uint8_t*) &must_use_backup,
+                                sizeof(must_use_backup)));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Adjusts KERNEL_VERSIONS space.
+ */
+static uint32_t RollbackTest_AdjustKernelVersions(int* wrong_value) {
+  uint8_t kdata[KERNEL_SPACE_SIZE];
+  int exists;
+  uint32_t result;
+
+  result = TlclRead(KERNEL_VERSIONS_NV_INDEX, kdata, sizeof(kdata));
+  if (result != TPM_SUCCESS && result != TPM_E_BADINDEX) {
+    return result;
+  }
+  *wrong_value = Memcmp(kdata + sizeof(uint32_t), KERNEL_SPACE_UID,
+                        KERNEL_SPACE_UID_SIZE);     /* for later use */
+  exists = result == TPM_SUCCESS;
+  if (RBTS.KERNEL_VERSIONS_exists && !exists) {
+    RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
+                                      TPM_NV_PER_PPWRITE, KERNEL_SPACE_SIZE));
+  }
+  if (!RBTS.KERNEL_VERSIONS_exists && exists) {
+    RETURN_ON_FAILURE(RollbackTest_RemoveSpace(KERNEL_VERSIONS_NV_INDEX));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Adjusts permissions of KERNEL_VERSIONS space.  Updates |wrong_value| to
+ * reflect that currently the space contains the wrong value (i.e. does not
+ * contain the GRWL identifier).
+ */
+static uint32_t RollbackTest_AdjustKernelPermissions(int* wrong_value) {
+  uint32_t perms;
+
+  /* Wrong permissions */
+  RETURN_ON_FAILURE(TlclGetPermissions(KERNEL_VERSIONS_NV_INDEX, &perms));
+  if (RBTS.KERNEL_VERSIONS_wrong_permissions && perms == TPM_NV_PER_PPWRITE) {
+    /* Redefines with wrong permissions. */
+    RETURN_ON_FAILURE(RollbackTest_RemoveSpace(KERNEL_VERSIONS_NV_INDEX));
+    RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
+                                      TPM_NV_PER_PPWRITE |
+                                      TPM_NV_PER_GLOBALLOCK,
+                                      KERNEL_SPACE_SIZE));
+    *wrong_value = 1;
+  }
+  if (!RBTS.KERNEL_VERSIONS_wrong_permissions &&
+      perms != TPM_NV_PER_PPWRITE) {
+    /* Redefines with right permissions. */
+    RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
+                                      TPM_NV_PER_PPWRITE, 0));
+    RETURN_ON_FAILURE(TlclDefineSpace(KERNEL_VERSIONS_NV_INDEX,
+                                      TPM_NV_PER_PPWRITE,
+                                      KERNEL_SPACE_SIZE));
+    *wrong_value = 1;
+  }
+  return TPM_SUCCESS;
+}
+
+static uint32_t RollbackTest_AdjustKernelValue(int wrong_value) {
+  if (!RBTS.KERNEL_VERSIONS_wrong_value && wrong_value) {
+    RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
+                                KERNEL_SPACE_INIT_DATA, KERNEL_SPACE_SIZE));
+  }
+  if (RBTS.KERNEL_VERSIONS_wrong_value && !wrong_value) {
+    RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_NV_INDEX,
+                                (uint8_t*) "mickey mouse",
+                                KERNEL_SPACE_SIZE));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Adjusts value of KERNEL_VERSIONS_BACKUP space.
+ */
+static uint32_t RollbackTest_AdjustKernelBackup(void) {
+  /* Same as backup */
+  uint32_t kv, kvbackup;
+  RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_NV_INDEX,
+                             (uint8_t*) &kv, sizeof(kv)));
+  RETURN_ON_FAILURE(TlclRead(KERNEL_VERSIONS_BACKUP_NV_INDEX,
+                             (uint8_t*) &kvbackup, sizeof(kvbackup)));
+  if (RBTS.KERNEL_VERSIONS_same_as_backup && kv != kvbackup) {
+    kvbackup = kv;
+    RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
+                                (uint8_t*) &kvbackup, sizeof(kvbackup)));
+  }
+  if (!RBTS.KERNEL_VERSIONS_same_as_backup && kv == kvbackup) {
+    kvbackup = kv + 1;
+    RETURN_ON_FAILURE(TlclWrite(KERNEL_VERSIONS_BACKUP_NV_INDEX,
+                                (uint8_t*) &kvbackup, sizeof(kvbackup)));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Adjust the value in the developer mode transition space.
+ */
+static uint32_t RollbackTest_AdjustDeveloperMode(void) {
+  uint32_t dev;
+  /* Developer mode transitions */
+  RETURN_ON_FAILURE(TlclRead(DEVELOPER_MODE_NV_INDEX,
+                             (uint8_t*) &dev, sizeof(dev)));
+
+  if (RBTS.developer != dev) {
+    dev = RBTS.developer;
+    RETURN_ON_FAILURE(TlclWrite(DEVELOPER_MODE_NV_INDEX,
+                                (uint8_t*) &dev, sizeof(dev)));
+  }
+  return TPM_SUCCESS;
+}
+
+/* Changes the unowned write count.
+ */
+static uint32_t RollbackTest_AdjustWriteCount(void) {
+  int i;
+  if (!RBTS.owned) {
+    /* Sets the unowned write count, but only if we think that it will make a
+     * difference for the test. In other words: we're trying to reduce the
+     * number if initial states with some reasoning that we hope is correct.
+     */
+    if (RBTS.writecount > low_writecount) {
+      if (!tpm_was_just_cleared) {
+        /* Unknown write count: must clear the TPM to reset to 0 */
+        RETURN_ON_FAILURE(TPMClearAndReenable());
+      }
+      for (i = 0; i < RBTS.writecount; i++) {
+        /* Changes the value to ensure that the TPM won't optimize away
+         * writes.
+         */
+        uint8_t b = (uint8_t) i;
+        RETURN_ON_FAILURE(TlclWrite(WRITE_BUCKET_NV_INDEX, &b, 1));
+      }
+    }
+  }
+  return TPM_SUCCESS;
+}
+
+/* Sets the TPM to the right state for the next test run.
+ *
+ * Functionally correct ordering is tricky.  Optimal ordering is even trickier
+ * (no claim to this).  May succeed only partially and require a reboot to
+ * continue (if the TPM was deactivated at boot).
+ */
+static uint32_t RollbackTest_SetTPMState(int initialize) {
+  uint8_t disable, deactivated;
+  int wrong_value = 0;
+
+  /* Initializes if needed */
+  if (initialize) {
+    TlclLibInit();
+    /* Don't worry if we're already started. */
+    (void) TlclStartup();
+    RETURN_ON_FAILURE(TlclContinueSelfTest());
+    RETURN_ON_FAILURE(TlclAssertPhysicalPresence());
+  }
+
+  RETURN_ON_FAILURE(RollbackTest_PartiallyAdjustFlags(&disable, &deactivated));
+  RETURN_ON_FAILURE(RollbackTest_AdjustIsInitialized());
+  RETURN_ON_FAILURE(RollbackTest_AdjustMustUseBackup());
+  RETURN_ON_FAILURE(RollbackTest_AdjustKernelVersions(&wrong_value));
+
+  if (RBTS.KERNEL_VERSIONS_exists) {
+    /* Adjusting these states only makes sense when the kernel versions space
+     * exists. */
+    RETURN_ON_FAILURE(RollbackTest_AdjustKernelPermissions(&wrong_value));
+    RETURN_ON_FAILURE(RollbackTest_AdjustKernelValue(wrong_value));
+    RETURN_ON_FAILURE(RollbackTest_AdjustKernelBackup());
+  }
+
+  RETURN_ON_FAILURE(RollbackTest_AdjustDeveloperMode());
+  RETURN_ON_FAILURE(RollbackTest_SetOwnership(RBTS.owned));
+  /* Do not remove spaces between SetOwnership and AdjustWriteCount, as that
+   * might change the ownership state.  Also do not issue any writes from now
+   * on, because AdjustWriteCount tries to avoid unneccessary clears, and after
+   * that, any writes will obviously change the write count.
+   */
+  RETURN_ON_FAILURE(RollbackTest_AdjustWriteCount());
+
+  /* Finally, disables and/or deactivates.  Must deactivate before disabling
+   */
+  if (!deactivated && RBTS.deactivated) {
+    RETURN_ON_FAILURE(TlclSetDeactivated(1));
+  }
+  /* It's better to do this last, even though most commands we use work with
+   * the TPM disabled.
+   */
+  if (!disable && RBTS.disable) {
+    RETURN_ON_FAILURE(TlclClearEnable());
+  }
+  return TPM_SUCCESS;
+}
+
+#define ADVANCE(rbts_field, min, max) do { \
+    if (RBTS.rbts_field == max) {          \
+      RBTS.rbts_field = min;               \
+    } else {                               \
+      RBTS.rbts_field++;                   \
+      return 0;                            \
+    }                                      \
+  } while (0)
+
+#define ADVANCEB(field) ADVANCE(field, 0, 1)
+
+static int RollbackTest_AdvanceState(void) {
+  /* This is a generalized counter.  It advances an element of the RTBS
+   * structure, and when it hits its maximum value, it resets the element and
+   * moves on to the next element, similar to the way a decimal counter
+   * increases each digit from 0 to 9 and back to 0 with a carry.
+   *
+   * Tip: put the expensive state changes at the end.
+   */
+  ADVANCEB(developer);
+  ADVANCEB(recovery);
+  ADVANCEB(TPM_IS_INITIALIZED_exists);
+  ADVANCEB(KERNEL_MUST_USE_BACKUP);
+  if (RBTS.owned) {
+    ADVANCEB(KERNEL_VERSIONS_exists);
+    ADVANCEB(KERNEL_VERSIONS_wrong_permissions);
+    ADVANCEB(KERNEL_VERSIONS_wrong_value);
+    ADVANCEB(KERNEL_VERSIONS_same_as_backup);
+  }
+  ADVANCEB(DEVELOPER_MODE);
+  /* The writecount is meaningful only when the TPM is not owned. */
+  if (!RBTS.owned) {
+    ADVANCE(writecount, low_writecount, high_writecount);
+    if (RBTS.TPM_IS_INITIALIZED_exists) {
+      /* We don't have to go through the full range in this case. */
+      if (RBTS.writecount < low_writecount_when_initialized) {
+        RBTS.writecount = low_writecount_when_initialized;
+      }
+    }
+  }
+  ADVANCEB(deactivated);
+  ADVANCEB(disable);
+  ADVANCEB(owned);
+  if (RBTS.owned == 0) {
+    /* overflow */
+    return 1;
+  }
+  return 0;
+}
+
+static void RollbackTest_InitializeState(void) {
+  FILE* file = fopen(STATEPATH, "w");
+  if (file == NULL) {
+    fprintf(stderr, "could not open %s for writing\n", STATEPATH);
+    exit(1);
+  }
+  RBTS.writecount = low_writecount;
+  RollbackTest_SaveState(file);
+}
+
+uint32_t RollbackTest_Test(void) {
+  uint16_t key_version, version;
+
+  if (RBTS.recovery) {
+    if (RBTS.developer) {
+      /* Developer Recovery mode */
+      RETURN_ON_FAILURE(RollbackKernelRecovery(1));
+    } else {
+      /* Normal Recovery mode */
+      RETURN_ON_FAILURE(RollbackKernelRecovery(0));
+    }
+  } else {
+    if (RBTS.developer) {
+      /* Developer mode */
+      key_version = 0;
+      version = 0;
+      RETURN_ON_FAILURE(RollbackFirmwareSetup(1));
+      RETURN_ON_FAILURE(RollbackFirmwareLock());
+    } else {
+      /* Normal mode */
+      key_version = 0;
+      version = 0;
+      RETURN_ON_FAILURE(RollbackFirmwareSetup(0));
+      RETURN_ON_FAILURE(RollbackFirmwareLock());
+      RETURN_ON_FAILURE(RollbackKernelRead(&key_version, &version));
+      RETURN_ON_FAILURE(RollbackKernelWrite(key_version, version));
+      RETURN_ON_FAILURE(RollbackKernelLock());
+    }
+  }
+  return TPM_SUCCESS;
+}
+
+/* One-time call to create the WRITE_BUCKET space.
+ */
+static uint32_t RollbackTest_InitializeTPM(void) {
+  TlclLibInit();
+  RETURN_ON_FAILURE(TlclStartup());
+  RETURN_ON_FAILURE(TlclContinueSelfTest());
+  RETURN_ON_FAILURE(TlclAssertPhysicalPresence());
+  RETURN_ON_FAILURE(TlclDefineSpace(WRITE_BUCKET_NV_INDEX,
+                                    TPM_NV_PER_PPWRITE, 1));
+  RETURN_ON_FAILURE(RollbackTest_SetTPMState(0));
+  return TPM_SUCCESS;
+}
+
+static void RollbackTest_Initialize(void) {
+  Log("initializing");
+  RollbackTest_InitializeState();
+  if (RollbackTest_InitializeTPM() != TPM_SUCCESS) {
+    Log("couldn't initialize TPM");
+    exit(1);
+  }
+}
+
+/* Advances the desired TPM state and sets the TPM to the new state.
+ */
+static void RollbackTest_Advance(FILE* file) {
+  uint32_t result;
+  Log("advancing state");
+  if (RollbackTest_AdvanceState()) {
+    Log("done");
+    exit(0);
+  }
+  result = RollbackTest_SetTPMState(1);
+  if (result == TPM_SUCCESS) {
+    RBTS.advancing = 0;
+    RollbackTest_SaveState(file);
+    reboot();
+  } else {
+    Log("SetTPMState failed with 0x%x\n", result);
+    exit(1);
+  }
+}
+
+/* Performs the test for the current TPM state, and verify that the outcome
+ * matches the expectations.
+ */
+static void RollbackTest_RunOneTest(FILE* file) {
+  uint32_t result;
+  uint32_t expected_result = TPM_SUCCESS;
+
+  if (!RBTS.KERNEL_VERSIONS_exists ||
+      RBTS.KERNEL_VERSIONS_wrong_permissions ||
+      RBTS.KERNEL_VERSIONS_wrong_value) {
+    expected_result = TPM_E_CORRUPTED_STATE;
+  }
+
+  if (!RBTS.KERNEL_VERSIONS_exists && !RBTS.TPM_IS_INITIALIZED_exists) {
+    /* The space will be recreated */
+    expected_result = TPM_SUCCESS;
+  }
+
+  if ((!RBTS.TPM_IS_INITIALIZED_exists || !RBTS.KERNEL_VERSIONS_exists)
+      && RBTS.owned) {
+    /* Cannot create spaces without owner authorization */
+    expected_result = TPM_E_OWNER_SET;
+  }
+
+  if (RBTS.TPM_IS_INITIALIZED_exists && !RBTS.KERNEL_VERSIONS_exists) {
+    expected_result = TPM_ANY_FAILURE;
+  }
+
+  result = RollbackTest_Test();
+
+  if (result == expected_result ||
+      (result != TPM_SUCCESS && expected_result == TPM_ANY_FAILURE)) {
+    Log("test succeeded with 0x%x\n", result);
+    RBTS.advancing = 1;
+    RollbackTest_SaveState(file);
+    reboot();
+  } else {
+    Log("test failed with 0x%x, expecting 0x%x\n", result, expected_result);
+    exit(1);
+  }
+}
+
+static FILE* RollbackTest_OpenState(void) {
+  FILE* file = fopen(STATEPATH, "r+");
+  if (file == NULL) {
+    Log("%s could not be opened", STATEPATH);
+    exit(1);
+  }
+  return file;
+}
+
+/* Sync saved state with TPM state.
+ */
+static void RollbackTest_Sync(void) {
+  FILE *file = RollbackTest_OpenState();
+  uint32_t result;
+  RollbackTest_RestoreState(file);
+  Log("Syncing state");
+  result = RollbackTest_SetTPMState(1);
+  if (result != TPM_SUCCESS) {
+    Log("Sync failed with %x", result);
+    exit(1);
+  }
+}
+
+/* Runs one testing iteration and advances the testing state.
+ */
+static void RollbackTest_Run(void) {
+  FILE* file = RollbackTest_OpenState();
+  RollbackTest_RestoreState(file);
+  RollbackTest_LogState();
+  if (RBTS.advancing) {
+    RollbackTest_Advance(file);
+  } else {
+    RollbackTest_RunOneTest(file);
+  }
+}
+
+int main(int argc, char** argv) {
+
+  openlog("rbtest", LOG_CONS | LOG_PERROR, LOG_USER);
+
+  if (geteuid() != 0) {
+    fprintf(stderr, "rollback-test: must run as root\n");
+    exit(1);
+  }
+
+  if (argc == 2 && strcmp(argv[1], "initialize") == 0) {
+    RollbackTest_Initialize();
+  } else if (argc == 2 && strcmp(argv[1], "sync") == 0) {
+    RollbackTest_Sync();
+  } else if (argc == 1) {
+    RollbackTest_Run();
+  } else {
+    fprintf(stderr, "usage: rollback-test [ initialize ]\n");
+    exit(1);
+  }
+  return 0;
+}