#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"
#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;
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 */
*/
};
+/* 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);
struct dm_verity_io *io = bio->bi_private;
if (error) {
+ verity_error(io->v, io, error);
verity_finish_io(io, error);
return;
}
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;
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.
*/
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) {
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;
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;
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: