| 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 |
| |