CHROMIUM: md: dm-verity fixes chromeos needs
[cascardo/linux.git] / drivers / md / dm-verity.c
index 68bf5c3..5bdce4a 100644 (file)
 #include "dm-bufio.h"
 
 #include <linux/module.h>
+#include <linux/async.h>
+#include <linux/delay.h>
 #include <linux/device-mapper.h>
 #include <crypto/hash.h>
+#include "dm-verity.h"
 
 #define DM_MSG_PREFIX                  "verity"
 
@@ -27,6 +30,7 @@
 #define DM_VERITY_DEFAULT_PREFETCH_SIZE        262144
 
 #define DM_VERITY_MAX_LEVELS           63
+#define DM_VERITY_NUM_POSITIONAL_ARGS  10
 
 static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE;
 
@@ -54,6 +58,7 @@ struct dm_verity {
        unsigned digest_size;   /* digest size for the current hash algorithm */
        unsigned shash_descsize;/* the size of temporary space for crypto */
        int hash_failed;        /* set to 1 if hash of any block failed */
+       int error_behavior;     /* selects error behavior on io erros */
 
        mempool_t *io_mempool;  /* mempool of struct dm_verity_io */
        mempool_t *vec_mempool; /* mempool of bio vector */
@@ -95,6 +100,153 @@ struct dm_verity_io {
         */
 };
 
+/* Provide a lightweight means of specifying the global default for
+ * error behavior: eio, reboot, or none
+ * Legacy support for 0 = eio, 1 = reboot/panic, 2 = none, 3 = notify.
+ * This is matched to the enum in dm-verity.h.
+ */
+static const char *allowed_error_behaviors[] = { "eio", "panic", "none",
+                                                "notify", NULL };
+static char *error_behavior = "eio";
+module_param(error_behavior, charp, 0644);
+MODULE_PARM_DESC(error_behavior, "Behavior on error "
+                                "(eio, panic, none, notify)");
+
+/* Controls whether verity_get_device will wait forever for a device. */
+static int dev_wait;
+module_param(dev_wait, int, 0444);
+MODULE_PARM_DESC(dev_wait, "Wait forever for a backing device");
+
+static BLOCKING_NOTIFIER_HEAD(verity_error_notifier);
+
+int dm_verity_register_error_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_register(&verity_error_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(dm_verity_register_error_notifier);
+
+int dm_verity_unregister_error_notifier(struct notifier_block *nb)
+{
+       return blocking_notifier_chain_unregister(&verity_error_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(dm_verity_unregister_error_notifier);
+
+/* If the request is not successful, this handler takes action.
+ * TODO make this call a registered handler.
+ */
+static void verity_error(struct dm_verity *v, struct dm_verity_io *io,
+                        int error)
+{
+       const char *message;
+       int error_behavior = DM_VERITY_ERROR_BEHAVIOR_PANIC;
+       dev_t devt = 0;
+       u64 block = ~0;
+       int transient = 1;
+       struct dm_verity_error_state error_state;
+
+       if (v) {
+               devt = v->data_dev->bdev->bd_dev;
+               error_behavior = v->error_behavior;
+       }
+
+       if (io) {
+               block = io->block;
+       }
+
+       switch (error) {
+       case -ENOMEM:
+               message = "out of memory";
+               break;
+       case -EBUSY:
+               message = "pending data seen during verify";
+               break;
+       case -EFAULT:
+               message = "crypto operation failure";
+               break;
+       case -EACCES:
+               message = "integrity failure";
+               /* Image is bad. */
+               transient = 0;
+               break;
+       case -EPERM:
+               message = "hash tree population failure";
+               /* Should be dm-bht specific errors */
+               transient = 0;
+               break;
+       case -EINVAL:
+               message = "unexpected missing/invalid data";
+               /* The device was configured incorrectly - fallback. */
+               transient = 0;
+               break;
+       default:
+               /* Other errors can be passed through as IO errors */
+               message = "unknown or I/O error";
+               return;
+       }
+
+       DMERR_LIMIT("verification failure occurred: %s", message);
+
+       if (error_behavior == DM_VERITY_ERROR_BEHAVIOR_NOTIFY) {
+               error_state.code = error;
+               error_state.transient = transient;
+               error_state.block = block;
+               error_state.message = message;
+               error_state.dev_start = v->data_start;
+               error_state.dev_len = v->data_blocks;
+               error_state.dev = v->data_dev->bdev;
+               error_state.hash_dev_start = v->hash_start;
+               error_state.hash_dev_len = v->hash_blocks;
+               error_state.hash_dev = v->hash_dev->bdev;
+
+               /* Set default fallthrough behavior. */
+               error_state.behavior = DM_VERITY_ERROR_BEHAVIOR_PANIC;
+               error_behavior = DM_VERITY_ERROR_BEHAVIOR_PANIC;
+
+               if (!blocking_notifier_call_chain(
+                   &verity_error_notifier, transient, &error_state)) {
+                       error_behavior = error_state.behavior;
+               }
+       }
+
+       switch (error_behavior) {
+       case DM_VERITY_ERROR_BEHAVIOR_EIO:
+               break;
+       case DM_VERITY_ERROR_BEHAVIOR_NONE:
+               break;
+       default:
+               goto do_panic;
+       }
+       return;
+
+do_panic:
+       panic("dm-verity failure: "
+             "device:%u:%u error:%d block:%llu message:%s",
+             MAJOR(devt), MINOR(devt), error, (u64)block, message);
+}
+
+/**
+ * verity_parse_error_behavior - parse a behavior charp to the enum
+ * @behavior:  NUL-terminated char array
+ *
+ * Checks if the behavior is valid either as text or as an index digit
+ * and returns the proper enum value or -1 on error.
+ */
+static int verity_parse_error_behavior(const char *behavior)
+{
+       const char **allowed = allowed_error_behaviors;
+       char index = '0';
+
+       for (; *allowed; allowed++, index++)
+               if (!strcmp(*allowed, behavior) || behavior[0] == index)
+                       break;
+
+       if (!*allowed)
+               return -1;
+
+       /* Convert to the integer index matching the enum. */
+       return allowed - allowed_error_behaviors;
+}
+
 static struct shash_desc *io_hash_desc(struct dm_verity *v, struct dm_verity_io *io)
 {
        return (struct shash_desc *)(io + 1);
@@ -415,6 +567,7 @@ static void verity_end_io(struct bio *bio, int error)
        struct dm_verity_io *io = bio->bi_private;
 
        if (error) {
+               verity_error(io->v, io, error);
                verity_finish_io(io, error);
                return;
        }
@@ -483,8 +636,10 @@ static int verity_map(struct dm_target *ti, struct bio *bio,
                return -EIO;
        }
 
-       if (bio_data_dir(bio) == WRITE)
+       if (bio_data_dir(bio) == WRITE) {
+               verity_error(v, NULL, -EIO);
                return -EIO;
+       }
 
        io = mempool_alloc(v->io_mempool, GFP_NOIO);
        io->v = v;
@@ -633,6 +788,289 @@ static void verity_dtr(struct dm_target *ti)
        kfree(v);
 }
 
+/**
+ * match_dev_by_uuid - callback for finding a partition using its uuid
+ * @dev:       device passed in by the caller
+ * @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 *data)
+{
+       u8 *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;
+}
+
+/**
+ * dm_get_device_by_uuid: claim a device using its UUID
+ * @ti:                        current dm_target
+ * @uuid_string:       36 byte UUID hex encoded
+ *                     (xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx)
+ * @dm_dev:            dm_dev to populate
+ *
+ * Wraps dm_get_device allowing it to use a unique partition id to
+ * find a given partition on any drive. This code is based on
+ * printk_all_partitions in that it walks all of the register block devices.
+ *
+ * N.B., uuid_string is not checked for safety just strlen().
+ */
+static int dm_get_device_by_uuid(struct dm_target *ti, const char *uuid_str,
+                            struct dm_dev **dm_dev)
+{
+       struct device *dev = NULL;
+       dev_t devt = 0;
+       char devt_buf[BDEVT_SIZE];
+       u8 uuid[16];
+       size_t 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);
+
+       /* The caller may specify +/-%u after the UUID if they want a partition
+        * before or after the one identified.
+        */
+       if (uuid_length > 36) {
+               unsigned int part_offset;
+               char sign;
+               unsigned minor = MINOR(devt);
+               if (sscanf(uuid_str + 36, "%c%u", &sign, &part_offset) == 2) {
+                       if (sign == '+') {
+                               minor += part_offset;
+                       } else if (sign == '-') {
+                               minor -= part_offset;
+                       } else {
+                               DMWARN("Trailing characters after UUID: %s\n",
+                                       uuid_str);
+                       }
+                       devt = MKDEV(MAJOR(devt), minor);
+               }
+       }
+
+       /* Construct the dev name to pass to dm_get_device.  dm_get_device
+        * doesn't support being passed a dev_t.
+        */
+       snprintf(devt_buf, sizeof(devt_buf), "%u:%u", MAJOR(devt), MINOR(devt));
+
+       /* TODO(wad) to make this generic we could also pass in the mode. */
+       if (!dm_get_device(ti, devt_buf, dm_table_get_mode(ti->table), dm_dev))
+               return 0;
+
+       ti->error = "Failed to acquire device";
+       DMDEBUG("Failed to acquire discovered device %s", devt_buf);
+       return -1;
+bad_uuid:
+       ti->error = "Bad UUID";
+       DMDEBUG("Supplied value '%s' is an invalid UUID", uuid_str);
+       return -1;
+found_nothing:
+       DMDEBUG("No matching partition for GUID: %s", uuid_str);
+       ti->error = "No matching GUID";
+       return -1;
+}
+
+static int verity_get_device(struct dm_target *ti, const char *devname,
+                            struct dm_dev **dm_dev)
+{
+       do {
+               /* Try the normal path first since if everything is ready, it
+                * will be the fastest.
+                */
+               if (!dm_get_device(ti, devname, /*FMODE_READ*/
+                                  dm_table_get_mode(ti->table), dm_dev))
+                       return 0;
+
+               /* Try the device by partition UUID */
+               if (!dm_get_device_by_uuid(ti, devname, dm_dev))
+                       return 0;
+
+               /* No need to be too aggressive since this is a slow path. */
+               msleep(500);
+       } while (dev_wait && (driver_probe_done() != 0 || *dm_dev == NULL));
+       async_synchronize_full();
+       return -1;
+}
+
+struct verity_args {
+       int version;
+       char *data_device;
+       char *hash_device;
+       int data_block_size_bits;
+       int hash_block_size_bits;
+       u64 num_data_blocks;
+       u64 hash_start_block;
+       char *algorithm;
+       char *digest;
+       char *salt;
+       char *error_behavior;
+};
+
+static void pr_args(struct verity_args *args)
+{
+       printk(KERN_INFO "VERITY args: version=%d data_device=%s hash_device=%s"
+               " data_block_size_bits=%d hash_block_size_bits=%d"
+               " num_data_blocks=%lld hash_start_block=%lld"
+               " algorithm=%s digest=%s salt=%s\n",
+               args->version,
+               args->data_device,
+               args->hash_device,
+               args->data_block_size_bits,
+               args->hash_block_size_bits,
+               args->num_data_blocks,
+               args->hash_start_block,
+               args->algorithm,
+               args->digest,
+               args->salt);
+}
+
+/*
+ * positional_args - collects the argments using the positional
+ * parameters.
+ * arg# - parameter
+ *    0 - version
+ *    1 - data device
+ *    2 - hash device - may be same as data device
+ *    3 - data block size log2
+ *    4 - hash block size log2
+ *    5 - number of data blocks
+ *    6 - hash start block
+ *    7 - algorithm
+ *    8 - digest
+ *    9 - salt
+ */
+static char *positional_args(unsigned argc, char **argv,
+                               struct verity_args *args)
+{
+       unsigned num;
+       unsigned long long num_ll;
+       char dummy;
+
+       if (argc != DM_VERITY_NUM_POSITIONAL_ARGS)
+               return "Invalid argument count: exactly 10 arguments required";
+
+       if (sscanf(argv[0], "%d%c", &num, &dummy) != 1 ||
+           num < 0 || num > 1)
+               return "Invalid version";
+       args->version = num;
+
+       args->data_device = argv[1];
+       args->hash_device = argv[2];
+
+
+       if (sscanf(argv[3], "%u%c", &num, &dummy) != 1 ||
+           !num || (num & (num - 1)) ||
+           num > PAGE_SIZE)
+               return "Invalid data device block size";
+       args->data_block_size_bits = ffs(num) - 1;
+
+       if (sscanf(argv[4], "%u%c", &num, &dummy) != 1 ||
+           !num || (num & (num - 1)) ||
+           num > INT_MAX)
+               return "Invalid hash device block size";
+       args->hash_block_size_bits = ffs(num) - 1;
+
+       if (sscanf(argv[5], "%llu%c", &num_ll, &dummy) != 1 ||
+           (sector_t)(num_ll << (args->data_block_size_bits - SECTOR_SHIFT))
+           >> (args->data_block_size_bits - SECTOR_SHIFT) != num_ll)
+               return "Invalid data blocks";
+       args->num_data_blocks = num_ll;
+
+
+       if (sscanf(argv[6], "%llu%c", &num_ll, &dummy) != 1 ||
+           (sector_t)(num_ll << (args->hash_block_size_bits - SECTOR_SHIFT))
+           >> (args->hash_block_size_bits - SECTOR_SHIFT) != num_ll)
+               return "Invalid hash start";
+       args->hash_start_block = num_ll;
+
+
+       args->algorithm = argv[7];
+       args->digest = argv[8];
+       args->salt = argv[9];
+
+       return NULL;
+}
+
+static void splitarg(char *arg, char **key, char **val)
+{
+       *key = strsep(&arg, "=");
+       *val = strsep(&arg, "");
+}
+
+static char *chromeos_args(unsigned argc, char **argv, struct verity_args *args)
+{
+       char *key, *val;
+       unsigned long num;
+       int i;
+
+       args->version = 0;
+       args->data_block_size_bits = 12;
+       args->hash_block_size_bits = 12;
+       for (i = 0; i < argc; ++i) {
+               DMWARN("Argument %d: '%s'", i, argv[i]);
+               splitarg(argv[i], &key, &val);
+               if (!key) {
+                       DMWARN("Bad argument %d: missing key?", i);
+                       return "Bad argument: missing key";
+               }
+               if (!val) {
+                       DMWARN("Bad argument %d='%s': missing value", i, key);
+                       return "Bad argument: missing value";
+               }
+               if (!strcmp(key, "alg")) {
+                       args->algorithm = val;
+               } else if (!strcmp(key, "payload")) {
+                       args->data_device = val;
+               } else if (!strcmp(key, "hashtree")) {
+                       args->hash_device = val;
+               } else if (!strcmp(key, "root_hexdigest")) {
+                       args->digest = val;
+               } else if (!strcmp(key, "hashstart")) {
+                       if (strict_strtoul(val, 10, &num))
+                               return "Invalid hashstart";
+                       args->hash_start_block =
+                               num >> (args->hash_block_size_bits - SECTOR_SHIFT);
+                       args->num_data_blocks = args->hash_start_block;
+               } else if (!strcmp(key, "error_behavior")) {
+                       args->error_behavior = val;
+               } else if (!strcmp(key, "salt")) {
+                       args->salt = val;
+               }
+       }
+       if (!args->salt)
+               args->salt = "";
+
+#define NEEDARG(n) \
+       if (!(n)) { \
+               return "Missing argument: " #n; \
+       }
+
+       NEEDARG(args->algorithm);
+       NEEDARG(args->data_device);
+       NEEDARG(args->hash_device);
+       NEEDARG(args->digest);
+
+#undef NEEDARG
+       return NULL;
+}
+
 /*
  * Target parameters:
  *     <version>       The current format is version 1.
@@ -649,13 +1087,21 @@ static void verity_dtr(struct dm_target *ti)
  */
 static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
 {
+       struct verity_args args = { 0 };
        struct dm_verity *v;
-       unsigned num;
-       unsigned long long num_ll;
        int r;
        int i;
        sector_t hash_position;
-       char dummy;
+
+       if (argc == DM_VERITY_NUM_POSITIONAL_ARGS)
+               ti->error = positional_args(argc, argv, &args);
+       else
+               ti->error = chromeos_args(argc, argv, &args);
+       if (ti->error)
+               return -EINVAL;
+       if (0)
+               pr_args(&args);
+
 
        v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL);
        if (!v) {
@@ -665,83 +1111,46 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
        ti->private = v;
        v->ti = ti;
 
-       if ((dm_table_get_mode(ti->table) & ~FMODE_READ)) {
-               ti->error = "Device must be readonly";
-               r = -EINVAL;
-               goto bad;
-       }
-
-       if (argc != 10) {
-               ti->error = "Invalid argument count: exactly 10 arguments required";
-               r = -EINVAL;
-               goto bad;
-       }
-
-       if (sscanf(argv[0], "%d%c", &num, &dummy) != 1 ||
-           num < 0 || num > 1) {
-               ti->error = "Invalid version";
-               r = -EINVAL;
-               goto bad;
-       }
-       v->version = num;
+       v->version = args.version;
 
-       r = dm_get_device(ti, argv[1], FMODE_READ, &v->data_dev);
+       r = verity_get_device(ti, args.data_device, &v->data_dev);
        if (r) {
                ti->error = "Data device lookup failed";
                goto bad;
        }
 
-       r = dm_get_device(ti, argv[2], FMODE_READ, &v->hash_dev);
+       r = verity_get_device(ti, args.hash_device, &v->hash_dev);
        if (r) {
                ti->error = "Data device lookup failed";
                goto bad;
        }
 
-       if (sscanf(argv[3], "%u%c", &num, &dummy) != 1 ||
-           !num || (num & (num - 1)) ||
-           num < bdev_logical_block_size(v->data_dev->bdev) ||
-           num > PAGE_SIZE) {
+       v->data_dev_block_bits = args.data_block_size_bits;
+       if ((1 << v->data_dev_block_bits) <
+           bdev_logical_block_size(v->data_dev->bdev)) {
                ti->error = "Invalid data device block size";
                r = -EINVAL;
                goto bad;
        }
-       v->data_dev_block_bits = ffs(num) - 1;
 
-       if (sscanf(argv[4], "%u%c", &num, &dummy) != 1 ||
-           !num || (num & (num - 1)) ||
-           num < bdev_logical_block_size(v->hash_dev->bdev) ||
-           num > INT_MAX) {
+       v->hash_dev_block_bits = args.hash_block_size_bits;
+       if ((1 << v->data_dev_block_bits) <
+           bdev_logical_block_size(v->hash_dev->bdev)) {
                ti->error = "Invalid hash device block size";
                r = -EINVAL;
                goto bad;
        }
-       v->hash_dev_block_bits = ffs(num) - 1;
-
-       if (sscanf(argv[5], "%llu%c", &num_ll, &dummy) != 1 ||
-           (sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT))
-           >> (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll) {
-               ti->error = "Invalid data blocks";
-               r = -EINVAL;
-               goto bad;
-       }
-       v->data_blocks = num_ll;
 
+       v->data_blocks = args.num_data_blocks;
        if (ti->len > (v->data_blocks << (v->data_dev_block_bits - SECTOR_SHIFT))) {
                ti->error = "Data device is too small";
                r = -EINVAL;
                goto bad;
        }
 
-       if (sscanf(argv[6], "%llu%c", &num_ll, &dummy) != 1 ||
-           (sector_t)(num_ll << (v->hash_dev_block_bits - SECTOR_SHIFT))
-           >> (v->hash_dev_block_bits - SECTOR_SHIFT) != num_ll) {
-               ti->error = "Invalid hash start";
-               r = -EINVAL;
-               goto bad;
-       }
-       v->hash_start = num_ll;
+       v->hash_start = args.hash_start_block;
 
-       v->alg_name = kstrdup(argv[7], GFP_KERNEL);
+       v->alg_name = kstrdup(args.algorithm, GFP_KERNEL);
        if (!v->alg_name) {
                ti->error = "Cannot allocate algorithm name";
                r = -ENOMEM;
@@ -770,23 +1179,23 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                r = -ENOMEM;
                goto bad;
        }
-       if (strlen(argv[8]) != v->digest_size * 2 ||
-           hex2bin(v->root_digest, argv[8], v->digest_size)) {
+       if (strlen(args.digest) != v->digest_size * 2 ||
+           hex2bin(v->root_digest, args.digest, v->digest_size)) {
                ti->error = "Invalid root digest";
                r = -EINVAL;
                goto bad;
        }
 
-       if (strcmp(argv[9], "-")) {
-               v->salt_size = strlen(argv[9]) / 2;
+       if (strcmp(args.salt, "-")) {
+               v->salt_size = strlen(args.salt) / 2;
                v->salt = kmalloc(v->salt_size, GFP_KERNEL);
                if (!v->salt) {
                        ti->error = "Cannot allocate salt";
                        r = -ENOMEM;
                        goto bad;
                }
-               if (strlen(argv[9]) != v->salt_size * 2 ||
-                   hex2bin(v->salt, argv[9], v->salt_size)) {
+               if (strlen(args.salt) != v->salt_size * 2 ||
+                   hex2bin(v->salt, args.salt, v->salt_size)) {
                        ti->error = "Invalid salt";
                        r = -EINVAL;
                        goto bad;
@@ -865,6 +1274,17 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
                goto bad;
        }
 
+       /* chromeos allows setting error_behavior from both the module
+        * parameters and the device args. However, chromeos only uses
+        * the module parametres
+        */
+       v->error_behavior = verity_parse_error_behavior(error_behavior);
+       if (v->error_behavior == -1) {
+               ti->error = "Bad error_behavior supplied";
+               r = -EINVAL;
+               goto bad;
+       }
+
        return 0;
 
 bad: