summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/md/dm-integrity.c50
1 files changed, 50 insertions, 0 deletions
diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c
index 0862f31b8498..802b377613b0 100644
--- a/drivers/md/dm-integrity.c
+++ b/drivers/md/dm-integrity.c
@@ -180,6 +180,7 @@ struct dm_integrity_c {
struct dm_bufio_client *bufio;
struct workqueue_struct *metadata_wq;
struct superblock *sb;
+ struct superblock *sb_copy;
unsigned int journal_pages;
unsigned int n_bitmap_blocks;
@@ -3858,6 +3859,35 @@ static void dm_integrity_postsuspend(struct dm_target *ti)
ic->journal_uptodate = true;
}
+/*
+ * The superblock is re-read from the device on every resume, so that we pick
+ * up the flags and the recalculate position. The geometry described by the
+ * superblock must not change though - the in-memory structures (and the
+ * journal in particular) were sized according to the superblock that was
+ * validated in the constructor. Reject a superblock that was modified behind
+ * our back.
+ *
+ * Only the fields that the driver never rewrites may be tested here. In
+ * particular, "version" is recalculated by sb_set_version on every superblock
+ * write and it depends on SB_FLAG_RECALCULATING and SB_FLAG_DIRTY_BITMAP, and
+ * SB_FLAG_DISCARD_KEYED may be set by dm_integrity_resume itself.
+ */
+static bool superblock_changed(struct dm_integrity_c *ic)
+{
+ const __le32 immutable_flags = cpu_to_le32(SB_FLAG_HAVE_JOURNAL_MAC |
+ SB_FLAG_FIXED_PADDING |
+ SB_FLAG_FIXED_HMAC |
+ SB_FLAG_INLINE);
+
+ return memcmp(ic->sb->magic, ic->sb_copy->magic, sizeof(ic->sb->magic)) != 0 ||
+ ic->sb->log2_interleave_sectors != ic->sb_copy->log2_interleave_sectors ||
+ ic->sb->integrity_tag_size != ic->sb_copy->integrity_tag_size ||
+ ic->sb->journal_sections != ic->sb_copy->journal_sections ||
+ ic->sb->log2_sectors_per_block != ic->sb_copy->log2_sectors_per_block ||
+ ((ic->sb->flags ^ ic->sb_copy->flags) & immutable_flags) != 0 ||
+ memcmp(ic->sb->salt, ic->sb_copy->salt, SALT_SIZE) != 0;
+}
+
static void dm_integrity_resume(struct dm_target *ti)
{
struct dm_integrity_c *ic = ti->private;
@@ -3876,6 +3906,18 @@ static void dm_integrity_resume(struct dm_target *ti)
if (r)
dm_integrity_io_error(ic, "reading superblock", r);
+ if (unlikely(superblock_changed(ic))) {
+ /*
+ * Restore the superblock that we validated in the constructor,
+ * so that the rest of the driver doesn't operate on values
+ * that don't match the in-memory structures.
+ */
+ memcpy(ic->sb, ic->sb_copy, sizeof(struct superblock));
+ DMERR("The superblock was changed while the device was suspended");
+ dm_integrity_io_error(ic, "superblock check", -EINVAL);
+ goto skip_writes;
+ }
+
if (ic->mode == 'R')
goto skip_writes;
@@ -5427,6 +5469,13 @@ try_smaller_buffer:
ic->just_formatted = true;
}
+ ic->sb_copy = kmemdup(ic->sb, sizeof(struct superblock), GFP_KERNEL);
+ if (!ic->sb_copy) {
+ ti->error = "Cannot allocate superblock copy";
+ r = -ENOMEM;
+ goto bad;
+ }
+
if (!ic->meta_dev && ic->mode != 'I') {
r = dm_set_target_max_io_len(ti, 1U << ic->sb->log2_interleave_sectors);
if (r)
@@ -5525,6 +5574,7 @@ static void dm_integrity_dtr(struct dm_target *ti)
kvfree(ic->journal_tree);
if (ic->sb)
free_pages_exact(ic->sb, SB_SECTORS << SECTOR_SHIFT);
+ kfree(ic->sb_copy);
if (ic->internal_shash)
crypto_free_shash(ic->internal_shash);