CHROMIUM: md: dm-verity fixes chromeos needs

This CL has all the changes that needed to be made to
the upstream version of dm-verity.
1. Notify
2. Same paramaters as the old version of dm-verity
3. Opening device by uuid

See CL 41206

At Mandeep's suggestion, I've modified the upstream version of
verity to accept the legacy parameters. This means we can move
to the upstream version of verity without making changes to
out build, install and signing scripts.

Results from platform_BootPerfServer/control.perfalerts
on a Lucas
			      baseline	CL
reboot_in_syslog		23	111
seconds_firmware_boot		30.86	30.75
seconds_kernel_to_chrome_exec	4.64	4.82
seconds_kernel_to_chrome_main	5.55	6.11
seconds_kernel_to_login		8.32	8.14
seconds_kernel_to_network	8.73	8.17
seconds_kernel_to_startup	1.63	1.72
seconds_kernel_to_startup_done	2.4	2.37
seconds_kernel_to_x_started	4.55	4.73
seconds_power_on_to_kernel	30.86	30.75
seconds_power_on_to_login	39.18	38.89 (30 sec wait for dev)

BUG=chromium-os:31803
TEST=platform_DMVerityCorruption, platform_DMVerityBitCorruption

Change-Id: Iaedc50bc679c931d77044589de1a305fcb5b0180
Signed-off-by: Paul Taysom <taysom@chromium.org>
Reviewed-on: https://gerrit.chromium.org/gerrit/41468

[benzh: 3.14 rebase. Resolved trivial conflicts]
Signed-off-by: Ben Zhang <benzh@chromium.org>
This commit is contained in:
Paul Taysom
2013-01-16 14:55:05 -08:00
committed by Ben Zhang
parent 6b72319836
commit 641df83fdc
2 changed files with 524 additions and 59 deletions

View File

@@ -17,8 +17,11 @@
#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 *vec_mempool; /* mempool of bio vector */
@@ -95,6 +100,153 @@ struct dm_verity_prefetch_work {
unsigned n_blocks;
};
/* 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);
@@ -400,6 +552,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;
}
@@ -489,8 +642,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 = dm_per_bio_data(bio, ti->per_bio_data_size);
io->v = v;
@@ -627,6 +782,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.
@@ -643,13 +1081,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) {
@@ -659,83 +1105,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;
}
v->version = args.version;
if (argc != 10) {
ti->error = "Invalid argument count: exactly 10 arguments required";
r = -EINVAL;
goto bad;
}
if (sscanf(argv[0], "%u%c", &num, &dummy) != 1 ||
num > 1) {
ti->error = "Invalid version";
r = -EINVAL;
goto bad;
}
v->version = num;
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);
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);
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;
@@ -764,23 +1173,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;
@@ -852,6 +1261,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:

45
drivers/md/dm-verity.h Normal file
View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2010 The Chromium OS Authors <chromium-os-dev@chromium.org>
* All Rights Reserved.
*
* This file is released under the GPL.
*
* Provide error types for use when creating a custom error handler.
* See Documentation/device-mapper/dm-verity.txt
*/
#ifndef DM_VERITY_H
#define DM_VERITY_H
#include <linux/notifier.h>
struct dm_verity_error_state {
int code;
int transient; /* Likely to not happen after a reboot */
u64 block;
const char *message;
sector_t dev_start;
sector_t dev_len;
struct block_device *dev;
sector_t hash_dev_start;
sector_t hash_dev_len;
struct block_device *hash_dev;
/* Final behavior after all notifications are completed. */
int behavior;
};
/* This enum must be matched to allowed_error_behaviors in dm-verity.c */
enum dm_verity_error_behavior {
DM_VERITY_ERROR_BEHAVIOR_EIO = 0,
DM_VERITY_ERROR_BEHAVIOR_PANIC,
DM_VERITY_ERROR_BEHAVIOR_NONE,
DM_VERITY_ERROR_BEHAVIOR_NOTIFY
};
int dm_verity_register_error_notifier(struct notifier_block *nb);
int dm_verity_unregister_error_notifier(struct notifier_block *nb);
#endif /* DM_VERITY_H */