Dev-mode only boots official kernels by default

Although we're now using a single unified BIOS, it is pretty nice to be able
to get a shell in developer mode while still using verified boot for the
kernel and filesystem. Alex & ZGB implemented this by requiring the dev-mode
user to install a special dev-mode BIOS. We don't do that, but we DO require
setting a special flag with "crossystem" to accomplish the same thing.

In order to allow booting a self-signed kernel, you must boot in developer
mode, open a shell, and run this:

  crossystem dev_boot_custom=1

Special note to internal developers: If you're in the habit (as I am) of
booting directly from a USB stick in dev-mode, you'll have to run this:

  crossystem dev_boot_custom=1 dev_boot_usb=1

Just using dev_boot_usb=1 is no longer enough, because the USB kernel is
signed using the recovery key and by pressing Ctrl-U, we validate it with
the kernel data key. That worked before this change because any self-signed
kernel was fine, and that's how the USB key was treated. Now it actually
requires a verified signature until you enable dev_boot_custom=1 also.

BUG=chrome-os-partner:5954
TEST=manual

Boot once in normal mode, which clears the special flags. Then switch to
developer mode. You should be able to boot and get a root shell.

Run

  crossystem dev_boot_usb=1

Obtain a USB recovery image that's keyed differently. For example, if you're
testing with dev-keys, use a PVT-signed image or vice-versa.

Reboot into dev-mode with the USB recovery stick inserted. At the dev-mode
screen, press Ctrl-U. You should hear a single beep, but it should not boot.

Press Ctrl-D to boot from the hard drive, log in to a shell and run

  crossystem dev_boot_custom=1

Repeat the previous test. This time when you press Ctrl-U, it should boot
the recovery image. Turn the system off before it does anything.

That's it.

Change-Id: I1811ee9a188974b3f94c83c52b00b60028b86c69
Reviewed-on: https://gerrit.chromium.org/gerrit/11442
Tested-by: Bill Richardson <wfrichar@chromium.org>
Reviewed-by: Randall Spangler <rspangler@chromium.org>
diff --git a/firmware/include/vboot_nvstorage.h b/firmware/include/vboot_nvstorage.h
index f010ddc..28a1a62 100644
--- a/firmware/include/vboot_nvstorage.h
+++ b/firmware/include/vboot_nvstorage.h
@@ -54,6 +54,8 @@
   VBNV_TEST_ERROR_NUM,
   /* Allow booting from USB in developer mode.  0=no, 1=yes. */
   VBNV_DEV_BOOT_USB,
+  /* Allow booting self-signed images in developer mode.  0=no, 1=yes. */
+  VBNV_DEV_BOOT_CUSTOM,
 } VbNvParam;
 
 
diff --git a/firmware/include/vboot_struct.h b/firmware/include/vboot_struct.h
index 795523e..3374d4c 100644
--- a/firmware/include/vboot_struct.h
+++ b/firmware/include/vboot_struct.h
@@ -234,6 +234,7 @@
 #define VBSD_LKP_CHECK_PREAMBLE_VALID     11
 #define VBSD_LKP_CHECK_BODY_ADDRESS       12
 #define VBSD_LKP_CHECK_BODY_OFFSET        13
+#define VBSD_LKP_CHECK_SELF_SIGNED        14
 #define VBSD_LKP_CHECK_BODY_EXCEEDS_MEM   15
 #define VBSD_LKP_CHECK_BODY_EXCEEDS_PART  16
 #define VBSD_LKP_CHECK_READ_DATA          17
