dm-integrity: replace forgeable discard filler with a keyed sector marker

The discard-block check in dm_integrity_rw_tag() treats a stored tag
of all 0xf6 bytes (DISCARD_FILLER) as proof a block was discarded and
skips HMAC verification. allow_discards is only accepted in
dm-integrity's standalone mode. An attacker with raw write access to
the backing device, but without the integrity key, can stamp any block
with an all-0xf6 tag and have it served as authentic.

Add a new "allow_discards_keyed" target argument that marks discarded
blocks with a keyed checksum of (salt || sector) instead, computed by
integrity_discard_checksum().

Fixes: 84597a44a9 ("dm integrity: add optional discard support")
Co-developed-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
Signed-off-by: Jo Van Bulck <jo.vanbulck@cs.kuleuven.be>
Signed-off-by: Shukai Ni <shukai.ni@kuleuven.be>
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>
This commit is contained in:
Shukai Ni 2026-07-28 16:33:50 +02:00 committed by Mikulas Patocka
parent fb582397cf
commit 68c5c42567
3 changed files with 133 additions and 24 deletions

View File

@ -424,7 +424,8 @@ section above) has the following data format for 'integrity' target.
target_attributes := <target_name> "," <target_version> "," <dev_name> "," <start> target_attributes := <target_name> "," <target_version> "," <dev_name> "," <start>
<tag_size> "," <mode> "," [<meta_device> ","] [<block_size> ","] <recalculate> "," <tag_size> "," <mode> "," [<meta_device> ","] [<block_size> ","] <recalculate> ","
<allow_discards> "," <fix_padding> "," <fix_hmac> "," <legacy_recalculate> "," <allow_discards> "," <allow_discards_keyed> "," <fix_padding> "," <fix_hmac> ","
<legacy_recalculate> ","
<journal_sectors> "," <interleave_sectors> "," <buffer_sectors> ";" <journal_sectors> "," <interleave_sectors> "," <buffer_sectors> ";"
target_name := "target_name=integrity" target_name := "target_name=integrity"
@ -438,6 +439,7 @@ section above) has the following data format for 'integrity' target.
block_size := "block_size=" <N> block_size := "block_size=" <N>
recalculate := "recalculate=" <yes_no> recalculate := "recalculate=" <yes_no>
allow_discards := "allow_discards=" <yes_no> allow_discards := "allow_discards=" <yes_no>
allow_discards_keyed := "allow_discards_keyed=" <yes_no>
fix_padding := "fix_padding=" <yes_no> fix_padding := "fix_padding=" <yes_no>
fix_hmac := "fix_hmac=" <yes_no> fix_hmac := "fix_hmac=" <yes_no>
legacy_recalculate := "legacy_recalculate=" <yes_no> legacy_recalculate := "legacy_recalculate=" <yes_no>
@ -455,7 +457,8 @@ section above) has the following data format for 'integrity' target.
dm_version=4.45.0; dm_version=4.45.0;
name=integrity1,uuid=,major=253,minor=1,minor_count=1,num_targets=1; name=integrity1,uuid=,major=253,minor=1,minor_count=1,num_targets=1;
target_index=0,target_begin=0,target_len=7856,target_name=integrity,target_version=1.10.0, target_index=0,target_begin=0,target_len=7856,target_name=integrity,target_version=1.10.0,
dev_name=253:0,start=0,tag_size=32,mode=J,recalculate=n,allow_discards=n,fix_padding=n, dev_name=253:0,start=0,tag_size=32,mode=J,recalculate=n,allow_discards=n,
allow_discards_keyed=n,fix_padding=n,
fix_hmac=n,legacy_recalculate=n,journal_sectors=88,interleave_sectors=32768,buffer_sectors=128; fix_hmac=n,legacy_recalculate=n,journal_sectors=88,interleave_sectors=32768,buffer_sectors=128;

View File

