From 148845aa1921d95ef5dc851c76e6a284f6657df6 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Wed, 26 Aug 2026 00:25:33 +0000 Subject: dm-crypt: fix a tiny race condition in crypt_dec_pending crypt_dec_pending reads io->error before calling atomic_dec_and_test. Another context, for example crypt_endio called from an interrupt, may set io->error and drop its reference between the read and the decrement. crypt_dec_pending then drops the last reference and completes the bio with the stale status - so a read that failed and was never decrypted, or a write that failed, is reported as successful. The read was placed before the decrement by commit b35f8caa0890 ("dm crypt: wait for endio to complete before destruction"), because that commit freed dm_crypt_io before calling bio_endio. This is no longer the case, dm_crypt_io lives in the per-bio data now. Read io->error after atomic_dec_and_test instead. atomic_dec_and_test is fully ordered, so no additional barrier is needed. Fixes: b35f8caa0890 ("dm crypt: wait for endio to complete before destruction") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-crypt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-crypt.c b/drivers/md/dm-crypt.c index 608b617fb817..9e170de50ad3 100644 --- a/drivers/md/dm-crypt.c +++ b/drivers/md/dm-crypt.c @@ -1745,7 +1745,6 @@ static void crypt_dec_pending(struct dm_crypt_io *io) { struct crypt_config *cc = io->cc; struct bio *base_bio = io->base_bio; - blk_status_t error = io->error; if (!atomic_dec_and_test(&io->io_pending)) return; @@ -1767,7 +1766,7 @@ static void crypt_dec_pending(struct dm_crypt_io *io) else kfree(io->integrity_metadata); - base_bio->bi_status = error; + base_bio->bi_status = io->error; bio_endio(base_bio); } -- cgit v1.2.3 From bc9781c0247de107876f32929f1637db93a42b34 Mon Sep 17 00:00:00 2001 From: Benjamin Marzinski Date: Tue, 25 Aug 2026 15:22:26 -0400 Subject: dm cache: fix issue with background work locking dm cache used a rw_semaphore for background_work_lock. Write locks on rw_semaphores have strict owner semantics, but there was no guarantee that the process that locked background_work_lock was the same process that unlocked it. This can be easily seen using a kernel compiled with CONFIG_DEBUG_RWSEMS. Given a dm cache device , run: 'dmsetup suspend && dmsetup resume '. This will trigger a kernel warning: DEBUG_RWSEMS_WARN_ON((rwsem_owner(sem) != current) && !rwsem_test_oflags(sem, RWSEM_NONSPINNABLE)) triggered by cache_resume(). To fix this, switch from a rw_semaphore to a spinlock and a wait queue. dm cache already has a wait queue and associated counter, migration_wait and nr_allocated_migrations, that was getting woken up when background work was getting completed, but wasn't actually used by anything. This is replaced by the background_work queue and counter. Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Benjamin Marzinski Reviewed-by: Matthew Sakai Reviewed-by: Ming-Hung Tsai Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 55 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index 33dbc71b730f..d8d9c63d2a67 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -340,8 +340,6 @@ struct cache { struct list_head invalidation_requests; sector_t migration_threshold; - wait_queue_head_t migration_wait; - atomic_t nr_allocated_migrations; /* * The number of in flight migrations that are performing @@ -397,7 +395,11 @@ struct cache { bool loaded_mappings:1; bool loaded_discards:1; - struct rw_semaphore background_work_lock; + /* background work management */ + bool background_work_allowed; + unsigned background_work_nr; + spinlock_t background_work_lock; + wait_queue_head_t background_work_wait; struct batcher committer; struct work_struct commit_ws; @@ -488,19 +490,13 @@ static struct dm_cache_migration *alloc_migration(struct cache *cache) memset(mg, 0, sizeof(*mg)); mg->cache = cache; - atomic_inc(&cache->nr_allocated_migrations); return mg; } static void free_migration(struct dm_cache_migration *mg) { - struct cache *cache = mg->cache; - - if (atomic_dec_and_test(&cache->nr_allocated_migrations)) - wake_up(&cache->migration_wait); - - mempool_free(mg, &cache->migration_pool); + mempool_free(mg, &mg->cache->migration_pool); } /*----------------------------------------------------------------*/ @@ -1030,34 +1026,39 @@ static void calc_discard_block_range(struct cache *cache, struct bio *bio, static void prevent_background_work(struct cache *cache) { - lockdep_off(); - down_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = false; + wait_event_lock_irq(cache->background_work_wait, + cache->background_work_nr == 0, + cache->background_work_lock); + spin_unlock_irq(&cache->background_work_lock); } static void allow_background_work(struct cache *cache) { - lockdep_off(); - up_write(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + cache->background_work_allowed = true; + spin_unlock_irq(&cache->background_work_lock); } static bool background_work_begin(struct cache *cache) { bool r; - lockdep_off(); - r = down_read_trylock(&cache->background_work_lock); - lockdep_on(); - + spin_lock_irq(&cache->background_work_lock); + r = cache->background_work_allowed; + if (r) + cache->background_work_nr++; + spin_unlock_irq(&cache->background_work_lock); return r; } static void background_work_end(struct cache *cache) { - lockdep_off(); - up_read(&cache->background_work_lock); - lockdep_on(); + spin_lock_irq(&cache->background_work_lock); + if (--cache->background_work_nr == 0) + wake_up(&cache->background_work_wait); + spin_unlock_irq(&cache->background_work_lock); } /*----------------------------------------------------------------*/ @@ -2507,9 +2508,7 @@ static int cache_create(struct cache_args *ca, struct cache **result) spin_lock_init(&cache->lock); bio_list_init(&cache->deferred_bios); - atomic_set(&cache->nr_allocated_migrations, 0); atomic_set(&cache->nr_io_migrations, 0); - init_waitqueue_head(&cache->migration_wait); r = -ENOMEM; atomic_set(&cache->nr_dirty, 0); @@ -2592,8 +2591,10 @@ static int cache_create(struct cache_args *ca, struct cache **result) issue_op, cache, cache->wq); dm_iot_init(&cache->tracker); - init_rwsem(&cache->background_work_lock); - prevent_background_work(cache); + init_waitqueue_head(&cache->background_work_wait); + spin_lock_init(&cache->background_work_lock); + cache->background_work_allowed = false; + cache->background_work_nr = 0; *result = cache; return 0; -- cgit v1.2.3 From b2fd92f016e9d692fd3c8c08d0ee014e9212279d Mon Sep 17 00:00:00 2001 From: Chen Cheng Date: Mon, 24 Aug 2026 19:34:49 +0800 Subject: dm-integrity: require stable writes for internal hash modes dm-integrity direct, bitmap and inline internal-hash modes compute integrity tags from the pages carried by the write bio. The lower data write also uses those pages, so the tag and the data write depend on the same memory contents staying unchanged while writeback is in flight. Without stable writes, a buffered writer can modify a writeback folio after dm-integrity has submitted the data bio and before the lower device has consumed the data. After a crash, this can leave data from the later contents with a tag calculated from the earlier contents, causing permanent checksum failures on read. Set BLK_FEAT_STABLE_WRITES for internal-hash D, B and I modes so filesystems wait for writeback folios to become stable before modifying them again. Journal mode is left unchanged because it copies data into the journal before computing and persisting the tag. Tested using dm-delay over a virtio-blk test disk, dm-integrity internal_hash:crc32c and no-journal ext4. The D and B reproducers both failed with checksum errors before this change and completed with READ_RC=0 and zero mismatches after it. Fixes: 7eada909bfd7 ("dm: add integrity target") Cc: stable@vger.kernel.org Reported-by: Sun Yangkai Link: https://github.com/chencheng-fnnas/reproducer/blob/main/dm-integrity-writeback-race.py Signed-off-by: Chen Cheng Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index c50feaa98bf9..0370d7d7ce72 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -4130,6 +4130,10 @@ static void dm_integrity_io_hints(struct dm_target *ti, struct queue_limits *lim limits->dma_alignment = limits->logical_block_size - 1; limits->discard_granularity = ic->sectors_per_block << SECTOR_SHIFT; + if (ic->internal_hash && + (ic->mode == 'D' || ic->mode == 'B' || ic->mode == 'I')) + limits->features |= BLK_FEAT_STABLE_WRITES; + if (!ic->internal_hash) { struct blk_integrity *bi = &limits->integrity; -- cgit v1.2.3 From 59e6f919d77d72ec79cbf171256f2f7819737580 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:57 +0000 Subject: dm-integrity: fix buffer overflow with keyed discard Since commit 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker"), integrity_metadata computes a checksum for every discarded block into the "checksums" buffer. integrity_sector_checksum always writes the whole digest. So if the tag size is smaller than the digest size, the checksum of the last block that fits into the buffer is written past the end of it. For example, with hmac(sha256) and tag size 16, a 4MiB discard writes 16 bytes past the kmalloc'ed page. Fix this by subtracting extra_space from the buffer size when computing max_blocks, like we do for writes. Fixes: 68c5c42567bc ("dm-integrity: replace forgeable discard filler with a keyed sector marker") Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 0370d7d7ce72..d4fe85d61d33 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1980,7 +1980,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; - unsigned int max_blocks = max_size / ic->tag_size; + unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; if (!ic->discard_keyed) -- cgit v1.2.3 From 18d80c77b4c7dd20699e81cedfbbff4e9d198f28 Mon Sep 17 00:00:00 2001 From: Ben Cressey Date: Thu, 20 Aug 2026 21:44:58 +0000 Subject: dm-integrity: fix infinite loop on discard with large tag size When integrity_metadata handles a discard, it fills a buffer with DISCARD_FILLER and writes it over the tags, max_blocks blocks at a time. If the kmalloc fails, the buffer is the on-stack array checksums_onstack and max_size is set to HASH_MAX_DIGESTSIZE. So if the tag size is larger than HASH_MAX_DIGESTSIZE, max_blocks is zero, bi_size is never decremented and the loop never terminates. Fix this by using sizeof(checksums_onstack) as max_size. The array has MAX_TAG_SIZE bytes since commit b93b6643e9b5 ("dm integrity: fix a crash with unusually large tag size"), so max_blocks is at least 1. Fixes: 84597a44a9d8 ("dm integrity: add optional discard support") Cc: stable@vger.kernel.org Reviewed-by: Jose Fernandez (Anthropic) Signed-off-by: Ben Cressey Assisted-by: Claude:unspecified Signed-off-by: Mikulas Patocka --- drivers/md/dm-integrity.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index d4fe85d61d33..5327d7c6a71c 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -1979,7 +1979,7 @@ static void integrity_metadata(struct work_struct *w) if (unlikely(dio->op == REQ_OP_DISCARD)) { unsigned int bi_size = dio->bio_details.bi_iter.bi_size; - unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : HASH_MAX_DIGESTSIZE; + unsigned int max_size = likely(checksums != checksums_onstack) ? PAGE_SIZE : sizeof(checksums_onstack); unsigned int max_blocks = (max_size - extra_space) / ic->tag_size; sector_t sector = dio->range.logical_sector; -- cgit v1.2.3 From 2ccb8878c149443c6acf628b438c9c942c20abb2 Mon Sep 17 00:00:00 2001 From: Ming-Hung Tsai Date: Tue, 18 Aug 2026 18:05:47 +0800 Subject: dm cache: fix demotion stats in passthrough mode The demotion counter is incremented per incoming write bio before the invalidation begins, causing the demotion count to exceed the actual number of cached blocks when multiple bios target the same cached block. Additionally, the counter is incremented unconditionally regardless of invalidation failure. Reproduce steps: 1. Create a cache device consisting of 512 cache entries modprobe brd rd_size=262144 dmsetup create cmeta --table "0 8192 linear /dev/ram0 0" dmsetup create cdata --table "0 65536 linear /dev/ram0 8192" dmsetup create corig --table "0 65536 linear /dev/ram0 262144" dd if=/dev/zero of=/dev/mapper/cmeta bs=4k count=1 oflag=direct dmsetup create cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 writethrough smq 0" 2. Populate the cache, and record the number of cached blocks fio --name=populate --filename=/dev/mapper/cache --rw=randwrite --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --io_size=2048m nr_cached=$(dmsetup status cache | awk '{split($7, a, "/"); print a[1]}') 3. Reload the cache into passthrough mode dmsetup suspend cache dmsetup reload cache --table "0 65536 cache /dev/mapper/cmeta \ /dev/mapper/cdata /dev/mapper/corig 128 2 metadata2 passthrough smq 0" dmsetup resume cache 4. Write to the passthrough cache with multiple jobs to trigger multiple bios hitting the same cached block. fio --filename=/dev/mapper/cache --name=test --rw=write --bs=4k \ --direct=1 --ioengine=libaio --iodepth=32 --numjobs=4 5. Check if demoted matches cached block count. These numbers should match but may differ due to overcounting per bio. nr_demoted=$(dmsetup status cache | awk '{print $12}') echo "$nr_cached, $nr_demoted" Fix by moving the demotion counter increment into invalidate_complete(), gated on the success flag. Reported-by: Ben Marzinski Fixes: b29d4986d0da ("dm cache: significant rework to leverage dm-bio-prison-v2") Cc: stable@vger.kernel.org Signed-off-by: Ming-Hung Tsai Reviewed-by: Benjamin Marzinski Signed-off-by: Mikulas Patocka --- drivers/md/dm-cache-target.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c index d8d9c63d2a67..1a5072425c4a 100644 --- a/drivers/md/dm-cache-target.c +++ b/drivers/md/dm-cache-target.c @@ -1463,6 +1463,9 @@ static void invalidate_complete(struct dm_cache_migration *mg, bool success) struct bio_list bios; struct cache *cache = mg->cache; + if (success) + atomic_inc(&cache->stats.demotion); + bio_list_init(&bios); if (mg->cell) { if (dm_cell_unlock_v2(cache->prison, mg->cell, &bios)) @@ -1734,7 +1737,6 @@ static int map_bio(struct cache *cache, struct bio *bio, dm_oblock_t block, if (passthrough_mode(cache)) { if (bio_data_dir(bio) == WRITE) { bio_drop_shared_lock(cache, bio); - atomic_inc(&cache->stats.demotion); invalidate_start(cache, cblock, block, bio); return DM_MAPIO_SUBMITTED; } else -- cgit v1.2.3 From 7d4d4f3b668d708d94f62ecdd33ac330a6fd8a84 Mon Sep 17 00:00:00 2001 From: Mikulas Patocka Date: Wed, 2 Sep 2026 16:36:26 +0200 Subject: dm-integrity: fix NULL pointer dereference when the 'R' flag is used If the dm-integrity device has the SB_FLAG_DIRTY_BITMAP flag set and the user activates the device in the 'R' mode, a crash in dm_integrity_resume happens because the function attempts to read the journal containing the bitmap. This patch makes dm-integrity skip any writes to the device in dm_integrity_resume if the device is activated in the 'R' mode. Signed-off-by: Mikulas Patocka Fixes: 468dfca38b1a ("dm integrity: add a bitmap mode") Cc: stable@vger.kernel.org --- drivers/md/dm-integrity.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/drivers/md/dm-integrity.c b/drivers/md/dm-integrity.c index 5327d7c6a71c..92970e12267a 100644 --- a/drivers/md/dm-integrity.c +++ b/drivers/md/dm-integrity.c @@ -3875,6 +3875,10 @@ static void dm_integrity_resume(struct dm_target *ti) r = sync_rw_sb(ic, REQ_OP_READ); if (r) dm_integrity_io_error(ic, "reading superblock", r); + + if (ic->mode == 'R') + goto skip_writes; + if ((ic->sb->flags & flags) != flags) { ic->sb->flags |= flags; r = sync_rw_sb(ic, REQ_OP_WRITE | REQ_FUA); @@ -3984,6 +3988,7 @@ static void dm_integrity_resume(struct dm_target *ti) } } +skip_writes: ic->reboot_notifier.notifier_call = dm_integrity_reboot; ic->reboot_notifier.next = NULL; ic->reboot_notifier.priority = INT_MAX - 1; /* be notified after md and before hardware drivers */ -- cgit v1.2.3 From 7ac81e2d2240f2c57bd073b0733e0b2abca38e82 Mon Sep 17 00:00:00 2001 From: Genjian Zhang Date: Fri, 7 Aug 2026 23:57:15 +0800 Subject: dm-ebs: fix incorrect device offset check in ebs_ctr() is a backing-device sector offset; ti->len is the virtual target length. Comparing them rejects valid tables, e.g.: dmsetup create ebs0 --table "0 1048576 ebs /dev/sda 2097152 1 8" -> ebs: Invalid device offset sector (-EINVAL) Drop the check. Bounds against the backing device are already enforced later by device_area_is_invalid() via ebs_iterate_devices(). Cc: stable@vger.kernel.org Fixes: d3c7b35c20d6 ("dm: add emulated block size target") Signed-off-by: Genjian Zhang Signed-off-by: Mikulas Patocka --- drivers/md/dm-ebs-target.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/drivers/md/dm-ebs-target.c b/drivers/md/dm-ebs-target.c index 1e52bde48b91..5d67c6c19d4b 100644 --- a/drivers/md/dm-ebs-target.c +++ b/drivers/md/dm-ebs-target.c @@ -265,8 +265,7 @@ static int ebs_ctr(struct dm_target *ti, unsigned int argc, char **argv) r = -EINVAL; if (sscanf(argv[1], "%llu%c", &tmp, &dummy) != 1 || - tmp != (sector_t)tmp || - (sector_t)tmp >= ti->len) { + tmp != (sector_t)tmp) { ti->error = "Invalid device offset sector"; goto bad; } -- cgit v1.2.3