From 70094662c1c95c05f1ec839c8117b3bd5668b361 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:23:27 +0200 Subject: [PATCH 01/46] dm-integrity: fix error message Change "reading tags" to "writing tags" because the error is reported when writing fails. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 1f2593f113f6..4daa3dc6caf8 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -3040,7 +3040,7 @@ static void do_journal_write(struct dm_integrity_c *ic, unsigned int write_start r = dm_integrity_rw_tag(ic, journal_entry_tag(ic, je2), &metadata_block, &metadata_offset, ic->tag_size, TAG_WRITE); if (unlikely(r)) - dm_integrity_io_error(ic, "reading tags", r); + dm_integrity_io_error(ic, "writing tags", r); } atomic_inc(&comp.in_flight); From 4e53905a9da1736a448ab89f455ce3454de9e633 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:24:05 +0200 Subject: [PATCH 02/46] dm-integrity: clean-up error handling Add "goto bad" to error handling. This commit doesn't fix any bug, just cleans up the code. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-integrity.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 4daa3dc6caf8..e65aec9b7a28 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -4634,6 +4634,7 @@ static int create_journal(struct dm_integrity_c *ic, char **error) if (!ic->journal_tree) { *error = "Could not allocate memory for journal tree"; r = -ENOMEM; + goto bad; } bad: kfree(crypt_data); From 553f9a9a36a600977f01bfbcc8139ce0ca419384 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:24:38 +0200 Subject: [PATCH 03/46] dm-integrity: fix wrong fallthrough in integrity_bio_wait If dm_integrity_map_inline returned DM_MAPIO_KILL, the code would set status BLK_STS_IOERR and then incorrectly fall through and submit the bio. Luckily, dm_integrity_map_inline can't return DM_MAPIO_KILL at this point, so the bug is just theoretical. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-integrity.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index e65aec9b7a28..5156079785dc 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -2782,7 +2782,8 @@ static void integrity_bio_wait(struct work_struct *w) switch (r) { case DM_MAPIO_KILL: bio->bi_status = BLK_STS_IOERR; - fallthrough; + bio_endio(bio); + return; case DM_MAPIO_REMAPPED: submit_bio_noacct(bio); fallthrough; From a02d51919b05a5b5b237012b3e6ea738bc533b08 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:25:21 +0200 Subject: [PATCH 04/46] dm-ioctl: delete a useless condition The condition "remaining <= 0" can never be true. The variable remaining has type size_t, thus it can't be negative. It can't be zero because we made sure earlier that "remaining > sizeof(struct dm_target_spec)" and then we added "sizeof(struct dm_target_spec)" to "outptr" (this means that we subtraceted "sizeof(struct dm_target_spec)" from "remaining"). Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-ioctl.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 61af2a437a05..d77e9971c28c 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1444,10 +1444,6 @@ static void retrieve_status(struct dm_table *table, outptr += sizeof(struct dm_target_spec); remaining = len - (outptr - outbuf); - if (remaining <= 0) { - param->flags |= DM_BUFFER_FULL_FLAG; - break; - } /* Get the status/table string from the target driver */ if (ti->type->status) { From d4ef8ad059e51348a613e7922b3462b0e949091b Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:25:53 +0200 Subject: [PATCH 05/46] dm-table: fix spelling Fix spelling: impementation -> implementation. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-table.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c index dc2eff6b739d..a483f49dd4ce 100644 --- a/drivers/md/dm-table.c +++ b/drivers/md/dm-table.c @@ -2035,7 +2035,7 @@ int dm_table_set_restrictions(struct dm_table *t, struct request_queue *q, limits->features &= ~BLK_FEAT_NOWAIT; /* - * The current polling impementation does not support request based + * The current polling implementation does not support request based * stacking. */ if (!__table_type_bio_based(t->type)) From 34dcadff8b70c409724506fbb42c366e3da2f5c8 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:26:54 +0200 Subject: [PATCH 06/46] dm-verity: remove pointless nested "bio" declaration Remove pointless declaration of "bio" and initialization using "dm_bio_from_per_bio_data". The variable "bio" is already declared and initialized in the upper block. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-4.6 --- drivers/md/dm-verity-target.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index 1b0763091254..241d3f3747e7 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -316,9 +316,7 @@ static int verity_verify_level(struct dm_verity *v, struct dm_verity_io *io, else if (verity_handle_err(v, DM_VERITY_BLOCK_TYPE_METADATA, hash_block)) { - struct bio *bio; io->had_mismatch = true; - bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); dm_audit_log_bio(DM_MSG_PREFIX, "verify-metadata", bio, block, 0); r = -EIO; From 00211ede62facec2915a7e6cfc17f8ce987a0078 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Wed, 15 Jul 2026 00:18:00 +0000 Subject: [PATCH 07/46] dm cache: parse invalidate_cblocks with kstrtouint() invalidate_cblocks parses cache block numbers with sscanf() and then stores them in the narrower dm_cblock_t type. Values larger than the cblock representation are truncated before invalidation, so a request for one cache block can invalidate a different block. Checking the parsed value after sscanf() is not sufficient because sscanf() does not reliably reject values beyond U64_MAX before storing into the destination. Such inputs can still be converted to a wrapped u64 value and then pass a later range check. Split ranges in place and parse each single value or range endpoint directly with kstrtouint() instead. This rejects malformed values and values that do not fit in dm_cblock_t before they can be converted to cblock values. The existing range validation continues to reject empty or out-of-cache ranges, including the single-value U32_MAX case whose exclusive end wraps to zero. Assisted-by: Codex:gpt-5.5-cyber-preview Signed-off-by: Samuel Moelius Signed-off-by: Mikulas Patocka Reviewed-by: Ming-Hung Tsai --- drivers/md/dm-cache-target.c | 56 ++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 097315a9bf0f..33dbc71b730f 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -3311,42 +3312,46 @@ struct cblock_range { dm_cblock_t end; }; +static inline dm_cblock_t cblock_succ(dm_cblock_t b) +{ + return to_cblock(from_cblock(b) + 1); +} + /* * A cache block range can take two forms: * * i) A single cblock, eg. '3456' * ii) A begin and end cblock with a dash between, eg. 123-234 */ -static int parse_cblock_range(struct cache *cache, const char *str, +static int parse_cblock_range(struct cache *cache, char *str, struct cblock_range *result) { - char dummy; - uint64_t b, e; + char *blocknr = strsep(&str, "-"); + unsigned int b, e; int r; - /* - * Try and parse form (ii) first. - */ - r = sscanf(str, "%llu-%llu%c", &b, &e, &dummy); + r = kstrtouint(blocknr, 10, &b); + if (r) + goto bad; + + result->begin = to_cblock(b); + + if (str) { + blocknr = str; + + r = kstrtouint(blocknr, 10, &e); + if (r) + goto bad; - if (r == 2) { - result->begin = to_cblock(b); result->end = to_cblock(e); - return 0; + } else { + result->end = cblock_succ(result->begin); } - /* - * That didn't work, try form (i). - */ - r = sscanf(str, "%llu%c", &b, &dummy); + return 0; - if (r == 1) { - result->begin = to_cblock(b); - result->end = to_cblock(from_cblock(result->begin) + 1u); - return 0; - } - - DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), str); +bad: + DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), blocknr); return -EINVAL; } @@ -3377,11 +3382,6 @@ static int validate_cblock_range(struct cache *cache, struct cblock_range *range return 0; } -static inline dm_cblock_t cblock_succ(dm_cblock_t b) -{ - return to_cblock(from_cblock(b) + 1); -} - static int request_invalidation(struct cache *cache, struct cblock_range *range) { int r = 0; @@ -3405,7 +3405,7 @@ static int request_invalidation(struct cache *cache, struct cblock_range *range) } static int process_invalidate_cblocks_message(struct cache *cache, unsigned int count, - const char **cblock_ranges) + char **cblock_ranges) { int r = 0; unsigned int i; @@ -3460,7 +3460,7 @@ static int cache_message(struct dm_target *ti, unsigned int argc, char **argv, } if (!strcasecmp(argv[0], "invalidate_cblocks")) - return process_invalidate_cblocks_message(cache, argc - 1, (const char **) argv + 1); + return process_invalidate_cblocks_message(cache, argc - 1, argv + 1); if (argc != 2) return -EINVAL; From c7391ebe33162c7962b313caea4d8e6b0bc2a671 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Sat, 11 Jul 2026 22:21:55 +0800 Subject: [PATCH 08/46] dm-switch: use WRITE_ONCE() in switch_region_table_write() switch_region_table_read() accesses the region table with READ_ONCE() and is called from the lockless switch_map() IO path. However, switch_region_table_write() stores to the same array with a plain assignment. This results in an inconsistent access pattern for a lockless shared variable and may trigger data race reports. Use WRITE_ONCE() to pair with the existing READ_ONCE() in switch_region_table_read(). Cc: stable@vger.kernel.org Fixes: 99eb1908e643 ("dm switch: factor out switch_region_table_read") Signed-off-by: Haotian Zhang Signed-off-by: Mikulas Patocka --- drivers/md/dm-switch.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-switch.c b/drivers/md/dm-switch.c index 5952f02de1e6..e5b507b4fa7b 100644 --- a/drivers/md/dm-switch.c +++ b/drivers/md/dm-switch.c @@ -184,7 +184,7 @@ static void switch_region_table_write(struct switch_ctx *sctx, unsigned long reg pte = sctx->region_table[region_index]; pte &= ~((((region_table_slot_t)1 << sctx->region_table_entry_bits) - 1) << bit); pte |= (region_table_slot_t)value << bit; - sctx->region_table[region_index] = pte; + WRITE_ONCE(sctx->region_table[region_index], pte); } /* From 0729a9b4ee5a4c30e4c3641bc8fc6d164e8617b4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 3 Jul 2026 09:09:18 +0700 Subject: [PATCH 09/46] dm: drop redundant nonseekable_open() return check nonseekable_open() never fails, so the error check is unnecessary. Remove the dead error handling path. Signed-off-by: Bui Duc Phuc Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index d77e9971c28c..daae22cf9639 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -2269,12 +2269,9 @@ static long dm_compat_ctl_ioctl(struct file *file, uint command, ulong u) static int dm_open(struct inode *inode, struct file *filp) { - int r; struct dm_file *priv; - r = nonseekable_open(inode, filp); - if (unlikely(r)) - return r; + nonseekable_open(inode, filp); priv = filp->private_data = kmalloc_obj(struct dm_file); if (!priv) From c92d632f8638df0c5b644739b1fd06e36a7630fb Mon Sep 17 00:00:00 2001 From: "Jiangong.Han" Date: Wed, 1 Jul 2026 16:21:02 +0800 Subject: [PATCH 10/46] dm: use `clear_and_wake_up_bit()` in device mapper The helper was introduced in 'commit 8236b0ae31c83 ("bdi: wake up concurrent wb_shutdown() callers.")' as a generic way of doing the same sequence of operations: clear_bit_unlock(); smp_mb__after_atomic(); wake_up_bit(); The helper was first implemented to avoid bugs caused by forgetting to call `wake_up_bit()` after `clear_bit_unlock()`. Replace the open-coded sequence with the helper to avoid duplicate code and reduce code paths to maintain. Suggested-by: code@agatha.dev Link: https://kernelnewbies.org/Beginner%20Cleanup%20and%20Refactor%20Tasks%20by%20Agatha%20Isabelle%20Moreira#task_001 Link: https://kernelnewbies.org/Beginner%20Cleanup%20and%20Refactor%20Tasks%20by%20Agatha%20Isabelle%20Moreira#task_002 Signed-off-by: Jiangong.Han Signed-off-by: Mikulas Patocka --- drivers/md/dm-snap.c | 4 +--- drivers/md/dm-zoned-metadata.c | 8 ++------ drivers/md/dm-zoned-reclaim.c | 4 +--- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index 1489fda9d24a..c8a9ca1eeae7 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -911,9 +911,7 @@ static int init_hash_tables(struct dm_snapshot *s) static void merge_shutdown(struct dm_snapshot *s) { - clear_bit_unlock(RUNNING_MERGE, &s->state_bits); - smp_mb__after_atomic(); - wake_up_bit(&s->state_bits, RUNNING_MERGE); + clear_and_wake_up_bit(RUNNING_MERGE, &s->state_bits); } static struct bio *__release_queued_bios_after_merge(struct dm_snapshot *s) diff --git a/drivers/md/dm-zoned-metadata.c b/drivers/md/dm-zoned-metadata.c index f4f81c79a080..094b63215882 100644 --- a/drivers/md/dm-zoned-metadata.c +++ b/drivers/md/dm-zoned-metadata.c @@ -519,9 +519,7 @@ static void dmz_mblock_bio_end_io(struct bio *bio) else flag = DMZ_META_READING; - clear_bit_unlock(flag, &mblk->state); - smp_mb__after_atomic(); - wake_up_bit(&mblk->state, flag); + clear_and_wake_up_bit(flag, &mblk->state); bio_put(bio); } @@ -1910,9 +1908,7 @@ void dmz_unlock_zone_reclaim(struct dm_zone *zone) WARN_ON(dmz_is_active(zone)); WARN_ON(!dmz_in_reclaim(zone)); - clear_bit_unlock(DMZ_RECLAIM, &zone->flags); - smp_mb__after_atomic(); - wake_up_bit(&zone->flags, DMZ_RECLAIM); + clear_and_wake_up_bit(DMZ_RECLAIM, &zone->flags); } /* diff --git a/drivers/md/dm-zoned-reclaim.c b/drivers/md/dm-zoned-reclaim.c index ad9c7bc21d54..c041413c729e 100644 --- a/drivers/md/dm-zoned-reclaim.c +++ b/drivers/md/dm-zoned-reclaim.c @@ -106,9 +106,7 @@ static void dmz_reclaim_kcopy_end(int read_err, unsigned long write_err, else zrc->kc_err = 0; - clear_bit_unlock(DMZ_RECLAIM_KCOPY, &zrc->flags); - smp_mb__after_atomic(); - wake_up_bit(&zrc->flags, DMZ_RECLAIM_KCOPY); + clear_and_wake_up_bit(DMZ_RECLAIM_KCOPY, &zrc->flags); } /* From 78726ba0ffe3aa16848e86c4571ab647ff7b1b4e Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:41 +0100 Subject: [PATCH 11/46] dm: __list_versions(): Only process targets once Instead of doing a prescan to determine the length of buffer required, checking the supplied buffer is big enough, and then doing a second scan to fill the output buffer just do a single scan and detect when the buffer is too short. This removes any problems that might occur if a 'target' is added between the scans. For additional safety only call strlen(tt->name) once and use the returned length for everything (incuding the copy). Ensure than all the pad bytes between the entries are zero. Set param->data_size to the actual size of the data. It was slightly large because ALIGN_MASK was added in instead of the size being rounded up. Signed-off-by: David Laight Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 73 +++++++++++++++++-------------------------- 1 file changed, 29 insertions(+), 44 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index daae22cf9639..4f876af97eaf 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -54,10 +54,8 @@ struct hash_cell { }; struct vers_iter { - size_t param_size; struct dm_target_versions *vers, *old_vers; char *end; - uint32_t flags; }; @@ -771,41 +769,38 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_ return 0; } -static void list_version_get_needed(struct target_type *tt, void *needed_param) -{ - size_t *needed = needed_param; - - *needed += sizeof(struct dm_target_versions); - *needed += strlen(tt->name) + 1; - *needed += ALIGN_MASK; -} - static void list_version_get_info(struct target_type *tt, void *param) { struct vers_iter *info = param; + struct dm_target_versions *vers = info->vers; + size_t name_len = strlen(tt->name); - /* Check space - it might have changed since the first iteration */ - if ((char *)info->vers + sizeof(struct dm_target_versions) + strlen(tt->name) + 1 > info->end) { - info->flags = DM_BUFFER_FULL_FLAG; + if (!vers) + return; + + info->old_vers = vers; + info->vers = align_ptr((void *)(info->vers + 1) + name_len + 1); + + /* Check space */ + if ((char *)info->vers > info->end) { + info->vers = NULL; return; } - if (info->old_vers) - info->old_vers->next = (uint32_t) ((void *)info->vers - (void *)info->old_vers); + /* Zero padding and terminate vers->name[] */ + ((u64 *)info->vers)[-1] = 0; - info->vers->version[0] = tt->version[0]; - info->vers->version[1] = tt->version[1]; - info->vers->version[2] = tt->version[2]; - info->vers->next = 0; - strcpy(info->vers->name, tt->name); + vers->next = (char *)info->vers - (char *)vers; - info->old_vers = info->vers; - info->vers = align_ptr((void *)(info->vers + 1) + strlen(tt->name) + 1); + vers->version[0] = tt->version[0]; + vers->version[1] = tt->version[1]; + vers->version[2] = tt->version[2]; + memcpy(vers->name, tt->name, name_len); } static int __list_versions(struct dm_ioctl *param, size_t param_size, const char *name) { - size_t len, needed = 0; + size_t len; struct dm_target_versions *vers; struct vers_iter iter_info; struct target_type *tt = NULL; @@ -816,41 +811,31 @@ static int __list_versions(struct dm_ioctl *param, size_t param_size, const char return -EINVAL; } - /* - * Loop through all the devices working out how much - * space we need. - */ - if (!tt) - dm_target_iterate(list_version_get_needed, &needed); - else - list_version_get_needed(tt, &needed); - /* * Grab our output buffer. */ vers = get_result_buffer(param, param_size, &len); - if (len < needed) { - param->flags |= DM_BUFFER_FULL_FLAG; - goto out; - } - param->data_size = param->data_start + needed; - iter_info.param_size = param_size; iter_info.old_vers = NULL; iter_info.vers = vers; - iter_info.flags = 0; - iter_info.end = (char *)vers + needed; + iter_info.end = (char *)vers + len; /* - * Now loop through filling out the names & versions. + * Loop through filling out the names & versions. */ if (!tt) dm_target_iterate(list_version_get_info, &iter_info); else list_version_get_info(tt, &iter_info); - param->flags |= iter_info.flags; - out: + if (iter_info.vers) { + if (iter_info.old_vers) + iter_info.old_vers->next = 0; + param->data_size = param->data_start + ((char *)iter_info.vers - (char *)vers); + } else { + param->flags |= DM_BUFFER_FULL_FLAG; + } + if (tt) dm_put_target_type(tt); return 0; From 96979634af39a3f176f3c213fdaee274e869057c Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:42 +0100 Subject: [PATCH 12/46] dm: list_devices(): Only process devices once Instead of doing a prescan to determine the length of buffer required, checking the supplied buffer is big enough, and then doing a second scan to fill the output buffer just do a single scan and detect when the buffer is too short. For additional safety only call strlen() once and use the returned length for everything (incuding the copy). Ensure than all the pad bytes between the entries are zero. Signed-off-by: David Laight Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 87 ++++++++++++++++++++----------------------- 1 file changed, 40 insertions(+), 47 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 4f876af97eaf..4dd46c7b4fdf 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -692,79 +692,72 @@ static int list_devices(struct file *filp, struct dm_ioctl *param, size_t param_ { struct rb_node *n; struct hash_cell *hc; - size_t len, needed = 0; - struct gendisk *disk; - struct dm_name_list *orig_nl, *nl, *old_nl = NULL; + size_t len; + struct dm_name_list *nl, *old_nl = NULL; + void *result_start, *result_limit; uint32_t *event_nr; - down_write(&_hash_lock); - - /* - * Loop through all the devices working out how much - * space we need. - */ - for (n = rb_first(&name_rb_tree); n; n = rb_next(n)) { - hc = container_of(n, struct hash_cell, name_node); - if (!filter_device(hc, param->name, param->uuid)) - continue; - needed += align_val(offsetof(struct dm_name_list, name) + strlen(hc->name) + 1); - needed += align_val(sizeof(uint32_t) * 2); - if (param->flags & DM_UUID_FLAG && hc->uuid) - needed += align_val(strlen(hc->uuid) + 1); - } - /* * Grab our output buffer. */ - nl = orig_nl = get_result_buffer(param, param_size, &len); - if (len < needed || len < sizeof(nl->dev)) { - param->flags |= DM_BUFFER_FULL_FLAG; - goto out; - } - param->data_size = param->data_start + needed; + nl = result_start = get_result_buffer(param, param_size, &len); + result_limit = result_start + len; - nl->dev = 0; /* Flags no data */ + if (len >= sizeof(*nl)) + nl->dev = 0; /* Flags no data */ + + down_write(&_hash_lock); /* - * Now loop through filling out the names. + * Loop through filling out the names. */ for (n = rb_first(&name_rb_tree); n; n = rb_next(n)) { - void *uuid_ptr; + void *next_nl; hc = container_of(n, struct hash_cell, name_node); if (!filter_device(hc, param->name, param->uuid)) continue; - if (old_nl) - old_nl->next = (uint32_t) ((void *) nl - - (void *) old_nl); - disk = dm_disk(hc->md); - nl->dev = huge_encode_dev(disk_devt(disk)); - nl->next = 0; - strcpy(nl->name, hc->name); - old_nl = nl; - event_nr = align_ptr(nl->name + strlen(hc->name) + 1); + len = strlen(hc->name); + event_nr = align_ptr(nl->name + len + 1); + next_nl = event_nr + 2; + if (next_nl > result_limit) + break; + + ((u64 *)event_nr)[-1] = 0; + memcpy(nl->name, hc->name, len); + + nl->dev = huge_encode_dev(disk_devt(dm_disk(hc->md))); + event_nr[0] = dm_get_event_nr(hc->md); event_nr[1] = 0; - uuid_ptr = align_ptr(event_nr + 2); + if (param->flags & DM_UUID_FLAG) { if (hc->uuid) { + len = strlen(hc->uuid); + next_nl = align_ptr(next_nl + len + 1); + if (next_nl > result_limit) + break; event_nr[1] |= DM_NAME_LIST_FLAG_HAS_UUID; - strcpy(uuid_ptr, hc->uuid); - uuid_ptr = align_ptr(uuid_ptr + strlen(hc->uuid) + 1); + ((u64 *)next_nl)[-1] = 0; + memcpy(event_nr + 2, hc->uuid, len); } else { event_nr[1] |= DM_NAME_LIST_FLAG_DOESNT_HAVE_UUID; } } - nl = uuid_ptr; + nl->next = next_nl - (void *)nl; + old_nl = nl; + nl = next_nl; } - /* - * If mismatch happens, security may be compromised due to buffer - * overflow, so it's better to crash. - */ - BUG_ON((char *)nl - (char *)orig_nl != needed); - out: + if (old_nl) + old_nl->next = 0; + + if (n) + param->flags |= DM_BUFFER_FULL_FLAG; + else + param->data_size = param->data_start + ((void *)nl - result_start); + up_write(&_hash_lock); return 0; } From cd516571e975b2d2ed4aeac47c87a3064e2b2633 Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:43 +0100 Subject: [PATCH 13/46] dm: lookup_ioctl(): Use designated array initialers Use designated initialisers for the _ioctls[] array and delete the unused field that contained the array index. Makes the code more robust against the order of the initialers. Any uninitialised entries would be processed corretly. Signed-off-by: David Laight Signed-off-by: Mikulas Patocka --- drivers/md/dm-ioctl.c | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/drivers/md/dm-ioctl.c b/drivers/md/dm-ioctl.c index 4dd46c7b4fdf..a6b8e97755cd 100644 --- a/drivers/md/dm-ioctl.c +++ b/drivers/md/dm-ioctl.c @@ -1972,33 +1972,32 @@ static int target_message(struct file *filp, struct dm_ioctl *param, size_t para static ioctl_fn lookup_ioctl(unsigned int cmd, int *ioctl_flags) { static const struct { - int cmd; int flags; ioctl_fn fn; } _ioctls[] = { - {DM_VERSION_CMD, 0, NULL}, /* version is dealt with elsewhere */ - {DM_REMOVE_ALL_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, remove_all}, - {DM_LIST_DEVICES_CMD, 0, list_devices}, + [DM_VERSION_CMD] = {0, NULL}, /* version is dealt with elsewhere */ + [DM_REMOVE_ALL_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, remove_all}, + [DM_LIST_DEVICES_CMD] = {0, list_devices}, - {DM_DEV_CREATE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_create}, - {DM_DEV_REMOVE_CMD, IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_remove}, - {DM_DEV_RENAME_CMD, IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_rename}, - {DM_DEV_SUSPEND_CMD, IOCTL_FLAGS_NO_PARAMS, dev_suspend}, - {DM_DEV_STATUS_CMD, IOCTL_FLAGS_NO_PARAMS, dev_status}, - {DM_DEV_WAIT_CMD, 0, dev_wait}, + [DM_DEV_CREATE_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_create}, + [DM_DEV_REMOVE_CMD] = {IOCTL_FLAGS_NO_PARAMS | IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_remove}, + [DM_DEV_RENAME_CMD] = {IOCTL_FLAGS_ISSUE_GLOBAL_EVENT, dev_rename}, + [DM_DEV_SUSPEND_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_suspend}, + [DM_DEV_STATUS_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_status}, + [DM_DEV_WAIT_CMD] = {0, dev_wait}, - {DM_TABLE_LOAD_CMD, 0, table_load}, - {DM_TABLE_CLEAR_CMD, IOCTL_FLAGS_NO_PARAMS, table_clear}, - {DM_TABLE_DEPS_CMD, 0, table_deps}, - {DM_TABLE_STATUS_CMD, 0, table_status}, + [DM_TABLE_LOAD_CMD] = {0, table_load}, + [DM_TABLE_CLEAR_CMD] = {IOCTL_FLAGS_NO_PARAMS, table_clear}, + [DM_TABLE_DEPS_CMD] = {0, table_deps}, + [DM_TABLE_STATUS_CMD] = {0, table_status}, - {DM_LIST_VERSIONS_CMD, 0, list_versions}, + [DM_LIST_VERSIONS_CMD] = {0, list_versions}, - {DM_TARGET_MSG_CMD, 0, target_message}, - {DM_DEV_SET_GEOMETRY_CMD, 0, dev_set_geometry}, - {DM_DEV_ARM_POLL_CMD, IOCTL_FLAGS_NO_PARAMS, dev_arm_poll}, - {DM_GET_TARGET_VERSION_CMD, 0, get_target_version}, - {DM_MPATH_PROBE_PATHS_CMD, 0, NULL}, /* block device ioctl */ + [DM_TARGET_MSG_CMD] = {0, target_message}, + [DM_DEV_SET_GEOMETRY_CMD] = {0, dev_set_geometry}, + [DM_DEV_ARM_POLL_CMD] = {IOCTL_FLAGS_NO_PARAMS, dev_arm_poll}, + [DM_GET_TARGET_VERSION_CMD] = {0, get_target_version}, + [DM_MPATH_PROBE_PATHS_CMD] = {0, NULL}, /* block device ioctl */ }; if (unlikely(cmd >= ARRAY_SIZE(_ioctls))) From 22eb919f1b7900e2af0e952b1f1bee522f612b4a Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Thu, 16 Jul 2026 16:02:48 +0200 Subject: [PATCH 14/46] dm-inlinecrypt: don't overwrite the error with -EINVAL get_key_size already returns -EINVAL on error, so we don't have to overwrite it again. No functional change. Signed-off-by: Mikulas Patocka --- drivers/md/dm-inlinecrypt.c | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/md/dm-inlinecrypt.c b/drivers/md/dm-inlinecrypt.c index 41293c18d10f..863cb471e95e 100644 --- a/drivers/md/dm-inlinecrypt.c +++ b/drivers/md/dm-inlinecrypt.c @@ -347,7 +347,6 @@ static int inlinecrypt_ctr(struct dm_target *ti, unsigned int argc, char **argv) err = get_key_size(&argv[1]); if (err < 0) { ti->error = "Cannot parse key size"; - err = -EINVAL; goto bad; } ctx->key_size = err; From 90c990a68460d7b5720e5634cf650eccdf0f4098 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:54 -0500 Subject: [PATCH 15/46] dm-pcache: validate seg_id fields from persistent memory cache_pos_decode(), cache_key_decode() and the last-kset branches of cache_replay(), the writeback worker and the GC worker take a cache segment id from the cache device metadata and index cache->segments[] with it without checking it against cache->n_segs. That metadata is only CRC-protected with a fixed public seed, so whoever supplies the cache device on a table load (CAP_SYS_ADMIN) controls the id; an out-of-range value forms a wild pcache_cache_segment pointer that is dereferenced and written through -- an out-of-bounds read and write driven by on-disk data. Add cache_seg_id_valid() and reject an out-of-range id at each decode site, failing the operation with -EIO instead of indexing past the array. Bound the id against the initialized-segment count (cache_info.n_segs) rather than the physical device total. A forged cache_info.n_segs below seg_num otherwise leaves segments[cache_info.n_segs..seg_num) as zeroed structs whose data pointer is NULL, so a forged id in that window would still be dereferenced. A later patch guarantees cache_info.n_segs <= seg_num, and a driver-created cache sets the two equal, so valid images are unaffected. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.c | 4 ++++ drivers/md/dm-pcache/cache.h | 15 +++++++++++++++ drivers/md/dm-pcache/cache_gc.c | 16 ++++++++++++++-- drivers/md/dm-pcache/cache_key.c | 11 +++++++++++ drivers/md/dm-pcache/cache_writeback.c | 23 +++++++++++++++++++---- 5 files changed, 63 insertions(+), 6 deletions(-) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index bb1ada31e483..e9d2b87174d7 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -118,6 +118,9 @@ int cache_pos_decode(struct pcache_cache *cache, if (!latest_addr) return -EIO; + if (!cache_seg_id_valid(cache, latest.cache_seg_id)) + return -EIO; + pos->cache_seg = &cache->segments[latest.cache_seg_id]; pos->seg_off = latest.seg_off; *seq = latest.header.seq; @@ -155,6 +158,7 @@ static int cache_init(struct dm_pcache *pcache) cache->cache_dev = &pcache->cache_dev; cache->n_segs = cache_dev->seg_num; atomic_set(&cache->gc_errors, 0); + atomic_set(&cache->writeback_errors, 0); spin_lock_init(&cache->seg_map_lock); spin_lock_init(&cache->key_head_lock); diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h index 27613b56be54..919736379b76 100644 --- a/drivers/md/dm-pcache/cache.h +++ b/drivers/md/dm-pcache/cache.h @@ -180,6 +180,7 @@ struct pcache_cache { u32 advance; int ret; } writeback_ctx; + atomic_t writeback_errors; char gc_kset_onmedia_buf[PCACHE_KSET_ONMEDIA_SIZE_MAX]; struct delayed_work gc_work; @@ -420,6 +421,20 @@ static inline bool cache_seg_is_ctrl_seg(u32 cache_seg_id) return (cache_seg_id == 0); } +/** + * cache_seg_id_valid - Validate a cache segment id read from the cache device. + * @cache: Pointer to the pcache_cache structure. + * @cache_seg_id: Segment id decoded from on-media metadata. + * + * On-media segment ids are only protected by a CRC, which an attacker who can + * format the cache device computes over their chosen value. Reject any id that + * would index cache->segments[] out of bounds before it is dereferenced. + */ +static inline bool cache_seg_id_valid(struct pcache_cache *cache, u32 cache_seg_id) +{ + return cache_seg_id < cache->cache_info.n_segs; +} + /** * cache_key_cutfront - Cuts a specified length from the front of a cache key. * @key: Pointer to pcache_cache_key structure. diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c index 94f8b276a021..02fa0ce03134 100644 --- a/drivers/md/dm-pcache/cache_gc.c +++ b/drivers/md/dm-pcache/cache_gc.c @@ -74,11 +74,17 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t * @cache: Pointer to the pcache_cache structure. * @kset_onmedia: Pointer to the kset_onmedia structure for the last kset. */ -static void last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia) +static int last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *kset_onmedia) { struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); struct pcache_cache_segment *cur_seg, *next_seg; + if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) { + pcache_dev_err(pcache, "invalid next_cache_seg_id %u in gc (n_segs %u)\n", + kset_onmedia->next_cache_seg_id, cache->n_segs); + return -EIO; + } + cur_seg = cache->key_tail.cache_seg; next_seg = &cache->segments[kset_onmedia->next_cache_seg_id]; @@ -94,6 +100,8 @@ static void last_kset_gc(struct pcache_cache *cache, struct pcache_cache_kset_on spin_lock(&cache->seg_map_lock); __clear_bit(cur_seg->cache_seg_id, cache->seg_map); spin_unlock(&cache->seg_map_lock); + + return 0; } void pcache_cache_gc_fn(struct work_struct *work) @@ -130,7 +138,11 @@ void pcache_cache_gc_fn(struct work_struct *work) if (dirty_tail.cache_seg == key_tail.cache_seg) break; - last_kset_gc(cache, kset_onmedia); + ret = last_kset_gc(cache, kset_onmedia); + if (ret) { + atomic_inc(&cache->gc_errors); + return; + } continue; } diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index e068e878231b..8eec5238c5da 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -94,6 +94,12 @@ int cache_key_decode(struct pcache_cache *cache, key->off = key_onmedia->off; key->len = key_onmedia->len; + if (!cache_seg_id_valid(cache, key_onmedia->cache_seg_id)) { + pcache_dev_err(pcache, "invalid cache_seg_id %u in cache key (n_segs %u)\n", + key_onmedia->cache_seg_id, cache->n_segs); + return -EIO; + } + key->cache_pos.cache_seg = &cache->segments[key_onmedia->cache_seg_id]; key->cache_pos.seg_off = key_onmedia->cache_seg_off; @@ -789,6 +795,11 @@ int cache_replay(struct pcache_cache *cache) pcache_dev_debug(pcache, "last kset replay, next: %u\n", kset_onmedia->next_cache_seg_id); + if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) { + ret = -EIO; + goto out; + } + next_seg = &cache->segments[kset_onmedia->next_cache_seg_id]; pos->cache_seg = next_seg; diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c index 87a82b3fe836..7751468d5118 100644 --- a/drivers/md/dm-pcache/cache_writeback.c +++ b/drivers/md/dm-pcache/cache_writeback.c @@ -196,12 +196,18 @@ static int cache_kset_insert_tree(struct pcache_cache *cache, struct pcache_cach return ret; } -static void last_kset_writeback(struct pcache_cache *cache, +static int last_kset_writeback(struct pcache_cache *cache, struct pcache_cache_kset_onmedia *last_kset_onmedia) { struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); struct pcache_cache_segment *next_seg; + if (!cache_seg_id_valid(cache, last_kset_onmedia->next_cache_seg_id)) { + pcache_dev_err(pcache, "invalid next_cache_seg_id %u in writeback (n_segs %u)\n", + last_kset_onmedia->next_cache_seg_id, cache->n_segs); + return -EIO; + } + pcache_dev_debug(pcache, "last kset, next: %u\n", last_kset_onmedia->next_cache_seg_id); next_seg = &cache->segments[last_kset_onmedia->next_cache_seg_id]; @@ -211,6 +217,8 @@ static void last_kset_writeback(struct pcache_cache *cache, cache->dirty_tail.seg_off = 0; cache_encode_dirty_tail(cache); mutex_unlock(&cache->dirty_tail_lock); + + return 0; } void cache_writeback_fn(struct work_struct *work) @@ -229,6 +237,9 @@ void cache_writeback_fn(struct work_struct *work) if (pcache_is_stopping(pcache)) goto unlock; + if (atomic_read(&cache->writeback_errors)) + goto unlock; + kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf; mutex_lock(&cache->dirty_tail_lock); @@ -241,15 +252,19 @@ void cache_writeback_fn(struct work_struct *work) } if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) { - last_kset_writeback(cache, kset_onmedia); + ret = last_kset_writeback(cache, kset_onmedia); + if (ret) { + atomic_inc(&cache->writeback_errors); + goto unlock; + } delay = 0; goto queue_work; } ret = cache_kset_insert_tree(cache, kset_onmedia); if (ret) { - delay = PCACHE_CACHE_WRITEBACK_INTERVAL; - goto queue_work; + atomic_inc(&cache->writeback_errors); + goto unlock; } cache_wb_tree_writeback(cache, get_kset_onmedia_size(kset_onmedia)); From 32d1809da31094ef76fd98dc1f1a8b55ca1295dd Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:55 -0500 Subject: [PATCH 16/46] dm-pcache: validate geometry fields from on-disk cache_info cache_segs_init() iterates cache_info->n_segs times indexing cache->segments[], which is sized to the cache device geometry, and get_seg_id() takes each segment id from the on-media cache_info and the per-segment next_seg link. Both come from cache device metadata that is only CRC-protected with a fixed public seed, so whoever supplies the cache device on a table load (CAP_SYS_ADMIN) controls them: an oversized n_segs or an out-of-range id drives an out-of-bounds access of cache->segments[] and a wild CACHE_DEV_SEGMENT() pointer into the device mapping -- an out-of-bounds read and write from on-disk data. Reject an n_segs that exceeds the device segment count and a segment id that is out of range before either is used. Valid metadata is unaffected. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index e9d2b87174d7..e4784578b9af 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -251,6 +251,13 @@ static int get_seg_id(struct pcache_cache *cache, } else { *seg_id = cache->cache_info.seg_id; } + + if (*seg_id >= cache_dev->seg_num) { + pcache_dev_err(pcache, "invalid segment id %u from cache device (seg_num %u)\n", + *seg_id, cache_dev->seg_num); + ret = -EIO; + goto err; + } } return 0; err: @@ -266,6 +273,13 @@ static int cache_segs_init(struct pcache_cache *cache) int ret; u32 i; + if (cache_info->n_segs > cache->cache_dev->seg_num) { + pcache_dev_err(CACHE_TO_PCACHE(cache), + "cache_info n_segs %u exceeds cache device segments %u\n", + cache_info->n_segs, cache->cache_dev->seg_num); + return -EIO; + } + for (i = 0; i < cache_info->n_segs; i++) { ret = get_seg_id(cache, prev_cache_seg, new_cache, &seg_id); if (ret) From f11deb032fd84081e7831cffcba895d893054a22 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:56 -0500 Subject: [PATCH 17/46] dm-pcache: validate kset key_num and intra-segment bounds Two more fields decoded from the cache device go unbounded. The kset key_num drives cache_kset_crc() and the replay loop in cache_replay(), the writeback worker and the GC worker, but only the magic and a fixed-seed CRC are checked first, so a non-last kset whose key_num exceeds the PCACHE_KSET_KEYS_MAX buffer reads past its end before the CRC compare. A key's intra-segment offset and length in cache_key_decode() are taken verbatim, so a key running past its segment is replayed into the cache tree and the data CRC check and every later read hit then copy adjacent persistent memory into the caller's bio -- an out-of-bounds read that leaks to user space. Both fields are controlled by whoever supplies the cache device (CAP_SYS_ADMIN); the CRC seed is public. Add kset_onmedia_valid() to bound key_num before any kset read, and reject a key whose offset plus length, computed in 64 bits, exceeds the segment data_size. Valid metadata is unaffected. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.h | 21 +++++++++++++++++++++ drivers/md/dm-pcache/cache_gc.c | 8 ++++---- drivers/md/dm-pcache/cache_key.c | 10 +++++++++- drivers/md/dm-pcache/cache_writeback.c | 8 ++++---- 4 files changed, 38 insertions(+), 9 deletions(-) diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h index 919736379b76..d9e3e09f18e3 100644 --- a/drivers/md/dm-pcache/cache.h +++ b/drivers/md/dm-pcache/cache.h @@ -506,6 +506,27 @@ static inline u32 cache_key_data_crc(struct pcache_cache_key *key) return crc32c(PCACHE_CRC_SEED, data, key->len); } +/** + * kset_onmedia_valid - Validate a kset header read from the cache device. + * @kset_onmedia: Pointer to the kset copied from on-media metadata. + * + * The magic and CRC are attacker-computable (fixed public seed). A non-last + * kset stores key_num keys inline, and cache_kset_crc() and the replay loop + * read struct_size(.., data, key_num) bytes from a buffer sized for + * PCACHE_KSET_KEYS_MAX keys, so key_num must be bounded before any such use. + */ +static inline bool kset_onmedia_valid(struct pcache_cache_kset_onmedia *kset_onmedia) +{ + if (kset_onmedia->magic != PCACHE_KSET_MAGIC) + return false; + + if (!(kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) && + kset_onmedia->key_num > PCACHE_KSET_KEYS_MAX) + return false; + + return true; +} + static inline u32 cache_kset_crc(struct pcache_cache_kset_onmedia *kset_onmedia) { u32 crc_size; diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c index 02fa0ce03134..1ed513745023 100644 --- a/drivers/md/dm-pcache/cache_gc.c +++ b/drivers/md/dm-pcache/cache_gc.c @@ -44,11 +44,11 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t return false; } - /* Check if kset_onmedia is corrupted */ - if (kset_onmedia->magic != PCACHE_KSET_MAGIC) { - pcache_dev_debug(pcache, "gc error: magic is not as expected. key_tail: %u:%u magic: %llx, expected: %llx\n", + /* Reject a corrupted or out-of-bounds kset before reading its keys */ + if (!kset_onmedia_valid(kset_onmedia)) { + pcache_dev_debug(pcache, "gc error: invalid kset. key_tail: %u:%u magic: %llx, key_num: %u\n", key_tail->cache_seg->cache_seg_id, key_tail->seg_off, - kset_onmedia->magic, PCACHE_KSET_MAGIC); + kset_onmedia->magic, kset_onmedia->key_num); return false; } diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index 8eec5238c5da..f4459b2e1b3b 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -103,6 +103,14 @@ int cache_key_decode(struct pcache_cache *cache, key->cache_pos.cache_seg = &cache->segments[key_onmedia->cache_seg_id]; key->cache_pos.seg_off = key_onmedia->cache_seg_off; + if ((u64)key->cache_pos.seg_off + key->len > + key->cache_pos.cache_seg->segment.data_size) { + pcache_dev_err(pcache, "key seg_off %u + len %u exceeds segment data size %u\n", + key->cache_pos.seg_off, key->len, + key->cache_pos.cache_seg->segment.data_size); + return -EIO; + } + key->seg_gen = key_onmedia->seg_gen; key->flags = key_onmedia->flags; @@ -784,7 +792,7 @@ int cache_replay(struct pcache_cache *cache) goto out; } - if (kset_onmedia->magic != PCACHE_KSET_MAGIC || + if (!kset_onmedia_valid(kset_onmedia) || kset_onmedia->crc != cache_kset_crc(kset_onmedia)) { break; } diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c index 7751468d5118..34c34b448e04 100644 --- a/drivers/md/dm-pcache/cache_writeback.c +++ b/drivers/md/dm-pcache/cache_writeback.c @@ -55,11 +55,11 @@ static inline bool is_cache_clean(struct pcache_cache *cache, struct pcache_cach return true; } - /* Check if the magic number matches the expected value */ - if (kset_onmedia->magic != PCACHE_KSET_MAGIC) { - pcache_dev_debug(pcache, "dirty_tail: %u:%u magic: %llx, not expected: %llx\n", + /* Reject a corrupted or out-of-bounds kset before reading its keys */ + if (!kset_onmedia_valid(kset_onmedia)) { + pcache_dev_debug(pcache, "dirty_tail: %u:%u invalid kset magic: %llx, key_num: %u\n", dirty_tail->cache_seg->cache_seg_id, dirty_tail->seg_off, - kset_onmedia->magic, PCACHE_KSET_MAGIC); + kset_onmedia->magic, kset_onmedia->key_num); return true; } From d1898576090a10d2ac2715218a652e78fb65a6b0 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:57 -0500 Subject: [PATCH 18/46] dm-pcache: bound the persisted tail-position offset cache_pos_decode() takes the persisted key_tail and dirty_tail seg_off from the cache device and addresses within the segment with it. A seg_off at or past the segment data_size, controllable by whoever supplies the device (CAP_SYS_ADMIN), reads past the segment data. Reject a decoded seg_off that is not below the segment data_size. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index e4784578b9af..a94eadb7affd 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -122,6 +122,10 @@ int cache_pos_decode(struct pcache_cache *cache, return -EIO; pos->cache_seg = &cache->segments[latest.cache_seg_id]; + + if (latest.seg_off >= pos->cache_seg->segment.data_size) + return -EIO; + pos->seg_off = latest.seg_off; *seq = latest.header.seq; *index = (latest_addr - pos_onmedia); From 7ac1f10f987a2ffae4aecf0e2ceca8f552b665cb Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:58 -0500 Subject: [PATCH 19/46] dm-pcache: reject a kset that overruns its segment cache_replay(), the writeback worker and the GC worker read a kset of get_kset_onmedia_size() bytes and advance the position by it. A forged key_num makes that size exceed the segment's remaining space, so the advance walks past the segment and trips the cache_pos_advance() BUG_ON. Reject a kset whose on-media size exceeds cache_seg_remain() before use. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_gc.c | 5 +++++ drivers/md/dm-pcache/cache_key.c | 5 +++++ drivers/md/dm-pcache/cache_writeback.c | 5 +++++ 3 files changed, 15 insertions(+) diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c index 1ed513745023..c483c7a3afaf 100644 --- a/drivers/md/dm-pcache/cache_gc.c +++ b/drivers/md/dm-pcache/cache_gc.c @@ -146,6 +146,11 @@ void pcache_cache_gc_fn(struct work_struct *work) continue; } + if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&key_tail)) { + atomic_inc(&cache->gc_errors); + return; + } + for (i = 0; i < kset_onmedia->key_num; i++) { struct pcache_cache_key key_tmp = { 0 }; diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index f4459b2e1b3b..d00497f9e462 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -818,6 +818,11 @@ int cache_replay(struct pcache_cache *cache) } /* Replay the kset and check for errors. */ + if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(pos)) { + ret = -EIO; + goto out; + } + ret = kset_replay(cache, kset_onmedia); if (ret) goto out; diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c index 34c34b448e04..7a85a9aed18e 100644 --- a/drivers/md/dm-pcache/cache_writeback.c +++ b/drivers/md/dm-pcache/cache_writeback.c @@ -261,6 +261,11 @@ void cache_writeback_fn(struct work_struct *work) goto queue_work; } + if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&dirty_tail)) { + atomic_inc(&cache->writeback_errors); + goto unlock; + } + ret = cache_kset_insert_tree(cache, kset_onmedia); if (ret) { atomic_inc(&cache->writeback_errors); From 16c3b3a326e70f246a605b3dc27b7f83ba4743e3 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:59 -0500 Subject: [PATCH 20/46] dm-pcache: detect a cycle in the last-kset chain during replay cache_replay() follows the on-media last-kset chain by next_cache_seg_id with no cond_resched(). A forged chain that points back into a segment it has already visited makes the replay loop follow it forever. Cap the last-kset hops at cache->n_segs; a valid chain visits each segment at most once. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_key.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index d00497f9e462..2284dbc0807b 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -768,7 +768,7 @@ int cache_replay(struct pcache_cache *cache) struct pcache_cache_pos pos_tail; struct pcache_cache_pos *pos; struct pcache_cache_kset_onmedia *kset_onmedia; - u32 to_copy, count = 0; + u32 to_copy, count = 0, last_hops = 0; int ret = 0; kset_onmedia = kzalloc(PCACHE_KSET_ONMEDIA_SIZE_MAX, GFP_KERNEL); @@ -808,6 +808,11 @@ int cache_replay(struct pcache_cache *cache) goto out; } + if (++last_hops > cache->n_segs) { + ret = -EIO; + goto out; + } + next_seg = &cache->segments[kset_onmedia->next_cache_seg_id]; pos->cache_seg = next_seg; From 97fc4b53dbe4a983fdf093243067fa6a64562307 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:00 -0500 Subject: [PATCH 21/46] dm-pcache: bound the logical key offset from persistent memory cache_key_decode() takes a key's logical off from the cache device and later indexes req_key_tree->subtrees[] by it in get_subtree(). An off past the device forms a subtree pointer outside the array, which rb_insert() writes through during replay. Reject a key of zero length, or whose off+len (computed in 64 bits) exceeds the device size, before it is used. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_key.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index 2284dbc0807b..86cc9565ffc7 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -90,10 +90,19 @@ int cache_key_decode(struct pcache_cache *cache, struct pcache_cache_key *key) { struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); + u64 dev_bytes = (u64)cache->dev_size << SECTOR_SHIFT; key->off = key_onmedia->off; key->len = key_onmedia->len; + if (key_onmedia->len == 0 || + key_onmedia->len > dev_bytes || + key_onmedia->off > dev_bytes - key_onmedia->len) { + pcache_dev_err(pcache, "key off %llu + len %u exceeds device size\n", + key_onmedia->off, key_onmedia->len); + return -EIO; + } + if (!cache_seg_id_valid(cache, key_onmedia->cache_seg_id)) { pcache_dev_err(pcache, "invalid cache_seg_id %u in cache key (n_segs %u)\n", key_onmedia->cache_seg_id, cache->n_segs); From becf07e2b0053027495ecd671b1f82fb2e615f68 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:01 -0500 Subject: [PATCH 22/46] dm-pcache: clamp the tail kset read to the segment data region The tail-kset read in cache_replay(), the writeback worker and the GC worker bounds its length by PCACHE_SEG_SIZE - seg_off, the raw segment size rather than the data region. A tail near the segment end reads past the segment data into the following control area. Clamp the read to cache_seg_remain(), the data region. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_gc.c | 2 +- drivers/md/dm-pcache/cache_key.c | 2 +- drivers/md/dm-pcache/cache_writeback.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c index c483c7a3afaf..d551848912b4 100644 --- a/drivers/md/dm-pcache/cache_gc.c +++ b/drivers/md/dm-pcache/cache_gc.c @@ -37,7 +37,7 @@ static bool need_gc(struct pcache_cache *cache, struct pcache_cache_pos *dirty_t kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->gc_kset_onmedia_buf; - to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - key_tail->seg_off); + to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(key_tail)); ret = copy_mc_to_kernel(kset_onmedia, key_addr, to_copy); if (ret) { pcache_dev_err(pcache, "error to read kset: %d", ret); diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index 86cc9565ffc7..f3ce319037be 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -794,7 +794,7 @@ int cache_replay(struct pcache_cache *cache) __set_bit(pos->cache_seg->cache_seg_id, cache->seg_map); while (true) { - to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - pos->seg_off); + to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(pos)); ret = copy_mc_to_kernel(kset_onmedia, cache_pos_addr(pos), to_copy); if (ret) { ret = -EIO; diff --git a/drivers/md/dm-pcache/cache_writeback.c b/drivers/md/dm-pcache/cache_writeback.c index 7a85a9aed18e..c8a4c8110a58 100644 --- a/drivers/md/dm-pcache/cache_writeback.c +++ b/drivers/md/dm-pcache/cache_writeback.c @@ -48,7 +48,7 @@ static inline bool is_cache_clean(struct pcache_cache *cache, struct pcache_cach addr = cache_pos_addr(dirty_tail); kset_onmedia = (struct pcache_cache_kset_onmedia *)cache->wb_kset_onmedia_buf; - to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, PCACHE_SEG_SIZE - dirty_tail->seg_off); + to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(dirty_tail)); ret = copy_mc_to_kernel(kset_onmedia, addr, to_copy); if (ret) { pcache_dev_err(pcache, "error to read kset: %d", ret); From 62d92e45abe9e087370f9fc5d876b95673aced34 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:02 -0500 Subject: [PATCH 23/46] dm-pcache: validate on-media seg_num against the cache device size seg_num is read from the crc32c-only superblock, so whoever supplies the cache device on a table load (CAP_SYS_ADMIN) controls it. It sizes cache->segments[] and is the value every later on-media segment id is bounded against, yet it is never checked against the device. Because cache_dev->mapping is the direct map of the pmem, CACHE_DEV_SEGMENT() for a segment id past the device resolves to ordinary kernel memory beyond the mapping; a new-cache init reaching such an id has cache_seg_init() -> cache_dev_zero_range() memset() 12 KiB over that memory -- an out-of-bounds write into the kernel heap at table load. A zero seg_num makes the segment allocations ZERO_SIZE_PTR. Reject a seg_num that is zero, larger than the device can hold, or larger than PCACHE_CACHE_SEGS_MAX before it is used. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_dev.c | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-pcache/cache_dev.c b/drivers/md/dm-pcache/cache_dev.c index ece689e6ce59..f0259353ee39 100644 --- a/drivers/md/dm-pcache/cache_dev.c +++ b/drivers/md/dm-pcache/cache_dev.c @@ -242,6 +242,8 @@ int cache_dev_start(struct dm_pcache *pcache) struct pcache_cache_dev *cache_dev = &pcache->cache_dev; struct pcache_sb sb; bool format = false; + u32 seg_num; + u64 max_segs; int ret; mutex_init(&cache_dev->seg_lock); @@ -269,7 +271,25 @@ int cache_dev_start(struct dm_pcache *pcache) goto dax_release; cache_dev->sb_flags = le32_to_cpu(sb.flags); - ret = cache_dev_init(cache_dev, le32_to_cpu(sb.seg_num)); + + /* + * seg_num is read from the crc32c-only superblock, so whoever supplies + * the cache device controls it. It is the ceiling every later on-media + * segment id is validated against, so bound it against what the device + * physically holds before it is trusted, or a forged seg_num lets a + * segment id address past the DAX mapping. + */ + seg_num = le32_to_cpu(sb.seg_num); + max_segs = (bdev_nr_bytes(cache_dev->dm_dev->bdev) - PCACHE_SEGMENTS_OFF) / + PCACHE_SEG_SIZE; + if (seg_num == 0 || seg_num > max_segs || seg_num > PCACHE_CACHE_SEGS_MAX) { + pcache_dev_err(pcache, "invalid seg_num %u from cache device (device holds %llu, max %u)\n", + seg_num, max_segs, (u32)PCACHE_CACHE_SEGS_MAX); + ret = -EIO; + goto dax_release; + } + + ret = cache_dev_init(cache_dev, seg_num); if (ret) goto dax_release; From 58d620ee9e01d4bdbceaf2ae1450d307a2a9d58b Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:03 -0500 Subject: [PATCH 24/46] dm-pcache: validate the persisted dirty_tail chain at load The writeback worker follows the persisted dirty_tail chain, which is decoded from the cache device independently of the key_tail chain that cache_replay() walks and bounds. A crafted image, whose on-media fields are authenticated only by a crc32c with a fixed seed, can aim dirty_tail at a chain of last ksets that never terminates, so cache_writeback_fn() re-arms itself with no delay forever. Walk the dirty_tail chain once at load with the same hop cap cache_replay() uses and fail the table load with -EIO if it does not reach an end within n_segs hops. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.c | 7 ++++ drivers/md/dm-pcache/cache.h | 2 + drivers/md/dm-pcache/cache_key.c | 69 ++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+) diff --git a/drivers/md/dm-pcache/cache.c b/drivers/md/dm-pcache/cache.c index a94eadb7affd..b0b3e21677de 100644 --- a/drivers/md/dm-pcache/cache.c +++ b/drivers/md/dm-pcache/cache.c @@ -202,6 +202,7 @@ static int cache_tail_init(struct pcache_cache *cache) { struct dm_pcache *pcache = CACHE_TO_PCACHE(cache); bool new_cache = !(cache->cache_info.flags & PCACHE_CACHE_FLAGS_INIT_DONE); + int ret; if (new_cache) { __set_bit(0, cache->seg_map); @@ -218,6 +219,12 @@ static int cache_tail_init(struct pcache_cache *cache) pcache_dev_err(pcache, "Corrupted key tail or dirty tail.\n"); return -EIO; } + + ret = cache_verify_dirty_tail(cache); + if (ret) { + pcache_dev_err(pcache, "dirty tail chain does not terminate (crafted cache image?)\n"); + return ret; + } } return 0; diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h index d9e3e09f18e3..8809ec5ae943 100644 --- a/drivers/md/dm-pcache/cache.h +++ b/drivers/md/dm-pcache/cache.h @@ -666,6 +666,8 @@ static inline int cache_decode_dirty_tail(struct pcache_cache *cache) &cache->dirty_tail_index); } +int cache_verify_dirty_tail(struct pcache_cache *cache); + int pcache_cache_init(void); void pcache_cache_exit(void); #endif /* _PCACHE_CACHE_H */ diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index f3ce319037be..1caea11a61a3 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -858,6 +858,75 @@ int cache_replay(struct pcache_cache *cache) return ret; } +/* + * cache_verify_dirty_tail - reject a persisted dirty_tail whose last-kset + * chain does not terminate. + * + * dirty_tail is decoded independently of the key_tail chain cache_replay() + * walks, so replay's hop cap does not cover it. A crafted chain that loops + * back on itself makes the writeback worker re-arm forever; walk it once here + * with the same cap and fail the load if it does not end within n_segs hops. + */ +int cache_verify_dirty_tail(struct pcache_cache *cache) +{ + struct pcache_cache_pos pos; + struct pcache_cache_kset_onmedia *kset_onmedia; + u32 to_copy, last_hops = 0, count = 0; + int ret = 0; + + kset_onmedia = kzalloc(PCACHE_KSET_ONMEDIA_SIZE_MAX, GFP_KERNEL); + if (!kset_onmedia) + return -ENOMEM; + + cache_pos_copy(&pos, &cache->dirty_tail); + + while (true) { + to_copy = min(PCACHE_KSET_ONMEDIA_SIZE_MAX, cache_seg_remain(&pos)); + ret = copy_mc_to_kernel(kset_onmedia, cache_pos_addr(&pos), to_copy); + if (ret) { + ret = -EIO; + goto out; + } + + /* A missing, short or corrupt kset is the normal end of the chain. */ + if (!kset_onmedia_valid(kset_onmedia) || + kset_onmedia->crc != cache_kset_crc(kset_onmedia)) { + ret = 0; + goto out; + } + + if (kset_onmedia->flags & PCACHE_KSET_FLAGS_LAST) { + if (!cache_seg_id_valid(cache, kset_onmedia->next_cache_seg_id)) { + ret = -EIO; + goto out; + } + + if (++last_hops > cache->n_segs) { + ret = -EIO; + goto out; + } + + pos.cache_seg = &cache->segments[kset_onmedia->next_cache_seg_id]; + pos.seg_off = 0; + continue; + } + + if (get_kset_onmedia_size(kset_onmedia) > cache_seg_remain(&pos)) { + ret = -EIO; + goto out; + } + + cache_pos_advance(&pos, get_kset_onmedia_size(kset_onmedia)); + if (++count > 512) { + cond_resched(); + count = 0; + } + } +out: + kfree(kset_onmedia); + return ret; +} + int cache_tree_init(struct pcache_cache *cache, struct pcache_cache_tree *cache_tree, u32 n_subtrees) { int ret; From 2df0fc042e299bae3c0f60ea5cd2af9285658e9f Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:04 -0500 Subject: [PATCH 25/46] dm-pcache: only hand out initialized cache segments get_cache_segment() scans the segment map up to cache->n_segs, the physical device segment count, but cache_segs_init() only initializes the first cache_info->n_segs segments. A crafted image with cache_info->n_segs smaller than the device count leaves the remaining pcache_cache_segment structs zeroed (segment.data == NULL), and the allocator can hand one to cache_kset_close(), which writes through the returned segment's data pointer with no NULL check. Bound the allocator's search to cache_info->n_segs so only initialized segments are ever returned. A conforming cache sets n_segs equal to the device segment count, so this rejects nothing legitimate. Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_segment.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-pcache/cache_segment.c b/drivers/md/dm-pcache/cache_segment.c index 9d92e2b067ed..c698ebbc626d 100644 --- a/drivers/md/dm-pcache/cache_segment.c +++ b/drivers/md/dm-pcache/cache_segment.c @@ -243,8 +243,16 @@ struct pcache_cache_segment *get_cache_segment(struct pcache_cache *cache) spin_lock(&cache->seg_map_lock); again: - seg_id = find_next_zero_bit(cache->seg_map, cache->n_segs, cache->last_cache_seg); - if (seg_id == cache->n_segs) { + /* + * Only allocate initialized segments. cache_segs_init() initializes + * cache_info.n_segs of the cache->n_segs device segments; a forged + * smaller cache_info.n_segs leaves the rest as zeroed structs whose data + * pointer is NULL. Bounding the search to cache_info.n_segs keeps such a + * segment from reaching cache_kset_close(), which writes through it. + */ + seg_id = find_next_zero_bit(cache->seg_map, cache->cache_info.n_segs, + cache->last_cache_seg); + if (seg_id == cache->cache_info.n_segs) { /* reset the hint of ->last_cache_seg and retry */ if (cache->last_cache_seg) { cache->last_cache_seg = 0; From 4cf795dd0e3981ddc652c4d503b0880fe5250e88 Mon Sep 17 00:00:00 2001 From: Matthew Sakai Date: Fri, 17 Jul 2026 12:01:11 -0400 Subject: [PATCH 26/46] dm vdo: don't read repair field in loop condition Respell the vio launch loop to use the existing vio_count value. The repair completion is not guaranteed to persist after all of the metadata_vios are launched. This can not currently cause problems due to the way vio callbacks are handled, but it is technically not safe to access those fields. Signed-off-by: Matthew Sakai Signed-off-by: Mikulas Patocka --- drivers/md/dm-vdo/repair.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/md/dm-vdo/repair.c b/drivers/md/dm-vdo/repair.c index bfed62260280..e5263a5716f6 100644 --- a/drivers/md/dm-vdo/repair.c +++ b/drivers/md/dm-vdo/repair.c @@ -1696,6 +1696,7 @@ void vdo_repair(struct vdo_completion *parent) struct vdo *vdo = parent->vdo; struct recovery_journal *journal = vdo->recovery_journal; physical_block_number_t pbn = journal->origin; + block_count_t i; block_count_t remaining = journal->size; block_count_t vio_count = DIV_ROUND_UP(remaining, MAX_BLOCKS_PER_VIO); page_count_t page_count = min_t(page_count_t, @@ -1749,9 +1750,8 @@ void vdo_repair(struct vdo_completion *parent) remaining -= blocks; } - for (vio_count = 0; vio_count < repair->vio_count; - vio_count++, pbn += MAX_BLOCKS_PER_VIO) { - vdo_submit_metadata_vio(&repair->vios[vio_count], pbn, read_journal_endio, + for (i = 0; i < vio_count; i++, pbn += MAX_BLOCKS_PER_VIO) { + vdo_submit_metadata_vio(&repair->vios[i], pbn, read_journal_endio, handle_journal_load_error, REQ_OP_READ); } } From 73c37fe54cd056d07461b142ab0b8b81e1ef6ad8 Mon Sep 17 00:00:00 2001 From: Ilya Krutskih Date: Sun, 19 Jul 2026 13:01:03 +0000 Subject: [PATCH 27/46] dm raid1: reserve space for NUL-terminator in build_constructor_string() Reserve space for the termination NUL after the maximum 20 decimal digits of a long long value to avoid buffer overflow in sprintf(). Fixes: f5db4af466e2 ("dm raid1: add userspace log") Cc: stable@vger.kernel.org Signed-off-by: Ilya Krutskih Signed-off-by: Mikulas Patocka --- drivers/md/dm-log-userspace-base.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/md/dm-log-userspace-base.c b/drivers/md/dm-log-userspace-base.c index 9d5918eb0a30..3ca35aa4e057 100644 --- a/drivers/md/dm-log-userspace-base.c +++ b/drivers/md/dm-log-userspace-base.c @@ -139,6 +139,7 @@ static int build_constructor_string(struct dm_target *ti, str_size += strlen(argv[i]) + 1; /* +1 for space between args */ str_size += 20; /* Max number of chars in a printed u64 number */ + str_size++; /* For NUL-terminator */ str = kzalloc(str_size, GFP_KERNEL); if (!str) { From fb9e17287a4ea1cbbcedc77e6866978ecc2a7b55 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Mon, 20 Jul 2026 17:46:48 +0800 Subject: [PATCH 28/46] dm-pcache: fix implicit u8 truncation of gc_percent in message handler When setting gc_percent via message, kstrtoul parses the input into an unsigned long, which is then implicitly truncated to u8 when passed to pcache_cache_set_gc_percent(). For example, value 266 (0x10A) silently truncates to 10 (0x0A), successfully bypassing the > 90 upper bound check in pcache_cache_set_gc_percent(), and setting a different value than the user intended. Use kstrtou8 directly instead of kstrtoul, so that overflow values are properly rejected. Cc: stable@vger.kernel.org Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/dm_pcache.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-pcache/dm_pcache.c b/drivers/md/dm-pcache/dm_pcache.c index d5cfd162c063..645fc27d82ba 100644 --- a/drivers/md/dm-pcache/dm_pcache.c +++ b/drivers/md/dm-pcache/dm_pcache.c @@ -439,13 +439,13 @@ static int dm_pcache_message(struct dm_target *ti, unsigned int argc, char **argv, char *result, unsigned int maxlen) { struct dm_pcache *pcache = ti->private; - unsigned long val; + u8 val; if (argc != 2) goto err; if (!strcasecmp(argv[0], "gc_percent")) { - if (kstrtoul(argv[1], 10, &val)) + if (kstrtou8(argv[1], 10, &val)) goto err; return pcache_cache_set_gc_percent(&pcache->cache, val); From c2e894eac398b258f12fdec73ed6ba081047f7b3 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Mon, 20 Jul 2026 11:36:32 +0800 Subject: [PATCH 29/46] dm-pcache: fix use-after-free and invalid seg operations in kset_replay() In kset_replay, when key->seg_gen is stale (key->seg_gen < key->cache_pos.cache_seg->gen), cache_key_put(key) is called but then key->cache_pos.cache_seg is accessed as the argument to cache_seg_get(). This is a use-after-free on the freed key memory. Although mempool recycled memory is not immediately reclaimed or overwritten in practice, this is still a potential UAF bug. Additionally, for expired invalid keys, setting the cache->seg_map bit and calling cache_seg_get() is unreasonable since the corresponding segment data is no longer valid. Fix both issues by moving cache_seg_get() and __set_bit() after the gen check, so they only execute for valid keys, and using continue to skip invalid keys. Cc: stable@vger.kernel.org Fixes: 1d57628ff95b ("dm-pcache: add persistent cache target in device-mapper") Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_key.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index 1caea11a61a3..9e1808eeee85 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -751,18 +751,17 @@ static int kset_replay(struct pcache_cache *cache, struct pcache_cache_kset_onme goto err; } - __set_bit(key->cache_pos.cache_seg->cache_seg_id, cache->seg_map); - /* Check if the segment generation is valid for insertion. */ if (key->seg_gen < key->cache_pos.cache_seg->gen) { cache_key_put(key); - } else { - cache_subtree = get_subtree(&cache->req_key_tree, key->off); - spin_lock(&cache_subtree->tree_lock); - cache_key_insert(&cache->req_key_tree, key, true); - spin_unlock(&cache_subtree->tree_lock); + continue; } + __set_bit(key->cache_pos.cache_seg->cache_seg_id, cache->seg_map); + cache_subtree = get_subtree(&cache->req_key_tree, key->off); + spin_lock(&cache_subtree->tree_lock); + cache_key_insert(&cache->req_key_tree, key, true); + spin_unlock(&cache_subtree->tree_lock); cache_seg_get(key->cache_pos.cache_seg); } From 8765dcb96f00b65de4a59b29c25fed5d8b3cedf1 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Fri, 17 Jul 2026 16:38:54 +0800 Subject: [PATCH 30/46] dm-pcache: replace tabs with spaces in comments to fix ASCII diagram alignment Some editors interpret tabs as 4 spaces while others use 2, causing ASCII art diagrams in comments to misalign and hurt readability. Replace tabs with spaces to ensure consistent display across all editors. Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.h | 4 ++-- drivers/md/dm-pcache/cache_key.c | 2 +- drivers/md/dm-pcache/cache_req.c | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h index 8809ec5ae943..021292c43677 100644 --- a/drivers/md/dm-pcache/cache.h +++ b/drivers/md/dm-pcache/cache.h @@ -274,7 +274,7 @@ struct pcache_cache_subtree_walk_ctx { struct list_head *submit_req_list; /* - * |--------| key_tmp + * |--------| key_tmp * |====| key */ int (*before)(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp, @@ -282,7 +282,7 @@ struct pcache_cache_subtree_walk_ctx { /* * |----------| key_tmp - * |=====| key + * |=====| key */ int (*after)(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp, struct pcache_cache_subtree_walk_ctx *ctx); diff --git a/drivers/md/dm-pcache/cache_key.c b/drivers/md/dm-pcache/cache_key.c index 9e1808eeee85..195157bef0f9 100644 --- a/drivers/md/dm-pcache/cache_key.c +++ b/drivers/md/dm-pcache/cache_key.c @@ -288,7 +288,7 @@ int cache_subtree_walk(struct pcache_cache_subtree_walk_ctx *ctx) /* * If key_tmp starts after the end of key, stop traversing. - * |--------| + * |--------| * |====| */ if (cache_key_lstart(key_tmp) >= cache_key_lend(key)) { diff --git a/drivers/md/dm-pcache/cache_req.c b/drivers/md/dm-pcache/cache_req.c index 7854a30e07b7..cc5747d472e9 100644 --- a/drivers/md/dm-pcache/cache_req.c +++ b/drivers/md/dm-pcache/cache_req.c @@ -317,7 +317,7 @@ static struct pcache_backing_dev_req *get_pre_alloc_req(struct pcache_cache_subt * * The scenario handled here: * - * |--------| key_tmp (existing cached range) + * |--------| key_tmp (existing cached range) * |====| key (requested range, preceding key_tmp) * * Since `key` is before `key_tmp`, it signifies that the requested data @@ -352,7 +352,7 @@ static int read_before(struct pcache_cache_key *key, struct pcache_cache_key *ke * During cache_subtree_walk, this function manages a scenario where part of the * requested data range overlaps with an existing cache node (`key_tmp`). * - * |----------------| key_tmp (existing cached range) + * |----------------| key_tmp (existing cached range) * |===========| key (requested range, overlapping the tail of key_tmp) */ static int read_overlap_tail(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp, @@ -474,8 +474,8 @@ static int read_overlap_contain(struct pcache_cache_key *key, struct pcache_cach } /* - * |-----------| key_tmp (existing cached range) - * |====| key (requested range, fully within key_tmp) + * |-----------| key_tmp (existing cached range) + * |====| key (requested range, fully within key_tmp) * * If `key_tmp` contains valid cached data, this function copies the relevant * portion to the request's bio. Otherwise, it sends a backing request to @@ -524,8 +524,8 @@ static int read_overlap_contained(struct pcache_cache_key *key, struct pcache_ca } /* - * |--------| key_tmp (existing cached range) - * |==========| key (requested range, overlapping the head of key_tmp) + * |--------| key_tmp (existing cached range) + * |==========| key (requested range, overlapping the head of key_tmp) */ static int read_overlap_head(struct pcache_cache_key *key, struct pcache_cache_key *key_tmp, struct pcache_cache_subtree_walk_ctx *ctx) From a46fd918f7907ecdfd1b1d03210463eef80f0def Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Fri, 17 Jul 2026 16:38:56 +0800 Subject: [PATCH 31/46] dm-pcache: remove unused 'allocated' variable in cache_data_alloc() The 'allocated' variable is never non-zero when its value is consumed. 'to_alloc' was always equal to key->len, so replace them with key->len directly. Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_req.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/drivers/md/dm-pcache/cache_req.c b/drivers/md/dm-pcache/cache_req.c index cc5747d472e9..b2cb3c7615d4 100644 --- a/drivers/md/dm-pcache/cache_req.c +++ b/drivers/md/dm-pcache/cache_req.c @@ -39,13 +39,11 @@ static int cache_data_alloc(struct pcache_cache *cache, struct pcache_cache_key struct pcache_cache_pos *head_pos; struct pcache_cache_segment *cache_seg; u32 seg_remain; - u32 allocated = 0, to_alloc; int ret = 0; preempt_disable(); data_head = get_data_head(cache); again: - to_alloc = key->len - allocated; if (!data_head->head_pos.cache_seg) { seg_remain = 0; } else { @@ -57,10 +55,9 @@ static int cache_data_alloc(struct pcache_cache *cache, struct pcache_cache_key seg_remain = cache_seg_remain(head_pos); } - if (seg_remain > to_alloc) { + if (seg_remain > key->len) { /* If remaining space in segment is sufficient for the cache key, allocate it. */ - cache_pos_advance(head_pos, to_alloc); - allocated += to_alloc; + cache_pos_advance(head_pos, key->len); cache_seg_get(cache_seg); } else if (seg_remain) { /* If remaining space is not enough, allocate the remaining space and adjust the cache key length. */ From 44b43ec132f1cf3275ecc182d0c82f50c3c4c3d5 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 27 Jul 2026 22:26:36 +0200 Subject: [PATCH 32/46] dm: fix resume-vs-remove race If the user issues the resume ioctl and the remove ioctl at the same time, it may be possible that the device is resumed after it is suspended in __dm_destroy. The result is that the table is destroyed without calling the postsuspend method. Dm targets expect that they may be removed only after the postsuspend method method was called. If we break this expectation, it can cause misbehavior in various targets. For example - in the dm-integrity target, the reboot notifier is not unregistered, leading to use-after-free. Fix this bug by refusing to resume if the device is being destroyed. Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm.c b/drivers/md/dm.c index d413bfaf3527..e907fe278c72 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c @@ -3140,7 +3140,7 @@ int dm_resume(struct mapped_device *md) r = -EINVAL; mutex_lock_nested(&md->suspend_lock, SINGLE_DEPTH_NESTING); - if (!dm_suspended_md(md)) + if (!dm_suspended_md(md) || test_bit(DMF_FREEING, &md->flags)) goto out; if (dm_suspended_internally_md(md)) { From 5380c7f6335cc6d77eb77d065105e81155c4d9d3 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 27 Jul 2026 22:27:07 +0200 Subject: [PATCH 33/46] dm: fix race when loading and unloading a table If the userspace calls two concurrent table load ioctls and one of them succeeds and the other fails, there is a race condition because dm_setup_md_queue walks &md->table_devices without any lock. If the walk races with dm_table_destroy -> free_devices -> dm_put_table_device, there is access to invalid memory. Fix this race by extending the lock over the list walk. Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/drivers/md/dm.c b/drivers/md/dm.c index e907fe278c72..eb9e32995b79 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c @@ -2630,9 +2630,10 @@ int dm_setup_md_queue(struct mapped_device *md, struct dm_table *t) */ mutex_lock(&md->table_devices_lock); r = add_disk(md->disk); - mutex_unlock(&md->table_devices_lock); - if (r) + if (r) { + mutex_unlock(&md->table_devices_lock); return r; + } /* * Register the holder relationship for devices added before the disk @@ -2643,18 +2644,21 @@ int dm_setup_md_queue(struct mapped_device *md, struct dm_table *t) if (r) goto out_undo_holders; } + mutex_unlock(&md->table_devices_lock); r = dm_sysfs_init(md); if (r) - goto out_undo_holders; + goto lock_out_undo_holders; md->type = type; + return 0; +lock_out_undo_holders: + mutex_lock(&md->table_devices_lock); out_undo_holders: list_for_each_entry_continue_reverse(td, &md->table_devices, list) bd_unlink_disk_holder(td->dm_dev.bdev, md->disk); - mutex_lock(&md->table_devices_lock); del_gendisk(md->disk); mutex_unlock(&md->table_devices_lock); return r; From 62dc37a819a5a5de5cba989ad9e96ee214b9253e Mon Sep 17 00:00:00 2001 From: Keith Busch Date: Tue, 16 Jun 2026 08:05:53 -0700 Subject: [PATCH 34/46] dm-io: clone the source bio instead of copying its biovec For DM_IO_BIO requests, do_region() built each destination bio by walking the source bio's biovec and re-adding the pages one at a time, tracking the remaining transfer in sectors. The vector lengths are byte granular and need not be sector aligned (e.g. a misaligned O_DIRECT buffer split across pages), so the sector-based accounting could lose a sub-sector fragment: to_sector() truncated the remainder and the outer loop spun forever submitting empty bios, hanging the I/O. There is no need to rebuild the biovec at all. The destination reads into (or writes from) exactly the same pages as the source bio, so the bio can simply clone the source's biovec with bio_alloc_clone() and remap it to the target device. The clone inherits the source's iterator and alignment, and the block layer splits it to the target's limits on submission, so the whole region maps to a single cloned bio with no manual page copying or sector accounting. This removes the per-page copy path (and its open-coded bvec dpages helpers) for bio-backed I/O and fixes the hang on misaligned direct I/O to a dm-mirror device. Page-list, vma and kmem sources keep the existing copy path. Fixes: 7eac33186957 ("iomap: simplify direct io validity check") Fixes: 5ff3f74e145a ("block: simplify direct io validity check") Cc: stable@vger.kernel.org Reported-by: Dr. David Alan Gilbert Reported-by: Vjaceslavs Klimovs Signed-off-by: Keith Busch Signed-off-by: Mikulas Patocka --- drivers/md/dm-io.c | 67 +++++++++++++++++----------------------------- 1 file changed, 24 insertions(+), 43 deletions(-) diff --git a/drivers/md/dm-io.c b/drivers/md/dm-io.c index 1db565b37620..28adfeb58f24 100644 --- a/drivers/md/dm-io.c +++ b/drivers/md/dm-io.c @@ -170,12 +170,11 @@ struct dpages { struct page **p, unsigned long *len, unsigned int *offset); void (*next_page)(struct dpages *dp); - union { - unsigned int context_u; - struct bvec_iter context_bi; - }; + unsigned int context_u; void *context_ptr; + struct bio *orig_bio; + void *vma_invalidate_address; unsigned long vma_invalidate_size; }; @@ -210,44 +209,6 @@ static void list_dp_init(struct dpages *dp, struct page_list *pl, unsigned int o dp->context_ptr = pl; } -/* - * Functions for getting the pages from a bvec. - */ -static void bio_get_page(struct dpages *dp, struct page **p, - unsigned long *len, unsigned int *offset) -{ - struct bio_vec bvec = bvec_iter_bvec((struct bio_vec *)dp->context_ptr, - dp->context_bi); - - *p = bvec.bv_page; - *len = bvec.bv_len; - *offset = bvec.bv_offset; - - /* avoid figuring it out again in bio_next_page() */ - dp->context_bi.bi_sector = (sector_t)bvec.bv_len; -} - -static void bio_next_page(struct dpages *dp) -{ - unsigned int len = (unsigned int)dp->context_bi.bi_sector; - - bvec_iter_advance((struct bio_vec *)dp->context_ptr, - &dp->context_bi, len); -} - -static void bio_dp_init(struct dpages *dp, struct bio *bio) -{ - dp->get_page = bio_get_page; - dp->next_page = bio_next_page; - - /* - * We just use bvec iterator to retrieve pages, so it is ok to - * access the bvec table directly here - */ - dp->context_ptr = bio->bi_io_vec; - dp->context_bi = bio->bi_iter; -} - /* * Functions for getting the pages from a VMA. */ @@ -332,6 +293,21 @@ static void do_region(const blk_opf_t opf, unsigned int region, return; } + if (dp->orig_bio) { + bio = bio_alloc_clone(where->bdev, dp->orig_bio, GFP_NOIO, + &io->client->bios); + bio->bi_iter.bi_sector = where->sector; + bio->bi_iter.bi_size = where->count << SECTOR_SHIFT; + bio->bi_opf = opf; + bio->bi_end_io = endio; + bio->bi_ioprio = ioprio; + store_io_and_region_in_bio(bio, io, region); + + atomic_inc(&io->count); + submit_bio(bio); + return; + } + /* * where->count may be zero if op holds a flush and we need to * send a zero-sized flush. @@ -468,6 +444,7 @@ static int dp_init(struct dm_io_request *io_req, struct dpages *dp, dp->vma_invalidate_address = NULL; dp->vma_invalidate_size = 0; + dp->orig_bio = NULL; switch (io_req->mem.type) { case DM_IO_PAGE_LIST: @@ -475,7 +452,11 @@ static int dp_init(struct dm_io_request *io_req, struct dpages *dp, break; case DM_IO_BIO: - bio_dp_init(dp, io_req->mem.ptr.bio); + /* + * The destination bios clone this bio's biovec directly, so + * there are no per-page accessors to set up here. + */ + dp->orig_bio = io_req->mem.ptr.bio; break; case DM_IO_VMA: From 47a5e62f39875f371bded6e34ffb9cf15ccd813d Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 27 Jul 2026 23:09:28 +0200 Subject: [PATCH 35/46] dm-io: report non-retryable errors separatedly The error codes BLK_STS_NOTSUPP and BLK_STS_INVAL should not cause leg failure on dm-raid1. This patch changes the interface to dm-io, so that it reports two error bitmaps - error_bits and unsup_bits. The unsup_bit bitmap tracks BLK_STS_NOTSUPP or BLK_STS_INVAL errors, the error_bits bitmap tracks all the other errors. dm-raid1 is changed so that it won't fail a leg if it receives an error in the unsup_bits bitmap. This patch (with 62dc37a819a5) fixes misbehavior if the user uses unaligned bio vectors on dm-raid1. Fixes: 7eac33186957 ("iomap: simplify direct io validity check") Fixes: 5ff3f74e145a ("block: simplify direct io validity check") Cc: stable@vger.kernel.org Signed-off-by: Mikulas Patocka --- drivers/md/dm-bufio.c | 10 +++++----- drivers/md/dm-integrity.c | 32 +++++++++++++++++++------------- drivers/md/dm-io.c | 29 +++++++++++++++++++++-------- drivers/md/dm-kcopyd.c | 10 +++++----- drivers/md/dm-log.c | 4 ++-- drivers/md/dm-raid1.c | 26 +++++++++++++++++--------- drivers/md/dm-snap-persistent.c | 4 ++-- drivers/md/dm-verity-target.c | 2 +- drivers/md/dm-writecache.c | 14 ++++++++------ include/linux/dm-io.h | 6 +++--- 10 files changed, 83 insertions(+), 54 deletions(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index a458b9fd2fcd..d58eed96a8ff 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -1287,11 +1287,11 @@ static void free_buffer(struct dm_buffer *b) * dm-io completion routine. It just calls b->bio.bi_end_io, pretending * that the request was handled directly with bio interface. */ -static void dmio_complete(unsigned long error, void *context) +static void dmio_complete(unsigned long error, unsigned long unsup, void *context) { struct dm_buffer *b = context; - b->end_io(b, unlikely(error != 0) ? BLK_STS_IOERR : 0); + b->end_io(b, unlikely(error != 0) ? BLK_STS_IOERR : unlikely(unsup != 0) ? BLK_STS_NOTSUPP : 0); } static void use_dmio(struct dm_buffer *b, enum req_op op, sector_t sector, @@ -1319,7 +1319,7 @@ static void use_dmio(struct dm_buffer *b, enum req_op op, sector_t sector, io_req.mem.ptr.vma = (char *)b->data + offset; } - r = dm_io(&io_req, 1, ®ion, NULL, ioprio); + r = dm_io(&io_req, 1, ®ion, NULL, NULL, ioprio); if (unlikely(r)) b->end_io(b, errno_to_blk_status(r)); } @@ -2220,7 +2220,7 @@ int dm_bufio_issue_flush(struct dm_bufio_client *c) if (WARN_ON_ONCE(dm_bufio_in_request())) return -EINVAL; - return dm_io(&io_req, 1, &io_reg, NULL, IOPRIO_DEFAULT); + return dm_io(&io_req, 1, &io_reg, NULL, NULL, IOPRIO_DEFAULT); } EXPORT_SYMBOL_GPL(dm_bufio_issue_flush); @@ -2246,7 +2246,7 @@ int dm_bufio_issue_discard(struct dm_bufio_client *c, sector_t block, sector_t c if (WARN_ON_ONCE(dm_bufio_in_request())) return -EINVAL; /* discards are optional */ - return dm_io(&io_req, 1, &io_reg, NULL, IOPRIO_DEFAULT); + return dm_io(&io_req, 1, &io_reg, NULL, NULL, IOPRIO_DEFAULT); } EXPORT_SYMBOL_GPL(dm_bufio_issue_discard); diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 5156079785dc..c19efee569d1 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -562,7 +562,7 @@ static int sync_rw_sb(struct dm_integrity_c *ic, blk_opf_t opf) } } - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) return r; @@ -1035,12 +1035,14 @@ static void encrypt_journal(struct dm_integrity_c *ic, bool encrypt, unsigned in return crypt_journal(ic, encrypt, section, n_sections, comp); } -static void complete_journal_io(unsigned long error, void *context) +static void complete_journal_io(unsigned long error, unsigned long unsup, void *context) { struct journal_completion *comp = context; if (unlikely(error != 0)) dm_integrity_io_error(comp->ic, "writing journal", -EIO); + else if (unlikely(unsup != 0)) + dm_integrity_io_error(comp->ic, "writing journal", -EOPNOTSUPP); complete_journal_op(comp); } @@ -1055,7 +1057,7 @@ static void rw_journal_sectors(struct dm_integrity_c *ic, blk_opf_t opf, if (unlikely(dm_integrity_failed(ic))) { if (comp) - complete_journal_io(-1UL, comp); + complete_journal_io(-1UL, -1UL, comp); return; } @@ -1080,13 +1082,13 @@ static void rw_journal_sectors(struct dm_integrity_c *ic, blk_opf_t opf, io_loc.sector = ic->start + SB_SECTORS + sector; io_loc.count = n_sectors; - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) { dm_integrity_io_error(ic, (opf & REQ_OP_MASK) == REQ_OP_READ ? "reading journal" : "writing journal", r); if (comp) { WARN_ONCE(1, "asynchronous dm_io failed: %d", r); - complete_journal_io(-1UL, comp); + complete_journal_io(-1UL, -1UL, comp); } } } @@ -1177,7 +1179,7 @@ static void copy_from_journal(struct dm_integrity_c *ic, unsigned int section, u BUG_ON((target | n_sectors | offset) & (unsigned int)(ic->sectors_per_block - 1)); if (unlikely(dm_integrity_failed(ic))) { - fn(-1UL, data); + fn(-1UL, -1UL, data); return; } @@ -1197,10 +1199,10 @@ static void copy_from_journal(struct dm_integrity_c *ic, unsigned int section, u io_loc.sector = target; io_loc.count = n_sectors; - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) { WARN_ONCE(1, "asynchronous dm_io failed: %d", r); - fn(-1UL, data); + fn(-1UL, -1UL, data); } } @@ -1493,12 +1495,14 @@ struct flush_request { struct completion comp; }; -static void flush_notify(unsigned long error, void *fr_) +static void flush_notify(unsigned long error, unsigned long unsup, void *fr_) { struct flush_request *fr = fr_; if (unlikely(error != 0)) dm_integrity_io_error(fr->ic, "flushing disk cache", -EIO); + else if (unlikely(unsup != 0)) + dm_integrity_io_error(fr->ic, "flushing disk cache", -EOPNOTSUPP); complete(&fr->comp); } @@ -1521,7 +1525,7 @@ static void dm_integrity_flush_buffers(struct dm_integrity_c *ic, bool flush_dat fr.io_reg.count = 0; fr.ic = ic; init_completion(&fr.comp); - r = dm_io(&fr.io_req, 1, &fr.io_reg, NULL, IOPRIO_DEFAULT); + r = dm_io(&fr.io_req, 1, &fr.io_reg, NULL, NULL, IOPRIO_DEFAULT); BUG_ON(r); } @@ -1837,7 +1841,7 @@ static noinline void integrity_recheck(struct dm_integrity_io *dio, char *checks buffer_offset = (sector - io_loc.sector) << SECTOR_SHIFT; io_loc.count = round_up(io_loc.count, alignment); - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) { dio->bi_status = errno_to_blk_status(r); goto free_ret; @@ -2891,7 +2895,7 @@ static void integrity_commit(struct work_struct *w) } } -static void complete_copy_from_journal(unsigned long error, void *context) +static void complete_copy_from_journal(unsigned long error, unsigned long unsup, void *context) { struct journal_io *io = context; struct journal_completion *comp = io->comp; @@ -2901,6 +2905,8 @@ static void complete_copy_from_journal(unsigned long error, void *context) mempool_free(io, &ic->journal_io_mempool); if (unlikely(error != 0)) dm_integrity_io_error(ic, "copying from journal", -EIO); + else if (unlikely(unsup != 0)) + dm_integrity_io_error(ic, "copying from journal", -EOPNOTSUPP); complete_journal_op(comp); } @@ -3216,7 +3222,7 @@ static void integrity_recalc(struct work_struct *w) io_loc.sector = get_data_sector(ic, area, offset); io_loc.count = n_sectors; - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) { dm_integrity_io_error(ic, "reading data", r); goto err; diff --git a/drivers/md/dm-io.c b/drivers/md/dm-io.c index 28adfeb58f24..777b917be400 100644 --- a/drivers/md/dm-io.c +++ b/drivers/md/dm-io.c @@ -33,6 +33,7 @@ struct dm_io_client { */ struct io { unsigned long error_bits; + unsigned long unsup_bits; atomic_t count; struct dm_io_client *client; io_notify_fn callback; @@ -119,6 +120,7 @@ static void retrieve_io_and_region_from_bio(struct bio *bio, struct io **io, static void complete_io(struct io *io) { unsigned long error_bits = io->error_bits; + unsigned long unsup_bits = io->unsup_bits; io_notify_fn fn = io->callback; void *context = io->context; @@ -127,13 +129,17 @@ static void complete_io(struct io *io) io->vma_invalidate_size); mempool_free(io, &io->client->pool); - fn(error_bits, context); + fn(error_bits, unsup_bits, context); } static void dec_count(struct io *io, unsigned int region, blk_status_t error) { - if (error) - set_bit(region, &io->error_bits); + if (unlikely(error)) { + if (error == BLK_STS_NOTSUPP || error == BLK_STS_INVAL) + set_bit(region, &io->unsup_bits); + else + set_bit(region, &io->error_bits); + } if (atomic_dec_and_test(&io->count)) complete_io(io); @@ -394,6 +400,7 @@ static void async_io(struct dm_io_client *client, unsigned int num_regions, io = mempool_alloc(&client->pool, GFP_NOIO); io->error_bits = 0; + io->unsup_bits = 0; atomic_set(&io->count, 1); /* see dispatch_io() */ io->client = client; io->callback = fn; @@ -407,20 +414,23 @@ static void async_io(struct dm_io_client *client, unsigned int num_regions, struct sync_io { unsigned long error_bits; + unsigned long unsup_bits; struct completion wait; }; -static void sync_io_complete(unsigned long error, void *context) +static void sync_io_complete(unsigned long error, unsigned long unsup, void *context) { struct sync_io *sio = context; sio->error_bits = error; + sio->unsup_bits = unsup; complete(&sio->wait); } static int sync_io(struct dm_io_client *client, unsigned int num_regions, struct dm_io_region *where, blk_opf_t opf, struct dpages *dp, - unsigned long *error_bits, unsigned short ioprio) + unsigned long *error_bits, unsigned long *unsup_bits, + unsigned short ioprio) { struct sync_io sio; @@ -433,8 +443,10 @@ static int sync_io(struct dm_io_client *client, unsigned int num_regions, if (error_bits) *error_bits = sio.error_bits; + if (unsup_bits) + *unsup_bits = sio.unsup_bits; - return sio.error_bits ? -EIO : 0; + return sio.error_bits ? -EIO : sio.unsup_bits ? -EOPNOTSUPP : 0; } static int dp_init(struct dm_io_request *io_req, struct dpages *dp, @@ -481,7 +493,7 @@ static int dp_init(struct dm_io_request *io_req, struct dpages *dp, int dm_io(struct dm_io_request *io_req, unsigned int num_regions, struct dm_io_region *where, unsigned long *sync_error_bits, - unsigned short ioprio) + unsigned long *sync_unsup_bits, unsigned short ioprio) { int r; struct dpages dp; @@ -497,7 +509,8 @@ int dm_io(struct dm_io_request *io_req, unsigned int num_regions, if (!io_req->notify.fn) return sync_io(io_req->client, num_regions, where, - io_req->bi_opf, &dp, sync_error_bits, ioprio); + io_req->bi_opf, &dp, sync_error_bits, + sync_unsup_bits, ioprio); async_io(io_req->client, num_regions, where, io_req->bi_opf, &dp, io_req->notify.fn, io_req->notify.context, ioprio); diff --git a/drivers/md/dm-kcopyd.c b/drivers/md/dm-kcopyd.c index 96c8b8ff61c2..5c9c24a3dcd9 100644 --- a/drivers/md/dm-kcopyd.c +++ b/drivers/md/dm-kcopyd.c @@ -517,16 +517,16 @@ static int run_complete_job(struct kcopyd_job *job) return 0; } -static void complete_io(unsigned long error, void *context) +static void complete_io(unsigned long error, unsigned long unsup, void *context) { struct kcopyd_job *job = context; struct dm_kcopyd_client *kc = job->kc; io_job_finish(kc->throttle); - if (error) { + if (unlikely((error | unsup) != 0)) { if (op_is_write(job->op)) - job->write_err |= error; + job->write_err |= error | unsup; else job->read_err = 1; @@ -578,9 +578,9 @@ static int run_io_job(struct kcopyd_job *job) io_job_start(job->kc->throttle); if (job->op == REQ_OP_READ) - r = dm_io(&io_req, 1, &job->source, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &job->source, NULL, NULL, IOPRIO_DEFAULT); else - r = dm_io(&io_req, job->num_dests, job->dests, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, job->num_dests, job->dests, NULL, NULL, IOPRIO_DEFAULT); return r; } diff --git a/drivers/md/dm-log.c b/drivers/md/dm-log.c index 2ddeb4250c59..4c114fc5ef83 100644 --- a/drivers/md/dm-log.c +++ b/drivers/md/dm-log.c @@ -300,7 +300,7 @@ static int rw_header(struct log_c *lc, enum req_op op) { lc->io_req.bi_opf = op; - return dm_io(&lc->io_req, 1, &lc->header_location, NULL, IOPRIO_DEFAULT); + return dm_io(&lc->io_req, 1, &lc->header_location, NULL, NULL, IOPRIO_DEFAULT); } static int flush_header(struct log_c *lc) @@ -313,7 +313,7 @@ static int flush_header(struct log_c *lc) lc->io_req.bi_opf = REQ_OP_WRITE | REQ_PREFLUSH; - return dm_io(&lc->io_req, 1, &null_location, NULL, IOPRIO_DEFAULT); + return dm_io(&lc->io_req, 1, &null_location, NULL, NULL, IOPRIO_DEFAULT); } static int read_header(struct log_c *log) diff --git a/drivers/md/dm-raid1.c b/drivers/md/dm-raid1.c index de5c00704e69..da2a5e2002ec 100644 --- a/drivers/md/dm-raid1.c +++ b/drivers/md/dm-raid1.c @@ -258,7 +258,7 @@ static void fail_mirror(struct mirror *m, enum dm_raid1_error error_type) static int mirror_flush(struct dm_target *ti) { struct mirror_set *ms = ti->private; - unsigned long error_bits; + unsigned long error_bits, unsup_bits; unsigned int i; struct dm_io_region io[MAX_NR_MIRRORS]; @@ -277,8 +277,8 @@ static int mirror_flush(struct dm_target *ti) } error_bits = -1; - dm_io(&io_req, ms->nr_mirrors, io, &error_bits, IOPRIO_DEFAULT); - if (unlikely(error_bits != 0)) { + dm_io(&io_req, ms->nr_mirrors, io, &error_bits, &unsup_bits, IOPRIO_DEFAULT); + if (unlikely((error_bits | unsup_bits) != 0)) { for (i = 0; i < ms->nr_mirrors; i++) if (test_bit(i, &error_bits)) fail_mirror(ms->mirror + i, @@ -511,7 +511,7 @@ static void hold_bio(struct mirror_set *ms, struct bio *bio) * Reads *--------------------------------------------------------------- */ -static void read_callback(unsigned long error, void *context) +static void read_callback(unsigned long error, unsigned long unsup, void *context) { struct bio *bio = context; struct mirror *m; @@ -520,6 +520,8 @@ static void read_callback(unsigned long error, void *context) bio_set_m(bio, NULL); if (likely(!error)) { + if (unlikely(unsup != 0)) + bio->bi_status = BLK_STS_INVAL; bio_endio(bio); return; } @@ -553,7 +555,7 @@ static void read_async_bio(struct mirror *m, struct bio *bio) map_region(&io, m, bio); bio_set_m(bio, m); - BUG_ON(dm_io(&io_req, 1, &io, NULL, IOPRIO_DEFAULT)); + BUG_ON(dm_io(&io_req, 1, &io, NULL, NULL, IOPRIO_DEFAULT)); } static inline int region_in_sync(struct mirror_set *ms, region_t region, @@ -600,7 +602,7 @@ static void do_reads(struct mirror_set *ms, struct bio_list *reads) * NOSYNC: increment pending, just write to the default mirror *--------------------------------------------------------------------- */ -static void write_callback(unsigned long error, void *context) +static void write_callback(unsigned long error, unsigned long unsup, void *context) { unsigned int i; struct bio *bio = context; @@ -617,7 +619,7 @@ static void write_callback(unsigned long error, void *context) * This way we handle both writes to SYNC and NOSYNC * regions with the same code. */ - if (likely(!error)) { + if (likely(!(error | unsup))) { bio_endio(bio); return; } @@ -632,6 +634,12 @@ static void write_callback(unsigned long error, void *context) return; } + if (!error && unsup) { + bio->bi_status = BLK_STS_INVAL; + bio_endio(bio); + return; + } + for (i = 0; i < ms->nr_mirrors; i++) if (test_bit(i, &error)) fail_mirror(ms->mirror + i, DM_RAID1_WRITE_ERROR); @@ -680,7 +688,7 @@ static void do_write(struct mirror_set *ms, struct bio *bio) */ bio_set_m(bio, get_default_mirror(ms)); - BUG_ON(dm_io(&io_req, ms->nr_mirrors, io, NULL, IOPRIO_DEFAULT)); + BUG_ON(dm_io(&io_req, ms->nr_mirrors, io, NULL, NULL, IOPRIO_DEFAULT)); } static void do_writes(struct mirror_set *ms, struct bio_list *writes) @@ -1262,7 +1270,7 @@ static int mirror_end_io(struct dm_target *ti, struct bio *bio, return DM_ENDIO_DONE; } - if (*error == BLK_STS_NOTSUPP) + if (*error == BLK_STS_NOTSUPP || *error == BLK_STS_INVAL) goto out; if (bio->bi_opf & REQ_RAHEAD) diff --git a/drivers/md/dm-snap-persistent.c b/drivers/md/dm-snap-persistent.c index aa239ccda270..e86d929b0e95 100644 --- a/drivers/md/dm-snap-persistent.c +++ b/drivers/md/dm-snap-persistent.c @@ -223,7 +223,7 @@ static void do_metadata(struct work_struct *work) { struct mdata_req *req = container_of(work, struct mdata_req, work); - req->result = dm_io(req->io_req, 1, req->where, NULL, IOPRIO_DEFAULT); + req->result = dm_io(req->io_req, 1, req->where, NULL, NULL, IOPRIO_DEFAULT); } /* @@ -247,7 +247,7 @@ static int chunk_io(struct pstore *ps, void *area, chunk_t chunk, blk_opf_t opf, struct mdata_req req; if (!metadata) - return dm_io(&io_req, 1, &where, NULL, IOPRIO_DEFAULT); + return dm_io(&io_req, 1, &where, NULL, NULL, IOPRIO_DEFAULT); req.where = &where; req.io_req = &io_req; diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index 241d3f3747e7..cb105ccc47ec 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -393,7 +393,7 @@ static noinline int verity_recheck(struct dm_verity *v, struct dm_verity_io *io, io_loc.bdev = v->data_dev->bdev; io_loc.sector = cur_block << (v->data_dev_block_bits - SECTOR_SHIFT); io_loc.count = 1 << (v->data_dev_block_bits - SECTOR_SHIFT); - r = dm_io(&io_req, 1, &io_loc, NULL, IOPRIO_DEFAULT); + r = dm_io(&io_req, 1, &io_loc, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) goto free_ret; diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index 493f5202ad04..f02305de1c88 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -474,12 +474,14 @@ struct io_notify { atomic_t count; }; -static void writecache_notify_io(unsigned long error, void *context) +static void writecache_notify_io(unsigned long error, unsigned long unsup, void *context) { struct io_notify *endio = context; if (unlikely(error != 0)) writecache_error(endio->wc, -EIO, "error writing metadata"); + else if (unlikely(unsup != 0)) + writecache_error(endio->wc, -EOPNOTSUPP, "error writing metadata"); BUG_ON(atomic_read(&endio->count) <= 0); if (atomic_dec_and_test(&endio->count)) complete(&endio->c); @@ -530,11 +532,11 @@ static void ssd_commit_flushed(struct dm_writecache *wc, bool wait_for_ios) req.notify.context = &endio; /* writing via async dm-io (implied by notify.fn above) won't return an error */ - (void) dm_io(&req, 1, ®ion, NULL, IOPRIO_DEFAULT); + (void) dm_io(&req, 1, ®ion, NULL, NULL, IOPRIO_DEFAULT); i = j; } - writecache_notify_io(0, &endio); + writecache_notify_io(0, 0, &endio); wait_for_completion_io(&endio.c); if (wait_for_ios) @@ -567,7 +569,7 @@ static void ssd_commit_superblock(struct dm_writecache *wc) req.notify.fn = NULL; req.notify.context = NULL; - r = dm_io(&req, 1, ®ion, NULL, IOPRIO_DEFAULT); + r = dm_io(&req, 1, ®ion, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) writecache_error(wc, r, "error writing superblock"); } @@ -595,7 +597,7 @@ static void writecache_disk_flush(struct dm_writecache *wc, struct dm_dev *dev) req.client = wc->dm_io; req.notify.fn = NULL; - r = dm_io(&req, 1, ®ion, NULL, IOPRIO_DEFAULT); + r = dm_io(&req, 1, ®ion, NULL, NULL, IOPRIO_DEFAULT); if (unlikely(r)) writecache_error(wc, r, "error flushing metadata: %d", r); } @@ -989,7 +991,7 @@ static int writecache_read_metadata(struct dm_writecache *wc, sector_t n_sectors req.client = wc->dm_io; req.notify.fn = NULL; - return dm_io(&req, 1, ®ion, NULL, IOPRIO_DEFAULT); + return dm_io(&req, 1, ®ion, NULL, NULL, IOPRIO_DEFAULT); } static void writecache_resume(struct dm_target *ti) diff --git a/include/linux/dm-io.h b/include/linux/dm-io.h index 7b2968612b7e..674683894064 100644 --- a/include/linux/dm-io.h +++ b/include/linux/dm-io.h @@ -27,7 +27,7 @@ struct page_list { struct page *page; }; -typedef void (*io_notify_fn)(unsigned int long error, void *context); +typedef void (*io_notify_fn)(unsigned long int error, unsigned long int unsup, void *context); enum dm_io_mem_type { DM_IO_PAGE_LIST,/* Page list */ @@ -80,8 +80,8 @@ void dm_io_client_destroy(struct dm_io_client *client); * error occurred doing io to the corresponding region. */ int dm_io(struct dm_io_request *io_req, unsigned int num_regions, - struct dm_io_region *region, unsigned int long *sync_error_bits, - unsigned short ioprio); + struct dm_io_region *region, unsigned long int *sync_error_bits, + unsigned long int *sync_unsup_bits, unsigned short ioprio); #endif /* __KERNEL__ */ #endif /* _LINUX_DM_IO_H */ From c4d9750d7de5072a537a3e7ac62d30024af642c4 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Sun, 26 Jul 2026 23:00:36 +0800 Subject: [PATCH 36/46] dm-pcache: remove unused miss_read_end_work_fn declaration This function is declared but never defined or called anywhere. The miss read completion is handled via miss_read_end_req callback instead. Remove the orphan declaration. Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache.h | 1 - 1 file changed, 1 deletion(-) diff --git a/drivers/md/dm-pcache/cache.h b/drivers/md/dm-pcache/cache.h index 021292c43677..e49cdfd16aaf 100644 --- a/drivers/md/dm-pcache/cache.h +++ b/drivers/md/dm-pcache/cache.h @@ -341,7 +341,6 @@ void cache_seg_set_next_seg(struct pcache_cache_segment *cache_seg, u32 seg_id); /* cache request*/ int pcache_cache_flush(struct pcache_cache *cache); -void miss_read_end_work_fn(struct work_struct *work); int pcache_cache_handle_req(struct pcache_cache *cache, struct pcache_request *pcache_req); /* gc */ From 40221c9d1dbfdb55fb692c20433095b7a3c3afd0 Mon Sep 17 00:00:00 2001 From: "shaikh.kamal" Date: Sun, 26 Jul 2026 12:13:09 +0530 Subject: [PATCH 37/46] docs: device-mapper: dm-inlinecrypt: fix 'bellow' spelling Fix spelling error reported by codespell: bellow -> below. No functional change. Signed-off-by: shaikh.kamal Signed-off-by: Mikulas Patocka --- Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst index 76b3aae21eb4..8258a72f00a8 100644 --- a/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst +++ b/Documentation/admin-guide/device-mapper/dm-inlinecrypt.rst @@ -33,7 +33,7 @@ Parameters:: or it can be passed as prefixed with single colon character (':') for keys residing in kernel keyring service. You can only use key sizes that are valid for the selected cipher. - Note that the size in bytes of a valid key must be in bellow range. + Note that the size in bytes of a valid key must be in below range. [BLK_CRYPTO_KEY_TYPE_RAW, BLK_CRYPTO_KEY_TYPE_HW_WRAPPED] From 3a643cca410946a5d91725c271d1f4743d5461a9 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Thu, 23 Jul 2026 20:18:16 +0800 Subject: [PATCH 38/46] dm-pcache: remove unused 'cache' parameter from cache_key_gc() The 'cache' parameter is never used in the function body, remove it. Signed-off-by: Jianyun Gao Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/cache_gc.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/drivers/md/dm-pcache/cache_gc.c b/drivers/md/dm-pcache/cache_gc.c index d551848912b4..9cebef16d75e 100644 --- a/drivers/md/dm-pcache/cache_gc.c +++ b/drivers/md/dm-pcache/cache_gc.c @@ -6,14 +6,13 @@ /** * cache_key_gc - Releases the reference of a cache key segment. - * @cache: Pointer to the pcache_cache structure. * @key: Pointer to the cache key to be garbage collected. * * This function decrements the reference count of the cache segment * associated with the given key. If the reference count drops to zero, * the segment may be invalidated and reused. */ -static void cache_key_gc(struct pcache_cache *cache, struct pcache_cache_key *key) +static void cache_key_gc(struct pcache_cache_key *key) { cache_seg_put(key->cache_pos.cache_seg); } @@ -169,7 +168,7 @@ void pcache_cache_gc_fn(struct work_struct *work) return; } - cache_key_gc(cache, key); + cache_key_gc(key); } pcache_dev_debug(pcache, "gc advance: %u:%u %u\n", From 83a056da33b1be1b3de4209718df926c47d1af18 Mon Sep 17 00:00:00 2001 From: corwin Date: Tue, 30 Jun 2026 19:05:07 -0400 Subject: [PATCH 39/46] dm vdo indexer: simplify sub-index parameter calculations Pull the calculations from split_config() into compute_volume_sub_index_parameters(). For sparse indexes, this eliminates the duplication of both the configs and geometries in favor of merely having 2 sub_index_parameters structures. Also expand the sub_index_parameters structure to include the small number of fields its users rely on from both the config and the geometry. Signed-off-by: corwin Signed-off-by: Matthew Sakai Signed-off-by: Mikulas Patocka --- drivers/md/dm-vdo/indexer/volume-index.c | 188 +++++++++++------------ 1 file changed, 93 insertions(+), 95 deletions(-) diff --git a/drivers/md/dm-vdo/indexer/volume-index.c b/drivers/md/dm-vdo/indexer/volume-index.c index e78d2725ce8b..03c6b39ccdcc 100644 --- a/drivers/md/dm-vdo/indexer/volume-index.c +++ b/drivers/md/dm-vdo/indexer/volume-index.c @@ -60,6 +60,12 @@ * the index. */ +enum sub_index_parameters_slot { + DENSE = 0, + HOOK = 0, + NON_HOOK = 1, +}; + struct sub_index_parameters { /* The number of bits in address mask */ u8 address_bits; @@ -77,16 +83,10 @@ struct sub_index_parameters { size_t memory_size; /* The number of bytes the index should keep free at all times */ size_t target_free_bytes; -}; - -struct split_config { - /* The hook subindex configuration */ - struct uds_configuration hook_config; - struct index_geometry hook_geometry; - - /* The non-hook subindex configuration */ - struct uds_configuration non_hook_config; - struct index_geometry non_hook_geometry; + /* The mean delta for the volume index */ + u32 volume_index_mean_delta; + /* The number of threads used to process index requests */ + unsigned int zone_count; }; struct chapter_range { @@ -196,8 +196,11 @@ unsigned int uds_get_volume_index_zone(const struct volume_index *volume_index, #define DELTA_LIST_SIZE 256 -static int compute_volume_sub_index_parameters(const struct uds_configuration *config, - struct sub_index_parameters *params) +static int compute_sub_index_parameters(const struct uds_configuration *config, + u64 records_per_chapter, + u32 chapters_per_volume, + bool reduced, + struct sub_index_parameters *params) { u64 entries_in_volume_index, address_span; u32 chapters_in_volume_index, invalid_chapters; @@ -207,17 +210,15 @@ static int compute_volume_sub_index_parameters(const struct uds_configuration *c u64 index_size_in_bits; size_t expected_index_size; u64 min_delta_lists = MAX_ZONES * MAX_ZONES; - struct index_geometry *geometry = config->geometry; - u64 records_per_chapter = geometry->records_per_chapter; - params->chapter_count = geometry->chapters_per_volume; + params->chapter_count = chapters_per_volume; /* * Make sure that the number of delta list records in the volume index does not change when * the volume is reduced by one chapter. This preserves the mapping from name to volume * index delta list. */ rounded_chapters = params->chapter_count; - if (uds_is_reduced_index_geometry(geometry)) + if (reduced) rounded_chapters += 1; delta_list_records = records_per_chapter * rounded_chapters; address_count = config->volume_index_mean_delta * DELTA_LIST_SIZE; @@ -274,9 +275,46 @@ static int compute_volume_sub_index_parameters(const struct uds_configuration *c params->memory_size = expected_index_size * 106 / 100; params->target_free_bytes = expected_index_size / 20; + params->volume_index_mean_delta = config->volume_index_mean_delta; + params->zone_count = config->zone_count; return UDS_SUCCESS; } +static int compute_volume_sub_index_parameters(const struct uds_configuration *config, + struct sub_index_parameters *params) +{ + struct index_geometry *geometry = config->geometry; + u64 sample_records; + u64 dense_chapters; + int result; + bool reduced = uds_is_reduced_index_geometry(geometry); + + if (!uds_is_sparse_index_geometry(config->geometry)) { + return compute_sub_index_parameters(config, + geometry->records_per_chapter, + geometry->chapters_per_volume, + reduced, + ¶ms[DENSE]); + } + + dense_chapters = geometry->chapters_per_volume - geometry->sparse_chapters_per_volume; + sample_records = geometry->records_per_chapter / config->sparse_sample_rate; + + result = compute_sub_index_parameters(config, + sample_records, + geometry->chapters_per_volume, + reduced, + ¶ms[HOOK]); + if (result != UDS_SUCCESS) + return result; + + return compute_sub_index_parameters(config, + geometry->records_per_chapter - sample_records, + dense_chapters, + reduced, + ¶ms[NON_HOOK]); +} + static void uninitialize_volume_sub_index(struct volume_sub_index *sub_index) { vdo_free(vdo_forget(sub_index->flush_chapters)); @@ -298,73 +336,32 @@ void uds_free_volume_index(struct volume_index *volume_index) } -static int compute_volume_sub_index_save_bytes(const struct uds_configuration *config, - size_t *bytes) +static size_t compute_volume_sub_index_save_bytes(struct sub_index_parameters *params) { - struct sub_index_parameters params = { .address_bits = 0 }; - int result; - - result = compute_volume_sub_index_parameters(config, ¶ms); - if (result != UDS_SUCCESS) - return result; - - *bytes = (sizeof(struct sub_index_data) + params.list_count * sizeof(u64) + - uds_compute_delta_index_save_bytes(params.list_count, - params.memory_size)); - return UDS_SUCCESS; -} - -/* This function is only useful if the configuration includes sparse chapters. */ -static void split_configuration(const struct uds_configuration *config, - struct split_config *split) -{ - u64 sample_rate, sample_records; - u64 dense_chapters, sparse_chapters; - - /* Start with copies of the base configuration. */ - split->hook_config = *config; - split->hook_geometry = *config->geometry; - split->hook_config.geometry = &split->hook_geometry; - split->non_hook_config = *config; - split->non_hook_geometry = *config->geometry; - split->non_hook_config.geometry = &split->non_hook_geometry; - - sample_rate = config->sparse_sample_rate; - sparse_chapters = config->geometry->sparse_chapters_per_volume; - dense_chapters = config->geometry->chapters_per_volume - sparse_chapters; - sample_records = config->geometry->records_per_chapter / sample_rate; - - /* Adjust the number of records indexed for each chapter. */ - split->hook_geometry.records_per_chapter = sample_records; - split->non_hook_geometry.records_per_chapter -= sample_records; - - /* Adjust the number of chapters indexed. */ - split->hook_geometry.sparse_chapters_per_volume = 0; - split->non_hook_geometry.sparse_chapters_per_volume = 0; - split->non_hook_geometry.chapters_per_volume = dense_chapters; + return (sizeof(struct sub_index_data) + params->list_count * sizeof(u64) + + uds_compute_delta_index_save_bytes(params->list_count, + params->memory_size)); } static int compute_volume_index_save_bytes(const struct uds_configuration *config, size_t *bytes) { - size_t hook_bytes, non_hook_bytes; - struct split_config split; int result; + struct sub_index_parameters parameters[2] = { + { .address_bits = 0 }, + { .address_bits = 0 }, + }; - if (!uds_is_sparse_index_geometry(config->geometry)) - return compute_volume_sub_index_save_bytes(config, bytes); - - split_configuration(config, &split); - result = compute_volume_sub_index_save_bytes(&split.hook_config, &hook_bytes); + result = compute_volume_sub_index_parameters(config, parameters); if (result != UDS_SUCCESS) return result; - result = compute_volume_sub_index_save_bytes(&split.non_hook_config, - &non_hook_bytes); - if (result != UDS_SUCCESS) - return result; + *bytes = compute_volume_sub_index_save_bytes(¶meters[HOOK]); + if (uds_is_sparse_index_geometry(config->geometry)) { + *bytes += compute_volume_sub_index_save_bytes(¶meters[NON_HOOK]); + *bytes += sizeof(struct volume_index_data); + } - *bytes = sizeof(struct volume_index_data) + hook_bytes + non_hook_bytes; return UDS_SUCCESS; } @@ -1170,48 +1167,43 @@ void uds_get_volume_index_stats(const struct volume_index *volume_index, stats->early_flushes += sparse_stats.early_flushes; } -static int initialize_volume_sub_index(const struct uds_configuration *config, +static int initialize_volume_sub_index(struct sub_index_parameters *params, u64 volume_nonce, u8 tag, struct volume_sub_index *sub_index) { - struct sub_index_parameters params = { .address_bits = 0 }; - unsigned int zone_count = config->zone_count; + unsigned int zone_count = params->zone_count; u64 available_bytes = 0; unsigned int z; int result; - result = compute_volume_sub_index_parameters(config, ¶ms); - if (result != UDS_SUCCESS) - return result; - - sub_index->address_bits = params.address_bits; - sub_index->address_mask = (1u << params.address_bits) - 1; - sub_index->chapter_bits = params.chapter_bits; - sub_index->chapter_mask = (1u << params.chapter_bits) - 1; - sub_index->chapter_count = params.chapter_count; - sub_index->list_count = params.list_count; + sub_index->address_bits = params->address_bits; + sub_index->address_mask = (1u << params->address_bits) - 1; + sub_index->chapter_bits = params->chapter_bits; + sub_index->chapter_mask = (1u << params->chapter_bits) - 1; + sub_index->chapter_count = params->chapter_count; + sub_index->list_count = params->list_count; sub_index->zone_count = zone_count; - sub_index->chapter_zone_bits = params.chapter_size_in_bits / zone_count; + sub_index->chapter_zone_bits = params->chapter_size_in_bits / zone_count; sub_index->volume_nonce = volume_nonce; result = uds_initialize_delta_index(&sub_index->delta_index, zone_count, - params.list_count, params.mean_delta, - params.chapter_bits, params.memory_size, + params->list_count, params->mean_delta, + params->chapter_bits, params->memory_size, tag); if (result != UDS_SUCCESS) return result; for (z = 0; z < sub_index->delta_index.zone_count; z++) available_bytes += sub_index->delta_index.delta_zones[z].size; - available_bytes -= params.target_free_bytes; + available_bytes -= params->target_free_bytes; sub_index->max_zone_bits = (available_bytes * BITS_PER_BYTE) / zone_count; sub_index->memory_size = (sub_index->delta_index.memory_size + sizeof(struct volume_sub_index) + - (params.list_count * sizeof(u64)) + + (params->list_count * sizeof(u64)) + (zone_count * sizeof(struct volume_sub_index_zone))); /* The following arrays are initialized to all zeros. */ - result = vdo_allocate(params.list_count, "first chapter to flush", + result = vdo_allocate(params->list_count, "first chapter to flush", &sub_index->flush_chapters); if (result != VDO_SUCCESS) return result; @@ -1222,10 +1214,13 @@ static int initialize_volume_sub_index(const struct uds_configuration *config, int uds_make_volume_index(const struct uds_configuration *config, u64 volume_nonce, struct volume_index **volume_index_ptr) { - struct split_config split; unsigned int zone; struct volume_index *volume_index; int result; + struct sub_index_parameters parameters[2] = { + { .address_bits = 0 }, + { .address_bits = 0 }, + }; result = vdo_allocate(1, "volume index", &volume_index); if (result != VDO_SUCCESS) @@ -1233,8 +1228,12 @@ int uds_make_volume_index(const struct uds_configuration *config, u64 volume_non volume_index->zone_count = config->zone_count; + result = compute_volume_sub_index_parameters(config, parameters); + if (result != UDS_SUCCESS) + return result; + if (!uds_is_sparse_index_geometry(config->geometry)) { - result = initialize_volume_sub_index(config, volume_nonce, 'm', + result = initialize_volume_sub_index(¶meters[DENSE], volume_nonce, 'm', &volume_index->vi_non_hook); if (result != UDS_SUCCESS) { uds_free_volume_index(volume_index); @@ -1257,8 +1256,7 @@ int uds_make_volume_index(const struct uds_configuration *config, u64 volume_non for (zone = 0; zone < config->zone_count; zone++) mutex_init(&volume_index->zones[zone].hook_mutex); - split_configuration(config, &split); - result = initialize_volume_sub_index(&split.non_hook_config, volume_nonce, 'd', + result = initialize_volume_sub_index(¶meters[NON_HOOK], volume_nonce, 'd', &volume_index->vi_non_hook); if (result != UDS_SUCCESS) { uds_free_volume_index(volume_index); @@ -1266,7 +1264,7 @@ int uds_make_volume_index(const struct uds_configuration *config, u64 volume_non "Error creating non hook volume index"); } - result = initialize_volume_sub_index(&split.hook_config, volume_nonce, 's', + result = initialize_volume_sub_index(¶meters[HOOK], volume_nonce, 's', &volume_index->vi_hook); if (result != UDS_SUCCESS) { uds_free_volume_index(volume_index); From fb582397cf5d08bc5c05b1b8736a28c96d22eb09 Mon Sep 17 00:00:00 2001 From: corwin Date: Wed, 22 Jul 2026 17:55:47 -0400 Subject: [PATCH 40/46] dm vdo indexer: embed geometry in parent structures Embed struct index_geometry in struct uds_configuration and struct volume directly, eliminating the need to allocate (and free) the geometry separately. Signed-off-by: corwin Signed-off-by: Matthew Sakai Signed-off-by: Mikulas Patocka --- drivers/md/dm-vdo/indexer/config.c | 34 ++++----- drivers/md/dm-vdo/indexer/config.h | 2 +- drivers/md/dm-vdo/indexer/geometry.c | 88 +++++++++--------------- drivers/md/dm-vdo/indexer/geometry.h | 13 +--- drivers/md/dm-vdo/indexer/index-layout.c | 2 +- drivers/md/dm-vdo/indexer/index.c | 30 ++++---- drivers/md/dm-vdo/indexer/open-chapter.c | 2 +- drivers/md/dm-vdo/indexer/open-chapter.h | 2 +- drivers/md/dm-vdo/indexer/volume-index.c | 8 +-- drivers/md/dm-vdo/indexer/volume.c | 74 +++++++++----------- drivers/md/dm-vdo/indexer/volume.h | 2 +- 11 files changed, 108 insertions(+), 149 deletions(-) diff --git a/drivers/md/dm-vdo/indexer/config.c b/drivers/md/dm-vdo/indexer/config.c index 4a2cc66cfd60..ccd26af37f7c 100644 --- a/drivers/md/dm-vdo/indexer/config.c +++ b/drivers/md/dm-vdo/indexer/config.c @@ -29,7 +29,7 @@ static bool are_matching_configurations(struct uds_configuration *saved_config, struct index_geometry *saved_geometry, struct uds_configuration *user) { - struct index_geometry *geometry = user->geometry; + const struct index_geometry *geometry = &user->geometry; bool result = true; if (saved_geometry->record_pages_per_chapter != geometry->record_pages_per_chapter) { @@ -141,8 +141,8 @@ int uds_validate_config_contents(struct buffered_reader *reader, return UDS_CORRUPT_DATA; if (is_version(INDEX_CONFIG_VERSION_6_02, version_buffer)) { - user_config->geometry->remapped_virtual = 0; - user_config->geometry->remapped_physical = 0; + user_config->geometry.remapped_virtual = 0; + user_config->geometry.remapped_physical = 0; } else { u8 remapping[sizeof(u64) + sizeof(u64)]; @@ -153,9 +153,9 @@ int uds_validate_config_contents(struct buffered_reader *reader, offset = 0; decode_u64_le(remapping, &offset, - &user_config->geometry->remapped_virtual); + &user_config->geometry.remapped_virtual); decode_u64_le(remapping, &offset, - &user_config->geometry->remapped_physical); + &user_config->geometry.remapped_physical); } if (!are_matching_configurations(&config, &geometry, user_config)) { @@ -175,7 +175,7 @@ int uds_write_config_contents(struct buffered_writer *writer, struct uds_configuration *config, u32 version) { int result; - struct index_geometry *geometry = config->geometry; + const struct index_geometry *geometry = &config->geometry; u8 buffer[sizeof(struct uds_configuration_8_02)]; size_t offset = 0; @@ -329,13 +329,10 @@ int uds_make_configuration(const struct uds_parameters *params, if (result != VDO_SUCCESS) return result; - result = uds_make_index_geometry(DEFAULT_BYTES_PER_PAGE, record_pages_per_chapter, - chapters_per_volume, sparse_chapters_per_volume, - 0, 0, &config->geometry); - if (result != UDS_SUCCESS) { - uds_free_configuration(config); - return result; - } + config->geometry = + uds_init_index_geometry(DEFAULT_BYTES_PER_PAGE, record_pages_per_chapter, + chapters_per_volume, sparse_chapters_per_volume, + 0, 0); config->zone_count = normalize_zone_count(params->zone_count); config->read_threads = normalize_read_threads(params->read_threads); @@ -355,22 +352,21 @@ int uds_make_configuration(const struct uds_parameters *params, void uds_free_configuration(struct uds_configuration *config) { if (config != NULL) { - uds_free_index_geometry(config->geometry); vdo_free(config); } } void uds_log_configuration(struct uds_configuration *config) { - struct index_geometry *geometry = config->geometry; + const struct index_geometry geometry = config->geometry; vdo_log_debug("Configuration:"); - vdo_log_debug(" Record pages per chapter: %10u", geometry->record_pages_per_chapter); - vdo_log_debug(" Chapters per volume: %10u", geometry->chapters_per_volume); - vdo_log_debug(" Sparse chapters per volume: %10u", geometry->sparse_chapters_per_volume); + vdo_log_debug(" Record pages per chapter: %10u", geometry.record_pages_per_chapter); + vdo_log_debug(" Chapters per volume: %10u", geometry.chapters_per_volume); + vdo_log_debug(" Sparse chapters per volume: %10u", geometry.sparse_chapters_per_volume); vdo_log_debug(" Cache size (chapters): %10u", config->cache_chapters); vdo_log_debug(" Volume index mean delta: %10u", config->volume_index_mean_delta); - vdo_log_debug(" Bytes per page: %10zu", geometry->bytes_per_page); + vdo_log_debug(" Bytes per page: %10zu", geometry.bytes_per_page); vdo_log_debug(" Sparse sample rate: %10u", config->sparse_sample_rate); vdo_log_debug(" Nonce: %llu", (unsigned long long) config->nonce); } diff --git a/drivers/md/dm-vdo/indexer/config.h b/drivers/md/dm-vdo/indexer/config.h index 08507dc2f7a1..cffbf5364d16 100644 --- a/drivers/md/dm-vdo/indexer/config.h +++ b/drivers/md/dm-vdo/indexer/config.h @@ -37,7 +37,7 @@ struct uds_configuration { /* Parameters for the volume */ /* The volume layout */ - struct index_geometry *geometry; + struct index_geometry geometry; /* Index owner's nonce */ u64 nonce; diff --git a/drivers/md/dm-vdo/indexer/geometry.c b/drivers/md/dm-vdo/indexer/geometry.c index 49f122a223d5..66c91d47ad24 100644 --- a/drivers/md/dm-vdo/indexer/geometry.c +++ b/drivers/md/dm-vdo/indexer/geometry.c @@ -53,75 +53,51 @@ * chapter it was moved to. */ -int uds_make_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter, - u32 chapters_per_volume, u32 sparse_chapters_per_volume, - u64 remapped_virtual, u64 remapped_physical, - struct index_geometry **geometry_ptr) +struct index_geometry uds_init_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter, + u32 chapters_per_volume, u32 sparse_chapters_per_volume, + u64 remapped_virtual, u64 remapped_physical) { - int result; - struct index_geometry *geometry; + struct index_geometry geometry = { + .bytes_per_page = bytes_per_page, + .record_pages_per_chapter = record_pages_per_chapter, + .chapters_per_volume = chapters_per_volume, + .sparse_chapters_per_volume = sparse_chapters_per_volume, + .dense_chapters_per_volume = chapters_per_volume - sparse_chapters_per_volume, + .remapped_virtual = remapped_virtual, + .remapped_physical = remapped_physical, + }; - result = vdo_allocate(1, "geometry", &geometry); - if (result != VDO_SUCCESS) - return result; + geometry.records_per_page = bytes_per_page / BYTES_PER_RECORD; + geometry.records_per_chapter = geometry.records_per_page * record_pages_per_chapter; + geometry.records_per_volume = (u64) geometry.records_per_chapter * chapters_per_volume; - geometry->bytes_per_page = bytes_per_page; - geometry->record_pages_per_chapter = record_pages_per_chapter; - geometry->chapters_per_volume = chapters_per_volume; - geometry->sparse_chapters_per_volume = sparse_chapters_per_volume; - geometry->dense_chapters_per_volume = chapters_per_volume - sparse_chapters_per_volume; - geometry->remapped_virtual = remapped_virtual; - geometry->remapped_physical = remapped_physical; - - geometry->records_per_page = bytes_per_page / BYTES_PER_RECORD; - geometry->records_per_chapter = geometry->records_per_page * record_pages_per_chapter; - geometry->records_per_volume = (u64) geometry->records_per_chapter * chapters_per_volume; - - geometry->chapter_mean_delta = 1 << DEFAULT_CHAPTER_MEAN_DELTA_BITS; - geometry->chapter_payload_bits = bits_per(record_pages_per_chapter - 1); + geometry.chapter_mean_delta = 1 << DEFAULT_CHAPTER_MEAN_DELTA_BITS; + geometry.chapter_payload_bits = bits_per(record_pages_per_chapter - 1); /* * We want 1 delta list for every 64 records in the chapter. * The "| 077" ensures that the chapter_delta_list_bits computation * does not underflow. */ - geometry->chapter_delta_list_bits = - bits_per((geometry->records_per_chapter - 1) | 077) - 6; - geometry->delta_lists_per_chapter = 1 << geometry->chapter_delta_list_bits; + geometry.chapter_delta_list_bits = bits_per((geometry.records_per_chapter - 1) | 077) - 6; + geometry.delta_lists_per_chapter = 1 << geometry.chapter_delta_list_bits; /* We need enough address bits to achieve the desired mean delta. */ - geometry->chapter_address_bits = + geometry.chapter_address_bits = (DEFAULT_CHAPTER_MEAN_DELTA_BITS - - geometry->chapter_delta_list_bits + - bits_per(geometry->records_per_chapter - 1)); - geometry->index_pages_per_chapter = - uds_get_delta_index_page_count(geometry->records_per_chapter, - geometry->delta_lists_per_chapter, - geometry->chapter_mean_delta, - geometry->chapter_payload_bits, + geometry.chapter_delta_list_bits + + bits_per(geometry.records_per_chapter - 1)); + geometry.index_pages_per_chapter = + uds_get_delta_index_page_count(geometry.records_per_chapter, + geometry.delta_lists_per_chapter, + geometry.chapter_mean_delta, + geometry.chapter_payload_bits, bytes_per_page); - geometry->pages_per_chapter = geometry->index_pages_per_chapter + record_pages_per_chapter; - geometry->pages_per_volume = geometry->pages_per_chapter * chapters_per_volume; - geometry->bytes_per_volume = - bytes_per_page * (geometry->pages_per_volume + HEADER_PAGES_PER_VOLUME); + geometry.pages_per_chapter = geometry.index_pages_per_chapter + record_pages_per_chapter; + geometry.pages_per_volume = geometry.pages_per_chapter * chapters_per_volume; + geometry.bytes_per_volume = + bytes_per_page * (geometry.pages_per_volume + HEADER_PAGES_PER_VOLUME); - *geometry_ptr = geometry; - return UDS_SUCCESS; -} - -int uds_copy_index_geometry(struct index_geometry *source, - struct index_geometry **geometry_ptr) -{ - return uds_make_index_geometry(source->bytes_per_page, - source->record_pages_per_chapter, - source->chapters_per_volume, - source->sparse_chapters_per_volume, - source->remapped_virtual, source->remapped_physical, - geometry_ptr); -} - -void uds_free_index_geometry(struct index_geometry *geometry) -{ - vdo_free(geometry); + return geometry; } u32 __must_check uds_map_to_physical_chapter(const struct index_geometry *geometry, diff --git a/drivers/md/dm-vdo/indexer/geometry.h b/drivers/md/dm-vdo/indexer/geometry.h index a2ecdb238cf2..e311608d0de8 100644 --- a/drivers/md/dm-vdo/indexer/geometry.h +++ b/drivers/md/dm-vdo/indexer/geometry.h @@ -95,16 +95,9 @@ enum { HEADER_PAGES_PER_VOLUME = 1, }; -int __must_check uds_make_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter, - u32 chapters_per_volume, - u32 sparse_chapters_per_volume, u64 remapped_virtual, - u64 remapped_physical, - struct index_geometry **geometry_ptr); - -int __must_check uds_copy_index_geometry(struct index_geometry *source, - struct index_geometry **geometry_ptr); - -void uds_free_index_geometry(struct index_geometry *geometry); +struct index_geometry uds_init_index_geometry(size_t bytes_per_page, u32 record_pages_per_chapter, + u32 chapters_per_volume, u32 sparse_chapters_per_volume, + u64 remapped_virtual, u64 remapped_physical); u32 __must_check uds_map_to_physical_chapter(const struct index_geometry *geometry, u64 virtual_chapter); diff --git a/drivers/md/dm-vdo/indexer/index-layout.c b/drivers/md/dm-vdo/indexer/index-layout.c index 2d529250000e..f5b30f8dd2b5 100644 --- a/drivers/md/dm-vdo/indexer/index-layout.c +++ b/drivers/md/dm-vdo/indexer/index-layout.c @@ -222,7 +222,7 @@ static int __must_check compute_sizes(const struct uds_configuration *config, struct save_layout_sizes *sls) { int result; - struct index_geometry *geometry = config->geometry; + const struct index_geometry *geometry = &config->geometry; memset(sls, 0, sizeof(*sls)); sls->save_count = MAX_SAVES; diff --git a/drivers/md/dm-vdo/indexer/index.c b/drivers/md/dm-vdo/indexer/index.c index 793bd32c1179..af1ab6cf5fcd 100644 --- a/drivers/md/dm-vdo/indexer/index.c +++ b/drivers/md/dm-vdo/indexer/index.c @@ -77,7 +77,7 @@ struct chapter_writer { static bool is_zone_chapter_sparse(const struct index_zone *zone, u64 virtual_chapter) { - return uds_is_chapter_sparse(zone->index->volume->geometry, + return uds_is_chapter_sparse(&zone->index->volume->geometry, zone->oldest_virtual_chapter, zone->newest_virtual_chapter, virtual_chapter); } @@ -154,7 +154,7 @@ static int simulate_index_zone_barrier_message(struct index_zone *zone, u64 sparse_virtual_chapter; if ((zone->index->zone_count > 1) || - !uds_is_sparse_index_geometry(zone->index->volume->geometry)) + !uds_is_sparse_index_geometry(&zone->index->volume->geometry)) return UDS_SUCCESS; sparse_virtual_chapter = triage_index_request(zone->index, request); @@ -278,7 +278,7 @@ static int open_next_chapter(struct index_zone *zone) } expiring = zone->oldest_virtual_chapter; - expire_chapters = uds_chapters_to_expire(zone->index->volume->geometry, + expire_chapters = uds_chapters_to_expire(&zone->index->volume->geometry, zone->newest_virtual_chapter); zone->oldest_virtual_chapter += expire_chapters; @@ -353,7 +353,7 @@ static int search_sparse_cache_in_zone(struct index_zone *zone, struct uds_reque request->virtual_chapter = virtual_chapter; volume = zone->index->volume; - chapter = uds_map_to_physical_chapter(volume->geometry, virtual_chapter); + chapter = uds_map_to_physical_chapter(&volume->geometry, virtual_chapter); return uds_search_cached_record_page(volume, request, chapter, record_page_number, found); } @@ -470,7 +470,7 @@ static int search_index_zone(struct index_zone *zone, struct uds_request *reques found = true; } else if (request->location == UDS_LOCATION_UNAVAILABLE) { found = false; - } else if (uds_is_sparse_index_geometry(zone->index->volume->geometry) && + } else if (uds_is_sparse_index_geometry(&zone->index->volume->geometry) && !uds_is_volume_index_sample(zone->index->volume_index, &request->record_name)) { result = search_sparse_cache_in_zone(zone, request, NO_CHAPTER, @@ -720,7 +720,7 @@ static void close_chapters(void *arg) mutex_lock(&writer->mutex); index->newest_virtual_chapter++; index->oldest_virtual_chapter += - uds_chapters_to_expire(index->volume->geometry, + uds_chapters_to_expire(&index->volume->geometry, index->newest_virtual_chapter); writer->result = result; writer->zones_to_write = 0; @@ -762,7 +762,7 @@ static int make_chapter_writer(struct uds_index *index, int result; struct chapter_writer *writer; size_t collated_records_size = - (sizeof(struct uds_volume_record) * index->volume->geometry->records_per_chapter); + (sizeof(struct uds_volume_record) * index->volume->geometry.records_per_chapter); result = vdo_allocate_extended(index->zone_count, chapters, "Chapter Writer", &writer); if (result != VDO_SUCCESS) @@ -780,7 +780,7 @@ static int make_chapter_writer(struct uds_index *index, } result = uds_make_open_chapter_index(&writer->open_chapter_index, - index->volume->geometry, + &index->volume->geometry, index->volume->nonce); if (result != UDS_SUCCESS) { free_chapter_writer(writer); @@ -824,7 +824,7 @@ static int rebuild_index_page_map(struct uds_index *index, u64 vcn) { int result; struct delta_index_page *chapter_index_page; - struct index_geometry *geometry = index->volume->geometry; + struct index_geometry *geometry = &index->volume->geometry; u32 chapter = uds_map_to_physical_chapter(geometry, vcn); u32 expected_list_number = 0; u32 index_page_number; @@ -980,7 +980,7 @@ static int replay_chapter(struct uds_index *index, u64 virtual, bool sparse) return -EBUSY; } - geometry = index->volume->geometry; + geometry = &index->volume->geometry; physical_chapter = uds_map_to_physical_chapter(geometry, virtual); uds_prefetch_volume_chapter(index->volume, physical_chapter); uds_set_volume_index_open_chapter(index->volume_index, virtual); @@ -1046,7 +1046,7 @@ static int replay_volume(struct uds_index *index) */ old_map_update = index->volume->index_page_map->last_update; for (virtual = from_virtual; virtual < upto_virtual; virtual++) { - will_be_sparse = uds_is_chapter_sparse(index->volume->geometry, + will_be_sparse = uds_is_chapter_sparse(&index->volume->geometry, from_virtual, upto_virtual, virtual); result = replay_chapter(index, virtual, will_be_sparse); @@ -1073,7 +1073,7 @@ static int rebuild_index(struct uds_index *index) u64 lowest; u64 highest; bool is_empty = false; - u32 chapters_per_volume = index->volume->geometry->chapters_per_volume; + u32 chapters_per_volume = index->volume->geometry.chapters_per_volume; index->volume->lookup_mode = LOOKUP_FOR_REBUILD; result = uds_find_volume_chapter_boundaries(index->volume, &lowest, &highest, @@ -1125,14 +1125,14 @@ static int make_index_zone(struct uds_index *index, unsigned int zone_number) if (result != VDO_SUCCESS) return result; - result = uds_make_open_chapter(index->volume->geometry, index->zone_count, + result = uds_make_open_chapter(&index->volume->geometry, index->zone_count, &zone->open_chapter); if (result != UDS_SUCCESS) { free_index_zone(zone); return result; } - result = uds_make_open_chapter(index->volume->geometry, index->zone_count, + result = uds_make_open_chapter(&index->volume->geometry, index->zone_count, &zone->writing_chapter); if (result != UDS_SUCCESS) { free_index_zone(zone); @@ -1202,7 +1202,7 @@ int uds_make_index(struct uds_configuration *config, enum uds_open_index_type op index->load_context = load_context; index->callback = callback; - result = initialize_index_queues(index, config->geometry); + result = initialize_index_queues(index, &config->geometry); if (result != UDS_SUCCESS) { uds_free_index(index); return result; diff --git a/drivers/md/dm-vdo/indexer/open-chapter.c b/drivers/md/dm-vdo/indexer/open-chapter.c index 89b91c600bfd..2977305a2b8f 100644 --- a/drivers/md/dm-vdo/indexer/open-chapter.c +++ b/drivers/md/dm-vdo/indexer/open-chapter.c @@ -343,7 +343,7 @@ int uds_save_open_chapter(struct uds_index *index, struct buffered_writer *write return uds_flush_buffered_writer(writer); } -u64 uds_compute_saved_open_chapter_size(struct index_geometry *geometry) +u64 uds_compute_saved_open_chapter_size(const struct index_geometry *geometry) { unsigned int records_per_chapter = geometry->records_per_chapter; diff --git a/drivers/md/dm-vdo/indexer/open-chapter.h b/drivers/md/dm-vdo/indexer/open-chapter.h index ea6d7336aea0..72a776d46adc 100644 --- a/drivers/md/dm-vdo/indexer/open-chapter.h +++ b/drivers/md/dm-vdo/indexer/open-chapter.h @@ -74,6 +74,6 @@ int __must_check uds_save_open_chapter(struct uds_index *index, int __must_check uds_load_open_chapter(struct uds_index *index, struct buffered_reader *reader); -u64 uds_compute_saved_open_chapter_size(struct index_geometry *geometry); +u64 uds_compute_saved_open_chapter_size(const struct index_geometry *geometry); #endif /* UDS_OPEN_CHAPTER_H */ diff --git a/drivers/md/dm-vdo/indexer/volume-index.c b/drivers/md/dm-vdo/indexer/volume-index.c index 03c6b39ccdcc..2da4c492ace9 100644 --- a/drivers/md/dm-vdo/indexer/volume-index.c +++ b/drivers/md/dm-vdo/indexer/volume-index.c @@ -283,13 +283,13 @@ static int compute_sub_index_parameters(const struct uds_configuration *config, static int compute_volume_sub_index_parameters(const struct uds_configuration *config, struct sub_index_parameters *params) { - struct index_geometry *geometry = config->geometry; + const struct index_geometry *geometry = &config->geometry; u64 sample_records; u64 dense_chapters; int result; bool reduced = uds_is_reduced_index_geometry(geometry); - if (!uds_is_sparse_index_geometry(config->geometry)) { + if (!uds_is_sparse_index_geometry(&config->geometry)) { return compute_sub_index_parameters(config, geometry->records_per_chapter, geometry->chapters_per_volume, @@ -357,7 +357,7 @@ static int compute_volume_index_save_bytes(const struct uds_configuration *confi return result; *bytes = compute_volume_sub_index_save_bytes(¶meters[HOOK]); - if (uds_is_sparse_index_geometry(config->geometry)) { + if (uds_is_sparse_index_geometry(&config->geometry)) { *bytes += compute_volume_sub_index_save_bytes(¶meters[NON_HOOK]); *bytes += sizeof(struct volume_index_data); } @@ -1232,7 +1232,7 @@ int uds_make_volume_index(const struct uds_configuration *config, u64 volume_non if (result != UDS_SUCCESS) return result; - if (!uds_is_sparse_index_geometry(config->geometry)) { + if (!uds_is_sparse_index_geometry(&config->geometry)) { result = initialize_volume_sub_index(¶meters[DENSE], volume_nonce, 'm', &volume_index->vi_non_hook); if (result != UDS_SUCCESS) { diff --git a/drivers/md/dm-vdo/indexer/volume.c b/drivers/md/dm-vdo/indexer/volume.c index af97c0cbeede..78c3729f1f65 100644 --- a/drivers/md/dm-vdo/indexer/volume.c +++ b/drivers/md/dm-vdo/indexer/volume.c @@ -83,17 +83,17 @@ union invalidate_counter { }; }; -static inline u32 map_to_page_number(struct index_geometry *geometry, u32 physical_page) +static inline u32 map_to_page_number(const struct index_geometry *geometry, u32 physical_page) { return (physical_page - HEADER_PAGES_PER_VOLUME) % geometry->pages_per_chapter; } -static inline u32 map_to_chapter_number(struct index_geometry *geometry, u32 physical_page) +static inline u32 map_to_chapter_number(const struct index_geometry *geometry, u32 physical_page) { return (physical_page - HEADER_PAGES_PER_VOLUME) / geometry->pages_per_chapter; } -static inline bool is_record_page(struct index_geometry *geometry, u32 physical_page) +static inline bool is_record_page(const struct index_geometry *geometry, u32 physical_page) { return map_to_page_number(geometry, physical_page) >= geometry->index_pages_per_chapter; } @@ -422,7 +422,7 @@ static int init_chapter_index_page(const struct volume *volume, u8 *index_page, u32 ci_chapter; u32 lowest_list; u32 highest_list; - struct index_geometry *geometry = volume->geometry; + const struct index_geometry *geometry = &volume->geometry; int result; result = uds_initialize_chapter_index_page(chapter_index_page, geometry, @@ -459,8 +459,8 @@ static int init_chapter_index_page(const struct volume *volume, u8 *index_page, static int initialize_index_page(const struct volume *volume, u32 physical_page, struct cached_page *page) { - u32 chapter = map_to_chapter_number(volume->geometry, physical_page); - u32 index_page_number = map_to_page_number(volume->geometry, physical_page); + u32 chapter = map_to_chapter_number(&volume->geometry, physical_page); + u32 index_page_number = map_to_page_number(&volume->geometry, physical_page); return init_chapter_index_page(volume, dm_bufio_get_block_data(page->buffer), chapter, index_page_number, &page->index_page); @@ -510,16 +510,16 @@ static int search_page(struct cached_page *page, const struct volume *volume, enum uds_index_region location; u16 record_page_number; - if (is_record_page(volume->geometry, physical_page)) { + if (is_record_page(&volume->geometry, physical_page)) { if (search_record_page(dm_bufio_get_block_data(page->buffer), - &request->record_name, volume->geometry, + &request->record_name, &volume->geometry, &request->old_metadata)) location = UDS_LOCATION_RECORD_PAGE_LOOKUP; else location = UDS_LOCATION_UNAVAILABLE; } else { result = uds_search_chapter_index_page(&page->index_page, - volume->geometry, + &volume->geometry, &request->record_name, &record_page_number); if (result != UDS_SUCCESS) @@ -571,7 +571,7 @@ static int process_entry(struct volume *volume, struct queued_read *entry) return UDS_SUCCESS; } - if (!is_record_page(volume->geometry, page_number)) { + if (!is_record_page(&volume->geometry, page_number)) { result = initialize_index_page(volume, page_number, page); if (result != UDS_SUCCESS) { vdo_log_warning("Error initializing chapter index page"); @@ -708,7 +708,7 @@ static int read_page_locked(struct volume *volume, u32 physical_page, return result; } - if (!is_record_page(volume->geometry, physical_page)) { + if (!is_record_page(&volume->geometry, physical_page)) { result = initialize_index_page(volume, physical_page, page); if (result != UDS_SUCCESS) { if (volume->lookup_mode != LOOKUP_FOR_REBUILD) @@ -807,7 +807,7 @@ static int get_volume_page(struct volume *volume, u32 chapter, u32 page_number, struct cached_page **page_ptr) { int result; - u32 physical_page = map_to_physical_page(volume->geometry, chapter, page_number); + u32 physical_page = map_to_physical_page(&volume->geometry, chapter, page_number); mutex_lock(&volume->read_threads_mutex); result = get_volume_page_locked(volume, physical_page, page_ptr); @@ -850,7 +850,7 @@ static int search_cached_index_page(struct volume *volume, struct uds_request *r int result; struct cached_page *page = NULL; unsigned int zone_number = request->zone_number; - u32 physical_page = map_to_physical_page(volume->geometry, chapter, + u32 physical_page = map_to_physical_page(&volume->geometry, chapter, index_page_number); /* @@ -867,7 +867,7 @@ static int search_cached_index_page(struct volume *volume, struct uds_request *r return result; } - result = uds_search_chapter_index_page(&page->index_page, volume->geometry, + result = uds_search_chapter_index_page(&page->index_page, &volume->geometry, &request->record_name, record_page_number); end_pending_search(&volume->page_cache, zone_number); @@ -882,7 +882,7 @@ int uds_search_cached_record_page(struct volume *volume, struct uds_request *req u32 chapter, u16 record_page_number, bool *found) { struct cached_page *record_page; - struct index_geometry *geometry = volume->geometry; + const struct index_geometry *geometry = &volume->geometry; unsigned int zone_number = request->zone_number; int result; u32 physical_page, page_number; @@ -899,7 +899,7 @@ int uds_search_cached_record_page(struct volume *volume, struct uds_request *req page_number = geometry->index_pages_per_chapter + record_page_number; - physical_page = map_to_physical_page(volume->geometry, chapter, page_number); + physical_page = map_to_physical_page(&volume->geometry, chapter, page_number); /* * Make sure the invalidate counter is updated before we try and read the mapping. This @@ -925,7 +925,7 @@ int uds_search_cached_record_page(struct volume *volume, struct uds_request *req void uds_prefetch_volume_chapter(const struct volume *volume, u32 chapter) { - const struct index_geometry *geometry = volume->geometry; + const struct index_geometry *geometry = &volume->geometry; u32 physical_page = map_to_physical_page(geometry, chapter, 0); dm_bufio_prefetch(volume->client, physical_page, geometry->pages_per_chapter); @@ -937,7 +937,7 @@ int uds_read_chapter_index_from_volume(const struct volume *volume, u64 virtual_ { int result; u32 i; - const struct index_geometry *geometry = volume->geometry; + const struct index_geometry *geometry = &volume->geometry; u32 physical_chapter = uds_map_to_physical_chapter(geometry, virtual_chapter); u32 physical_page = map_to_physical_page(geometry, physical_chapter, 0); @@ -969,7 +969,7 @@ int uds_search_volume_page_cache(struct volume *volume, struct uds_request *requ { int result; u32 physical_chapter = - uds_map_to_physical_chapter(volume->geometry, request->virtual_chapter); + uds_map_to_physical_chapter(&volume->geometry, request->virtual_chapter); u32 index_page_number; u16 record_page_number; @@ -996,7 +996,7 @@ int uds_search_volume_page_cache_for_rebuild(struct volume *volume, u64 virtual_chapter, bool *found) { int result; - struct index_geometry *geometry = volume->geometry; + struct index_geometry *geometry = &volume->geometry; struct cached_page *page; u32 physical_chapter = uds_map_to_physical_chapter(geometry, virtual_chapter); u32 index_page_number; @@ -1049,13 +1049,13 @@ static void invalidate_page(struct page_cache *cache, u32 physical_page) void uds_forget_chapter(struct volume *volume, u64 virtual_chapter) { u32 physical_chapter = - uds_map_to_physical_chapter(volume->geometry, virtual_chapter); - u32 first_page = map_to_physical_page(volume->geometry, physical_chapter, 0); + uds_map_to_physical_chapter(&volume->geometry, virtual_chapter); + u32 first_page = map_to_physical_page(&volume->geometry, physical_chapter, 0); u32 i; vdo_log_debug("forgetting chapter %llu", (unsigned long long) virtual_chapter); mutex_lock(&volume->read_threads_mutex); - for (i = 0; i < volume->geometry->pages_per_chapter; i++) + for (i = 0; i < volume->geometry.pages_per_chapter; i++) invalidate_page(&volume->page_cache, first_page + i); mutex_unlock(&volume->read_threads_mutex); } @@ -1070,7 +1070,7 @@ static int donate_index_page_locked(struct volume *volume, u32 physical_chapter, int result; struct cached_page *page = NULL; u32 physical_page = - map_to_physical_page(volume->geometry, physical_chapter, + map_to_physical_page(&volume->geometry, physical_chapter, index_page_number); page = select_victim_in_cache(&volume->page_cache); @@ -1097,7 +1097,7 @@ static int donate_index_page_locked(struct volume *volume, u32 physical_chapter, static int write_index_pages(struct volume *volume, u32 physical_chapter_number, struct open_chapter_index *chapter_index) { - struct index_geometry *geometry = volume->geometry; + struct index_geometry *geometry = &volume->geometry; struct dm_buffer *page_buffer; u32 first_index_page = map_to_physical_page(geometry, physical_chapter_number, 0); u32 delta_list_number = 0; @@ -1184,7 +1184,7 @@ static int encode_record_page(const struct volume *volume, { int result; u32 i; - u32 records_per_page = volume->geometry->records_per_page; + u32 records_per_page = volume->geometry.records_per_page; const struct uds_volume_record **record_pointers = volume->record_pointers; for (i = 0; i < records_per_page; i++) @@ -1208,7 +1208,7 @@ static int write_record_pages(struct volume *volume, u32 physical_chapter_number const struct uds_volume_record *records) { u32 record_page_number; - struct index_geometry *geometry = volume->geometry; + struct index_geometry *geometry = &volume->geometry; struct dm_buffer *page_buffer; const struct uds_volume_record *next_record = records; u32 first_record_page = map_to_physical_page(geometry, physical_chapter_number, @@ -1248,7 +1248,7 @@ int uds_write_chapter(struct volume *volume, struct open_chapter_index *chapter_ { int result; u32 physical_chapter_number = - uds_map_to_physical_chapter(volume->geometry, + uds_map_to_physical_chapter(&volume->geometry, chapter_index->virtual_chapter_number); result = write_index_pages(volume, physical_chapter_number, chapter_index); @@ -1269,7 +1269,7 @@ int uds_write_chapter(struct volume *volume, struct open_chapter_index *chapter_ static void probe_chapter(struct volume *volume, u32 chapter_number, u64 *virtual_chapter_number) { - const struct index_geometry *geometry = volume->geometry; + const struct index_geometry *geometry = &volume->geometry; u32 expected_list_number = 0; u32 i; u64 vcn = BAD_CHAPTER; @@ -1353,7 +1353,7 @@ static void find_real_end_of_volume(struct volume *volume, u32 limit, u32 *limit static int find_chapter_limits(struct volume *volume, u32 chapter_limit, u64 *lowest_vcn, u64 *highest_vcn) { - struct index_geometry *geometry = volume->geometry; + struct index_geometry *geometry = &volume->geometry; u64 zero_vcn; u64 lowest = BAD_CHAPTER; u64 highest = BAD_CHAPTER; @@ -1451,7 +1451,7 @@ static int find_chapter_limits(struct volume *volume, u32 chapter_limit, u64 *lo int uds_find_volume_chapter_boundaries(struct volume *volume, u64 *lowest_vcn, u64 *highest_vcn, bool *is_empty) { - u32 chapter_limit = volume->geometry->chapters_per_volume; + u32 chapter_limit = volume->geometry.chapters_per_volume; find_real_end_of_volume(volume, chapter_limit, &chapter_limit); if (chapter_limit == 0) { @@ -1486,7 +1486,7 @@ int __must_check uds_replace_volume_storage(struct volume *volume, if (volume->client != NULL) dm_bufio_client_destroy(vdo_forget(volume->client)); - return uds_open_volume_bufio(layout, volume->geometry->bytes_per_page, + return uds_open_volume_bufio(layout, volume->geometry.bytes_per_page, volume->reserved_buffers, &volume->client); } @@ -1552,13 +1552,8 @@ int uds_make_volume(const struct uds_configuration *config, struct index_layout volume->nonce = uds_get_volume_nonce(layout); - result = uds_copy_index_geometry(config->geometry, &volume->geometry); - if (result != UDS_SUCCESS) { - uds_free_volume(volume); - return vdo_log_warning_strerror(result, - "failed to allocate geometry: error"); - } - geometry = volume->geometry; + volume->geometry = config->geometry; + geometry = &volume->geometry; /* * Reserve a buffer for each entry in the page cache, one for the chapter writer, and one @@ -1685,7 +1680,6 @@ void uds_free_volume(struct volume *volume) uds_free_index_page_map(volume->index_page_map); uds_free_radix_sorter(volume->radix_sorter); - vdo_free(volume->geometry); vdo_free(volume->record_pointers); vdo_free(volume); } diff --git a/drivers/md/dm-vdo/indexer/volume.h b/drivers/md/dm-vdo/indexer/volume.h index 8679a5e55347..0cba3146688d 100644 --- a/drivers/md/dm-vdo/indexer/volume.h +++ b/drivers/md/dm-vdo/indexer/volume.h @@ -97,7 +97,7 @@ struct page_cache { }; struct volume { - struct index_geometry *geometry; + struct index_geometry geometry; struct dm_bufio_client *client; u64 nonce; size_t cache_size; From 68c5c42567bc462139128968ebbfadd0aefff519 Mon Sep 17 00:00:00 2001 From: Shukai Ni Date: Tue, 28 Jul 2026 16:33:50 +0200 Subject: [PATCH 41/46] 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: 84597a44a9d8 ("dm integrity: add optional discard support") Co-developed-by: Jo Van Bulck Signed-off-by: Jo Van Bulck Signed-off-by: Shukai Ni Signed-off-by: Mikulas Patocka --- .../admin-guide/device-mapper/dm-ima.rst | 7 +- .../device-mapper/dm-integrity.rst | 13 ++ drivers/md/dm-integrity.c | 137 +++++++++++++++--- 3 files changed, 133 insertions(+), 24 deletions(-) diff --git a/Documentation/admin-guide/device-mapper/dm-ima.rst b/Documentation/admin-guide/device-mapper/dm-ima.rst index a4aa50a828e0..2a3b50ffbee4 100644 --- a/Documentation/admin-guide/device-mapper/dm-ima.rst +++ b/Documentation/admin-guide/device-mapper/dm-ima.rst @@ -424,7 +424,8 @@ section above) has the following data format for 'integrity' target. target_attributes := "," "," "," "," "," [ ","] [ ","] "," - "," "," "," "," + "," "," "," "," + "," "," "," ";" target_name := "target_name=integrity" @@ -438,6 +439,7 @@ section above) has the following data format for 'integrity' target. block_size := "block_size=" recalculate := "recalculate=" allow_discards := "allow_discards=" + allow_discards_keyed := "allow_discards_keyed=" fix_padding := "fix_padding=" fix_hmac := "fix_hmac=" legacy_recalculate := "legacy_recalculate=" @@ -455,7 +457,8 @@ section above) has the following data format for 'integrity' target. dm_version=4.45.0; 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, - 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; diff --git a/Documentation/admin-guide/device-mapper/dm-integrity.rst b/Documentation/admin-guide/device-mapper/dm-integrity.rst index c2e18ecc065c..9c21301423c9 100644 --- a/Documentation/admin-guide/device-mapper/dm-integrity.rst +++ b/Documentation/admin-guide/device-mapper/dm-integrity.rst @@ -190,6 +190,19 @@ allow_discards Allow block discard requests (a.k.a. TRIM) for the integrity device. 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 Use a smaller padding of the tag area that is more space-efficient. If this option is not present, large padding is diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index c19efee569d1..c50feaa98bf9 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -66,6 +66,7 @@ #define SB_VERSION_4 4 #define SB_VERSION_5 5 #define SB_VERSION_6 6 +#define SB_VERSION_7 7 #define SB_SECTORS 8 #define MAX_SECTORS_PER_BLOCK 8 @@ -91,6 +92,7 @@ struct superblock { #define SB_FLAG_FIXED_PADDING 0x8 #define SB_FLAG_FIXED_HMAC 0x10 #define SB_FLAG_INLINE 0x20 +#define SB_FLAG_DISCARD_KEYED 0x40 #define JOURNAL_ENTRY_ROUNDUP 8 @@ -277,6 +279,7 @@ struct dm_integrity_c { bool recalculate_flag; bool reset_recalculate_flag; bool discard; + bool discard_keyed; bool fix_padding; bool fix_hmac; 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) { - 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; else if (ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_HMAC)) 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 char mismatch_hash = 0; - unsigned char mismatch_filler = !ic->discard; + unsigned char mismatch_filler = !ic->discard || ic->discard_keyed; do { 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; 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, - const char *data, unsigned offset, char *result) + const char *data, unsigned offset, + unsigned int len, char *result) { __le64 sector_le = cpu_to_le64(sector); 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; } - r = crypto_shash_update(req, data + offset, ic->sectors_per_block << SECTOR_SHIFT); - if (unlikely(r < 0)) { - dm_integrity_io_error(ic, "crypto_shash_update", r); - goto failed; + if (likely(len)) { + r = crypto_shash_update(req, data + offset, len); + if (unlikely(r < 0)) { + dm_integrity_io_error(ic, "crypto_shash_update", r); + goto failed; + } } 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, - 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); struct ahash_request *req; @@ -1708,6 +1717,7 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah int r; unsigned int digest_size; unsigned int nbytes = 0; + unsigned int nents = 1 + (len ? 1 : 0); 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); 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); nbytes += SALT_SIZE; s++; } else { - sg_init_table(sg, 2); + sg_init_table(sg, nents); } if (likely(!is_vmalloc_addr(§or_le))) { @@ -1739,8 +1749,10 @@ static void integrity_sector_checksum_ahash(struct dm_integrity_c *ic, struct ah nbytes += sizeof(sector_le); s++; - sg_set_page(s, page, ic->sectors_per_block << SECTOR_SHIFT, offset); - nbytes += ic->sectors_per_block << SECTOR_SHIFT; + if (likely(len)) { + sg_set_page(s, page, len, offset); + nbytes += len; + } 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, 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)) - integrity_sector_checksum_shash(ic, sector, data, offset, result); + integrity_sector_checksum_shash(ic, sector, data, offset, len, result); 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) @@ -1796,6 +1838,29 @@ static void *integrity_identity(struct dm_integrity_c *ic, void *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) { 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 *buffer = page_to_virt(page); unsigned int buffer_offset; + char on_disk_tag[MAX_T(size_t, HASH_MAX_DIGESTSIZE, MAX_TAG_SIZE)]; int r; struct dm_io_request io_req; 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); - r = dm_integrity_rw_tag(ic, checksum, &dio->metadata_block, - &dio->metadata_offset, ic->tag_size, TAG_CMP); + r = integrity_recheck_verify_tag(dio, checksum, on_disk_tag, + logical_sector); if (r) { if (r > 0) { 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 max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; 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) { unsigned int this_step_blocks = bi_size >> (SECTOR_SHIFT + ic->sb->log2_sectors_per_block); this_step_blocks = min(this_step_blocks, max_blocks); + if (ic->discard_keyed) + integrity_discard_fill_tags(ic, &dio->ahash_req, checksums, + §or, this_step_blocks); r = dm_integrity_rw_tag(ic, checksums, &dio->metadata_block, &dio->metadata_offset, this_step_blocks * ic->tag_size, TAG_WRITE); if (unlikely(r)) { @@ -3799,6 +3870,8 @@ static void dm_integrity_resume(struct dm_target *ti) ic->wrote_to_journal = false; 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); if (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->sb->flags & cpu_to_le32(SB_FLAG_RECALCULATING)); 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 == 'J'; /* journal_sectors */ 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"); if (ic->reset_recalculate_flag) DMEMIT(" reset_recalculate"); - if (ic->discard) + if (ic->discard && !ic->discard_keyed) DMEMIT(" allow_discards"); + if (ic->discard_keyed) + DMEMIT(" allow_discards_keyed"); if (ic->mode != 'I') DMEMIT(" interleave_sectors:%u", 1U << ic->sb->log2_interleave_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)) ? 'y' : 'n'); DMEMIT(",allow_discards=%c", ic->discard ? 'y' : 'n'); + DMEMIT(",allow_discards_keyed=%c", ic->discard_keyed ? 'y' : 'n'); DMEMIT(",fix_padding=%c", ((ic->sb->flags & cpu_to_le32(SB_FLAG_FIXED_PADDING)) != 0) ? 'y' : 'n'); 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); } + if (ic->discard_keyed) + ic->sb->flags |= cpu_to_le32(SB_FLAG_DISCARD_KEYED); + if (!ic->meta_dev) { if (ic->fix_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; } else if (!strcmp(opt_string, "allow_discards")) { 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")) { ic->fix_padding = true; } 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"; 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_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; } - if (!ic->sb->version || ic->sb->version > SB_VERSION_6) { + if (!ic->sb->version || ic->sb->version > SB_VERSION_7) { r = -EINVAL; ti->error = "Unknown version"; goto bad; @@ -5129,6 +5217,11 @@ static int dm_integrity_ctr(struct dm_target *ti, unsigned int argc, char **argv 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) { r = -EINVAL; ti->error = "Journal mac mismatch"; @@ -5444,7 +5537,7 @@ static void dm_integrity_dtr(struct dm_target *ti) static struct target_type integrity_target = { .name = "integrity", - .version = {1, 14, 0}, + .version = {1, 15, 0}, .module = THIS_MODULE, .features = DM_TARGET_SINGLETON | DM_TARGET_INTEGRITY, .ctr = dm_integrity_ctr, From 2965787723084835b18dfe993cd450ebf5bd4540 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 17:54:54 -0500 Subject: [PATCH 42/46] dm array: validate array block headers on read array_block_check() validates blocknr and csum and nothing else, while node_check(), next to it, has bounded the structural fields since both were written. dm_array_cursor_next() takes its loop bound from the on-disk nr_entries and element_at() is unguarded pointer arithmetic, so a count larger than the block holds keeps the cursor in one block while the index grows past it and the read walks off the dm-bufio buffer -- dm_cache_load_mappings() drives it once per cache block at activation. Check the header against itself: reject a zero value_size, require max_entries to equal calc_max_entries() for that value_size and block size, and require nr_entries to fit. Equality rather than an upper bound, since a count below the real capacity trips BUG_ON() in fill_ablock() and trim_ablock(). Metadata dm-array writes satisfies all three. Fixes: 6513c29f44f2 ("dm persistent data: add transactional array") Suggested-by: Ming-Hung Tsai Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Ming-Hung Tsai Signed-off-by: Mikulas Patocka --- drivers/md/persistent-data/dm-array.c | 37 +++++++++++++++++++++------ 1 file changed, 29 insertions(+), 8 deletions(-) diff --git a/drivers/md/persistent-data/dm-array.c b/drivers/md/persistent-data/dm-array.c index 8f8792e55806..7ba0a566f0ad 100644 --- a/drivers/md/persistent-data/dm-array.c +++ b/drivers/md/persistent-data/dm-array.c @@ -38,6 +38,14 @@ struct array_block { */ #define CSUM_XOR 595846735 +/* + * Each array block can hold this many values. + */ +static uint32_t calc_max_entries(size_t value_size, size_t size_of_block) +{ + return (size_of_block - sizeof(struct array_block)) / value_size; +} + static void array_block_prepare_for_write(const struct dm_block_validator *v, struct dm_block *b, size_t size_of_block) @@ -55,6 +63,7 @@ static int array_block_check(const struct dm_block_validator *v, size_t size_of_block) { struct array_block *bh_le = dm_block_data(b); + uint32_t nr_entries, max_entries, value_size; __le32 csum_disk; if (dm_block_location(b) != le64_to_cpu(bh_le->blocknr)) { @@ -74,6 +83,26 @@ static int array_block_check(const struct dm_block_validator *v, return -EILSEQ; } + nr_entries = le32_to_cpu(bh_le->nr_entries); + max_entries = le32_to_cpu(bh_le->max_entries); + value_size = le32_to_cpu(bh_le->value_size); + + if (!value_size) { + DMERR_LIMIT("%s failed: value_size is zero", __func__); + return -EILSEQ; + } + + if (max_entries != calc_max_entries(value_size, size_of_block)) { + DMERR_LIMIT("%s failed: max_entries %u invalid for value_size %u", + __func__, max_entries, value_size); + return -EILSEQ; + } + + if (nr_entries > max_entries) { + DMERR_LIMIT("%s failed: too many entries", __func__); + return -EILSEQ; + } + return 0; } @@ -138,14 +167,6 @@ static void dec_ablock_entries(struct dm_array_info *info, struct array_block *a on_entries(info, ab, vt->dec); } -/* - * Each array block can hold this many values. - */ -static uint32_t calc_max_entries(size_t value_size, size_t size_of_block) -{ - return (size_of_block - sizeof(struct array_block)) / value_size; -} - /* * Allocate a new array block. The caller will need to unlock block. */ From 4538a287bdf5d0f9a379c678e5262b9f5783f547 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 31 Jul 2026 17:54:55 -0500 Subject: [PATCH 43/46] dm array: reject an array block whose value size is not the caller's array_block_check() can only compare the header against itself, so a block with value_size 4 and max_entries 1018 is internally consistent and passes. dm-cache keeps two arrays -- mappings at 8 bytes and hints at 4 -- and the roots for both live in the superblock. Point the mappings root at a hint block and __load_mappings() walks it through an info whose value size is 8, so element_at() strides 8 bytes over 4-byte entries and reaches offset 8160 of a 4096-byte block. get_ablock() and __shadow_ablock() are the two places that hold the block and the caller at once. Reject there when the two value sizes disagree. Arrays only ever read their own blocks, so this fires on crafted metadata only. Fixes: 6513c29f44f2 ("dm persistent data: add transactional array") Suggested-by: Ming-Hung Tsai Cc: stable@vger.kernel.org Signed-off-by: Bryam Vargas Reviewed-by: Ming-Hung Tsai Signed-off-by: Mikulas Patocka --- drivers/md/persistent-data/dm-array.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/drivers/md/persistent-data/dm-array.c b/drivers/md/persistent-data/dm-array.c index 7ba0a566f0ad..961fa3c1439a 100644 --- a/drivers/md/persistent-data/dm-array.c +++ b/drivers/md/persistent-data/dm-array.c @@ -246,6 +246,14 @@ static int get_ablock(struct dm_array_info *info, dm_block_t b, return r; *ab = dm_block_data(*block); + if (le32_to_cpu((*ab)->value_size) != info->value_type.size) { + DMERR_LIMIT("%s failed: value_size %u != wanted %u", __func__, + le32_to_cpu((*ab)->value_size), + info->value_type.size); + dm_tm_unlock(info->btree_info.tm, *block); + return -EILSEQ; + } + return 0; } @@ -308,6 +316,14 @@ static int __shadow_ablock(struct dm_array_info *info, dm_block_t b, return r; *ab = dm_block_data(*block); + if (le32_to_cpu((*ab)->value_size) != info->value_type.size) { + DMERR_LIMIT("%s failed: value_size %u != wanted %u", __func__, + le32_to_cpu((*ab)->value_size), + info->value_type.size); + dm_tm_unlock(info->btree_info.tm, *block); + return -EILSEQ; + } + if (inc) inc_ablock_entries(info, *ab); From cc87e26d9cce22061dc21e51e11afef29dbbc36a Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 3 Aug 2026 23:34:02 +0200 Subject: [PATCH 44/46] dm-stats: fix a crash if allocation of per-cpu data fails If "dm_kvzalloc(percpu_alloc_size, cpu_to_node(cpu))" fails, the code jumps to the "out" label and calls dm_stat_free. dm_stat_free does "for_each_possible_cpu(cpu) { dm_kvfree(s->stat_percpu[cpu][0].histogram, s->histogram_alloc_size);", which crashes with NULL pointer dereference if s->stat_percpu[cpu] is NULL. This commit fixes the bug by testing s->stat_percpu[cpu] for NULL before using it. Reported-by: Junzhe Yu Signed-off-by: Mikulas Patocka Fixes: fd2ed4d25270 ("dm: add statistics support") Cc: stable@vger.kernel.org --- drivers/md/dm-stats.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-stats.c b/drivers/md/dm-stats.c index 5df710061a11..beabbe3b39f3 100644 --- a/drivers/md/dm-stats.c +++ b/drivers/md/dm-stats.c @@ -178,8 +178,10 @@ static void dm_stat_free(struct rcu_head *head) kfree(s->program_id); kfree(s->aux_data); for_each_possible_cpu(cpu) { - dm_kvfree(s->stat_percpu[cpu][0].histogram, s->histogram_alloc_size); - dm_kvfree(s->stat_percpu[cpu], s->percpu_alloc_size); + if (s->stat_percpu[cpu]) { + dm_kvfree(s->stat_percpu[cpu][0].histogram, s->histogram_alloc_size); + dm_kvfree(s->stat_percpu[cpu], s->percpu_alloc_size); + } } dm_kvfree(s->stat_shared[0].tmp.histogram, s->histogram_alloc_size); dm_kvfree(s, s->shared_alloc_size); From 066976b7dbc7e2022efa0244457d804163bc1ee8 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Mon, 3 Aug 2026 14:09:13 +0000 Subject: [PATCH 45/46] dm dust: make badblock messages target-relative dm-dust currently treats addbadblock, removebadblock and queryblock arguments as block numbers on the underlying device. That is surprising for a device-mapper target: a dm-dust table with a non-zero backing offset can add bad blocks that are outside the mapped target, and a badblock added for logical block 0 is missed because the I/O path checks the remapped backing-device block instead. Interpret badblock message arguments as blocks relative to the start of the dm-dust target instead. Bound the arguments by the target length and perform badblock lookup using target-relative sectors before remapping the bio to the underlying device. This intentionally changes the non-zero backing-offset behavior to make the badblock control interface match the mapped dm-dust device, rather than the underlying device. Assisted-by: Codex:gpt-5.5-cyber-preview Signed-off-by: Samuel Moelius Tested-by: Bryan Gurney Reviewed-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-dust.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/drivers/md/dm-dust.c b/drivers/md/dm-dust.c index c7e3077fb1f5..954f4ec5a51c 100644 --- a/drivers/md/dm-dust.c +++ b/drivers/md/dm-dust.c @@ -196,7 +196,6 @@ static int __dust_map_write(struct dust_device *dd, sector_t thisblock) dd->badblock_count--; kfree(bblk); if (!dd->quiet_mode) { - sector_div(thisblock, dd->sect_per_block); DMINFO("block %llu removed from badblocklist by write", (unsigned long long)thisblock); } @@ -224,15 +223,16 @@ static int dust_map_write(struct dust_device *dd, sector_t thisblock, static int dust_map(struct dm_target *ti, struct bio *bio) { struct dust_device *dd = ti->private; + sector_t dust_sector = dm_target_offset(ti, bio->bi_iter.bi_sector); int r; bio_set_dev(bio, dd->dev->bdev); - bio->bi_iter.bi_sector = dd->start + dm_target_offset(ti, bio->bi_iter.bi_sector); + bio->bi_iter.bi_sector = dd->start + dust_sector; if (bio_data_dir(bio) == READ) - r = dust_map_read(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb); + r = dust_map_read(dd, dust_sector, dd->fail_read_on_bb); else - r = dust_map_write(dd, bio->bi_iter.bi_sector, dd->fail_read_on_bb); + r = dust_map_write(dd, dust_sector, dd->fail_read_on_bb); return r; } @@ -415,7 +415,7 @@ static int dust_message(struct dm_target *ti, unsigned int argc, char **argv, char *result, unsigned int maxlen) { struct dust_device *dd = ti->private; - sector_t size = bdev_nr_sectors(dd->dev->bdev); + sector_t size = dm_sector_div_up(ti->len, dd->sect_per_block); bool invalid_msg = false; int r = -EINVAL; unsigned long long tmp, block; @@ -462,8 +462,7 @@ static int dust_message(struct dm_target *ti, unsigned int argc, char **argv, return r; block = tmp; - sector_div(size, dd->sect_per_block); - if (block > size) { + if (block >= size) { DMERR("selected block value out of range"); return r; } @@ -490,8 +489,7 @@ static int dust_message(struct dm_target *ti, unsigned int argc, char **argv, return r; } wr_fail_cnt = tmp_ui; - sector_div(size, dd->sect_per_block); - if (block > size) { + if (block >= size) { DMERR("selected block value out of range"); return r; } From 39c5aa3bd8ec3912d2cd0b3fe092642b0d2b0713 Mon Sep 17 00:00:00 2001 From: liyouhong Date: Fri, 31 Jul 2026 10:08:49 +0800 Subject: [PATCH 46/46] dm-era: fix shadowed superblock leak on take-snap failure metadata_take_snap() bumps the live superblock refcount and then dm_tm_shadow_block() allocates a new block for the metadata snapshot. If the subsequent dm_sm_inc_block() of writeset_tree_root or era_array_root fails, the function only unlocks the clone and returns. The newly allocated shadow block is never returned to the metadata space map, so each failed take-snap permanently leaks one metadata block. Free the clone with dm_sm_dec_block() on those error paths, matching the final step of metadata_drop_snap(). Fixes: eec40579d848 ("dm: add era target") Cc: stable@vger.kernel.org Signed-off-by: liyouhong Signed-off-by: Mikulas Patocka --- drivers/md/dm-era-target.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/dm-era-target.c b/drivers/md/dm-era-target.c index 7fe4d19ade4f..ea499adca4ce 100644 --- a/drivers/md/dm-era-target.c +++ b/drivers/md/dm-era-target.c @@ -1034,6 +1034,7 @@ static int metadata_checkpoint(struct era_metadata *md) static int metadata_take_snap(struct era_metadata *md) { int r, inc; + dm_block_t location; struct dm_block *clone; if (md->metadata_snap != SUPERBLOCK_LOCATION) { @@ -1071,7 +1072,9 @@ static int metadata_take_snap(struct era_metadata *md) r = dm_sm_inc_block(md->sm, md->writeset_tree_root); if (r) { DMERR("%s: couldn't inc writeset tree root", __func__); + location = dm_block_location(clone); dm_tm_unlock(md->tm, clone); + dm_sm_dec_block(md->sm, location); return r; } @@ -1079,7 +1082,9 @@ static int metadata_take_snap(struct era_metadata *md) if (r) { DMERR("%s: couldn't inc era tree root", __func__); dm_sm_dec_block(md->sm, md->writeset_tree_root); + location = dm_block_location(clone); dm_tm_unlock(md->tm, clone); + dm_sm_dec_block(md->sm, location); return r; }