@ -190,6 +190,19 @@ allow_discards
Allow block discard requests (a.k.a. TRIM) for the integrity device. Allow block discard requests (a.k.a. TRIM) for the integrity device.
Discards are only allowed to devices using internal hash. Discards are only allowed to devices using internal hash.
A discarded block is marked with a constant filler tag that anyone
with raw write access to the backing device can forge without the
key. Use allow_discards_keyed instead on new volumes.
allow_discards_keyed
Like allow_discards, but marks a discarded block with a keyed
checksum of the sector number, HMAC_key(salt || sector), instead of
the constant filler tag, so it can't be forged without the
integrity key.
Not compatible with volumes that already have discarded blocks
marked the old way; only use on a freshly formatted volume.
fix_padding fix_padding
Use a smaller padding of the tag area that is more Use a smaller padding of the tag area that is more
space-efficient. If this option is not present, large padding is space-efficient. If this option is not present, large padding is

View File

@ -66,6 +66,7 @@
#define SB_VERSION_4 4 #define SB_VERSION_4 4
#define SB_VERSION_5 5 #define SB_VERSION_5 5
#define SB_VERSION_6 6 #define SB_VERSION_6 6
#define SB_VERSION_7 7
#define SB_SECTORS 8 #define SB_SECTORS 8
#define MAX_SECTORS_PER_BLOCK 8 #define MAX_SECTORS_PER_BLOCK 8
@ -91,6 +92,7 @@ struct superblock {
#define SB_FLAG_FIXED_PADDING 0x8 #define SB_FLAG_FIXED_PADDING 0x8
#define SB_FLAG_FIXED_HMAC 0x10 #define SB_FLAG_FIXED_HMAC 0x10
#define SB_FLAG_INLINE 0x20 #define SB_FLAG_INLINE 0x20
#define SB_FLAG_DISCARD_KEYED 0x40
#define JOURNAL_ENTRY_ROUNDUP 8 #define JOURNAL_ENTRY_ROUNDUP 8
@ -277,6 +279,7 @@ struct dm_integrity_c {
bool recalculate_flag; bool recalculate_flag;
bool reset_recalculate_flag; bool reset_recalculate_flag;
bool discard; bool discard;
bool discard_keyed;
bool fix_padding; bool fix_padding;
bool fix_hmac; bool fix_hmac;
bool legacy_recalculate; bool legacy_recalculate;
@ -483,7 +486,9 @@ static void wraparound_section(struct dm_integrity_c *ic, unsigned int *sec_ptr)
static void sb_set_version(struct dm_integrity_c *ic) static void sb_set_version(struct dm_integrity_c *ic)
{ {
if (ic->sb->flags & cpu_to_le32(SB_FLAG_INLINE)) if (ic->sb->flags & cpu_to_le32(SB_FLAG_DISCARD_KEYED))
ic->sb->version = SB_VERSION_7;
else if (ic->sb->flags & cpu_to_le32(SB_FLAG_INLINE))
ic->sb->version = SB_VERSION_6; ic->sb->version = SB_VERSION_6;
else if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) else if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC))
ic->sb->version = SB_VERSION_5; ic->sb->version = SB_VERSION_5;
@ -1416,7 +1421,7 @@ static int dm_integrity_rw_tag(struct dm_integrity_c *ic, unsigned char *tag, se
{ {
unsigned int hash_offset = 0; unsigned int hash_offset = 0;
unsigned char mismatch_hash = 0; unsigned char mismatch_hash = 0;
unsigned char mismatch_filler = !ic->discard; unsigned char mismatch_filler = !ic->discard || ic->discard_keyed;
do { do {
unsigned char *data, *dp; unsigned char *data, *dp;
@ -1468,7 +1473,7 @@ static int dm_integrity_rw_tag(struct dm_integrity_c *ic, unsigned char *tag, se
} }
hash_offset = 0; hash_offset = 0;
mismatch_hash = 0; mismatch_hash = 0;
mismatch_filler = !ic->discard; mismatch_filler = !ic->discard || ic->discard_keyed;
} }
} }
} }
@ -1646,7 +1651,8 @@ static void integrity_end_io(struct bio *bio)
} }
static void integrity_sector_checksum_shash(struct dm_integrity_c *ic, sector_t sector, static void integrity_sector_checksum_shash(struct dm_integrity_c *ic, sector_t sector,
const char *data, unsigned offset, char *result) const char *data, unsigned offset,
unsigned int len, char *result)
{ {
__le64 sector_le = cpu_to_le64(sector); __le64 sector_le = cpu_to_le64(sector);
SHASH_DESC_ON_STACK(req, ic->internal_shash); SHASH_DESC_ON_STACK(req, ic->internal_shash);
@ -1675,10 +1681,12 @@ static void integrity_sector_checksum_shash(struct dm_integrity_c *ic, sector_t
goto failed; goto failed;
} }
r = crypto_shash_update(req, data + offset, ic->sectors_per_block << SECTOR_SHIFT); if (likely(len)) {
if (unlikely(r < 0)) { r = crypto_shash_update(req, data + offset, len);
dm_integrity_io_error(ic, "crypto_shash_update", r); if (unlikely(r < 0)) {
goto failed; dm_integrity_io_error(ic, "crypto_shash_update", r);
goto failed;
}
} }
r = crypto_shash_final(req, result); r = crypto_shash_final(req, result);
@ -1699,7 +1707,8 @@ static void integrity_sector_checksum_shash(struct dm_integrity_c *ic, sector_t
} }
static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ahash_request **ahash_req, static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ahash_request **ahash_req,
sector_t sector, struct page *page, unsigned offset, char *result) sector_t sector, struct page *page, unsigned offset,
unsigned int len, char *result)
{ {
__le64 sector_le = cpu_to_le64(sector); __le64 sector_le = cpu_to_le64(sector);
struct ahash_request *req; struct ahash_request *req;
@ -1708,6 +1717,7 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah
int r; int r;
unsigned int digest_size; unsigned int digest_size;
unsigned int nbytes = 0; unsigned int nbytes = 0;
unsigned int nents = 1 + (len ? 1 : 0);
might_sleep(); might_sleep();
@ -1721,12 +1731,12 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah
ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait); ahash_request_set_callback(req, CRYPTO_TFM_REQ_MAY_SLEEP, crypto_req_done, &wait);
if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) { if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) {
sg_init_table(sg, 3); sg_init_table(sg, nents + 1);
sg_set_buf(s, (const __u8 *)&ic->sb->salt, SALT_SIZE); sg_set_buf(s, (const __u8 *)&ic->sb->salt, SALT_SIZE);
nbytes += SALT_SIZE; nbytes += SALT_SIZE;
s++; s++;
} else { } else {
sg_init_table(sg, 2); sg_init_table(sg, nents);
} }
if (likely(!is_vmalloc_addr(&sector_le))) { if (likely(!is_vmalloc_addr(&sector_le))) {
@ -1739,8 +1749,10 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah
nbytes += sizeof(sector_le); nbytes += sizeof(sector_le);
s++; s++;
sg_set_page(s, page, ic->sectors_per_block << SECTOR_SHIFT, offset); if (likely(len)) {
nbytes += ic->sectors_per_block << SECTOR_SHIFT; sg_set_page(s, page, len, offset);
nbytes += len;
}
ahash_request_set_crypt(req, sg, result, nbytes); ahash_request_set_crypt(req, sg, result, nbytes);
@ -1764,10 +1776,40 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah
static void integrity_sector_checksum(struct dm_integrity_c *ic, struct ahash_request **ahash_req, static void integrity_sector_checksum(struct dm_integrity_c *ic, struct ahash_request **ahash_req,
sector_t sector, const char *data, unsigned offset, char *result) sector_t sector, const char *data, unsigned offset, char *result)
{ {
unsigned int len = ic->sectors_per_block << SECTOR_SHIFT;
if (likely(ic->internal_shash != NULL)) if (likely(ic->internal_shash != NULL))
integrity_sector_checksum_shash(ic, sector, data, offset, result); integrity_sector_checksum_shash(ic, sector, data, offset, len, result);
else else
integrity_sector_checksum_ahash(ic, ahash_req, sector, (struct page *)data, offset, result); integrity_sector_checksum_ahash(ic, ahash_req, sector, (struct page *)data,
offset, len, result);
}
/*
* Authenticated marker for a discarded block: HMAC_key(salt || sector), with
* no data payload. Because a real data tag's input always covers a full
* block, its length differs from this marker's, so the two can never
* collide structurally, regardless of block content.
*/
static void integrity_discard_checksum(struct dm_integrity_c *ic, struct ahash_request **ahash_req,
sector_t sector, char *result)
{
if (likely(ic->internal_shash != NULL))
integrity_sector_checksum_shash(ic, sector, NULL, 0, 0, result);
else
integrity_sector_checksum_ahash(ic, ahash_req, sector, NULL, 0, 0, result);
}
static void integrity_discard_fill_tags(struct dm_integrity_c *ic, struct ahash_request **ahash_req,
unsigned char *checksums, sector_t *sector,
unsigned int blocks)
{
unsigned int i;
for (i = 0; i < blocks; i++) {
integrity_discard_checksum(ic, ahash_req, *sector, checksums + i * ic->tag_size);
*sector += ic->sectors_per_block;
}
} }
static void *integrity_kmap(struct dm_integrity_c *ic, struct page *p) static void *integrity_kmap(struct dm_integrity_c *ic, struct page *p)
@ -1796,6 +1838,29 @@ static void *integrity_identity(struct dm_integrity_c *ic, void *data)
return virt_to_page(data); return virt_to_page(data);
} }
static int integrity_recheck_verify_tag(struct dm_integrity_io *dio, char *checksum,
char *on_disk_tag, sector_t logical_sector)
{
struct dm_integrity_c *ic = dio->ic;
int r;
if (!ic->discard_keyed)
return dm_integrity_rw_tag(ic, checksum, &dio->metadata_block,
&dio->metadata_offset, ic->tag_size, TAG_CMP);
r = dm_integrity_rw_tag(ic, on_disk_tag, &dio->metadata_block,
&dio->metadata_offset, ic->tag_size, TAG_READ);
if (unlikely(r))
return r;
r = crypto_memneq(on_disk_tag, checksum, ic->tag_size);
if (unlikely(r)) {
integrity_discard_checksum(ic, &dio->ahash_req, logical_sector, checksum);
r = crypto_memneq(on_disk_tag, checksum, ic->tag_size);
}
return r;
}
static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checksum) static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checksum)
{ {
struct bio *bio = dm_bio_from_per_bio_data(dio, sizeof(struct dm_integrity_io)); struct bio *bio = dm_bio_from_per_bio_data(dio, sizeof(struct dm_integrity_io));
@ -1821,6 +1886,7 @@ static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checks
char *mem; char *mem;
char *buffer = page_to_virt(page); char *buffer = page_to_virt(page);
unsigned int buffer_offset; unsigned int buffer_offset;
char on_disk_tag[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)];
int r; int r;
struct dm_io_request io_req; struct dm_io_request io_req;
struct dm_io_region io_loc; struct dm_io_region io_loc;
@ -1848,8 +1914,8 @@ static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checks
} }
integrity_sector_checksum(ic, &dio->ahash_req, logical_sector, integrity_identity(ic, buffer), buffer_offset, checksum); integrity_sector_checksum(ic, &dio->ahash_req, logical_sector, integrity_identity(ic, buffer), buffer_offset, checksum);
r = dm_integrity_rw_tag(ic, checksum, &dio->metadata_block, r = integrity_recheck_verify_tag(dio, checksum, on_disk_tag,
&dio->metadata_offset, ic->tag_size, TAG_CMP); logical_sector);
if (r) { if (r) {
if (r > 0) { if (r > 0) {
DMERR_LIMIT("%pg: Checksum failed at sector 0x%llx", DMERR_LIMIT("%pg: Checksum failed at sector 0x%llx",
@ -1915,13 +1981,18 @@ static void integrity_metadata(struct work_struct *w)
unsigned int bi_size = dio->bio_details.bi_iter.bi_size; unsigned int bi_size = dio->bio_details.bi_iter.bi_size;
unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE;
unsigned int max_blocks = max_size / ic->tag_size; unsigned int max_blocks = max_size / ic->tag_size;
sector_t sector = dio->range.logical_sector;
memset(checksums, DISCARD_FILLER, max_size); if (!ic->discard_keyed)
memset(checksums, DISCARD_FILLER, max_size);
while (bi_size) { while (bi_size) {
unsigned int this_step_blocks = bi_size >> (SECTOR_SHIFT + ic->sb->log2_sectors_per_block); unsigned int this_step_blocks = bi_size >> (SECTOR_SHIFT + ic->sb->log2_sectors_per_block);
this_step_blocks = min(this_step_blocks, max_blocks); this_step_blocks = min(this_step_blocks, max_blocks);
if (ic->discard_keyed)
integrity_discard_fill_tags(ic, &dio->ahash_req, checksums,
&sector, this_step_blocks);
r = dm_integrity_rw_tag(ic, checksums, &dio->metadata_block, &dio->metadata_offset, r = dm_integrity_rw_tag(ic, checksums, &dio->metadata_block, &dio->metadata_offset,
this_step_blocks * ic->tag_size, TAG_WRITE); this_step_blocks * ic->tag_size, TAG_WRITE);
if (unlikely(r)) { if (unlikely(r)) {
@ -3799,6 +3870,8 @@ static void dm_integrity_resume(struct dm_target *ti)
ic->wrote_to_journal = false; ic->wrote_to_journal = false;
flags = ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING); flags = ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING);
if (ic->discard_keyed)
flags |= cpu_to_le32(SB_FLAG_DISCARD_KEYED);
r = sync_rw_sb(ic, REQ_OP_READ); r = sync_rw_sb(ic, REQ_OP_READ);
if (r) if (r)
dm_integrity_io_error(ic, "reading superblock", r); dm_integrity_io_error(ic, "reading superblock", r);
@ -3946,7 +4019,8 @@ static void dm_integrity_status(struct dm_target *ti, status_type_t type,
arg_count += ic->sectors_per_block != 1; arg_count += ic->sectors_per_block != 1;
arg_count += !!(ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)); arg_count += !!(ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING));
arg_count += ic->reset_recalculate_flag; arg_count += ic->reset_recalculate_flag;
arg_count += ic->discard; arg_count += ic->discard && !ic->discard_keyed;
arg_count += ic->discard_keyed;
arg_count += ic->mode != 'I'; /* interleave_sectors */ arg_count += ic->mode != 'I'; /* interleave_sectors */
arg_count += ic->mode == 'J'; /* journal_sectors */ arg_count += ic->mode == 'J'; /* journal_sectors */
arg_count += ic->mode == 'J'; /* journal_watermark */ arg_count += ic->mode == 'J'; /* journal_watermark */
@ -3969,8 +4043,10 @@ static void dm_integrity_status(struct dm_target *ti, status_type_t type,
DMEMIT(" recalculate"); DMEMIT(" recalculate");
if (ic->reset_recalculate_flag) if (ic->reset_recalculate_flag)
DMEMIT(" reset_recalculate"); DMEMIT(" reset_recalculate");
if (ic->discard) if (ic->discard && !ic->discard_keyed)
DMEMIT(" allow_discards"); DMEMIT(" allow_discards");
if (ic->discard_keyed)
DMEMIT(" allow_discards_keyed");
if (ic->mode != 'I') if (ic->mode != 'I')
DMEMIT(" interleave_sectors:%u", 1U << ic->sb->log2_interleave_sectors); DMEMIT(" interleave_sectors:%u", 1U << ic->sb->log2_interleave_sectors);
DMEMIT(" buffer_sectors:%u", 1U << ic->log2_buffer_sectors); DMEMIT(" buffer_sectors:%u", 1U << ic->log2_buffer_sectors);
@ -4020,6 +4096,7 @@ static void dm_integrity_status(struct dm_target *ti, status_type_t type,
DMEMIT(",recalculate=%c", (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)) ? DMEMIT(",recalculate=%c", (ic->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)) ?
'y' : 'n'); 'y' : 'n');
DMEMIT(",allow_discards=%c", ic->discard ? 'y' : 'n'); DMEMIT(",allow_discards=%c", ic->discard ? 'y' : 'n');
DMEMIT(",allow_discards_keyed=%c", ic->discard_keyed ? 'y' : 'n');
DMEMIT(",fix_padding=%c", DMEMIT(",fix_padding=%c",
((ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_PADDING)) != 0) ? 'y' : 'n'); ((ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_PADDING)) != 0) ? 'y' : 'n');
DMEMIT(",fix_hmac=%c", DMEMIT(",fix_hmac=%c",
@ -4177,6 +4254,9 @@ static int initialize_superblock(struct dm_integrity_c *ic,
get_random_bytes(ic->sb->salt, SALT_SIZE); get_random_bytes(ic->sb->salt, SALT_SIZE);
} }
if (ic->discard_keyed)
ic->sb->flags |= cpu_to_le32(SB_FLAG_DISCARD_KEYED);
if (!ic->meta_dev) { if (!ic->meta_dev) {
if (ic->fix_padding) if (ic->fix_padding)
ic->sb->flags |= cpu_to_le32(SB_FLAG_FIXED_PADDING); ic->sb->flags |= cpu_to_le32(SB_FLAG_FIXED_PADDING);
@ -4835,6 +4915,9 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
ic->reset_recalculate_flag = true; ic->reset_recalculate_flag = true;
} else if (!strcmp(opt_string, "allow_discards")) { } else if (!strcmp(opt_string, "allow_discards")) {
ic->discard = true; ic->discard = true;
} else if (!strcmp(opt_string, "allow_discards_keyed")) {
ic->discard = true;
ic->discard_keyed = true;
} else if (!strcmp(opt_string, "fix_padding")) { } else if (!strcmp(opt_string, "fix_padding")) {
ic->fix_padding = true; ic->fix_padding = true;
} else if (!strcmp(opt_string, "fix_hmac")) { } else if (!strcmp(opt_string, "fix_hmac")) {
@ -4963,6 +5046,11 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
ti->error = "Discard can be only used with internal hash"; ti->error = "Discard can be only used with internal hash";
goto bad; goto bad;
} }
if (ic->discard_keyed && !ic->internal_hash_alg.key) {
r = -EINVAL;
ti->error = "Keyed discard can only be used with keyed internal hash";
goto bad;
}
ic->autocommit_jiffies = msecs_to_jiffies(sync_msec); ic->autocommit_jiffies = msecs_to_jiffies(sync_msec);
ic->autocommit_msec = sync_msec; ic->autocommit_msec = sync_msec;
@ -5081,7 +5169,7 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
should_write_sb = true; should_write_sb = true;
} }
if (!ic->sb->version || ic->sb->version > SB_VERSION_6) { if (!ic->sb->version || ic->sb->version > SB_VERSION_7) {
r = -EINVAL; r = -EINVAL;
ti->error = "Unknown version"; ti->error = "Unknown version";
goto bad; goto bad;
@ -5129,6 +5217,11 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv
goto bad; goto bad;
} }
} }
if (!ic->discard_keyed && (ic->sb->flags & cpu_to_le32(SB_FLAG_DISCARD_KEYED))) {
r = -EINVAL;
ti->error = "Keyed discard cannot be disabled once enabled";
goto bad;
}
if (!!(ic->sb->flags & cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC)) != !!ic->journal_mac_alg.alg_string) { if (!!(ic->sb->flags & cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC)) != !!ic->journal_mac_alg.alg_string) {
r = -EINVAL; r = -EINVAL;
ti->error = "Journal mac mismatch"; ti->error = "Journal mac mismatch";
@ -5444,7 +5537,7 @@ static void dm_integrity_dtr(struct dm_target *ti)
static struct target_type integrity_target = { static struct target_type integrity_target = {
.name = "integrity", .name = "integrity",
.version = {1, 14, 0}, .version = {1, 15, 0},
.module = THIS_MODULE, .module = THIS_MODULE,
.features = DM_TARGET_SINGLETON | DM_TARGET_INTEGRITY, .features = DM_TARGET_SINGLETON | DM_TARGET_INTEGRITY,
.ctr = dm_integrity_ctr, .ctr = dm_integrity_ctr,