dm verity: add 'check_at_most_once' option to only validate hashes once
This allows platforms that are CPU/memory contrained to verify data blocks only the first time they are read from the data device, rather than every time. As such, it provides a reduced level of security because only offline tampering of the data device's content will be detected, not online tampering. Hash blocks are still verified each time they are read from the hash device, since verification of hash blocks is less performance critical than data blocks, and a hash block will not be verified any more after all the data blocks it covers have been verified anyway. This option introduces a bitset that is used to check if a block has been validated before or not. A block can be validated more than once as there is no thread protection for the bitset. These changes were developed and tested on entry-level Android Go devices. Signed-off-by: Patrik Torstensson <totte@google.com> Signed-off-by: Mike Snitzer <snitzer@redhat.com>
This commit is contained in:
parent
45354f1eb6
commit
843f38d382
|
@ -109,6 +109,17 @@ fec_start <offset>
|
|||
This is the offset, in <data_block_size> blocks, from the start of the
|
||||
FEC device to the beginning of the encoding data.
|
||||
|
||||
check_at_most_once
|
||||
Verify data blocks only the first time they are read from the data device,
|
||||
rather than every time. This reduces the overhead of dm-verity so that it
|
||||
can be used on systems that are memory and/or CPU constrained. However, it
|
||||
provides a reduced level of security because only offline tampering of the
|
||||
data device's content will be detected, not online tampering.
|
||||
|
||||
Hash blocks are still verified each time they are read from the hash device,
|
||||
since verification of hash blocks is less performance critical than data
|
||||
blocks, and a hash block will not be verified any more after all the data
|
||||
blocks it covers have been verified anyway.
|
||||
|
||||
Theory of operation
|
||||
===================
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#define DM_VERITY_OPT_LOGGING "ignore_corruption"
|
||||
#define DM_VERITY_OPT_RESTART "restart_on_corruption"
|
||||
#define DM_VERITY_OPT_IGN_ZEROES "ignore_zero_blocks"
|
||||
#define DM_VERITY_OPT_AT_MOST_ONCE "check_at_most_once"
|
||||
|
||||
#define DM_VERITY_OPTS_MAX (2 + DM_VERITY_OPTS_FEC)
|
||||
|
||||
|
@ -432,6 +433,18 @@ static int verity_bv_zero(struct dm_verity *v, struct dm_verity_io *io,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Moves the bio iter one data block forward.
|
||||
*/
|
||||
static inline void verity_bv_skip_block(struct dm_verity *v,
|
||||
struct dm_verity_io *io,
|
||||
struct bvec_iter *iter)
|
||||
{
|
||||
struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size);
|
||||
|
||||
bio_advance_iter(bio, iter, 1 << v->data_dev_block_bits);
|
||||
}
|
||||
|
||||
/*
|
||||
* Verify one "dm_verity_io" structure.
|
||||
*/
|
||||
|
@ -445,9 +458,16 @@ static int verity_verify_io(struct dm_verity_io *io)
|
|||
|
||||
for (b = 0; b < io->n_blocks; b++) {
|
||||
int r;
|
||||
sector_t cur_block = io->block + b;
|
||||
struct ahash_request *req = verity_io_hash_req(v, io);
|
||||
|
||||
r = verity_hash_for_block(v, io, io->block + b,
|
||||
if (v->validated_blocks &&
|
||||
likely(test_bit(cur_block, v->validated_blocks))) {
|
||||
verity_bv_skip_block(v, io, &io->iter);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = verity_hash_for_block(v, io, cur_block,
|
||||
verity_io_want_digest(v, io),
|
||||
&is_zero);
|
||||
if (unlikely(r < 0))
|
||||
|
@ -481,13 +501,16 @@ static int verity_verify_io(struct dm_verity_io *io)
|
|||
return r;
|
||||
|
||||
if (likely(memcmp(verity_io_real_digest(v, io),
|
||||
verity_io_want_digest(v, io), v->digest_size) == 0))
|
||||
verity_io_want_digest(v, io), v->digest_size) == 0)) {
|
||||
if (v->validated_blocks)
|
||||
set_bit(cur_block, v->validated_blocks);
|
||||
continue;
|
||||
}
|
||||
else if (verity_fec_decode(v, io, DM_VERITY_BLOCK_TYPE_DATA,
|
||||
io->block + b, NULL, &start) == 0)
|
||||
cur_block, NULL, &start) == 0)
|
||||
continue;
|
||||
else if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_DATA,
|
||||
io->block + b))
|
||||
cur_block))
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
@ -673,6 +696,8 @@ static void verity_status(struct dm_target *ti, status_type_t type,
|
|||
args += DM_VERITY_OPTS_FEC;
|
||||
if (v->zero_digest)
|
||||
args++;
|
||||
if (v->validated_blocks)
|
||||
args++;
|
||||
if (!args)
|
||||
return;
|
||||
DMEMIT(" %u", args);
|
||||
|
@ -691,6 +716,8 @@ static void verity_status(struct dm_target *ti, status_type_t type,
|
|||
}
|
||||
if (v->zero_digest)
|
||||
DMEMIT(" " DM_VERITY_OPT_IGN_ZEROES);
|
||||
if (v->validated_blocks)
|
||||
DMEMIT(" " DM_VERITY_OPT_AT_MOST_ONCE);
|
||||
sz = verity_fec_status_table(v, sz, result, maxlen);
|
||||
break;
|
||||
}
|
||||
|
@ -740,6 +767,7 @@ static void verity_dtr(struct dm_target *ti)
|
|||
if (v->bufio)
|
||||
dm_bufio_client_destroy(v->bufio);
|
||||
|
||||
kvfree(v->validated_blocks);
|
||||
kfree(v->salt);
|
||||
kfree(v->root_digest);
|
||||
kfree(v->zero_digest);
|
||||
|
@ -760,6 +788,26 @@ static void verity_dtr(struct dm_target *ti)
|
|||
kfree(v);
|
||||
}
|
||||
|
||||
static int verity_alloc_most_once(struct dm_verity *v)
|
||||
{
|
||||
struct dm_target *ti = v->ti;
|
||||
|
||||
/* the bitset can only handle INT_MAX blocks */
|
||||
if (v->data_blocks > INT_MAX) {
|
||||
ti->error = "device too large to use check_at_most_once";
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
v->validated_blocks = kvzalloc(BITS_TO_LONGS(v->data_blocks) *
|
||||
sizeof(unsigned long), GFP_KERNEL);
|
||||
if (!v->validated_blocks) {
|
||||
ti->error = "failed to allocate bitset for check_at_most_once";
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verity_alloc_zero_digest(struct dm_verity *v)
|
||||
{
|
||||
int r = -ENOMEM;
|
||||
|
@ -829,6 +877,12 @@ static int verity_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v)
|
|||
}
|
||||
continue;
|
||||
|
||||
} else if (!strcasecmp(arg_name, DM_VERITY_OPT_AT_MOST_ONCE)) {
|
||||
r = verity_alloc_most_once(v);
|
||||
if (r)
|
||||
return r;
|
||||
continue;
|
||||
|
||||
} else if (verity_is_fec_opt_arg(arg_name)) {
|
||||
r = verity_fec_parse_opt_args(as, v, &argc, arg_name);
|
||||
if (r)
|
||||
|
@ -1096,7 +1150,7 @@ bad:
|
|||
|
||||
static struct target_type verity_target = {
|
||||
.name = "verity",
|
||||
.version = {1, 3, 0},
|
||||
.version = {1, 4, 0},
|
||||
.module = THIS_MODULE,
|
||||
.ctr = verity_ctr,
|
||||
.dtr = verity_dtr,
|
||||
|
|
|
@ -63,6 +63,7 @@ struct dm_verity {
|
|||
sector_t hash_level_block[DM_VERITY_MAX_LEVELS];
|
||||
|
||||
struct dm_verity_fec *fec; /* forward error correction */
|
||||
unsigned long *validated_blocks; /* bitset blocks validated */
|
||||
};
|
||||
|
||||
struct dm_verity_io {
|
||||
|
|
Loading…
Reference in a new issue