From 70094662c1c95c05f1ec839c8117b3bd5668b361 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:23:27 +0200 Subject: 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(-) (limited to 'drivers/md') 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); -- cgit v1.2.3 From 4e53905a9da1736a448ab89f455ce3454de9e633 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:24:05 +0200 Subject: 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(+) (limited to 'drivers/md') 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 @@ retest_commit_id: if (!ic->journal_tree) { *error = "Could not allocate memory for journal tree"; r = -ENOMEM; + goto bad; } bad: kfree(crypt_data); -- cgit v1.2.3 From 553f9a9a36a600977f01bfbcc8139ce0ca419384 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:24:38 +0200 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From a02d51919b05a5b5b237012b3e6ea738bc533b08 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:25:21 +0200 Subject: 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(-) (limited to 'drivers/md') 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) { -- cgit v1.2.3 From d4ef8ad059e51348a613e7922b3462b0e949091b Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:25:53 +0200 Subject: 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(-) (limited to 'drivers/md') 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)) -- cgit v1.2.3 From 34dcadff8b70c409724506fbb42c366e3da2f5c8 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 13 Jul 2026 21:26:54 +0200 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From 00211ede62facec2915a7e6cfc17f8ce987a0078 Mon Sep 17 00:00:00 2001 From: Samuel Moelius Date: Wed, 15 Jul 2026 00:18:00 +0000 Subject: 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(-) (limited to 'drivers/md') 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; - if (r == 2) { - result->begin = to_cblock(b); - result->end = to_cblock(e); - return 0; - } + result->begin = to_cblock(b); - /* - * That didn't work, try form (i). - */ - r = sscanf(str, "%llu%c", &b, &dummy); + if (str) { + blocknr = str; - if (r == 1) { - result->begin = to_cblock(b); - result->end = to_cblock(from_cblock(result->begin) + 1u); - return 0; + r = kstrtouint(blocknr, 10, &e); + if (r) + goto bad; + + result->end = to_cblock(e); + } else { + result->end = cblock_succ(result->begin); } - DMERR("%s: invalid cblock range '%s'", cache_device_name(cache), str); + return 0; + +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; -- cgit v1.2.3 From c7391ebe33162c7962b313caea4d8e6b0bc2a671 Mon Sep 17 00:00:00 2001 From: Haotian Zhang Date: Sat, 11 Jul 2026 22:21:55 +0800 Subject: 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(-) (limited to 'drivers/md') 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); } /* -- cgit v1.2.3 From 0729a9b4ee5a4c30e4c3641bc8fc6d164e8617b4 Mon Sep 17 00:00:00 2001 From: bui duc phuc Date: Fri, 3 Jul 2026 09:09:18 +0700 Subject: 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(-) (limited to 'drivers/md') 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) -- cgit v1.2.3 From c92d632f8638df0c5b644739b1fd06e36a7630fb Mon Sep 17 00:00:00 2001 From: "Jiangong.Han" Date: Wed, 1 Jul 2026 16:21:02 +0800 Subject: 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(-) (limited to 'drivers/md') 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); } /* -- cgit v1.2.3 From 78726ba0ffe3aa16848e86c4571ab647ff7b1b4e Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:41 +0100 Subject: 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(-) (limited to 'drivers/md') 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); + + if (!vers) + return; - /* 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; + 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; -- cgit v1.2.3 From 96979634af39a3f176f3c213fdaee274e869057c Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:42 +0100 Subject: 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(-) (limited to 'drivers/md') 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; } -- cgit v1.2.3 From cd516571e975b2d2ed4aeac47c87a3064e2b2633 Mon Sep 17 00:00:00 2001 From: David Laight Date: Wed, 24 Jun 2026 15:52:43 +0100 Subject: 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 | 47 +++++++++++++++++++++++------------------------ 1 file changed, 23 insertions(+), 24 deletions(-) (limited to 'drivers/md') 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_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_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_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_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_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))) -- cgit v1.2.3 From 22eb919f1b7900e2af0e952b1f1bee522f612b4a Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Thu, 16 Jul 2026 16:02:48 +0200 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From 90c990a68460d7b5720e5634cf650eccdf0f4098 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:54 -0500 Subject: 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(-) (limited to 'drivers/md') 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 @@ clear_tree: 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)); -- cgit v1.2.3 From 32d1809da31094ef76fd98dc1f1a8b55ca1295dd Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:55 -0500 Subject: 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(+) (limited to 'drivers/md') 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) -- cgit v1.2.3 From f11deb032fd84081e7831cffcba895d893054a22 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:56 -0500 Subject: 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(-) (limited to 'drivers/md') 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; } -- cgit v1.2.3 From d1898576090a10d2ac2715218a652e78fb65a6b0 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:57 -0500 Subject: 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(+) (limited to 'drivers/md') 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); -- cgit v1.2.3 From 7ac1f10f987a2ffae4aecf0e2ceca8f552b665cb Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:58 -0500 Subject: 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(+) (limited to 'drivers/md') 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); -- cgit v1.2.3 From 16c3b3a326e70f246a605b3dc27b7f83ba4743e3 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:26:59 -0500 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From 97fc4b53dbe4a983fdf093243067fa6a64562307 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:00 -0500 Subject: 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(+) (limited to 'drivers/md') 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); -- cgit v1.2.3 From becf07e2b0053027495ecd671b1f82fb2e615f68 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:01 -0500 Subject: 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(-) (limited to 'drivers/md') 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); -- cgit v1.2.3 From 62d92e45abe9e087370f9fc5d876b95673aced34 Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:02 -0500 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From 58d620ee9e01d4bdbceaf2ae1450d307a2a9d58b Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:03 -0500 Subject: 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(+) (limited to 'drivers/md') 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 @@ out: 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; -- cgit v1.2.3 From 2df0fc042e299bae3c0f60ea5cd2af9285658e9f Mon Sep 17 00:00:00 2001 From: Bryam Vargas Date: Fri, 17 Jul 2026 06:27:04 -0500 Subject: 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(-) (limited to 'drivers/md') 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; -- cgit v1.2.3 From 4cf795dd0e3981ddc652c4d503b0880fe5250e88 Mon Sep 17 00:00:00 2001 From: Matthew Sakai Date: Fri, 17 Jul 2026 12:01:11 -0400 Subject: 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(-) (limited to 'drivers/md') 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); } } -- cgit v1.2.3 From 73c37fe54cd056d07461b142ab0b8b81e1ef6ad8 Mon Sep 17 00:00:00 2001 From: Ilya Krutskih Date: Sun, 19 Jul 2026 13:01:03 +0000 Subject: 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(+) (limited to 'drivers/md') 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) { -- cgit v1.2.3 From fb9e17287a4ea1cbbcedc77e6866978ecc2a7b55 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Mon, 20 Jul 2026 17:46:48 +0800 Subject: 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(-) (limited to 'drivers/md') 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); -- cgit v1.2.3 From c2e894eac398b258f12fdec73ed6ba081047f7b3 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Mon, 20 Jul 2026 11:36:32 +0800 Subject: 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(-) (limited to 'drivers/md') 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); } -- cgit v1.2.3 From 8765dcb96f00b65de4a59b29c25fed5d8b3cedf1 Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Fri, 17 Jul 2026 16:38:54 +0800 Subject: 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(-) (limited to 'drivers/md') 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) -- cgit v1.2.3 From a46fd918f7907ecdfd1b1d03210463eef80f0def Mon Sep 17 00:00:00 2001 From: Jianyun Gao Date: Fri, 17 Jul 2026 16:38:56 +0800 Subject: 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(-) (limited to 'drivers/md') 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 @@ again: 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. */ -- cgit v1.2.3