CHROMIUM: md: dm-verity-chromeos: changed how kernel parititon is corrupted
authorPaul Taysom <taysom@chromium.org>
Mon, 25 Mar 2013 02:47:07 +0000 (19:47 -0700)
committerChromeBot <chrome-bot@google.com>
Wed, 27 Mar 2013 01:03:22 +0000 (18:03 -0700)
Changed the error handling to use the linux cmdline
to find the kernel partition to corrupt it so when
an error is detected in the root volume, the bios
will know to switch partitions.

BUG=chromium:222954
TEST=1. manually corrupt root partition and bootcache and reboot 2. manually corrupted root paritions with no bootcache
Signed-off-by: Paul Taysom <taysom@chromium.org>
Change-Id: I9ea8c5462d573fc8599be36b4c6eaf1f4c7eb99c
Reviewed-on: https://gerrit.chromium.org/gerrit/46375
Tested-by: Paul Taysom <taysom@chromium.org>
Reviewed-by: Will Drewry <wad@chromium.org>
Commit-Queue: Paul Taysom <taysom@chromium.org>

drivers/md/dm-verity-chromeos.c

index dceca8f..dd71e75 100644 (file)
@@ -3,8 +3,18 @@
  *                    All Rights Reserved.
  *
  * This file is released under the GPL.
- *
+ */
+/*
  * Implements a Chrome OS platform specific error handler.
+ * When verity detects an invalid block, this error handling will
+ * attempt to corrupt the kernel boot image. On reboot, the bios will
+ * detect the kernel corruption and switch to the alternate kernel
+ * and root file system partitions.
+ *
+ * Assumptions:
+ * 1. Partitions are specified on the command line using uuid.
+ * 2. The kernel partition is the partition number is one less
+ *    than the root partition number.
  */
 #include <linux/bio.h>
 #include <linux/blkdev.h>
@@ -16,6 +26,7 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/notifier.h>
+#include <linux/string.h>
 #include <asm/page.h>
 
 #include "dm-verity.h"
@@ -67,6 +78,94 @@ static int chromeos_invalidate_kernel_submit(struct bio *bio,
        return 0;
 }
 
+static char *get_info_from_cmdline(const char *key)
+{
+       const char *dev;
+       const char *end;
+       int len;
+
+       dev = strstr(saved_command_line, key);
+       if (!dev)
+               return NULL;
+       len = strlen(key);
+       dev = &dev[len];
+       end = strchr(dev, ' ');
+       return kstrndup(dev, end - dev, GFP_ATOMIC);
+}
+
+/**
+ * match_dev_by_uuid - callback for finding a partition using its uuid
+ * @dev:       device passed in by the caller
+ * @uuid_data: opaque pointer to a uuid packed by part_pack_uuid().
+ *
+ * Returns 1 if the device matches, and 0 otherwise.
+ */
+static int match_dev_by_uuid(struct device *dev, void *uuid_data)
+{
+       u8 *uuid = uuid_data;
+       struct hd_struct *part = dev_to_part(dev);
+
+       if (!part->info)
+               goto no_match;
+
+       if (memcmp(uuid, part->info->uuid, sizeof(part->info->uuid)))
+               goto no_match;
+
+       return 1;
+no_match:
+       return 0;
+}
+
+static dev_t get_boot_dev_from_root_dev(struct block_device *root_bdev)
+{
+       /* Very basic sanity checking. This should be better. */
+       if (!root_bdev || !root_bdev->bd_part ||
+           MAJOR(root_bdev->bd_dev) == 254 ||
+           root_bdev->bd_part->partno <= 1) {
+               return 0;
+       }
+       return MKDEV(MAJOR(root_bdev->bd_dev), MINOR(root_bdev->bd_dev) - 1);
+}
+
+/* get_boot_dev is bassed on dm_get_device_by_uuid in dm_bootcache. */
+static dev_t get_boot_dev(void)
+{
+       const char *uuid_str;
+       struct device *dev = NULL;
+       dev_t devt = 0;
+       u8 uuid[16];
+       size_t uuid_length;
+
+       uuid_str = get_info_from_cmdline(" kern_guid=");
+       if (!uuid_str) {
+               DMERR("Couldn't get uuid, try root dev");
+               return 0;
+       }
+       uuid_length = strlen(uuid_str);
+       if (uuid_length != 36)
+               goto bad_uuid;
+       /* Pack the requested UUID in the expected format. */
+       part_pack_uuid(uuid_str, uuid);
+
+       dev = class_find_device(&block_class, NULL, uuid, &match_dev_by_uuid);
+       if (!dev)
+               goto found_nothing;
+
+       devt = dev->devt;
+       put_device(dev);
+
+       return devt;
+
+bad_uuid:
+       kfree(uuid_str);
+       DMDEBUG("Supplied value '%s' is an invalid UUID", uuid_str);
+       return 0;
+found_nothing:
+       kfree(uuid_str);
+       DMDEBUG("No matching partition for GUID: %s", uuid_str);
+       return 0;
+}
+
 /* Replaces the first 8 bytes of a partition with DMVERROR */
 static int chromeos_invalidate_kernel(struct block_device *root_bdev)
 {
@@ -74,30 +173,26 @@ static int chromeos_invalidate_kernel(struct block_device *root_bdev)
        struct block_device *bdev;
        struct bio *bio;
        struct page *page;
-       int partno = root_bdev->bd_part->partno - 1;
-       dev_t kdev = MKDEV(0, 0);
+       dev_t devt;
        fmode_t dev_mode;
        /* Ensure we do synchronous unblocked I/O. We may also need
         * sync_bdev() on completion, but it really shouldn't.
         */
        int rw = REQ_SYNC | REQ_SOFTBARRIER | REQ_NOIDLE;
 
-       /* Very basic sanity checking. This should be better. */
-       if (!root_bdev || !root_bdev->bd_part ||
-           root_bdev->bd_part->partno <= 1) {
-               DMERR("invalidate_kernel: partition layout unexpected");
-               return -EINVAL;
+       devt = get_boot_dev_from_root_dev(root_bdev);
+       if (!devt) {
+               devt = get_boot_dev();
+               if (!devt)
+                       return -EINVAL;
        }
-       kdev = MKDEV(MAJOR(root_bdev->bd_dev), MINOR(root_bdev->bd_dev) - 1);
-
-       DMERR("Attempting to invalidate kernel (part:%d,devt:%d)",
-             partno, kdev);
 
        /* First we open the device for reading. */
        dev_mode = FMODE_READ | FMODE_EXCL;
-       bdev = blkdev_get_by_dev(kdev, dev_mode, chromeos_invalidate_kernel);
+       bdev = blkdev_get_by_dev(devt, dev_mode, chromeos_invalidate_kernel);
        if (IS_ERR(bdev)) {
                DMERR("invalidate_kernel: could not open device for reading");
+               dev_mode = 0;
                ret = -1;
                goto failed_to_read;
        }
@@ -134,7 +229,7 @@ static int chromeos_invalidate_kernel(struct block_device *root_bdev)
        /* The block dev was being changed on read. Let's reopen here. */
        blkdev_put(bdev, dev_mode);
        dev_mode = FMODE_WRITE | FMODE_EXCL;
-       bdev = blkdev_get_by_dev(kdev, dev_mode, chromeos_invalidate_kernel);
+       bdev = blkdev_get_by_dev(devt, dev_mode, chromeos_invalidate_kernel);
        if (IS_ERR(bdev)) {
                DMERR("invalidate_kernel: could not open device for reading");
                dev_mode = 0;