diff --git a/firmware/lib/vboot_api_kernel.c b/firmware/lib/vboot_api_kernel.c
index a836860..76ab9ae 100644
--- a/firmware/lib/vboot_api_kernel.c
+++ b/firmware/lib/vboot_api_kernel.c
@@ -97,10 +97,11 @@
 /* Handle a normal boot. */
 VbError_t VbBootNormal(VbCommonParams* cparams, LoadKernelParams* p) {
 
-  /* Force dev_boot_usb flag disabled.  This ensures the flag will be
+  /* Force dev_boot_* flags disabled.  This ensures they will be
    * initially disabled if the user later transitions back into
    * developer mode. */
   VbNvSet(&vnc, VBNV_DEV_BOOT_USB, 0);
+  VbNvSet(&vnc, VBNV_DEV_BOOT_CUSTOM, 0);
 
   /* Boot from fixed disk only */
   return VbTryLoadKernel(cparams, p, VB_DISK_FLAG_FIXED);
diff --git a/firmware/lib/vboot_display.c b/firmware/lib/vboot_display.c
index c0df16b..a721b3d 100644
--- a/firmware/lib/vboot_display.c
+++ b/firmware/lib/vboot_display.c
@@ -521,6 +521,11 @@
   used += Strncat(buf + used, "\ndev_boot_usb: ", DEBUG_INFO_SIZE - used);
   used += Uint64ToString(buf + used, DEBUG_INFO_SIZE - used, i, 10, 0);
 
+  /* Add dev_boot_custom flag */
+  VbNvGet(vncptr, VBNV_DEV_BOOT_CUSTOM, &i);
+  used += Strncat(buf + used, "\ndev_boot_custom: ", DEBUG_INFO_SIZE - used);
+  used += Uint64ToString(buf + used, DEBUG_INFO_SIZE - used, i, 10, 0);
+
   /* Add TPM versions */
   used += Strncat(buf + used, "\nTPM: fwver=0x", DEBUG_INFO_SIZE - used);
   used += Uint64ToString(buf + used, DEBUG_INFO_SIZE - used,
diff --git a/firmware/lib/vboot_kernel.c b/firmware/lib/vboot_kernel.c
index e38c470..83c5eca 100644
--- a/firmware/lib/vboot_kernel.c
+++ b/firmware/lib/vboot_kernel.c
@@ -121,6 +121,7 @@
 /* disable MSVC warning on const logical expression (as in } while(0);) */
 __pragma(warning(disable: 4127))
 
+
 VbError_t LoadKernel(LoadKernelParams* params) {
   VbSharedDataHeader* shared = (VbSharedDataHeader*)params->shared_data_blob;
   VbSharedDataKernelCall* shcall = NULL;
@@ -139,6 +140,7 @@
   int rec_switch, dev_switch;
   BootMode boot_mode;
   uint32_t test_err = 0;
+  uint32_t allow_self_signed = 0;
 
   VbError_t retval = VBERROR_UNKNOWN;
   int recovery = VBNV_RECOVERY_RO_UNSPECIFIED;
@@ -162,12 +164,14 @@
   /* Calculate switch positions and boot mode */
   rec_switch = (BOOT_FLAG_RECOVERY & params->boot_flags ? 1 : 0);
   dev_switch = (BOOT_FLAG_DEVELOPER & params->boot_flags ? 1 : 0);
-  if (rec_switch)
+  if (rec_switch) {
     boot_mode = kBootRecovery;
-  else if (dev_switch)
+  } else if (dev_switch) {
     boot_mode = kBootDev;
-  else
+    VbNvGet(vnc, VBNV_DEV_BOOT_CUSTOM, &allow_self_signed);
+  } else {
     boot_mode = kBootNormal;
+  }
 
   /* Set up tracking for this call.  This wraps around if called many times,
    * so we need to initialize the call entry each time. */
@@ -250,7 +254,7 @@
       int key_block_valid = 1;
 
       VBDEBUG(("Found kernel entry at %" PRIu64 " size %" PRIu64 "\n",
-              part_start, part_size));
+               part_start, part_size));
 
       /* Set up tracking for this partition.  This wraps around if called
        * many times, so initialize the partition entry each time. */
@@ -293,8 +297,14 @@
         if (kBootDev != boot_mode)
           goto bad_kernel;
 
-        /* In developer mode, we can continue if the SHA-512 hash of the key
-         * block is valid. */
+        /* In developer mode, we have to explictly allow self-signed kernels */
+        if (!allow_self_signed) {
+          VBDEBUG(("Self-signed custom kernels are not enabled.\n"));
+          shpart->check_result = VBSD_LKP_CHECK_SELF_SIGNED;
+          goto bad_kernel;
+        }
+
+        /* Allow the kernel if the SHA-512 hash of the key block is valid. */
         if (0 != KeyBlockVerify(key_block, KBUF_SIZE, kernel_subkey, 1)) {
           VBDEBUG(("Verifying key block hash failed.\n"));
           shpart->check_result = VBSD_LKP_CHECK_KEY_BLOCK_HASH;
@@ -489,7 +499,7 @@
       /* Continue, so that we skip the error handling code below */
       continue;
 
-   bad_kernel:
+    bad_kernel:
       /* Handle errors parsing this kernel */
       if (NULL != data_key)
         RSAPublicKeyFree(data_key);
diff --git a/firmware/lib/vboot_nvstorage.c b/firmware/lib/vboot_nvstorage.c
index 86fafb0..75910ed 100644
--- a/firmware/lib/vboot_nvstorage.c
+++ b/firmware/lib/vboot_nvstorage.c
@@ -28,6 +28,7 @@
 
 #define DEV_FLAGS_OFFSET             4
 #define DEV_BOOT_USB_MASK               0x01
+#define DEV_BOOT_CUSTOM_MASK            0x02
 
 #define FIRMWARE_FLAGS_OFFSET        5
 #define FIRMWARE_TEST_ERR_FUNC_MASK     0x38
@@ -142,6 +143,10 @@
       *dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_USB_MASK ? 1 : 0);
       return 0;
 
+    case VBNV_DEV_BOOT_CUSTOM:
+      *dest = (raw[DEV_FLAGS_OFFSET] & DEV_BOOT_CUSTOM_MASK ? 1 : 0);
+      return 0;
+
     default:
       return 1;
   }
