Commit 65ff5b7d authored by Sami Tolvanen's avatar Sami Tolvanen Committed by Mike Snitzer

dm verity: add error handling modes for corrupted blocks

Add device specific modes to dm-verity to specify how corrupted
blocks should be handled.  The following modes are defined:

  - DM_VERITY_MODE_EIO is the default behavior, where reading a
    corrupted block results in -EIO.

  - DM_VERITY_MODE_LOGGING only logs corrupted blocks, but does
    not block the read.

  - DM_VERITY_MODE_RESTART calls kernel_restart when a corrupted
    block is discovered.

In addition, each mode sends a uevent to notify userspace of
corruption and to allow further recovery actions.

The driver defaults to previous behavior (DM_VERITY_MODE_EIO)
and other modes can be enabled with an additional parameter to
the verity table.
Signed-off-by: default avatarSami Tolvanen <samitolvanen@google.com>
Signed-off-by: default avatarMike Snitzer <snitzer@redhat.com>
parent 0e0e32c1
...@@ -11,6 +11,7 @@ Construction Parameters ...@@ -11,6 +11,7 @@ Construction Parameters
<data_block_size> <hash_block_size> <data_block_size> <hash_block_size>
<num_data_blocks> <hash_start_block> <num_data_blocks> <hash_start_block>
<algorithm> <digest> <salt> <algorithm> <digest> <salt>
[<#opt_params> <opt_params>]
<version> <version>
This is the type of the on-disk hash format. This is the type of the on-disk hash format.
...@@ -62,6 +63,22 @@ Construction Parameters ...@@ -62,6 +63,22 @@ Construction Parameters
<salt> <salt>
The hexadecimal encoding of the salt value. The hexadecimal encoding of the salt value.
<#opt_params>
Number of optional parameters. If there are no optional parameters,
the optional paramaters section can be skipped or #opt_params can be zero.
Otherwise #opt_params is the number of following arguments.
Example of optional parameters section:
1 ignore_corruption
ignore_corruption
Log corrupted blocks, but allow read operations to proceed normally.
restart_on_corruption
Restart the system when a corrupted block is discovered. This option is
not compatible with ignore_corruption and requires user space support to
avoid restart loops.
Theory of operation Theory of operation
=================== ===================
......
...@@ -18,20 +18,39 @@ ...@@ -18,20 +18,39 @@
#include <linux/module.h> #include <linux/module.h>
#include <linux/device-mapper.h> #include <linux/device-mapper.h>
#include <linux/reboot.h>
#include <crypto/hash.h> #include <crypto/hash.h>
#define DM_MSG_PREFIX "verity" #define DM_MSG_PREFIX "verity"
#define DM_VERITY_ENV_LENGTH 42
#define DM_VERITY_ENV_VAR_NAME "DM_VERITY_ERR_BLOCK_NR"
#define DM_VERITY_IO_VEC_INLINE 16 #define DM_VERITY_IO_VEC_INLINE 16
#define DM_VERITY_MEMPOOL_SIZE 4 #define DM_VERITY_MEMPOOL_SIZE 4
#define DM_VERITY_DEFAULT_PREFETCH_SIZE 262144 #define DM_VERITY_DEFAULT_PREFETCH_SIZE 262144
#define DM_VERITY_MAX_LEVELS 63 #define DM_VERITY_MAX_LEVELS 63
#define DM_VERITY_MAX_CORRUPTED_ERRS 100
#define DM_VERITY_OPT_LOGGING "ignore_corruption"
#define DM_VERITY_OPT_RESTART "restart_on_corruption"
static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE; static unsigned dm_verity_prefetch_cluster = DM_VERITY_DEFAULT_PREFETCH_SIZE;
module_param_named(prefetch_cluster, dm_verity_prefetch_cluster, uint, S_IRUGO | S_IWUSR); module_param_named(prefetch_cluster, dm_verity_prefetch_cluster, uint, S_IRUGO | S_IWUSR);
enum verity_mode {
DM_VERITY_MODE_EIO,
DM_VERITY_MODE_LOGGING,
DM_VERITY_MODE_RESTART
};
enum verity_block_type {
DM_VERITY_BLOCK_TYPE_DATA,
DM_VERITY_BLOCK_TYPE_METADATA
};
struct dm_verity { struct dm_verity {
struct dm_dev *data_dev; struct dm_dev *data_dev;
struct dm_dev *hash_dev; struct dm_dev *hash_dev;
...@@ -54,6 +73,8 @@ struct dm_verity { ...@@ -54,6 +73,8 @@ struct dm_verity {
unsigned digest_size; /* digest size for the current hash algorithm */ unsigned digest_size; /* digest size for the current hash algorithm */
unsigned shash_descsize;/* the size of temporary space for crypto */ unsigned shash_descsize;/* the size of temporary space for crypto */
int hash_failed; /* set to 1 if hash of any block failed */ int hash_failed; /* set to 1 if hash of any block failed */
enum verity_mode mode; /* mode for handling verification errors */
unsigned corrupted_errs;/* Number of errors for corrupted blocks */
mempool_t *vec_mempool; /* mempool of bio vector */ mempool_t *vec_mempool; /* mempool of bio vector */
...@@ -174,6 +195,57 @@ static void verity_hash_at_level(struct dm_verity *v, sector_t block, int level, ...@@ -174,6 +195,57 @@ static void verity_hash_at_level(struct dm_verity *v, sector_t block, int level,
*offset = idx << (v->hash_dev_block_bits - v->hash_per_block_bits); *offset = idx << (v->hash_dev_block_bits - v->hash_per_block_bits);
} }
/*
* Handle verification errors.
*/
static int verity_handle_err(struct dm_verity *v, enum verity_block_type type,
unsigned long long block)
{
char verity_env[DM_VERITY_ENV_LENGTH];
char *envp[] = { verity_env, NULL };
const char *type_str = "";
struct mapped_device *md = dm_table_get_md(v->ti->table);
/* Corruption should be visible in device status in all modes */
v->hash_failed = 1;
if (v->corrupted_errs >= DM_VERITY_MAX_CORRUPTED_ERRS)
goto out;
v->corrupted_errs++;
switch (type) {
case DM_VERITY_BLOCK_TYPE_DATA:
type_str = "data";
break;
case DM_VERITY_BLOCK_TYPE_METADATA:
type_str = "metadata";
break;
default:
BUG();
}
DMERR("%s: %s block %llu is corrupted", v->data_dev->name, type_str,
block);
if (v->corrupted_errs == DM_VERITY_MAX_CORRUPTED_ERRS)
DMERR("%s: reached maximum errors", v->data_dev->name);
snprintf(verity_env, DM_VERITY_ENV_LENGTH, "%s=%d,%llu",
DM_VERITY_ENV_VAR_NAME, type, block);
kobject_uevent_env(&disk_to_dev(dm_disk(md))->kobj, KOBJ_CHANGE, envp);
out:
if (v->mode == DM_VERITY_MODE_LOGGING)
return 0;
if (v->mode == DM_VERITY_MODE_RESTART)
kernel_restart("dm-verity device corrupted");
return 1;
}
/* /*
* Verify hash of a metadata block pertaining to the specified data block * Verify hash of a metadata block pertaining to the specified data block
* ("block" argument) at a specified level ("level" argument). * ("block" argument) at a specified level ("level" argument).
...@@ -251,11 +323,11 @@ static int verity_verify_level(struct dm_verity_io *io, sector_t block, ...@@ -251,11 +323,11 @@ static int verity_verify_level(struct dm_verity_io *io, sector_t block,
goto release_ret_r; goto release_ret_r;
} }
if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) { if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
DMERR_LIMIT("metadata block %llu is corrupted", if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_METADATA,
(unsigned long long)hash_block); hash_block)) {
v->hash_failed = 1; r = -EIO;
r = -EIO; goto release_ret_r;
goto release_ret_r; }
} else } else
aux->hash_verified = 1; aux->hash_verified = 1;
} }
...@@ -367,10 +439,9 @@ static int verity_verify_io(struct dm_verity_io *io) ...@@ -367,10 +439,9 @@ static int verity_verify_io(struct dm_verity_io *io)
return r; return r;
} }
if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) { if (unlikely(memcmp(result, io_want_digest(v, io), v->digest_size))) {
DMERR_LIMIT("data block %llu is corrupted", if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA,
(unsigned long long)(io->block + b)); io->block + b))
v->hash_failed = 1; return -EIO;
return -EIO;
} }
} }
...@@ -546,6 +617,19 @@ static void verity_status(struct dm_target *ti, status_type_t type, ...@@ -546,6 +617,19 @@ static void verity_status(struct dm_target *ti, status_type_t type,
else else
for (x = 0; x < v->salt_size; x++) for (x = 0; x < v->salt_size; x++)
DMEMIT("%02x", v->salt[x]); DMEMIT("%02x", v->salt[x]);
if (v->mode != DM_VERITY_MODE_EIO) {
DMEMIT(" 1 ");
switch (v->mode) {
case DM_VERITY_MODE_LOGGING:
DMEMIT(DM_VERITY_OPT_LOGGING);
break;
case DM_VERITY_MODE_RESTART:
DMEMIT(DM_VERITY_OPT_RESTART);
break;
default:
BUG();
}
}
break; break;
} }
} }
...@@ -647,13 +731,19 @@ static void verity_dtr(struct dm_target *ti) ...@@ -647,13 +731,19 @@ static void verity_dtr(struct dm_target *ti)
static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
{ {
struct dm_verity *v; struct dm_verity *v;
unsigned num; struct dm_arg_set as;
const char *opt_string;
unsigned int num, opt_params;
unsigned long long num_ll; unsigned long long num_ll;
int r; int r;
int i; int i;
sector_t hash_position; sector_t hash_position;
char dummy; char dummy;
static struct dm_arg _args[] = {
{0, 1, "Invalid number of feature args"},
};
v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL); v = kzalloc(sizeof(struct dm_verity), GFP_KERNEL);
if (!v) { if (!v) {
ti->error = "Cannot allocate verity structure"; ti->error = "Cannot allocate verity structure";
...@@ -668,8 +758,8 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) ...@@ -668,8 +758,8 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
goto bad; goto bad;
} }
if (argc != 10) { if (argc < 10) {
ti->error = "Invalid argument count: exactly 10 arguments required"; ti->error = "Not enough arguments";
r = -EINVAL; r = -EINVAL;
goto bad; goto bad;
} }
...@@ -790,6 +880,39 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv) ...@@ -790,6 +880,39 @@ static int verity_ctr(struct dm_target *ti, unsigned argc, char **argv)
} }
} }
argv += 10;
argc -= 10;
/* Optional parameters */
if (argc) {
as.argc = argc;
as.argv = argv;
r = dm_read_arg_group(_args, &as, &opt_params, &ti->error);
if (r)
goto bad;
while (opt_params) {
opt_params--;
opt_string = dm_shift_arg(&as);
if (!opt_string) {
ti->error = "Not enough feature arguments";
r = -EINVAL;
goto bad;
}
if (!strcasecmp(opt_string, DM_VERITY_OPT_LOGGING))
v->mode = DM_VERITY_MODE_LOGGING;
else if (!strcasecmp(opt_string, DM_VERITY_OPT_RESTART))
v->mode = DM_VERITY_MODE_RESTART;
else {
ti->error = "Invalid feature arguments";
r = -EINVAL;
goto bad;
}
}
}
v->hash_per_block_bits = v->hash_per_block_bits =
__fls((1 << v->hash_dev_block_bits) / v->digest_size); __fls((1 << v->hash_dev_block_bits) / v->digest_size);
......
...@@ -3483,6 +3483,7 @@ struct gendisk *dm_disk(struct mapped_device *md) ...@@ -3483,6 +3483,7 @@ struct gendisk *dm_disk(struct mapped_device *md)
{ {
return md->disk; return md->disk;
} }
EXPORT_SYMBOL_GPL(dm_disk);
struct kobject *dm_kobject(struct mapped_device *md) struct kobject *dm_kobject(struct mapped_device *md)
{ {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment