From 936293c9f3f4f2a0d1b80df9332e96c552ac3b37 Mon Sep 17 00:00:00 2001 From: Matthew Sakai Date: Tue, 11 Aug 2026 15:02:33 -0400 Subject: [PATCH 01/29] dm vdo indexer: prevent delta list overflow on load Check for corrupted delta lists sizes that overflow the memory available for the delta_lists. An overflow can cause the delta list data to be written outside the reserved delta memory space. Signed-off-by: Matthew Sakai Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm-vdo/indexer/delta-index.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/drivers/md/dm-vdo/indexer/delta-index.c b/drivers/md/dm-vdo/indexer/delta-index.c index b288749067de..257612b81a9d 100644 --- a/drivers/md/dm-vdo/indexer/delta-index.c +++ b/drivers/md/dm-vdo/indexer/delta-index.c @@ -800,7 +800,7 @@ static void compute_new_list_offsets(struct delta_zone *delta_zone, u32 growing_ (delta_zone->size * BITS_PER_BYTE - delta_lists[tail_guard_index].size); } -static void rebalance_lists(struct delta_zone *delta_zone) +static int rebalance_lists(struct delta_zone *delta_zone) { struct delta_list *delta_lists; u32 i; @@ -811,9 +811,17 @@ static void rebalance_lists(struct delta_zone *delta_zone) for (i = 0; i <= delta_zone->list_count + 1; i++) used_space += get_delta_list_byte_size(&delta_lists[i]); + if (delta_zone->size < used_space) { + return vdo_log_warning_strerror(UDS_CORRUPT_DATA, + "delta zone lists overflow zone size %zu", + delta_zone->size); + } + compute_new_list_offsets(delta_zone, 0, 0, used_space); for (i = 1; i <= delta_zone->list_count + 1; i++) delta_lists[i].start = delta_zone->new_offsets[i]; + + return UDS_SUCCESS; } /* Start restoring a delta index from multiple input streams. */ @@ -941,8 +949,11 @@ int uds_start_restoring_delta_index(struct delta_index *delta_index, } /* Prepare each zone to start receiving the delta list data. */ - for (z = 0; z < delta_index->zone_count; z++) - rebalance_lists(&delta_index->delta_zones[z]); + for (z = 0; z < delta_index->zone_count; z++) { + result = rebalance_lists(&delta_index->delta_zones[z]); + if (result != UDS_SUCCESS) + return result; + } return UDS_SUCCESS; } From e3683a76a313a81f3f7bc7f65e4fc1e61232838b Mon Sep 17 00:00:00 2001 From: Matthew Sakai Date: Tue, 11 Aug 2026 15:02:34 -0400 Subject: [PATCH 02/29] dm vdo indexer: validate record page numbers better Move the record_page_number validation check into the chapter index lookup function in order to detect (and discard) corrupted values on all paths. Signed-off-by: Matthew Sakai Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org --- drivers/md/dm-vdo/indexer/chapter-index.c | 15 +++++++++++---- drivers/md/dm-vdo/indexer/volume.c | 6 ------ 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/drivers/md/dm-vdo/indexer/chapter-index.c b/drivers/md/dm-vdo/indexer/chapter-index.c index bb3b0ab5d50d..9fb9e59238d4 100644 --- a/drivers/md/dm-vdo/indexer/chapter-index.c +++ b/drivers/md/dm-vdo/indexer/chapter-index.c @@ -284,10 +284,17 @@ int uds_search_chapter_index_page(struct delta_index_page *index_page, if (result != UDS_SUCCESS) return result; - if (was_entry_found(&entry, address)) - *record_page_ptr = uds_get_delta_entry_value(&entry); - else + if (!was_entry_found(&entry, address)) { + *record_page_ptr = NO_CHAPTER_INDEX_ENTRY; + return UDS_SUCCESS; + } + + *record_page_ptr = uds_get_delta_entry_value(&entry); + result = VDO_ASSERT(*record_page_ptr < geometry->record_pages_per_chapter, + "0 <= %d < %u", *record_page_ptr, + geometry->record_pages_per_chapter); + if (result != VDO_SUCCESS) *record_page_ptr = NO_CHAPTER_INDEX_ENTRY; - return UDS_SUCCESS; + return result; } diff --git a/drivers/md/dm-vdo/indexer/volume.c b/drivers/md/dm-vdo/indexer/volume.c index 78c3729f1f65..38e51fa1cbd0 100644 --- a/drivers/md/dm-vdo/indexer/volume.c +++ b/drivers/md/dm-vdo/indexer/volume.c @@ -891,12 +891,6 @@ int uds_search_cached_record_page(struct volume *volume, struct uds_request *req if (record_page_number == NO_CHAPTER_INDEX_ENTRY) return UDS_SUCCESS; - result = VDO_ASSERT(record_page_number < geometry->record_pages_per_chapter, - "0 <= %d < %u", record_page_number, - geometry->record_pages_per_chapter); - if (result != VDO_SUCCESS) - return result; - page_number = geometry->index_pages_per_chapter + record_page_number; physical_page = map_to_physical_page(&volume->geometry, chapter, page_number); From 624c30d967ac81e74f24230e6abbd4e62f0a4009 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Sat, 15 Aug 2026 13:05:33 +0200 Subject: [PATCH 03/29] dm-crypt: Use memzero_explicit() to wipe key material The code still wipes key material with memset(). Use memzero_explicit() to prevent the compiler from optimizing these wipes away. Also use size_t for the string length passed to memzero_explicit() and drop the redundant sizeof(u8) multiplications. Signed-off-by: Thorsten Blum Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 9e170de50ad3..1229490dd811 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -513,7 +513,7 @@ static void crypt_iv_lmk_wipe(struct crypt_config *cc) struct iv_lmk_private *lmk = &cc->iv_gen_private.lmk; if (lmk->seed) - memset(lmk->seed, 0, LMK_SEED_SIZE); + memzero_explicit(lmk->seed, LMK_SEED_SIZE); } static void crypt_iv_lmk_one(struct crypt_config *cc, u8 *iv, @@ -630,8 +630,8 @@ static void crypt_iv_tcw_wipe(struct crypt_config *cc) { struct iv_tcw_private *tcw = &cc->iv_gen_private.tcw; - memset(tcw->iv_seed, 0, cc->iv_size); - memset(tcw->whitening, 0, TCW_WHITENING_SIZE); + memzero_explicit(tcw->iv_seed, cc->iv_size); + memzero_explicit(tcw->whitening, TCW_WHITENING_SIZE); } static void crypt_iv_tcw_whitening(struct crypt_config *cc, @@ -2595,7 +2595,7 @@ static int get_key_size(char **key_string) static int crypt_set_key(struct crypt_config *cc, char *key) { int r = -EINVAL; - int key_string_len = strlen(key); + size_t key_string_len = strlen(key); /* Hyphen (which gives a key_size of zero) means there is no key. */ if (!cc->key_size && strcmp(key, "-")) @@ -2624,7 +2624,7 @@ static int crypt_set_key(struct crypt_config *cc, char *key) out: /* Hex key string not needed after here, so wipe it. */ - memset(key, '0', key_string_len); + memzero_explicit(key, key_string_len); return r; } @@ -2643,7 +2643,7 @@ static int crypt_wipe_key(struct crypt_config *cc) kfree_sensitive(cc->key_string); cc->key_string = NULL; r = crypt_setkey(cc); - memset(&cc->key, 0, cc->key_size * sizeof(u8)); + memzero_explicit(cc->key, cc->key_size); return r; } @@ -3064,7 +3064,7 @@ static int crypt_ctr_cipher(struct dm_target *ti, char *cipher_in, char *key) /* wipe the kernel key payload copy */ if (cc->key_string) - memset(cc->key, 0, cc->key_size * sizeof(u8)); + memzero_explicit(cc->key, cc->key_size); return ret; } @@ -3643,7 +3643,7 @@ static int crypt_message(struct dm_target *ti, unsigned int argc, char **argv, /* The key size may not be changed. */ key_size = get_key_size(&argv[2]); if (key_size < 0 || cc->key_size != key_size) { - memset(argv[2], '0', strlen(argv[2])); + memzero_explicit(argv[2], strlen(argv[2])); return -EINVAL; } @@ -3654,7 +3654,7 @@ static int crypt_message(struct dm_target *ti, unsigned int argc, char **argv, ret = cc->iv_gen_ops->init(cc); /* wipe the kernel key payload copy */ if (cc->key_string) - memset(cc->key, 0, cc->key_size * sizeof(u8)); + memzero_explicit(cc->key, cc->key_size); return ret; } if (argc == 2 && !strcasecmp(argv[1], "wipe")) From 769d5353c8e61c1f50a4843839514b5569321eb8 Mon Sep 17 00:00:00 2001 From: Elouan Da Costa Peixoto Date: Tue, 25 Aug 2026 17:01:21 +0200 Subject: [PATCH 04/29] dm-clone: remove unused memset Remove unused memset() with superblock_disk uuid field. The uuid field is not used, so there's no need to set it using memset(). Remove memset() call and FIXME comment. Signed-off-by: Elouan Da Costa Peixoto Signed-off-by: Mikulas Patocka --- drivers/md/dm-clone-metadata.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/md/dm-clone-metadata.c b/drivers/md/dm-clone-metadata.c index e50a5b5a4aea..75ea39d18c12 100644 --- a/drivers/md/dm-clone-metadata.c +++ b/drivers/md/dm-clone-metadata.c @@ -300,9 +300,6 @@ static void __prepare_superblock(struct dm_clone_metadata *cmd, { sb->flags = cpu_to_le32(0UL); - /* FIXME: UUID is currently unused */ - memset(sb->uuid, 0, sizeof(sb->uuid)); - sb->magic = cpu_to_le64(SUPERBLOCK_MAGIC); sb->version = cpu_to_le32(DM_CLONE_MAX_METADATA_VERSION); From ba56ca86f925f639d2d1d529dda2bf534fd8b853 Mon Sep 17 00:00:00 2001 From: Thorsten Blum Date: Thu, 13 Aug 2026 10:13:36 +0200 Subject: [PATCH 05/29] dm-crypt: Reject malformed raw keys in crypt_set_key() dm-crypt calculates the raw key size by dividing the hex string length by two. If the string has an odd number of characters, the key size is rounded down and hex2bin() only decodes complete byte pairs. For example, a 33-character raw key string is malformed, but dm-crypt treats it as a 16-byte key and ignores the last character. Reject raw key strings whose length does not match the expected number of hex characters. This restores the trailing character check that was lost when the open-coded decoder was replaced with hex2bin(). Signed-off-by: Thorsten Blum Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 1229490dd811..722caab3f8eb 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -2614,6 +2614,9 @@ static int crypt_set_key(struct crypt_config *cc, char *key) kfree_sensitive(cc->key_string); cc->key_string = NULL; + if (cc->key_size && key_string_len != cc->key_size * 2) + goto out; + /* Decode key from its hex representation. */ if (cc->key_size && hex2bin(cc->key, key, cc->key_size) < 0) goto out; From 74d433a7886166108ef80862e22ea18d53c8c6c0 Mon Sep 17 00:00:00 2001 From: Matthias Goergens Date: Fri, 14 Aug 2026 12:58:12 +0800 Subject: [PATCH 06/29] dm-delay: advertise flush support dm-delay has a flush delay class: the 9-argument table form takes , delay_map() routes REQ_PREFLUSH bios to it, num_flush_bios is 1, and Documentation/admin-guide/device-mapper/delay.rst documents it. But the target never sets ti->flush_supported, so the dm device only supports flushes when an underlying device stacks BLK_FEAT_WRITE_CACHE into its limits. Over a write-through device (write_cache "write through") it does not: submit_bio_noacct() strips REQ_PREFLUSH and REQ_FUA before any bio reaches the target, and a configured flush delay measures nothing. Set ti->flush_supported when dc->flush.delay is non-zero. A configured flush delay then always sees its flushes. Without one, dm-delay keeps stacking flush support from the underlying device like the other targets, so an undelayed dm-delay behaves like linear for flushes. As for any table with flush support, the dm queue then also advertises FUA; delay_map() keeps routing REQ_FUA writes to the write class, and only REQ_PREFLUSH goes to the flush class. Verified in a VM with dm-delay over a write-through virtio-scsi disk, using a 9-argument table with write delay 0 and a flush delay of 200 or 800 ms. Without this change the dm queue reports write_cache "write through", fua 0, and five 4 KiB dd oflag=dsync writes to the dm device complete in 0-1 ms. With it the queue reports "write back", fua 1, and the same writes take 204-210 ms and 812-880 ms. Zero-delay tables over the write-through disk and over a write-back disk behave the same with and without the change and keep the underlying queue's settings. Suggested-by: Benjamin Marzinski Signed-off-by: Matthias Goergens Signed-off-by: Mikulas Patocka --- drivers/md/dm-delay.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-delay.c b/drivers/md/dm-delay.c index 4864671c2222..20c97e92d806 100644 --- a/drivers/md/dm-delay.c +++ b/drivers/md/dm-delay.c @@ -301,6 +301,7 @@ static int delay_ctr(struct dm_target *ti, unsigned int argc, char **argv) } ti->num_flush_bios = 1; + ti->flush_supported = dc->flush.delay != 0; ti->num_discard_bios = 1; ti->accounts_remapped_io = true; ti->per_io_data_size = sizeof(struct dm_delay_info); @@ -451,7 +452,7 @@ static int delay_iterate_devices(struct dm_target *ti, static struct target_type delay_target = { .name = "delay", - .version = {1, 5, 0}, + .version = {1, 6, 0}, .features = DM_TARGET_PASSES_INTEGRITY | DM_TARGET_ZONED_HM, .module = THIS_MODULE, .ctr = delay_ctr, From 73df38ae2b00bcb72c61716c46b17a2de2751cf0 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Thu, 3 Sep 2026 16:23:25 +0100 Subject: [PATCH 07/29] dm vdo: Fix spelling mistake "postive" -> "positive" There is another spelling mistake in a vdo_log_error message that I somehow overlooked last time. Fix it. Signed-off-by: Colin Ian King Reviewed-by: Matthew Sakai Signed-off-by: Mikulas Patocka --- drivers/md/dm-vdo/dm-vdo-target.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-vdo/dm-vdo-target.c b/drivers/md/dm-vdo/dm-vdo-target.c index 1d8375cc3c3e..043549b1fc2e 100644 --- a/drivers/md/dm-vdo/dm-vdo-target.c +++ b/drivers/md/dm-vdo/dm-vdo-target.c @@ -443,7 +443,7 @@ static int __must_check parse_slab_size(const char *slab_str, block_count_t *sla result = kstrtoull(slab_str, 10, &value); if (result) { - vdo_log_error("optional parameter error: invalid slab size, must be a postive integer"); + vdo_log_error("optional parameter error: invalid slab size, must be a positive integer"); return -EINVAL; } From a1986bf2accfb973d40debdf4cbb98527019271f Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:44:46 +0530 Subject: [PATCH 08/29] md: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-metadata.c | 2 +- drivers/md/dm-crypt.c | 2 +- drivers/md/dm-exception-store.h | 2 +- drivers/md/dm-raid.c | 2 +- drivers/md/dm-raid1.c | 2 +- drivers/md/dm-unstripe.c | 2 +- drivers/md/persistent-data/dm-btree-remove.c | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-cache-metadata.c b/drivers/md/dm-cache-metadata.c index acd9b179fcb3..179b76173255 100644 --- a/drivers/md/dm-cache-metadata.c +++ b/drivers/md/dm-cache-metadata.c @@ -49,7 +49,7 @@ enum superblock_flag_bits { enum mapping_bits { /* * A valid mapping. Because we're using an array we clear this - * flag for an non existant mapping. + * flag for an non existent mapping. */ M_VALID = 1, diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 722caab3f8eb..8e838530faab 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -2365,7 +2365,7 @@ static unsigned int crypt_authenckey_size(struct crypt_config *cc) /* * If AEAD is composed like authenc(hmac(sha256),xts(aes)), * the key must be for some reason in special format. - * This funcion converts cc->key to this special format. + * This function converts cc->key to this special format. */ static void crypt_copy_authenckey(char *p, const void *key, unsigned int enckeylen, unsigned int authkeylen) diff --git a/drivers/md/dm-exception-store.h b/drivers/md/dm-exception-store.h index 061b4d310813..6924d673cf45 100644 --- a/drivers/md/dm-exception-store.h +++ b/drivers/md/dm-exception-store.h @@ -135,7 +135,7 @@ struct dm_dev *dm_snap_origin(struct dm_snapshot *snap); struct dm_dev *dm_snap_cow(struct dm_snapshot *snap); /* - * Funtions to manipulate consecutive chunks + * Functions to manipulate consecutive chunks */ #define DM_CHUNK_CONSECUTIVE_BITS 8 #define DM_CHUNK_NUMBER_BITS 56 diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index 8f5a5e1342a9..ac019a0f5a92 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -1784,7 +1784,7 @@ static int rs_check_takeover(struct raid_set *rs) /* raid10* -> raid0 */ if (mddev->new_level == 0) { - /* Can takeover raid10_near with raid disks divisable by data copies! */ + /* Can takeover raid10_near with raid disks divisible by data copies! */ if (near_copies > 1 && !(mddev->raid_disks % near_copies)) { mddev->raid_disks /= near_copies; diff --git a/drivers/md/dm-raid1.c b/drivers/md/dm-raid1.c index da2a5e2002ec..36516338ca6f 100644 --- a/drivers/md/dm-raid1.c +++ b/drivers/md/dm-raid1.c @@ -1393,7 +1393,7 @@ static void mirror_resume(struct dm_target *ti) * we have encountered. * A => Alive - No failures * D => Dead - A write failure occurred leaving mirror out-of-sync - * S => Sync - A sychronization failure occurred, mirror out-of-sync + * S => Sync - A synchronization failure occurred, mirror out-of-sync * R => Read - A read failure occurred, mirror data unaffected * * Returns: diff --git a/drivers/md/dm-unstripe.c b/drivers/md/dm-unstripe.c index bfcbe6bfa71a..770a9958def0 100644 --- a/drivers/md/dm-unstripe.c +++ b/drivers/md/dm-unstripe.c @@ -33,7 +33,7 @@ static void cleanup_unstripe(struct unstripe_c *uc, struct dm_target *ti) } /* - * Contruct an unstriped mapping. + * Construct an unstriped mapping. * */ static int unstripe_ctr(struct dm_target *ti, unsigned int argc, char **argv) diff --git a/drivers/md/persistent-data/dm-btree-remove.c b/drivers/md/persistent-data/dm-btree-remove.c index aeec5b9a1dd5..2357ff9b53f3 100644 --- a/drivers/md/persistent-data/dm-btree-remove.c +++ b/drivers/md/persistent-data/dm-btree-remove.c @@ -31,7 +31,7 @@ * least MIN_ENTRIES + 1. We do this in the following ways: * * [A] No siblings => this can only happen if the node is the root, in which - * case we copy the childs contents over the root. + * case we copy the children contents over the root. * * [B] No left sibling * ==> rebalance(node, right sibling) From c69cc511362ab8c8219bbecac3723bfe1ff2acac Mon Sep 17 00:00:00 2001 From: Hemanth Selam Date: Fri, 4 Sep 2026 16:47:22 +0530 Subject: [PATCH 09/29] md: fix typos in comments Fix typos in comments, reported by scripts/checkpatch.pl using the misspelling list in scripts/spelling.txt. Only touches comments, no code changes. Assisted-by: Cursor:claude-opus-5 Signed-off-by: Hemanth Selam Signed-off-by: Mikulas Patocka --- drivers/md/dm-pcache/backing_dev.c | 2 +- drivers/md/dm-vdo/block-map.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-pcache/backing_dev.c b/drivers/md/dm-pcache/backing_dev.c index 7165fc0364bb..23002beb192a 100644 --- a/drivers/md/dm-pcache/backing_dev.c +++ b/drivers/md/dm-pcache/backing_dev.c @@ -68,7 +68,7 @@ void backing_dev_stop(struct dm_pcache *pcache) struct pcache_backing_dev *backing_dev = &pcache->backing_dev; /* - * There should not be any new request comming, just wait + * There should not be any new request coming, just wait * inflight requests done. */ wait_event(backing_dev->inflight_wq, diff --git a/drivers/md/dm-vdo/block-map.c b/drivers/md/dm-vdo/block-map.c index 5ffc360540ed..258ad5521f51 100644 --- a/drivers/md/dm-vdo/block-map.c +++ b/drivers/md/dm-vdo/block-map.c @@ -240,7 +240,7 @@ static int __must_check allocate_cache_components(struct vdo_page_cache *cache) * assert_on_cache_thread() - Assert that a function has been called on the VDO page cache's * thread. * @cache: The page cache. - * @function_name: The funtion name to report if the assertion fails. + * @function_name: The function name to report if the assertion fails. */ static inline void assert_on_cache_thread(struct vdo_page_cache *cache, const char *function_name) From 28e78fd0d2c915971508642483b54f1c25bda673 Mon Sep 17 00:00:00 2001 From: Henry Wong Date: Mon, 7 Sep 2026 14:46:47 -0700 Subject: [PATCH 10/29] dm-writecache: Reduce metadata memory use by 8 bytes per cache block wc_entry.index removed: This can be computed by pointer arithmetic without storage. wc_entry.age reduced to 32 bits: It only needs to be big enough to hold max_age, which is limited to well below 32-bit range. Reduces wc_entry size from 56 bytes to 48 bytes (from 72 to 64 bytes if DM_WRITECACHE_HANDLE_HARDWARE_ERRORS enabled). Signed-off-by: Henry Wong Signed-off-by: Mikulas Patocka --- drivers/md/dm-writecache.c | 39 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 21 deletions(-) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index 978646f71f99..68e3fb300fc1 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -81,15 +81,9 @@ struct wc_memory_superblock { struct wc_entry { struct rb_node rb_node; struct list_head lru; + u32 age; // jiffies unsigned short wc_list_contiguous; -#if BITS_PER_LONG == 64 - bool write_in_progress : 1; - unsigned long index : 47; -#else bool write_in_progress; - unsigned long index; -#endif - unsigned long age; #ifdef DM_WRITECACHE_HANDLE_HARDWARE_ERRORS uint64_t original_sector; uint64_t seq_count; @@ -389,20 +383,28 @@ static struct wc_memory_superblock *sb(struct dm_writecache *wc) return wc->memory_map; } +static inline unsigned long wc_entry_index(const struct wc_entry *base, const struct wc_entry *entry) +{ + return entry - base; +} + static struct wc_memory_entry *memory_entry(struct dm_writecache *wc, struct wc_entry *e) { - return &sb(wc)->entries[e->index]; + const unsigned long index = wc_entry_index(wc->entries, e); + return &sb(wc)->entries[index]; } static void *memory_data(struct dm_writecache *wc, struct wc_entry *e) { - return (char *)wc->block_start + (e->index << wc->block_size_bits); + const unsigned long index = wc_entry_index(wc->entries, e); + return (char *)wc->block_start + (index << wc->block_size_bits); } static sector_t cache_sector(struct dm_writecache *wc, struct wc_entry *e) { + const unsigned long index = wc_entry_index(wc->entries, e); return wc->start_sector + wc->metadata_sectors + - ((sector_t)e->index << (wc->block_size_bits - SECTOR_SHIFT)); + ((sector_t)index << (wc->block_size_bits - SECTOR_SHIFT)); } static uint64_t read_original_sector(struct dm_writecache *wc, struct wc_entry *e) @@ -668,7 +670,7 @@ static void writecache_insert_entry(struct dm_writecache *wc, struct wc_entry *i rb_link_node(&ins->rb_node, parent, node); rb_insert_color(&ins->rb_node, &wc->tree); list_add(&ins->lru, &wc->lru); - ins->age = jiffies; + ins->age = (u32)jiffies; } static void writecache_unlink(struct dm_writecache *wc, struct wc_entry *e) @@ -969,7 +971,6 @@ static int writecache_alloc_entries(struct dm_writecache *wc) for (b = 0; b < wc->n_blocks; b++) { struct wc_entry *e = &wc->entries[b]; - e->index = b; e->write_in_progress = false; cond_resched(); } @@ -1986,7 +1987,7 @@ static void writecache_writeback(struct work_struct *work) while (!list_empty(&wc->lru) && (wc->writeback_all || wc->freelist_size + wc->writeback_size <= wc->freelist_low_watermark || - (jiffies - container_of(wc->lru.prev, struct wc_entry, lru)->age >= + ((u32)(jiffies - container_of(wc->lru.prev, struct wc_entry, lru)->age) >= wc->max_age - wc->max_age / MAX_AGE_DIV))) { n_walked++; @@ -2107,7 +2108,6 @@ static int calculate_memory_size(uint64_t device_size, unsigned int block_size, size_t *n_blocks_p, size_t *n_metadata_blocks_p) { uint64_t n_blocks, offset; - struct wc_entry e; n_blocks = device_size; do_div(n_blocks, block_size + sizeof(struct wc_memory_entry)); @@ -2126,11 +2126,6 @@ static int calculate_memory_size(uint64_t device_size, unsigned int block_size, n_blocks--; } - /* check if the bit field overflows */ - e.index = n_blocks; - if (e.index != n_blocks) - return -EFBIG; - if (n_blocks_p) *n_blocks_p = n_blocks; if (n_metadata_blocks_p) @@ -2444,13 +2439,15 @@ static int writecache_ctr(struct dm_target *ti, unsigned int argc, char **argv) wc->autocommit_time_set = true; } else if (!strcasecmp(string, "max_age") && opt_params >= 1) { unsigned int max_age_msecs; + unsigned long max_age_jiffies; string = dm_shift_arg(&as), opt_params--; if (sscanf(string, "%u%c", &max_age_msecs, &dummy) != 1) goto invalid_optional; - if (max_age_msecs > 86400000) + max_age_jiffies = msecs_to_jiffies(max_age_msecs); + if (max_age_jiffies >= min(MAX_JIFFY_OFFSET, (7 << 28))) // 7/8ths of 1 << 31 goto invalid_optional; - wc->max_age = msecs_to_jiffies(max_age_msecs); + wc->max_age = max_age_jiffies; wc->max_age_set = true; wc->max_age_value = max_age_msecs; } else if (!strcasecmp(string, "cleaner")) { From 53064d23f00f1b921f9f08555bfc5a1b229664a5 Mon Sep 17 00:00:00 2001 From: Peng Fan Date: Mon, 21 Sep 2026 16:10:07 +0800 Subject: [PATCH 11/29] dm: use assign_bit() where applicable Convert open-coded if/else with set_bit/clear_bit to the assign_bit API. Done with Coccinelle semantic patch: // set_bit -> clear_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - set_bit(bit, addr); -else - clear_bit(bit, addr); +assign_bit(bit, addr, cond); // clear_bit -> set_bit => assign_bit @@ expression cond, bit, addr; @@ -if (cond) - clear_bit(bit, addr); -else - set_bit(bit, addr); +assign_bit(bit, addr, !cond); Signed-off-by: Peng Fan Signed-off-by: Mikulas Patocka --- drivers/md/dm-mpath.c | 5 +---- drivers/md/dm-zone.c | 6 ++---- drivers/md/persistent-data/dm-bitset.c | 5 +---- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/drivers/md/dm-mpath.c b/drivers/md/dm-mpath.c index 7cb7bb6233b6..ae9318f5f21b 100644 --- a/drivers/md/dm-mpath.c +++ b/drivers/md/dm-mpath.c @@ -1604,10 +1604,7 @@ static void pg_init_done(void *data, int errors) goto out; if (test_bit(MPATHF_PG_INIT_REQUIRED, &m->flags)) { - if (delay_retry) - set_bit(MPATHF_PG_INIT_DELAY_RETRY, &m->flags); - else - clear_bit(MPATHF_PG_INIT_DELAY_RETRY, &m->flags); + assign_bit(MPATHF_PG_INIT_DELAY_RETRY, &m->flags, delay_retry); if (__pg_init_all_paths(m)) goto out; diff --git a/drivers/md/dm-zone.c b/drivers/md/dm-zone.c index f29acf64429a..35916c6e490e 100644 --- a/drivers/md/dm-zone.c +++ b/drivers/md/dm-zone.c @@ -447,10 +447,8 @@ void dm_finalize_zone_settings(struct dm_table *t, struct queue_limits *lim) struct mapped_device *md = t->md; if (lim->features & BLK_FEAT_ZONED) { - if (dm_table_supports_zone_append(t)) - clear_bit(DMF_EMULATE_ZONE_APPEND, &md->flags); - else - set_bit(DMF_EMULATE_ZONE_APPEND, &md->flags); + assign_bit(DMF_EMULATE_ZONE_APPEND, &md->flags, + !dm_table_supports_zone_append(t)); } else { clear_bit(DMF_EMULATE_ZONE_APPEND, &md->flags); md->disk->nr_zones = 0; diff --git a/drivers/md/persistent-data/dm-bitset.c b/drivers/md/persistent-data/dm-bitset.c index 00c0a3f186b7..6680f05c25ec 100644 --- a/drivers/md/persistent-data/dm-bitset.c +++ b/drivers/md/persistent-data/dm-bitset.c @@ -59,10 +59,7 @@ static int pack_bits(uint32_t index, void *value, void *context) if (r) return r; - if (bv) - set_bit(bit, (unsigned long *) &word); - else - clear_bit(bit, (unsigned long *) &word); + assign_bit(bit, (unsigned long *)&word, bv); } *((__le64 *) value) = cpu_to_le64(word); From bec2fa7bdf06d6523e40e05fd93117b0c1c8bd30 Mon Sep 17 00:00:00 2001 From: Ayoub Zaki Date: Mon, 7 Sep 2026 19:19:38 +0200 Subject: [PATCH 12/29] dm-verity: add DM_VERITY_VERIFY_ROOTHASH_SIG_FORCE Add DM_VERITY_VERIFY_ROOTHASH_SIG_FORCE Kconfig option. When enabled, dm-verity always requires a valid root hash signature: require_signatures defaults to true and can no longer be cleared on the command line. When disabled, the existing require_signatures module parameter controls enforcement. Signed-off-by: Ayoub Zaki Signed-off-by: Mikulas Patocka --- Documentation/admin-guide/device-mapper/verity.rst | 5 +++++ drivers/md/Kconfig | 14 ++++++++++++++ drivers/md/dm-verity-verify-sig.c | 4 ++-- 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/Documentation/admin-guide/device-mapper/verity.rst b/Documentation/admin-guide/device-mapper/verity.rst index eb9475d7e196..9e9d4b8157c0 100644 --- a/Documentation/admin-guide/device-mapper/verity.rst +++ b/Documentation/admin-guide/device-mapper/verity.rst @@ -163,6 +163,11 @@ root_hash_sig_key_desc also gain new certificates at run time if they are signed by a certificate already in the secondary trusted keyring. + Whether a signature is required for every dm-verity device is controlled by + the dm_verity.require_signatures parameter which defaults to off. Setting + DM_VERITY_VERIFY_ROOTHASH_SIG_FORCE makes it default to on in which case it + can no longer be turned off. + try_verify_in_tasklet If verity hashes are in cache and the IO size does not exceed the limit, verify data blocks in bottom half instead of workqueue. This option can diff --git a/drivers/md/Kconfig b/drivers/md/Kconfig index df27c7d066d2..9a90e81817c7 100644 --- a/drivers/md/Kconfig +++ b/drivers/md/Kconfig @@ -610,6 +610,20 @@ config DM_VERITY_VERIFY_ROOTHASH_SIG_PLATFORM_KEYRING If unsure, say N. +config DM_VERITY_VERIFY_ROOTHASH_SIG_FORCE + bool "Require dm-verity root hash signature verification" + depends on DM_VERITY_VERIFY_ROOTHASH_SIG + help + Reject dm-verity devices that are created without a valid root hash + signature. Without this, whether a signature is required is decided + at boot time by the dm_verity.require_signatures parameter which + defaults to off. + + Enabling this makes that parameter default to on and it can then no + longer be turned off. + + If unsure, say N. + config DM_VERITY_FEC bool "Verity forward error correction support" depends on DM_VERITY diff --git a/drivers/md/dm-verity-verify-sig.c b/drivers/md/dm-verity-verify-sig.c index b2b55c41e2cb..aadcf5e4a47c 100644 --- a/drivers/md/dm-verity-verify-sig.c +++ b/drivers/md/dm-verity-verify-sig.c @@ -21,8 +21,8 @@ static bool dm_verity_keyring_unsealed __ro_after_init; module_param_named(keyring_unsealed, dm_verity_keyring_unsealed, bool, 0444); MODULE_PARM_DESC(keyring_unsealed, "Leave the dm-verity keyring unsealed"); -static bool require_signatures; -module_param(require_signatures, bool, 0444); +static bool require_signatures = IS_ENABLED(CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG_FORCE); +module_param(require_signatures, bool_enable_only, 0444); MODULE_PARM_DESC(require_signatures, "Verify the roothash of dm-verity hash tree"); From 99909b74fb34174b22965c397782cd1a370517ff Mon Sep 17 00:00:00 2001 From: Hao Zhang Date: Sat, 12 Sep 2026 00:07:33 +0800 Subject: [PATCH 13/29] dm thin: Remove unnecessary locking from pool_map() pool_map() takes pool->lock only while setting the bio's data device. The pool target's data_dev is initialized when the target is constructed and is not changed afterwards. The device-mapper target remains alive while its map callback is running, so pool->lock is not needed to protect data_dev. Remove the unnecessary locking. This also avoids holding pool->lock across bio_set_dev(), which may perform a blkcg lookup and acquire the request queue's queue_lock. Signed-off-by: Hao Zhang Signed-off-by: Mikulas Patocka --- drivers/md/dm-thin.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/drivers/md/dm-thin.c b/drivers/md/dm-thin.c index 59392de7a477..358ed77ffb2b 100644 --- a/drivers/md/dm-thin.c +++ b/drivers/md/dm-thin.c @@ -3438,14 +3438,11 @@ static int pool_ctr(struct dm_target *ti, unsigned int argc, char **argv) static int pool_map(struct dm_target *ti, struct bio *bio) { struct pool_c *pt = ti->private; - struct pool *pool = pt->pool; /* * As this is a singleton target, ti->begin is always zero. */ - spin_lock_irq(&pool->lock); bio_set_dev(bio, pt->data_dev->bdev); - spin_unlock_irq(&pool->lock); return DM_MAPIO_REMAPPED; } From a279804752e6c767b7f9ecbb32a15bdfd2b80aea Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:35:31 +0200 Subject: [PATCH 14/29] dm: fix a bug in dm_accept_partial_bio dm-writecache doesn't work in pmem mode - mkfs.ext4 and the following fsck.ext4 fail with data corruption reported. The commit 7dd76d1feec7 ("dm: improve bio splitting and associated IO accounting") introduced a latent bug in dm_accept_partial_bio - it set tio->io->sectors = n_sectors;. This is incorrect, according to the diagram in the source code at dm_accept_partial_bio, it should be the size of regions 1+2, not the region 2 alone. The latent bug become real bug with the commit 8b211aaccb91 ("dm: add two stage requeue mechanism"). dm-writecache in pmem mode is the only target that actually advances bio with bio_advance and then calls dm_accept_partial_bio (so, the region 1 is non-empty). All the other targets call dm_accept_partial_bio with region 1 empty, so bug went unnoticed for so long. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Fixes: 8b211aaccb91 ("dm: add two stage requeue mechanism") --- 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 eb9e32995b79..1881d86111fb 100644 --- a/drivers/md/dm.c +++ b/drivers/md/dm.c @@ -1350,7 +1350,7 @@ void dm_accept_partial_bio(struct bio *bio, unsigned int n_sectors) * for accounting but it is being reduced so update accordingly. */ dm_io_set_flag(io, DM_IO_WAS_SPLIT); - io->sectors = n_sectors; + io->sectors = *tio->len_ptr; io->sector_offset = bio_sectors(io->orig_bio); } EXPORT_SYMBOL_GPL(dm_accept_partial_bio); From 78a6afc2999d26e5a30a56db77db34c22b88f5d5 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:38:25 +0200 Subject: [PATCH 15/29] dm-writecache: report I/O error if flush failed There was a bug that if writecache_flush fails due to I/O error, the error is not reported in the FUA bio. This commit fixes it. Signed-off-by: Mikulas Patocka Assisted-by: opencode:big-pickle Cc: stable@vger.kernel.org Fixes: 48debafe4f2f ("dm: add writecache target") --- drivers/md/dm-writecache.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index 68e3fb300fc1..e278359cb268 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -1494,10 +1494,13 @@ static enum wc_map_op writecache_map_write(struct dm_writecache *wc, struct bio } } while (bio->bi_iter.bi_size); - if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= wc->autocommit_blocks)) + if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= wc->autocommit_blocks)) { writecache_flush(wc); - else + if (writecache_has_error(wc)) + bio->bi_status = BLK_STS_IOERR; + } else { writecache_schedule_autocommit(wc); + } return WC_MAP_SUBMIT; } From c1ab80af0d99a906b60b769f2e692c37d2db1323 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:39:44 +0200 Subject: [PATCH 16/29] dm-writecache: fix a quirk in statistics writecache_bio_copy_ssd() unconditionally adds the number of extra blocks in the merged bio to stats.writes_allocate. That is only correct when the entries were taken off the freelist. When the function is called with search_used == true -- i.e. from the write_hits_uncommitted / write_hits_committed paths in writecache_map_write() -- the first entry was found in the rbtree and every following entry is picked up with rb_next(), so nothing is allocated at all. Accordingly the writes_allocate++ that accompanies a successful writecache_pop_from_freelist() is skipped for that case, but the batch accounting below still adds n - 1. An in-place overwrite of n contiguous cached blocks therefore reports n - 1 allocations that never happened. Only the dmsetup status output is affected; there is no functional change. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org Fixes: b2676e1482af ("dm writecache: count number of blocks written, not number of write bios") --- drivers/md/dm-writecache.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index e278359cb268..e974204b0d6f 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -1425,7 +1425,8 @@ static void writecache_bio_copy_ssd(struct dm_writecache *wc, struct bio *bio, dm_accept_partial_bio(bio, bio_size >> SECTOR_SHIFT); wc->stats.writes += bio->bi_iter.bi_size >> wc->block_size_bits; - wc->stats.writes_allocate += (bio->bi_iter.bi_size - wc->block_size) >> wc->block_size_bits; + if (!search_used) + wc->stats.writes_allocate += (bio->bi_iter.bi_size - wc->block_size) >> wc->block_size_bits; if (unlikely(wc->uncommitted_blocks >= wc->autocommit_blocks)) { wc->uncommitted_blocks = 0; From b15dc73f60ebf02410bc7014ce34fb7fd1040045 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:41:11 +0200 Subject: [PATCH 17/29] dm-writecache: fix a crash if we attempt to use non-dax device If we specify a non-DAX device as a cache device in the 'P' mode, dm-writecache crashes on NULL pointer dereference inside dax_synchronous. Note that this is not a security bug because only root can trigger it. Signed-off-by: Mikulas Patocka Fixes: a46624580376 ("dm writecache: reject asynchronous pmem devices") Cc: stable@vger.kernel.org Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-writecache.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index e974204b0d6f..2b3950dd45e8 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -2499,6 +2499,11 @@ static int writecache_ctr(struct dm_target *ti, unsigned int argc, char **argv) } if (WC_MODE_PMEM(wc)) { + if (!wc->ssd_dev->dax_dev) { + r = -EOPNOTSUPP; + ti->error = "Cache device is not a DAX device"; + goto bad; + } if (!dax_synchronous(wc->ssd_dev->dax_dev)) { r = -EOPNOTSUPP; ti->error = "Asynchronous persistent memory not supported as pmem cache"; From 8a0cc5f3446a324b13f62020eeddb32d6ba1c2db Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:42:03 +0200 Subject: [PATCH 18/29] dm-writecache: fix uncommitted_blocks underflow Revert the commit 39495b12ef1cf602e6abd350dce2ef4199906531 writecache_bio_copy_ssd sets wc->uncommitted_blocks = 0; and then queues the flush work: queue_work(wc->writeback_wq, &wc->flush_work); It may be possible that discard is dispatched in this window, and the discard would decrement wc->uncommitted_blocks to -1U. Note that this bug is benign - wc->uncommitted_blocks is only used to trigged autocommit, so decrementing it to -1U will trigger a spurious autocommit, but won't cause any data corruption. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 Fixes: 39495b12ef1c ("dm writecache: correct uncommitted_block when discarding uncommitted entry") Cc: stable@vger.kernel.org --- drivers/md/dm-writecache.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index 2b3950dd45e8..a38780a7b1be 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -901,8 +901,6 @@ static void writecache_discard(struct dm_writecache *wc, sector_t start, sector_ } discarded_something = true; } - if (!writecache_entry_is_committed(wc, e)) - wc->uncommitted_blocks--; writecache_free_entry(wc, e); } From c0df022cb0fa2bbee74bd9b90c66790bcd4e3050 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 30 Sep 2026 21:43:26 +0200 Subject: [PATCH 19/29] dm-writecache: fix REQ_FUA processing REQ_FUA did not flush metadata in SSD mode, this commit fixes it. Signed-off-by: Mikulas Patocka Cc: stable@vger.kernel.org Fixes: c1005322ff02 ("dm writecache: handle REQ_FUA") Assisted-by: Claude:claude-opus-5 Assisted-by: Codex:gpt-6-sol --- drivers/md/dm-writecache.c | 45 +++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/md/dm-writecache.c b/drivers/md/dm-writecache.c index a38780a7b1be..6f977fb8c939 100644 --- a/drivers/md/dm-writecache.c +++ b/drivers/md/dm-writecache.c @@ -188,6 +188,7 @@ struct dm_writecache { struct task_struct *endio_thread; struct task_struct *flush_thread; + spinlock_t flush_list_lock; struct bio_list flush_list; struct dm_kcopyd_client *dm_kcopyd; @@ -1289,9 +1290,11 @@ static int writecache_flush_thread(void *data) struct bio *bio; wc_lock(wc); + spin_lock_irq(&wc->flush_list_lock); bio = bio_list_pop(&wc->flush_list); if (!bio) { set_current_state(TASK_INTERRUPTIBLE); + spin_unlock_irq(&wc->flush_list_lock); wc_unlock(wc); if (unlikely(kthread_should_stop())) { @@ -1302,6 +1305,7 @@ static int writecache_flush_thread(void *data) schedule(); continue; } + spin_unlock_irq(&wc->flush_list_lock); if (bio_op(bio) == REQ_OP_DISCARD) { writecache_discard(wc, bio->bi_iter.bi_sector, @@ -1323,9 +1327,12 @@ static int writecache_flush_thread(void *data) static void writecache_offload_bio(struct dm_writecache *wc, struct bio *bio) { + unsigned long flags; + spin_lock_irqsave(&wc->flush_list_lock, flags); if (bio_list_empty(&wc->flush_list)) wake_up_process(wc->flush_thread); bio_list_add(&wc->flush_list, bio); + spin_unlock_irqrestore(&wc->flush_list_lock, flags); } enum wc_map_op { @@ -1437,6 +1444,8 @@ static void writecache_bio_copy_ssd(struct dm_writecache *wc, struct bio *bio, static enum wc_map_op writecache_map_write(struct dm_writecache *wc, struct bio *bio) { struct wc_entry *e; + enum wc_map_op ret = WC_MAP_SUBMIT; + bool need_flush = false; do { bool found_entry = false; @@ -1473,7 +1482,8 @@ static enum wc_map_op writecache_map_write(struct dm_writecache *wc, struct bio writecache_map_remap_origin(wc, bio, e); wc->stats.writes_around += bio->bi_iter.bi_size >> wc->block_size_bits; wc->stats.writes += bio->bi_iter.bi_size >> wc->block_size_bits; - return WC_MAP_REMAP_ORIGIN; + ret = WC_MAP_REMAP_ORIGIN; + goto flush_pmem_and_ret; } wc->stats.writes_blocked_on_freelist++; writecache_wait_on_freelist(wc); @@ -1487,21 +1497,25 @@ static enum wc_map_op writecache_map_write(struct dm_writecache *wc, struct bio if (WC_MODE_PMEM(wc)) { bio_copy_block(wc, bio, memory_data(wc, e)); wc->stats.writes++; + need_flush = true; } else { writecache_bio_copy_ssd(wc, bio, e, search_used); return WC_MAP_REMAP; } } while (bio->bi_iter.bi_size); - if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= wc->autocommit_blocks)) { - writecache_flush(wc); - if (writecache_has_error(wc)) - bio->bi_status = BLK_STS_IOERR; - } else { - writecache_schedule_autocommit(wc); +flush_pmem_and_ret: + if (need_flush) { + if (unlikely(bio->bi_opf & REQ_FUA || wc->uncommitted_blocks >= wc->autocommit_blocks)) { + writecache_flush(wc); + if (writecache_has_error(wc)) + ret = WC_MAP_ERROR; + } else { + writecache_schedule_autocommit(wc); + } } - return WC_MAP_SUBMIT; + return ret; } static enum wc_map_op writecache_map_flush(struct dm_writecache *wc, struct bio *bio) @@ -1591,7 +1605,10 @@ static int writecache_map(struct dm_target *ti, struct bio *bio) case WC_MAP_REMAP: /* make sure that writecache_end_io decrements bio_in_progress: */ - bio->bi_private = (void *)1; + if (unlikely(bio->bi_opf & REQ_FUA) && bio_op(bio) == REQ_OP_WRITE) + bio->bi_private = (void *)3; + else + bio->bi_private = (void *)1; atomic_inc(&wc->bio_in_progress[bio_data_dir(bio)]); wc_unlock(wc); return DM_MAPIO_REMAPPED; @@ -1621,12 +1638,19 @@ static int writecache_end_io(struct dm_target *ti, struct bio *bio, blk_status_t { struct dm_writecache *wc = ti->private; - if (bio->bi_private == (void *)1) { + if (bio->bi_private == (void *)1 || bio->bi_private == (void *)3) { int dir = bio_data_dir(bio); + bool fua = bio->bi_private == (void *)3; if (atomic_dec_and_test(&wc->bio_in_progress[dir])) if (unlikely(waitqueue_active(&wc->bio_in_progress_wait[dir]))) wake_up(&wc->bio_in_progress_wait[dir]); + + if (fua && likely(*status == BLK_STS_OK)) { + bio->bi_private = NULL; + writecache_offload_bio(wc, bio); + return DM_ENDIO_INCOMPLETE; + } } else if (bio->bi_private == (void *)2) { dm_iot_io_end(&wc->iot, 1); } @@ -2519,6 +2543,7 @@ static int writecache_ctr(struct dm_target *ti, unsigned int argc, char **argv) wc->memory_map_size -= (uint64_t)wc->start_sector << SECTOR_SHIFT; + spin_lock_init(&wc->flush_list_lock); bio_list_init(&wc->flush_list); wc->flush_thread = kthread_run(writecache_flush_thread, wc, "dm_writecache_flush"); if (IS_ERR(wc->flush_thread)) { From fe9d8c26ab25dcdc7481b3a8a1238cb8f58daab6 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 8 Oct 2026 16:28:07 -0400 Subject: [PATCH 20/29] dm-mpath: streamline setup work in the HST path selector fixed_power() is convoluted and does unnecessary work here, since hst_set_weights() calculates x^n for n = [1,64]. x^1 is x, and x^(n) is just x * x^(n-1), so all these values can be trivially computed from the previous value. Signed-off-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-ps-historical-service-time.c | 49 +++------------------- 1 file changed, 6 insertions(+), 43 deletions(-) diff --git a/drivers/md/dm-ps-historical-service-time.c b/drivers/md/dm-ps-historical-service-time.c index c4755112287a..e6324424d23a 100644 --- a/drivers/md/dm-ps-historical-service-time.c +++ b/drivers/md/dm-ps-historical-service-time.c @@ -36,6 +36,7 @@ #define HST_FIXED_SHIFT 10 /* 10 bits of decimal precision */ #define HST_FIXED_MAX (ULLONG_MAX >> HST_FIXED_SHIFT) #define HST_FIXED_1 (1 << HST_FIXED_SHIFT) +#define HST_FIXED_POINT_5 (1 << (HST_FIXED_SHIFT - 1)) #define HST_FIXED_95 972 #define HST_MAX_INFLIGHT HST_FIXED_1 #define HST_BUCKET_SHIFT 24 /* Buckets are ~ 16ms */ @@ -66,46 +67,6 @@ struct path_info { u64 outstanding; }; -/** - * fixed_power - compute: x^n, in O(log n) time - * - * @x: base of the power - * @frac_bits: fractional bits of @x - * @n: power to raise @x to. - * - * By exploiting the relation between the definition of the natural power - * function: x^n := x*x*...*x (x multiplied by itself for n times), and - * the binary encoding of numbers used by computers: n := \Sum n_i * 2^i, - * (where: n_i \elem {0, 1}, the binary vector representing n), - * we find: x^n := x^(\Sum n_i * 2^i) := \Prod x^(n_i * 2^i), which is - * of course trivially computable in O(log_2 n), the length of our binary - * vector. - * - * (see: kernel/sched/loadavg.c) - */ -static u64 fixed_power(u64 x, unsigned int frac_bits, unsigned int n) -{ - unsigned long result = 1UL << frac_bits; - - if (n) { - for (;;) { - if (n & 1) { - result *= x; - result += 1UL << (frac_bits - 1); - result >>= frac_bits; - } - n >>= 1; - if (!n) - break; - x *= x; - x += 1UL << (frac_bits - 1); - x >>= frac_bits; - } - } - - return result; -} - /* * Calculate the next value of an exponential moving average * a_1 = a_0 * e + a * (1 - e) @@ -123,7 +84,7 @@ static u64 fixed_ema(u64 last, u64 next, u64 weight) { last *= weight; last += next * (HST_FIXED_1 - weight); - last += 1ULL << (HST_FIXED_SHIFT - 1); + last += HST_FIXED_POINT_5; return last >> HST_FIXED_SHIFT; } @@ -167,8 +128,10 @@ static void hst_set_weights(struct path_selector *ps, unsigned int base) if (base >= HST_FIXED_1) return; - for (i = 0; i < HST_WEIGHT_COUNT - 1; i++) - s->weights[i] = fixed_power(base, HST_FIXED_SHIFT, i + 1); + s->weights[0] = base; + for (i = 1; i < HST_WEIGHT_COUNT - 1; i++) + s->weights[i] = (base * s->weights[i - 1] + + HST_FIXED_POINT_5) >> HST_FIXED_SHIFT; s->weights[HST_WEIGHT_COUNT - 1] = 0; } From eba776a4d9e47ad6baea96beeb8446c813f70ce1 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 8 Oct 2026 16:28:08 -0400 Subject: [PATCH 21/29] dm-mpath: standardize repeat_count handling in the HST path selector The Historical Service Time path selector ignores the repeat_count argument just like all the other path selectors. But all of the other path_selectors explicitly print a warning message when the repeat_count is set greater than 1. Also, all of the other selectors print 1 for the repeat_count when printing the table. Standardize the HST path selector to match. Signed-off-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-ps-historical-service-time.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-ps-historical-service-time.c b/drivers/md/dm-ps-historical-service-time.c index e6324424d23a..e0103eefe28f 100644 --- a/drivers/md/dm-ps-historical-service-time.c +++ b/drivers/md/dm-ps-historical-service-time.c @@ -31,7 +31,7 @@ #define DM_MSG_PREFIX "multipath historical-service-time" #define HST_MIN_IO 1 -#define HST_VERSION "0.1.1" +#define HST_VERSION "0.2.0" #define HST_FIXED_SHIFT 10 /* 10 bits of decimal precision */ #define HST_FIXED_MAX (ULLONG_MAX >> HST_FIXED_SHIFT) @@ -216,7 +216,7 @@ static int hst_status(struct path_selector *ps, struct dm_path *path, pi->outstanding, pi->stale_after); break; case STATUSTYPE_TABLE: - DMEMIT("0 "); + DMEMIT("%u ", pi->repeat_count); break; case STATUSTYPE_IMA: *result = '\0'; @@ -251,6 +251,11 @@ static int hst_add_path(struct path_selector *ps, struct dm_path *path, return -EINVAL; } + if (repeat_count > 1) { + DMWARN_LIMIT("repeat_count > 1 is deprecated, using 1 instead"); + repeat_count = 1; + } + /* allocate the path */ pi = kmalloc_obj(*pi); if (!pi) { From 17383c7b81b9fa0736bae1db21fed200eff3c6e3 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Thu, 8 Oct 2026 16:28:09 -0400 Subject: [PATCH 22/29] dm-mpath: check values in hst_status while holding spinlock To keep the values of pi->historical_service_time, pi->outstanding, and pi->stale_after consistent, get them while holding a spin_lock in hst_status(), like all other accesses do. Signed-off-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-ps-historical-service-time.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/drivers/md/dm-ps-historical-service-time.c b/drivers/md/dm-ps-historical-service-time.c index e0103eefe28f..3ad40505ff46 100644 --- a/drivers/md/dm-ps-historical-service-time.c +++ b/drivers/md/dm-ps-historical-service-time.c @@ -202,6 +202,7 @@ static int hst_status(struct path_selector *ps, struct dm_path *path, { unsigned int sz = 0; struct path_info *pi; + unsigned long flags; if (!path) { struct selector *s = ps->context; @@ -212,8 +213,10 @@ static int hst_status(struct path_selector *ps, struct dm_path *path, switch (type) { case STATUSTYPE_INFO: + spin_lock_irqsave(&pi->lock, flags); DMEMIT("%llu %llu %llu ", pi->historical_service_time, pi->outstanding, pi->stale_after); + spin_unlock_irqrestore(&pi->lock, flags); break; case STATUSTYPE_TABLE: DMEMIT("%u ", pi->repeat_count); From 1acc65c30f85d3cce8dbd53ab40d59339a0add85 Mon Sep 17 00:00:00 2001 From: Itai Handler Date: Sun, 4 Oct 2026 13:16:22 +0300 Subject: [PATCH 23/29] dm-crypt: allow encryption sector size up to PAGE_SIZE The "sector_size:" option is capped at 4096 bytes. The reason is stated in commit 8f0009a22517 ("dm crypt: optionally support larger encryption sector size"): "the maximal IO must fit into the page limit, so the limit is set to the minimal page size possible (4096 bytes)." The rule is right; only the way it is resolved is not. The page limit it refers to is a property of the kernel that is running, but it was written as the smallest page size of any architecture, so a kernel with larger pages is held to a limit that belongs to a different one. The block layer has since stopped doing that for its own block size, in commit 47dd67532303 ("block/bdev: lift block size restrictions to 64k"), and dm-crypt is now the stricter of the two. Apply the same rule to the kernel being built: raise the cap to min(PAGE_SIZE, BLK_MAX_BLOCK_SIZE). PAGE_SIZE is still the page limit the original commit meant. A sector is passed to the crypto API as a single scatterlist entry, bio_iter_iovec() never returns more than PAGE_SIZE bytes, and crypt_alloc_buffer() may fall back to order-0 pages for the write bounce buffer. BLK_MAX_BLOCK_SIZE caps the logical block size that crypt_io_hints() announces. It is PAGE_SIZE without transparent hugepages, and 64K with them, which no architecture having a PAGE_SIZE above 64K can enable - so it does not lower the bound on any configuration today. It is in the expression so that this target cannot announce a block size blk_validate_limits() would reject. A larger unit also turns several crypto requests per page into one. Whether that is worth anything depends on the driver: for a CPU cipher the per-request cost is small, and it only pays off where a request carries a large fixed cost, as when the cipher is offloaded over DMA. Widen sector_size to unsigned int so that it can hold a sector larger than 65535. That also makes the option reject an argument of 69632, which %hu truncates to 4096 and accepts as a 4096-byte sector. Document that an encryption sector larger than the unit the device writes atomically can be torn by a power failure, and what each cipher mode does when that happens. The maximum stays 4096 wherever PAGE_SIZE is 4096, the default stays 512 bytes, and every table using a size from 512 to 4096 behaves as it did. The one behavioural change is the truncation above: an argument that wrapped into range is now rejected rather than silently accepted. A mapping larger than 4096 bytes can only be activated where PAGE_SIZE allows, so it is not suitable for portable on-disk formats. Bump the target version so that userspace can detect the new limit. Assisted-by: LLM Signed-off-by: Itai Handler Signed-off-by: Mikulas Patocka --- .../admin-guide/device-mapper/dm-crypt.rst | 20 ++++++++++++- drivers/md/dm-crypt.c | 30 +++++++++++++++---- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/Documentation/admin-guide/device-mapper/dm-crypt.rst b/Documentation/admin-guide/device-mapper/dm-crypt.rst index 4467f6d4b632..3a87cd4daf13 100644 --- a/Documentation/admin-guide/device-mapper/dm-crypt.rst +++ b/Documentation/admin-guide/device-mapper/dm-crypt.rst @@ -153,9 +153,27 @@ integrity_key_size: sector_size: Use as the encryption unit instead of 512 bytes sectors. - This option can be in range 512 - 4096 bytes and must be power of two. + This option can be in range 512 - PAGE_SIZE bytes, further limited by + the block layer's maximum block size, and must be power of two. Virtual device will announce this size as a minimal IO and logical sector. + An encryption unit larger than 4096 bytes can only be used on a system + whose PAGE_SIZE is at least that large, so such a mapping is not + portable across architectures and is unsuitable for portable on-disk + formats such as LUKS. + + An encryption sector larger than the unit the underlying device writes + atomically can be torn by a power failure, leaving part of the sector + written and part not. A device that advertises no atomic write unit + gives no such guarantee beyond a single logical block, so this is + already possible at 4096 bytes; a larger sector widens the window. + With XTS and ECB the torn sector decrypts to a mixture of old and new + data, as a torn write does on an unencrypted device. With chaining + modes the block at the tear also decrypts to garbage, with AEAD the + whole sector fails authentication, and with the wide-block diffusers + the whole sector decrypts to garbage. Use a large sector only where + losing a sector to a power failure is acceptable. + iv_large_sectors IV generators will use sector number counted in units instead of default 512 bytes sectors. diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 8e838530faab..045face6924b 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -182,7 +182,7 @@ struct crypt_config { } iv_gen_private; u64 iv_offset; unsigned int iv_size; - unsigned short sector_size; + unsigned int sector_size; unsigned char sector_shift; union { @@ -241,6 +241,24 @@ struct crypt_config { #define MAX_TAG_SIZE 480 #define POOL_ENTRY_SIZE 512 +/* + * Largest encryption sector size that can be requested with the + * "sector_size:" option. + * + * A sector is handed to the crypto API as a single scatterlist entry, so it + * has to be covered by one bio_vec. bio_iter_iovec() never returns more than + * PAGE_SIZE bytes, and crypt_alloc_buffer() may fall back to order-0 pages + * for the write bounce buffer, so PAGE_SIZE is the ceiling. + * + * crypt_io_hints() announces the sector size as the logical block size, which + * the block layer caps at BLK_MAX_BLOCK_SIZE. That cap is never below + * PAGE_SIZE in any configuration today, so it does not lower the limit; take + * the minimum anyway so that this target cannot announce a block size + * blk_validate_limits() would reject. + */ +#define DM_CRYPT_MAX_SECTOR_SIZE MIN_T(unsigned int, PAGE_SIZE, \ + BLK_MAX_BLOCK_SIZE) + static DEFINE_SPINLOCK(dm_crypt_clients_lock); static unsigned int dm_crypt_clients_n; static volatile unsigned long dm_crypt_pages_per_client; @@ -3137,9 +3155,9 @@ static int crypt_ctr_optional(struct dm_target *ti, unsigned int argc, char **ar } cc->key_mac_size = val; set_bit(CRYPT_KEY_MAC_SIZE_SET, &cc->cipher_flags); - } else if (sscanf(opt_string, "sector_size:%hu%c", &cc->sector_size, &dummy) == 1) { + } else if (sscanf(opt_string, "sector_size:%u%c", &cc->sector_size, &dummy) == 1) { if (cc->sector_size < (1 << SECTOR_SHIFT) || - cc->sector_size > 4096 || + cc->sector_size > DM_CRYPT_MAX_SECTOR_SIZE || (cc->sector_size & (cc->sector_size - 1))) { ti->error = "Invalid feature value for sector_size"; return -EINVAL; @@ -3559,7 +3577,7 @@ static void crypt_status(struct dm_target *ti, status_type_t type, if (cc->used_tag_size) DMEMIT(" integrity:%u:%s", cc->used_tag_size, cc->cipher_auth); if (cc->sector_size != (1 << SECTOR_SHIFT)) - DMEMIT(" sector_size:%d", cc->sector_size); + DMEMIT(" sector_size:%u", cc->sector_size); if (test_bit(CRYPT_IV_LARGE_SECTORS, &cc->cipher_flags)) DMEMIT(" iv_large_sectors"); if (test_bit(CRYPT_KEY_MAC_SIZE_SET, &cc->cipher_flags)) @@ -3585,7 +3603,7 @@ static void crypt_status(struct dm_target *ti, status_type_t type, DMEMIT(",integrity_tag_size=%u,cipher_auth=%s", cc->used_tag_size, cc->cipher_auth); if (cc->sector_size != (1 << SECTOR_SHIFT)) - DMEMIT(",sector_size=%d", cc->sector_size); + DMEMIT(",sector_size=%u", cc->sector_size); if (cc->cipher_string) DMEMIT(",cipher_string=%s", cc->cipher_string); @@ -3703,7 +3721,7 @@ static void crypt_io_hints(struct dm_target *ti, struct queue_limits *limits) static struct target_type crypt_target = { .name = "crypt", - .version = {1, 29, 0}, + .version = {1, 30, 0}, .module = THIS_MODULE, .ctr = crypt_ctr, .dtr = crypt_dtr, From 829c4d21148a205eca864429e06530319e1f92d5 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Fri, 9 Oct 2026 18:14:45 +0200 Subject: [PATCH 24/29] dm-snapshot: fix crash if dm_io_client_create fails If dm_io_client_create() fails, it stores error into the ps->io_client variable. The code would try to free it later with dm_io_client_destroy(ps->io_client), causing an invalid pointer dereference. Fix this by setting ps->io_client = NULL. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 Cc: stable@vger.kernel.org --- drivers/md/dm-snap-persistent.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/drivers/md/dm-snap-persistent.c b/drivers/md/dm-snap-persistent.c index e86d929b0e95..b587ee733056 100644 --- a/drivers/md/dm-snap-persistent.c +++ b/drivers/md/dm-snap-persistent.c @@ -325,8 +325,11 @@ static int read_header(struct pstore *ps, int *new_snapshot) } ps->io_client = dm_io_client_create(); - if (IS_ERR(ps->io_client)) - return PTR_ERR(ps->io_client); + if (IS_ERR(ps->io_client)) { + r = PTR_ERR(ps->io_client); + ps->io_client = NULL; + return r; + } r = alloc_area(ps); if (r) From bbd6f974d0386036e560af332b6b8cdd17010a72 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Fri, 9 Oct 2026 18:16:02 +0200 Subject: [PATCH 25/29] dm-snapshot: make the chunk list sorted by the chunk number Commit 8581b19eb2c5 ("dm-snapshot: fix 'scheduling while atomic' on real-time kernels") replaced hlist_bl_for_each_entry with hlist_for_each_entry, but the two iteration macros differ after the loop ends: hlist_bl_for_each_entry leaves "e" pointing at the last element, while hlist_for_each_entry() sets it to NULL. The result is that the list is no longer sorted by chunk number as it was intended to be. This bug doesn't cause data corruption because dm_lookup_exception walks the full list. Restore the invariant by tracking the list tail in the "prev" variable and appending after it with hlist_add_behind. Signed-off-by: Mikulas Patocka Fixes: 8581b19eb2c5 ("dm-snapshot: fix 'scheduling while atomic' on real-time kernels") Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-snap.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/drivers/md/dm-snap.c b/drivers/md/dm-snap.c index c8a9ca1eeae7..96620e4a21c1 100644 --- a/drivers/md/dm-snap.c +++ b/drivers/md/dm-snap.c @@ -766,7 +766,7 @@ static void dm_insert_exception(struct dm_exception_table *eh, struct dm_exception *new_e) { struct hlist_head *l; - struct dm_exception *e = NULL; + struct dm_exception *e = NULL, *prev = NULL; l = &eh->table[exception_hash(eh, new_e->old_chunk)].head; @@ -798,21 +798,23 @@ static void dm_insert_exception(struct dm_exception_table *eh, if (new_e->old_chunk < e->old_chunk) break; + + prev = e; } out: - if (!e) { + if (e && new_e->old_chunk < e->old_chunk) { + /* Add before an existing exception */ + hlist_add_before(&new_e->hash_list, &e->hash_list); + } else if (prev) { + /* Add to l's tail: prev is the last exception in this slot */ + hlist_add_behind(&new_e->hash_list, &prev->hash_list); + } else { /* * Either the table doesn't support consecutive chunks or slot * l is empty. */ hlist_add_head(&new_e->hash_list, l); - } else if (new_e->old_chunk < e->old_chunk) { - /* Add before an existing exception */ - hlist_add_before(&new_e->hash_list, &e->hash_list); - } else { - /* Add to l's tail: e is the last exception in this slot */ - hlist_add_behind(&new_e->hash_list, &e->hash_list); } } From 1190dbeaeaa026a4879e675b8556a150c36a7ced Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 12 Oct 2026 16:07:58 +0200 Subject: [PATCH 26/29] dm-bufio: fix non-working memory limit The function evict_old is supposed to loop and evict buffers while the allocated size is above the threshold. However if it hits one bufio client that is empty, it stops and doesn't attempt to scan other clients. This commit fixes it, so that it skips empty clients and retries up to dm_bufio_client_count times. Fixes: 450e8dee51aa ("dm bufio: improve concurrent IO performance") Cc: stable@vger.kernel.org Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-bufio.c | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index d58eed96a8ff..edad64654ef6 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -2545,6 +2545,13 @@ struct dm_bufio_client *dm_bufio_client_create(struct block_device *bdev, unsign init_waitqueue_head(&c->free_buffer_wait); c->async_write_error = 0; + /* + * A client with no buffers yet is not holding the oldest buffer in + * the system; leaving this zeroed would claim exactly that and keep + * the client pinned at the front of dm_bufio_all_clients. + */ + c->oldest_buffer = jiffies; + c->dm_io = dm_io_client_create(); if (IS_ERR(c->dm_io)) { r = PTR_ERR(c->dm_io); @@ -2779,9 +2786,23 @@ static unsigned long __evict_a_few(unsigned long nr_buffers) dm_bufio_unlock(c); - if (count) + if (count) { c->oldest_buffer = oldest_buffer; - __insert_client(c); + __insert_client(c); + } else { + /* + * We evicted nothing, so 'oldest_buffer' tells us nothing new + * and the client keeps its old, possibly very stale, value. + * Requeue it at the back rather than by that value, otherwise + * __insert_client() would put it straight back at the front + * and every subsequent call would pop this same client again, + * never reaching any of the others. + * + * This leaves the list slightly out of order, which is fine - + * it is only ever an approximation. + */ + list_add_tail(&c->client_list, &dm_bufio_all_clients); + } return count; } @@ -2806,11 +2827,23 @@ static void evict_old(void) { unsigned long threshold = dm_bufio_cache_size - dm_bufio_cache_size / DM_BUFIO_LOW_WATERMARK_RATIO; + int unproductive = 0; mutex_lock(&dm_bufio_clients_lock); while (dm_bufio_current_allocated > threshold) { - if (!__evict_a_few(64)) - break; + if (__evict_a_few(64)) { + unproductive = 0; + } else { + /* + * This client had nothing to give. Try the others + * before giving up: one idle client, or one whose + * buffers are all held, must not stop us evicting + * from everybody else. Stop once a whole sweep of + * the client list has freed nothing. + */ + if (++unproductive >= dm_bufio_client_count) + break; + } cond_resched(); } mutex_unlock(&dm_bufio_clients_lock); From 005d2ec5723cd4ac740de678274623f1fadd6a6e Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 12 Oct 2026 16:09:52 +0200 Subject: [PATCH 27/29] dm-bufio: fix a race leading to an unsigned integer underflow The function __scan tests "count - freed <= retain_target" and then sets "atomic_long_set(&c->need_shrink, 0);" It exits with the condition "if (!atomic_long_read(&c->need_shrink)) break;" It may be possible that between these two statements, another thread sets &c->need_shrink to a non-zero value. In this case, the function __scan continues and it may possibly the "freed" variable is larger than the "count" variable. The expression "count - freed" underflows and the function __scan will continue to free all the buffers, even those that are below retain_target. Fix this bug by immediatelly exiting if we set "atomic_long_set(&c->need_shrink, 0)"; and don't attempt to re-read c->need_shrink afterwards. Fixes: 450e8dee51aa ("dm bufio: improve concurrent IO performance") Cc: stable@vger.kernel.org Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-bufio.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index edad64654ef6..5f0226270761 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -2426,8 +2426,10 @@ static void __scan(struct dm_bufio_client *c) for (l = 0; l < LIST_SIZE; l++) { while (true) { - if (count - freed <= retain_target) + if (count - freed <= retain_target) { atomic_long_set(&c->need_shrink, 0); + break; + } if (!atomic_long_read(&c->need_shrink)) break; From 17f7f7ef5dc2f4607ba8cdf486cb25cc564cd6f7 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Mon, 12 Oct 2026 17:56:06 +0200 Subject: [PATCH 28/29] dm-bufio: clean-up cache size reading In the original dm-bufio, setting dm_bufio_cache_size to 0 meant to set the default value. However, when the code was refactored in the commit 450e8dee51aa, the meaning of 0 is inconsistent, at some places, we just read the variable dm_bufio_cache_size without testing it to zero. This commit cleans up the zero handling, all reads of dm_bufio_cache_size go through the function dm_budio_get_cache_size() and this function tests the variable for zero and sets the default cache size in this case. Signed-off-by: Mikulas Patocka Assisted-by: Claude:claude-opus-5 --- drivers/md/dm-bufio.c | 59 ++++++++++++++----------------------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/drivers/md/dm-bufio.c b/drivers/md/dm-bufio.c index 5f0226270761..3b66cf1cb931 100644 --- a/drivers/md/dm-bufio.c +++ b/drivers/md/dm-bufio.c @@ -1044,12 +1044,6 @@ static unsigned long dm_bufio_default_cache_size; */ static unsigned long dm_bufio_cache_size; -/* - * A copy of dm_bufio_cache_size because dm_bufio_cache_size can change - * at any time. If it disagrees, the user has changed cache size. - */ -static unsigned long dm_bufio_cache_size_latch; - static DEFINE_SPINLOCK(global_spinlock); static unsigned int dm_bufio_max_age; /* No longer does anything */ @@ -1076,7 +1070,7 @@ static int dm_bufio_client_count; static LIST_HEAD(dm_bufio_all_clients); /* - * This mutex protects dm_bufio_cache_size_latch and dm_bufio_client_count + * This mutex protects dm_bufio_client_count */ static DEFINE_MUTEX(dm_bufio_clients_lock); @@ -1093,6 +1087,17 @@ static void buffer_record_stack(struct dm_buffer *b) /*----------------------------------------------------------------*/ +static unsigned long dm_budio_get_cache_size(void) +{ + unsigned long s = READ_ONCE(dm_bufio_cache_size); + if (unlikely(!s)) { + unsigned long d = dm_bufio_default_cache_size; + (void)cmpxchg(&dm_bufio_cache_size, 0, d); + s = d; + } + return s; +} + static void adjust_total_allocated(struct dm_buffer *b, bool unlink) { unsigned char data_mode; @@ -1120,35 +1125,13 @@ static void adjust_total_allocated(struct dm_buffer *b, bool unlink) dm_bufio_peak_allocated = dm_bufio_current_allocated; if (!unlink) { - if (dm_bufio_current_allocated > dm_bufio_cache_size) + if (dm_bufio_current_allocated > dm_budio_get_cache_size()) queue_work(dm_bufio_wq, &dm_bufio_replacement_work); } spin_unlock(&global_spinlock); } -/* - * Change the number of clients and recalculate per-client limit. - */ -static void __cache_size_refresh(void) -{ - if (WARN_ON(!mutex_is_locked(&dm_bufio_clients_lock))) - return; - if (WARN_ON(dm_bufio_client_count < 0)) - return; - - dm_bufio_cache_size_latch = READ_ONCE(dm_bufio_cache_size); - - /* - * Use default if set to 0 and report the actual cache size used. - */ - if (!dm_bufio_cache_size_latch) { - (void)cmpxchg(&dm_bufio_cache_size, 0, - dm_bufio_default_cache_size); - dm_bufio_cache_size_latch = dm_bufio_default_cache_size; - } -} - /* * Allocating buffer data. * @@ -1618,7 +1601,7 @@ static struct dm_buffer *__alloc_buffer_wait_no_callback(struct dm_bufio_client * be allocated. */ while (1) { - if (dm_bufio_cache_size_latch != 1) { + if (dm_budio_get_cache_size() != 1) { b = alloc_buffer(c, GFP_NOWAIT | __GFP_NORETRY | __GFP_NOMEMALLOC); if (b) return b; @@ -1627,7 +1610,7 @@ static struct dm_buffer *__alloc_buffer_wait_no_callback(struct dm_bufio_client if (nf == NF_PREFETCH) return NULL; - if (dm_bufio_cache_size_latch != 1 && !tried_noio_alloc) { + if (dm_budio_get_cache_size() != 1 && !tried_noio_alloc) { dm_bufio_unlock(c); b = alloc_buffer(c, GFP_NOIO | __GFP_NORETRY | __GFP_NOMEMALLOC | __GFP_NOWARN); dm_bufio_lock(c); @@ -2616,7 +2599,6 @@ struct dm_bufio_client *dm_bufio_client_create(struct block_device *bdev, unsign mutex_lock(&dm_bufio_clients_lock); dm_bufio_client_count++; list_add(&c->client_list, &dm_bufio_all_clients); - __cache_size_refresh(); mutex_unlock(&dm_bufio_clients_lock); return c; @@ -2658,7 +2640,6 @@ void dm_bufio_client_destroy(struct dm_bufio_client *c) list_del(&c->client_list); dm_bufio_client_count--; - __cache_size_refresh(); mutex_unlock(&dm_bufio_clients_lock); @@ -2827,8 +2808,8 @@ static void check_watermarks(void) static void evict_old(void) { - unsigned long threshold = dm_bufio_cache_size - - dm_bufio_cache_size / DM_BUFIO_LOW_WATERMARK_RATIO; + unsigned long cs = dm_budio_get_cache_size(); + unsigned long threshold = cs - cs / DM_BUFIO_LOW_WATERMARK_RATIO; int unproductive = 0; mutex_lock(&dm_bufio_clients_lock); @@ -2889,10 +2870,8 @@ static int __init dm_bufio_init(void) #endif dm_bufio_default_cache_size = mem; - - mutex_lock(&dm_bufio_clients_lock); - __cache_size_refresh(); - mutex_unlock(&dm_bufio_clients_lock); + if (!dm_bufio_cache_size) + dm_bufio_cache_size = mem; dm_bufio_wq = alloc_workqueue("dm_bufio_cache", WQ_MEM_RECLAIM | WQ_PERCPU, 0); From 334714e4bdf8868c17a7650a7502ba1c90a8721f Mon Sep 17 00:00:00 2001 From: Fan Wu Date: Mon, 21 Sep 2026 15:05:16 -0700 Subject: [PATCH 29/29] dm-verity: clear LSM integrity state on suspend dm-verity publishes its root hash and signature state to LSMs in verity_preresume(). LSMs such as IPE store this information in the block device's security blob and use it to authorize access to files. The original LSM integration relied on DM_TARGET_IMMUTABLE to prevent verity from being replaced by a different target type. However, DM allows tables containing a DM_TARGET_WILDCARD target to replace immutable targets. The block device can then retain the old integrity state even though its data is no longer verified by dm-verity. Clear the published root hash and signature state in verity_postsuspend() so that the block device does not retain stale dm-verity attributes after the verity target is replaced. Assisted-by: gpt-6-astra Signed-off-by: Fan Wu Signed-off-by: Mikulas Patocka --- drivers/md/dm-verity-target.c | 39 ++++++++++++++++++++++++++++------- 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/drivers/md/dm-verity-target.c b/drivers/md/dm-verity-target.c index cb105ccc47ec..6d589867623c 100644 --- a/drivers/md/dm-verity-target.c +++ b/drivers/md/dm-verity-target.c @@ -825,13 +825,6 @@ static int verity_map(struct dm_target *ti, struct bio *bio) return DM_MAPIO_SUBMITTED; } -static void verity_postsuspend(struct dm_target *ti) -{ - struct dm_verity *v = ti->private; - flush_workqueue(v->verify_wq); - dm_bufio_client_reset(v->bufio); -} - /* * Status: V (valid) or C (corruption found) */ @@ -1733,6 +1726,11 @@ static int verity_security_set_signature(struct block_device *bdev, v->sig_size); } +static void verity_security_clear_signature(struct block_device *bdev) +{ + security_bdev_setintegrity(bdev, LSM_INT_DMVERITY_SIG_VALID, NULL, 0); +} + #else static inline int verity_security_set_signature(struct block_device *bdev, @@ -1741,6 +1739,10 @@ static inline int verity_security_set_signature(struct block_device *bdev, return 0; } +static inline void verity_security_clear_signature(struct block_device *bdev) +{ +} + #endif /* CONFIG_DM_VERITY_VERIFY_ROOTHASH_SIG */ /* @@ -1779,8 +1781,31 @@ static int verity_preresume(struct dm_target *ti) return r; } +static void verity_security_clear_integrity(struct dm_target *ti) +{ + struct block_device *bdev = dm_disk(dm_table_get_md(ti->table))->part0; + + security_bdev_setintegrity(bdev, LSM_INT_DMVERITY_ROOTHASH, NULL, 0); + verity_security_clear_signature(bdev); +} + +#else + +static inline void verity_security_clear_integrity(struct dm_target *ti) +{ +} + #endif /* CONFIG_SECURITY */ +static void verity_postsuspend(struct dm_target *ti) +{ + struct dm_verity *v = ti->private; + + flush_workqueue(v->verify_wq); + dm_bufio_client_reset(v->bufio); + verity_security_clear_integrity(ti); +} + static struct target_type verity_target = { .name = "verity", /* Note: the LSMs depend on the singleton and immutable features */