blob: c01943a21ecb220233d8b49b8db2192ec8df9a15 [file] [log] [blame]
From 3d690710b77e5fca15ed30557fdbb2ae1409dfe6 Mon Sep 17 00:00:00 2001
From: Sonny Rao <sonnyrao@chromium.org>
Date: Wed, 15 May 2013 13:45:48 -0700
Subject: [PATCH] CHROMIUM: verity: bring over dm-verity-chromeos.c
I dropped this as part of the 3.8 re-base thinking that it wasn't
necessary in upstream verity, but that wasn't correct. Bring over the
version from current chromeos-3.4, and fix up the UUID matching code.
BUG=chromium:238920
TEST=platform_CorruptRootfs test passes
Change-Id: I4817f6ba6e0eea2926b58b652797b2147fee6167
Signed-off-by: Sonny Rao <sonnyrao@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/51363
Reviewed-by: Paul Taysom <taysom@chromium.org>
Conflicts:
drivers/md/Makefile
[filbranden: 3.18 rebase. Solved trivial conflicts.]
Signed-off-by: Filipe Brandenburger <filbranden@chromium.org>
Conflicts:
drivers/md/Makefile
[rebase44(groeck): Resolved trivial conflicts;
Adjusted for upstream API changes (bi_end_io no longer has an 'err'
parameter, and BIO_EOPNOTSUPP / BIO_UPTODATE flags have been
dropped)
Squashed commits:
'CHROMIUM: md: verity: Make DM_VERITY_CHROMEOS tristate'
'CHROMIUM: Fixup verity_chromeos and bootcache for 3.14'
'CHROMIUM: md: dm-bootcache, dm-verity, dm-verity-chromeps -
support for PARTUUID'
'verity: Add error path for ubiblock-backed dm-verity'
'CHROMIUM: md: verity-chromeos: get_boot_dev by UUID before
rootdev'
'CHROMIUM: md: verity-chromeos: Use module_param_string()'
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Conflicts:
drivers/md/Makefile
init/do_mounts_dm.c
[rebase412(groeck): Resolved conflicts;
adjusted for core API changes to submit_bio();
changed request/op flag handling]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Conflicts:
drivers/md/dm-verity-chromeos.c
[rebase414(teravest): fix for removed bio->b_dev, bi_error now bi_status]
Signed-off-by: Justin TerAvest <teravest@chromium.org>
Conflicts:
drivers/md/Makefile
[rebase419(groeck):
Fix context conflicts
mtd_erase is now synchronous
Use #if IS_REACHABLE(CONFIG_MTD) instead of #ifdef
Instead of calling disk_name(), access the disk name directly.
]
Signed-off-by: Guenter Roeck <groeck@chromium.org>
Change-Id: Ie290f36f5f6e51f76e1be788169358206b2bdb96
---
drivers/md/Kconfig | 13 ++
drivers/md/Makefile | 1 +
drivers/md/dm-verity-chromeos.c | 367 ++++++++++++++++++++++++++++++++
3 files changed, 381 insertions(+)
create mode 100644 drivers/md/dm-verity-chromeos.c
diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig
index f2014385d48b..bdfd5407362d 100644
--- a/drivers/md/Kconfig
+++ b/drivers/md/Kconfig
@@ -287,6 +287,19 @@ config DM_CRYPT
If unsure, say N.
+config DM_VERITY_CHROMEOS
+ tristate "Support Chrome OS specific verity error behavior"
+ depends on DM_VERITY
+ help
+ Enables Chrome OS platform-specific error behavior. In particular,
+ it will modify the partition preceding the verified block device
+ when non-transient error occurs (followed by a panic).
+
+ This module relies on linux/chromeos_platform.h and will behave
+ reasonably if it only supplies the stubs.
+
+ If unsure, say N.
+
config DM_SNAPSHOT
tristate "Snapshot target"
depends on BLK_DEV_DM
diff --git a/drivers/md/Makefile b/drivers/md/Makefile
index ef7ddc27685c..b2fd99975d2b 100644
--- a/drivers/md/Makefile
+++ b/drivers/md/Makefile
@@ -83,6 +83,7 @@ obj-$(CONFIG_DM_LOG_WRITES) += dm-log-writes.o
obj-$(CONFIG_DM_INTEGRITY) += dm-integrity.o
obj-$(CONFIG_DM_ZONED) += dm-zoned.o
obj-$(CONFIG_DM_WRITECACHE) += dm-writecache.o
+obj-$(CONFIG_DM_VERITY_CHROMEOS) += dm-verity-chromeos.o
ifeq ($(CONFIG_DM_INIT),y)
dm-mod-objs += dm-init.o
diff --git a/drivers/md/dm-verity-chromeos.c b/drivers/md/dm-verity-chromeos.c
new file mode 100644
index 000000000000..fbc5810f8d6e
--- /dev/null
+++ b/drivers/md/dm-verity-chromeos.c
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2010 The Chromium OS Authors <chromium-os-dev@chromium.org>
+ * 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>
+#include <linux/chromeos_platform.h>
+#include <linux/device.h>
+#include <linux/device-mapper.h>
+#include <linux/err.h>
+#include <linux/genhd.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mount.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/ubi.h>
+#include <linux/notifier.h>
+#include <linux/string.h>
+#include <asm/page.h>
+
+#include "dm-verity.h"
+
+#define DM_MSG_PREFIX "verity-chromeos"
+#define DMVERROR "DMVERROR"
+
+static void chromeos_invalidate_kernel_endio(struct bio *bio)
+{
+ if (bio->bi_status)
+ chromeos_set_need_recovery();
+
+ complete(bio->bi_private);
+}
+
+static int chromeos_invalidate_kernel_submit(struct bio *bio,
+ struct block_device *bdev,
+ int flags, int op,
+ struct page *page)
+{
+ DECLARE_COMPLETION_ONSTACK(wait);
+
+ bio->bi_private = &wait;
+ bio->bi_end_io = chromeos_invalidate_kernel_endio;
+ bio_set_dev(bio, bdev);
+
+ bio->bi_iter.bi_sector = 0;
+ bio->bi_vcnt = 1;
+ bio->bi_iter.bi_idx = 0;
+ bio->bi_iter.bi_size = 512;
+ bio->bi_iter.bi_bvec_done = 0;
+ bio_set_op_attrs(bio, op, flags);
+ bio->bi_io_vec[0].bv_page = page;
+ bio->bi_io_vec[0].bv_len = 512;
+ bio->bi_io_vec[0].bv_offset = 0;
+
+ submit_bio(bio);
+ /* Wait up to 2 seconds for completion or fail. */
+ if (!wait_for_completion_timeout(&wait, msecs_to_jiffies(2000)))
+ return -1;
+ 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);
+}
+
+static char kern_guid[48];
+
+/* get_boot_dev is bassed on dm_get_device_by_uuid in dm_bootcache. */
+static dev_t get_boot_dev(void)
+{
+ const char partuuid[] = "PARTUUID=";
+ char uuid[sizeof(partuuid) + 36];
+ char *uuid_str;
+ dev_t devt = 0;
+
+ if (!strlen(kern_guid)) {
+ DMERR("Couldn't get uuid, try root dev");
+ return 0;
+ }
+
+ if (strncmp(kern_guid, partuuid, strlen(partuuid))) {
+ /* Not prefixed with "PARTUUID=", so add it */
+ strcpy(uuid, partuuid);
+ strlcat(uuid, kern_guid, sizeof(uuid));
+ uuid_str = uuid;
+ } else {
+ uuid_str = kern_guid;
+ }
+ devt = name_to_dev_t(uuid_str);
+ if (!devt)
+ goto found_nothing;
+ return devt;
+
+found_nothing:
+ 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_bio(struct block_device *root_bdev)
+{
+ int ret = 0;
+ struct block_device *bdev;
+ struct bio *bio;
+ struct page *page;
+ dev_t devt;
+ fmode_t dev_mode;
+
+ devt = get_boot_dev();
+ if (!devt) {
+ devt = get_boot_dev_from_root_dev(root_bdev);
+ if (!devt)
+ return -EINVAL;
+ }
+
+ /* First we open the device for reading. */
+ dev_mode = FMODE_READ | FMODE_EXCL;
+ bdev = blkdev_get_by_dev(devt, dev_mode,
+ chromeos_invalidate_kernel_bio);
+ if (IS_ERR(bdev)) {
+ DMERR("invalidate_kernel: could not open device for reading");
+ dev_mode = 0;
+ ret = -1;
+ goto failed_to_read;
+ }
+
+ bio = bio_alloc(GFP_NOIO, 1);
+ if (!bio) {
+ ret = -1;
+ goto failed_bio_alloc;
+ }
+
+ page = alloc_page(GFP_NOIO);
+ if (!page) {
+ ret = -ENOMEM;
+ goto failed_to_alloc_page;
+ }
+
+ if (chromeos_invalidate_kernel_submit(bio, bdev, RQF_SOFTBARRIER,
+ REQ_OP_READ | REQ_SYNC, page)) {
+ ret = -1;
+ goto failed_to_submit_read;
+ }
+
+ /* We have a page. Let's make sure it looks right. */
+ if (memcmp("CHROMEOS", page_address(page), 8)) {
+ DMERR("invalidate_kernel called on non-kernel partition");
+ ret = -EINVAL;
+ goto invalid_header;
+ } else {
+ DMERR("invalidate_kernel: found CHROMEOS kernel partition");
+ }
+
+ /* Stamp it and rewrite */
+ memcpy(page_address(page), DMVERROR, strlen(DMVERROR));
+
+ /* 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(devt, dev_mode,
+ chromeos_invalidate_kernel_bio);
+ if (IS_ERR(bdev)) {
+ DMERR("invalidate_kernel: could not open device for reading");
+ dev_mode = 0;
+ ret = -1;
+ goto failed_to_write;
+ }
+
+ /* We re-use the same bio to do the write after the read. Need to reset
+ * it to initialize bio->bi_remaining.
+ */
+ bio_reset(bio);
+
+ if (chromeos_invalidate_kernel_submit(bio, bdev, RQF_SOFTBARRIER,
+ REQ_OP_WRITE | REQ_SYNC, page)) {
+ ret = -1;
+ goto failed_to_submit_write;
+ }
+
+ DMERR("invalidate_kernel: completed.");
+ ret = 0;
+failed_to_submit_write:
+failed_to_write:
+invalid_header:
+ __free_page(page);
+failed_to_submit_read:
+ /* Technically, we'll leak a page with the pending bio, but
+ * we're about to panic so it's safer to do the panic() we expect.
+ */
+failed_to_alloc_page:
+ bio_put(bio);
+failed_bio_alloc:
+ if (dev_mode)
+ blkdev_put(bdev, dev_mode);
+failed_to_read:
+ return ret;
+}
+
+#if IS_REACHABLE(CONFIG_MTD)
+
+/* The maximum number of volumes per one UBI device, from ubi-media.h */
+#define UBI_MAX_VOLUMES 128
+
+static int chromeos_invalidate_kernel_nand(struct block_device *root_bdev)
+{
+ struct erase_info instr;
+ int ret;
+ int partnum;
+ struct mtd_info *dev;
+ loff_t offset;
+ size_t retlen;
+ char *page = NULL;
+
+ /* TODO(dehrenberg): replace translation of the ubiblock device
+ * number with using kern_guid= once mtd UUIDs are worked out. */
+ partnum = MINOR(root_bdev->bd_dev) / UBI_MAX_VOLUMES - 1;
+ DMDEBUG("Invalidating kernel on MTD partition %d", partnum);
+ dev = get_mtd_device(NULL, partnum);
+ if (IS_ERR(dev))
+ return PTR_ERR(dev);
+ /* Find the first good block to erase. Erasing a bad block might
+ * not cause a verification failure on the next boot. */
+ for (offset = 0; offset < dev->size; offset += dev->erasesize) {
+ if (!mtd_block_isbad(dev, offset))
+ goto good;
+ }
+ /* No good blocks in the kernel; shouldn't happen, so to recovery */
+ ret = -ERANGE;
+ goto out;
+good:
+ /* Erase the first good block of the kernel. This will prevent
+ * that kernel from booting. */
+ memset(&instr, 0, sizeof(instr));
+ instr.addr = offset;
+ instr.len = dev->erasesize;
+ ret = mtd_erase(dev, &instr);
+ if (ret)
+ goto out;
+ /* Write DMVERROR on the first page. If this fails, still return
+ * success since we will still be causing the kernel to not be
+ * selected, so no need to put the device in recovery mode. */
+ page = kzalloc(dev->writesize, GFP_KERNEL);
+ if (!page) {
+ ret = 0;
+ goto out;
+ }
+ memcpy(page, DMVERROR, strlen(DMVERROR));
+ mtd_write(dev, offset, dev->writesize, &retlen, page);
+ ret = 0;
+ /* Ignore return value; no action is taken if dead */
+out:
+ kfree(page);
+ put_mtd_device(dev);
+ return ret;
+}
+
+#endif /* CONFIG_MTD */
+
+/*
+ * Invalidate the kernel which corresponds to the root block device.
+ *
+ * This function stamps DMVERROR on the beginning of the kernel partition.
+ * If the device is operating on raw NAND:
+ * Raw NAND mode is identified by checking if the underlying block device
+ * is an ubiblock device.
+ * The kernel partition is found by subtracting 1 from the mtd partition
+ * underlying the ubiblock device.
+ * The first erase block of the NAND is erased and "DMVERROR" is written
+ * in its place.
+ * Otherwise, in the normal eMMC/SATA case:
+ * The kernel partition is attempted to be found by subtracting 1 from
+ * the root partition.
+ * If that fails, then the kernel_guid commandline parameter is used to
+ * find the kernel partition number.
+ * The DMVERROR string is stamped over only the CHROMEOS string at the
+ * beginning of the kernel blob, leaving the rest of it intact.
+ */
+static int chromeos_invalidate_kernel(struct block_device *root_bdev)
+{
+#if IS_REACHABLE(CONFIG_MTD)
+ if (root_bdev && root_bdev->bd_disk) {
+ if (!(strncmp(root_bdev->bd_disk->disk_name, "ubiblock",
+ strlen("ubiblock"))))
+ return chromeos_invalidate_kernel_nand(root_bdev);
+ }
+#endif
+
+ return chromeos_invalidate_kernel_bio(root_bdev);
+}
+
+static int error_handler(struct notifier_block *nb, unsigned long transient,
+ void *opaque_err)
+{
+ struct dm_verity_error_state *err =
+ (struct dm_verity_error_state *) opaque_err;
+ err->behavior = DM_VERITY_ERROR_BEHAVIOR_PANIC;
+ if (transient)
+ return 0;
+
+ /* TODO(wad) Implement phase 2:
+ * - Attempt to read the dev_status_offset from the hash dev.
+ * - If the status offset is 0, replace the first byte of the sector
+ * with 01 and panic().
+ * - If the status offset is not 0, invalidate the associated kernel
+ * partition, then reboot.
+ * - make user space tools clear the last sector
+ */
+ if (chromeos_invalidate_kernel(err->dev))
+ chromeos_set_need_recovery();
+ return 0;
+}
+
+static struct notifier_block chromeos_nb = {
+ .notifier_call = &error_handler,
+ .next = NULL,
+ .priority = 1,
+};
+
+static int __init dm_verity_chromeos_init(void)
+{
+ int r;
+
+ r = dm_verity_register_error_notifier(&chromeos_nb);
+ if (r < 0)
+ DMERR("failed to register handler: %d", r);
+ else
+ DMINFO("dm-verity-chromeos registered");
+ return r;
+}
+
+static void __exit dm_verity_chromeos_exit(void)
+{
+ dm_verity_unregister_error_notifier(&chromeos_nb);
+}
+
+module_init(dm_verity_chromeos_init);
+module_exit(dm_verity_chromeos_exit);
+
+MODULE_AUTHOR("Will Drewry <wad@chromium.org>");
+MODULE_DESCRIPTION("chromeos-specific error handler for dm-verity");
+MODULE_LICENSE("GPL");
+
+/* Declare parameter with no module prefix */
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX ""
+module_param_string(kern_guid, kern_guid, sizeof(kern_guid), 0);
--
2.17.1