@@ -227,6 +232,13 @@
         raw[DEV_FLAGS_OFFSET] &= ~DEV_BOOT_USB_MASK;
       break;
 
+    case VBNV_DEV_BOOT_CUSTOM:
+      if (value)
+        raw[DEV_FLAGS_OFFSET] |= DEV_BOOT_CUSTOM_MASK;
+      else
+        raw[DEV_FLAGS_OFFSET] &= ~DEV_BOOT_CUSTOM_MASK;
+      break;
+
     default:
       return 1;
   }
diff --git a/host/lib/crossystem.c b/host/lib/crossystem.c
index ef3c6d1..5bd4c62 100644
--- a/host/lib/crossystem.c
+++ b/host/lib/crossystem.c
@@ -390,6 +390,8 @@
     value = VbGetNvStorage(VBNV_LOCALIZATION_INDEX);
   } else if (!strcasecmp(name,"dev_boot_usb")) {
     value = VbGetNvStorage(VBNV_DEV_BOOT_USB);
+  } else if (!strcasecmp(name,"dev_boot_custom")) {
+    value = VbGetNvStorage(VBNV_DEV_BOOT_CUSTOM);
   }
   /* Other parameters */
   else if (!strcasecmp(name,"cros_debug")) {
@@ -469,6 +471,8 @@
     return VbSetNvStorage(VBNV_LOCALIZATION_INDEX, value);
   } else if (!strcasecmp(name,"dev_boot_usb")) {
     return VbSetNvStorage(VBNV_DEV_BOOT_USB, value);
+  } else if (!strcasecmp(name,"dev_boot_custom")) {
+    return VbSetNvStorage(VBNV_DEV_BOOT_CUSTOM, value);
   }
 
   return -1;
diff --git a/tests/vboot_nvstorage_test.c b/tests/vboot_nvstorage_test.c
index ae42c0b..be600fa 100644
--- a/tests/vboot_nvstorage_test.c
+++ b/tests/vboot_nvstorage_test.c
@@ -32,6 +32,7 @@
   {VBNV_TEST_ERROR_FUNC, 0, 1, 7, "verified boot test error func"},
   {VBNV_TEST_ERROR_NUM, 0, 3, 6, "verified boot test error number"},
   {VBNV_DEV_BOOT_USB, 0, 1, 0, "dev boot usb"},
+  {VBNV_DEV_BOOT_CUSTOM, 0, 1, 0, "dev boot custom"},
   {0, 0, 0, 0, NULL}
 };
 
diff --git a/utility/crossystem_main.c b/utility/crossystem_main.c
index b914d62..3972073 100644
--- a/utility/crossystem_main.c
+++ b/utility/crossystem_main.c
@@ -39,6 +39,8 @@
   {"dbg_reset", CAN_WRITE, "Debug reset mode request (writable)"},
   {"dev_boot_usb", CAN_WRITE,
    "Enable developer mode boot from USB/SD (writable)"},
+  {"dev_boot_custom", CAN_WRITE,
+   "Enable developer mode boot using self-signed kernels (writable)"},
   {"devsw_boot", 0, "Developer switch position at boot"},
   {"devsw_cur",  0, "Developer switch current position"},
   {"ecfw_act", IS_STRING, "Active EC firmware"},