All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics
@ 2026-07-19 14:44 Mykola Marzhan
  2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Mykola Marzhan @ 2026-07-19 14:44 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

While testing llbitmap we found two bugs with the same root cause:
the bitmap superblock's state field is untrusted input, but both
bitmap loaders copy it into runtime flags unmasked.  A single wrong
bit on disk -- which pre-v4.15 kernels wrote themselves, and which
corruption or an external write can plant today -- can brick an
array (patch 1, classic bitmap: assemble fails with -EIO) or
silently disable its bitmap for the array's lifetime (patch 2,
llbitmap).  Patch 3 fixes a kmap_local ordering violation in the
llbitmap superblock reader (hardening; no observable bug on
production configs, hence no stable tag).  Patch 4 adds the events
counters to the "kicking non-fresh" warning so a kicked member can
be judged from dmesg alone.

Testing: patches 1-2 verified with their in-message dd reproducers
on unpatched and patched kernels (state field forged on every
member); patch 3 by build and inspection; patch 4's warning format
exercised during a non-fresh assemble.  The series builds W=1-clean
and applies to current master and to md-7.2.  Independent of the
raid10-recovery series posted separately.

The patches were developed with AI assistance (see the Assisted-by
trailers); all code was human-reviewed and tested as described above.

Mykola Marzhan (4):
  md/md-bitmap: mask sb->state on load to drop runtime-only bits
  md/md-llbitmap: mask sb->state on load to drop runtime-only bits
  md/md-llbitmap: unmap sb page before freeing it
  md: include events counters when kicking non-fresh device

 drivers/md/md-bitmap.c   |  9 +++++++--
 drivers/md/md-llbitmap.c | 12 ++++++++++--
 drivers/md/md.c          | 25 +++++++++++++++++++++++--
 3 files changed, 40 insertions(+), 6 deletions(-)


base-commit: 0e35b9b6ec0ffcc5e23cbdec09f5c622ad532b53
-- 
2.43.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics Mykola Marzhan
@ 2026-07-19 14:44 ` Mykola Marzhan
  2026-07-19 15:06   ` sashiko-bot
  2026-07-20  1:54   ` yu kuai
  2026-07-19 14:44 ` [PATCH 2/4] md/md-llbitmap: " Mykola Marzhan
                   ` (2 subsequent siblings)
  3 siblings, 2 replies; 12+ messages in thread
From: Mykola Marzhan @ 2026-07-19 14:44 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

md_bitmap_read_sb() does `bitmap->flags |= le32_to_cpu(sb->state)`
with no mask, so any bit set in the on-disk state field becomes a
runtime flag.  With BITMAP_WRITE_ERROR (bit 2) set on disk,
md_bitmap_create() fails with -EIO and the array refuses to assemble
until the bitmap superblock is rewritten externally.

That bit can genuinely be on disk: kernels before v4.15 wrote
sb->state unmasked -- commit 97f0eb9f0fec ("md/bitmap: clear
BITMAP_WRITE_ERROR bit before writing it to sb") added the
write-side clear -- so an array that hit a transient bitmap write
error under an older kernel carries the bit forever.  On-disk
corruption or an external write plants it today.

The write side has masked WRITE_ERROR out since v4.15; the load side
is the missing half.  Mask the loaded state to BITMAP_STALE, the
only bit this loader consumes: WRITE_ERROR and DAEMON_BUSY are
kernel-runtime signals, FIRST_USE and CLEAN are used only by the
llbitmap loader (a superblock this loader accepts cannot carry
them, since versions above BITMAP_MAJOR_CLUSTERED are rejected),
and HOSTENDIAN is derived from sb->version, not sb->state.

Reproducer: write 0x04 into byte offset 48 (the state field) of the
bitmap superblock on every member (a single forged member can be
masked by another member's clean copy), then assemble: RUN_ARRAY
fails with -EIO.  With this fix the array starts and sb->events
advances normally.

Fixes: 32a7627cf3a3 ("[PATCH] md: optimised resync using Bitmap based intent logging")
Cc: stable@vger.kernel.org
Assisted-by: Claude-Code:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/md-bitmap.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
index 0f02e2956398..4dd3fc2a9e15 100644
--- a/drivers/md/md-bitmap.c
+++ b/drivers/md/md-bitmap.c
@@ -931,8 +931,13 @@ static int md_bitmap_read_sb(struct bitmap *bitmap)
 		}
 	}
 
-	/* assign fields using values from superblock */
-	bitmap->flags |= le32_to_cpu(sb->state);
+	/*
+	 * Only BITMAP_STALE is meaningful to this loader from sb->state:
+	 * WRITE_ERROR and DAEMON_BUSY are kernel-runtime signals (kernels
+	 * before v4.15 persisted WRITE_ERROR), FIRST_USE/CLEAN are
+	 * llbitmap-only, and HOSTENDIAN is derived from sb->version below.
+	 */
+	bitmap->flags |= le32_to_cpu(sb->state) & BIT(BITMAP_STALE);
 	if (le32_to_cpu(sb->version) == BITMAP_MAJOR_HOSTENDIAN)
 		set_bit(BITMAP_HOSTENDIAN, &bitmap->flags);
 	bitmap->events_cleared = le64_to_cpu(sb->events_cleared);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 2/4] md/md-llbitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics Mykola Marzhan
  2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
@ 2026-07-19 14:44 ` Mykola Marzhan
  2026-07-19 15:00   ` sashiko-bot
  2026-07-20  2:00   ` yu kuai
  2026-07-19 14:44 ` [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it Mykola Marzhan
  2026-07-19 14:44 ` [PATCH 4/4] md: include events counters when kicking non-fresh device Mykola Marzhan
  3 siblings, 2 replies; 12+ messages in thread
From: Mykola Marzhan @ 2026-07-19 14:44 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

llbitmap_read_sb() copies sb->state straight into llbitmap->flags
with no mask, so any bit set in the on-disk state field becomes a
runtime flag.  With BITMAP_WRITE_ERROR (bit 2) on disk,
llbitmap_enabled() returns false for the array's entire lifetime:
llbitmap_update_sb() early-returns, the state machine returns
BitNone, the bits attribute reports "bitmap io error", and on-disk
sb->events stops advancing.  The bitmap is silently disabled with no
log line attributing the cause, and the next clean reassemble forces
a full resync because sb->events is stale.

Runtime bits do reach disk: llbitmap_update_sb() writes the raw
flags word, so BITMAP_DAEMON_BUSY is persisted whenever a superblock
update coincides with a daemon-period overrun -- the condition that
logs "daemon_work not finished".  WRITE_ERROR itself has no
reachable write-side path today (its only setter is update_sb's own
read-failure branch, which returns before writing), but on-disk
corruption or an external write plants it all the same.

Mask the loaded state to the bits this loader consumes (STALE,
FIRST_USE and CLEAN), as the previous patch did for the classic
loader.  Masking at load neutralises whatever is on disk and is the
minimal backportable fix; masking the write side is left as a
follow-up cleanup.

Reproducer: write 0x04 into byte offset 48 (the state field) of the
llbitmap superblock on every member (a forge on a single member can
be masked by another member's clean copy), then assemble.  Without this
fix the bits attribute reports "bitmap io error" and sb->events is
frozen; with it the bitmap is alive and sb->events advances
normally.

Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap")
Cc: stable@vger.kernel.org # v6.18+
Assisted-by: Claude-Code:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/md-llbitmap.c | 10 +++++++++-
 1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index e8f853e8461d..0b591124dc63 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -981,7 +981,15 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 		else
 			mddev->bitmap_info.space = mddev->bitmap_info.default_space;
 	}
-	llbitmap->flags = le32_to_cpu(sb->state);
+	/*
+	 * Mask the loaded state to the bits this loader consumes.
+	 * WRITE_ERROR and DAEMON_BUSY are kernel-runtime signals; a stray
+	 * WRITE_ERROR trusted from disk silently disables the bitmap for
+	 * the array's entire lifetime.
+	 */
+	llbitmap->flags = le32_to_cpu(sb->state) &
+			  (BIT(BITMAP_STALE) | BIT(BITMAP_FIRST_USE) |
+			   BIT(BITMAP_CLEAN));
 	if (test_and_clear_bit(BITMAP_FIRST_USE, &llbitmap->flags)) {
 		ret = llbitmap_init(llbitmap);
 		goto out_put_page;
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it
  2026-07-19 14:44 [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics Mykola Marzhan
  2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
  2026-07-19 14:44 ` [PATCH 2/4] md/md-llbitmap: " Mykola Marzhan
@ 2026-07-19 14:44 ` Mykola Marzhan
  2026-07-19 14:57   ` sashiko-bot
  2026-07-20  2:02   ` yu kuai
  2026-07-19 14:44 ` [PATCH 4/4] md: include events counters when kicking non-fresh device Mykola Marzhan
  3 siblings, 2 replies; 12+ messages in thread
From: Mykola Marzhan @ 2026-07-19 14:44 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

Every path in llbitmap_read_sb() that maps the superblock page ends
at the out_put_page label, which frees the page and only then unmaps
it:

	__free_page(sb_page);
	kunmap_local(sb);

The page comes from alloc_page(GFP_KERNEL), so kunmap_local() is
currently a no-op everywhere except CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP
builds -- where the page really is freed while a live mapping still
points at it.  Unmap before freeing.

Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap")
Assisted-by: Claude-Code:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/md-llbitmap.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
index 0b591124dc63..63ff2c0d8b43 100644
--- a/drivers/md/md-llbitmap.c
+++ b/drivers/md/md-llbitmap.c
@@ -1035,8 +1035,8 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
 	ret = llbitmap_cache_pages(llbitmap);
 
 out_put_page:
-	__free_page(sb_page);
 	kunmap_local(sb);
+	__free_page(sb_page);
 	return ret;
 }
 
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH 4/4] md: include events counters when kicking non-fresh device
  2026-07-19 14:44 [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics Mykola Marzhan
                   ` (2 preceding siblings ...)
  2026-07-19 14:44 ` [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it Mykola Marzhan
@ 2026-07-19 14:44 ` Mykola Marzhan
  2026-07-20  2:14   ` yu kuai
  3 siblings, 1 reply; 12+ messages in thread
From: Mykola Marzhan @ 2026-07-19 14:44 UTC (permalink / raw)
  To: Song Liu, Yu Kuai; +Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

When analyze_sbs() kicks a member with a stale events counter, the
warning names the device but not the divergence that caused the
kick, so judging whether the kicked member is salvageable means
running mdadm --examine on every member.  Print the member's on-disk
events counter and the freshest events counter in the warning
itself.

rdev->sb_events cannot be used here: it is only assigned when a
superblock is written and still reads 0 during assemble.  Read the
counter from the superblock page instead -- md_event() for 0.90
metadata, the little-endian events field for 1.x.

The legacy substring "kicking non-fresh %pg from array!" is
unchanged, so existing log parsers continue to match.

Assisted-by: Claude-Code:claude-opus-4-8
Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
---
 drivers/md/md.c | 25 +++++++++++++++++++++++--
 1 file changed, 23 insertions(+), 2 deletions(-)

diff --git a/drivers/md/md.c b/drivers/md/md.c
index d60ea7aaca3a..9ec87375a717 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -3934,8 +3934,29 @@ static int analyze_sbs(struct mddev *mddev)
 		if (rdev != freshest) {
 			if (super_types[mddev->major_version].
 			    validate_super(mddev, freshest, rdev)) {
-				pr_warn("md: kicking non-fresh %pg from array!\n",
-					rdev->bdev);
+				u64 rdev_events = 0;
+
+				/*
+				 * rdev->sb_events is only assigned when
+				 * writing a superblock and reads 0 during
+				 * assemble; read events from the on-disk
+				 * superblock instead.
+				 */
+				if (rdev->sb_page) {
+					if (mddev->major_version == 1) {
+						struct mdp_superblock_1 *sb =
+							page_address(rdev->sb_page);
+						rdev_events = le64_to_cpu(sb->events);
+					} else if (mddev->major_version == 0) {
+						mdp_super_t *sb =
+							page_address(rdev->sb_page);
+						rdev_events = md_event(sb);
+					}
+				}
+				pr_warn("md: kicking non-fresh %pg from array! (events=%llu, freshest=%llu)\n",
+					rdev->bdev,
+					(unsigned long long)rdev_events,
+					(unsigned long long)mddev->events);
 				md_kick_rdev_from_array(rdev);
 				continue;
 			}
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it
  2026-07-19 14:44 ` [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it Mykola Marzhan
@ 2026-07-19 14:57   ` sashiko-bot
  2026-07-20  2:02   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-19 14:57 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:

Pre-existing issues:
- [High] Integer Truncation of 64-bit event counter causes spurious array recoveries on 32-bit architectures
- [High] Use-After-Free in teardown sequence due to re-arming timer
- [High] Lost Write to `sb->sync_size` during superblock load
- [High] Integer overflow causing division by zero when calculating minimum chunksize
- [High] Integer overflow in page count calculation leading to out-of-bounds heap access
--

--- Patch [3]: [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it ---
commit 6be442dfef7ad93b44cadb093b6d078f152b2522
Author: Mykola Marzhan <mykola@meshstor.io>

md/md-llbitmap: unmap sb page before freeing it

The patch swaps __free_page(sb_page) and kunmap_local(sb) in
llbitmap_read_sb() to avoid unmapping a page after it has been freed.

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 2d3de38f9b784..2d6c7ad0923a1 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -1035,8 +1035,8 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
>  	ret = llbitmap_cache_pages(llbitmap);

[Severity: High]
This is a pre-existing issue, but is there an integer overflow when
calculating nr_pages on 32-bit architectures inside llbitmap_cache_pages()?

drivers/md/md-llbitmap.c:llbitmap_cache_pages() {
    ...
    unsigned int nr_pages = DIV_ROUND_UP(llbitmap->chunks +
                                         BITMAP_DATA_OFFSET, PAGE_SIZE);
    ...
    llbitmap->pctl = kmalloc_array(nr_pages, sizeof(void *),
                                   GFP_KERNEL | __GFP_ZERO);
    ...
}

If a malicious superblock sets a high resync_max_sectors and chunksize of 2,
could llbitmap->chunks + BITMAP_DATA_OFFSET overflow to 0? Would this result
in a zero-sized array allocation and subsequent out-of-bounds access?

>  
>  out_put_page:
> -	__free_page(sb_page);
>  	kunmap_local(sb);
> +	__free_page(sb_page);

[Severity: High]
This is a pre-existing issue, but is the update to sb->sync_size lost here?

Earlier in llbitmap_read_sb(), sb->sync_size is updated:

    sb->sync_size = cpu_to_le64(mddev->resync_max_sectors);

Since sb_page is unmapped and unconditionally freed here without being
written back to disk or retained in memory, does the runtime state lose
this update?

>  	return ret;
>  }

There are a few other pre-existing issues in this file that might need
attention.

[Severity: High]
This is a pre-existing issue, but does assigning the 64-bit sb->events to the
unsigned long events variable truncate it on 32-bit architectures?

drivers/md/md-llbitmap.c:llbitmap_read_sb() {
    unsigned long events;
    ...
    events = le64_to_cpu(sb->events);
    if (events < mddev->events) {
        ...
    }
}

When the 64-bit counter exceeds 4 billion, would this truncation cause a
spurious full array resync by incorrectly making the bitmap appear stale?


[Severity: High]
This is a pre-existing issue, but can the timer fire on freed memory during
teardown?

drivers/md/md-llbitmap.c:llbitmap_destroy() {
    ...
    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);
    ...
}

drivers/md/md-llbitmap.c:md_llbitmap_daemon_fn() {
    ...
    if (restart)
        mod_timer(&llbitmap->pending_timer, ...);
}

If llbitmap_destroy() deletes the timer and flushes the workqueue, could
md_llbitmap_daemon_fn() unconditionally re-arm the timer at the end of its
execution, leading to a use-after-free when llbitmap is subsequently freed?


[Severity: High]
This is a pre-existing issue, but can this overflow and cause a division by
zero panic?

drivers/md/md-llbitmap.c:llbitmap_read_sb() {
    ...
    if (chunksize < DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors,
                                          mddev->bitmap_info.space << SECTOR_SHIFT)) {
    ...
}

On 32-bit systems, mddev->bitmap_info.space is a 32-bit unsigned long. If an
attacker-controlled superblock sets a value where space << 9 overflows to 0,
would DIV_ROUND_UP_SECTOR_T divide by zero?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260719144409.940492-1-mykola@meshstor.io?part=3

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] md/md-llbitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 ` [PATCH 2/4] md/md-llbitmap: " Mykola Marzhan
@ 2026-07-19 15:00   ` sashiko-bot
  2026-07-20  2:00   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-19 15:00 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: linux-raid, yukuai

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

Pre-existing issues:
- [High] Teardown symmetry bug: `llbitmap_destroy()` deletes the daemon timer and waits for the daemon work, but the work can re-arm the timer, leading to a use-after-free when the timer fires on the freed `llbitmap` object.
- [High] Preexisting UAF in llbitmap_create() error path due to clearing mddev->bitmap outside the mutex after kfree.
- [High] Preexisting race condition in llbitmap_write_page() iterating mddev->disks without required locks.
- [High] A maliciously crafted `sectors_reserved` value in the llbitmap superblock can cause an integer overflow during a bitwise shift on 32-bit systems, resulting in a zero divisor and a kernel panic.
--

--- Patch [2]: [PATCH 2/4] md/md-llbitmap: mask sb->state on load to drop runtime-only bits ---
commit 48600506c718f0300f8ce2473c95dff7a9e47cc2
Author: Mykola Marzhan <mykola@meshstor.io>

md/md-llbitmap: mask sb->state on load to drop runtime-only bits

This commit masks the loaded superblock state in llbitmap_read_sb() to only
keep STALE, FIRST_USE, and CLEAN bits. It prevents runtime-only flags like
WRITE_ERROR from being loaded from disk and permanently disabling the bitmap.

Cc: stable@vger.kernel.org # v6.18+

> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index 5a4e2abaa7577..2d3de38f9b784 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -981,7 +981,15 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
>  		else
>  			mddev->bitmap_info.space = mddev->bitmap_info.default_space;
>  	}

[Severity: High]
This isn't a bug introduced by this patch, but can this sectors_reserved
check lead to an integer overflow?

In llbitmap_read_sb(), we read the untrusted value:

    int room = le32_to_cpu(sb->sectors_reserved);
    if (room)
        mddev->bitmap_info.space = room;

Later in the same function:

    if (chunksize < DIV_ROUND_UP_SECTOR_T(mddev->resync_max_sectors,
                                          mddev->bitmap_info.space << SECTOR_SHIFT)) {

Could a crafted superblock with sectors_reserved = 0x00800000 cause
(mddev->bitmap_info.space << SECTOR_SHIFT) to wrap to 0 on 32-bit systems,
resulting in a division by zero panic?

> -	llbitmap->flags = le32_to_cpu(sb->state);
> +	/*
> +	 * Mask the loaded state to the bits this loader consumes.
[ ... ]

[Severity: High]
This is a pre-existing issue, but does the teardown path have a symmetry
issue?

In llbitmap_destroy():

    timer_delete_sync(&llbitmap->pending_timer);
    flush_workqueue(md_llbitmap_io_wq);

If md_llbitmap_daemon_fn() is actively running and finds unexpired pages:

    if (restart)
        mod_timer(&llbitmap->pending_timer, ...);

Does this allow the daemon to blindly re-arm the timer after
timer_delete_sync() has finished, leading to a use-after-free when the timer
fires on the freed llbitmap object?

[Severity: High]
This is a pre-existing issue, but is there a use-after-free in the
llbitmap_create() error path?

When llbitmap_create() fails, it does this:

    mutex_unlock(&mddev->bitmap_info.mutex);
    if (ret) {
        kfree(llbitmap);
        mddev->bitmap = NULL;
    }

Can a concurrent reader like metadata_show() acquire the lock and dereference
the freed object?

metadata_show() does:

    mutex_lock(&mddev->bitmap_info.mutex);
    llbitmap = mddev->bitmap;
    if (!llbitmap) {
    ...

[Severity: High]
This isn't a bug introduced by this patch, but does llbitmap_write_page()
iterate the disks without required locks?

In llbitmap_write_page(), executed from workqueues:

        rdev_for_each(rdev, mddev) {
            sector_t sector;

This iterates a dynamic, RCU-protected linked list without taking
rcu_read_lock() or reconfig_mutex.

If a disk is concurrently hot-removed, can list_del_rcu() and
synchronize_rcu() free the element while llbitmap_write_page() is traversing
it?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260719144409.940492-1-mykola@meshstor.io?part=2

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
@ 2026-07-19 15:06   ` sashiko-bot
  2026-07-20  1:54   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-07-19 15:06 UTC (permalink / raw)
  To: Mykola Marzhan; +Cc: yukuai, linux-raid

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

Pre-existing issues:
- [High] The lockless bitmap implementation (llbitmap) remains vulnerable to the exact same persistent WRITE_ERROR bug that this commit fixes for the standard bitmap.
- [High] Internal bitmap pages allocated with alloc_page() are leaked during array teardown when CONFIG_MD_BITMAP_FILE is enabled.
- [High] Cluster superblock flags leak across `re_read` loop iterations, causing spurious state inheritance from node 0.
--

--- Patch [1]: [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits ---
commit f4f8e55ab2fda3c56252ec133561c13874aeb19a
Author: Mykola Marzhan <mykola@meshstor.io>

md/md-bitmap: mask sb->state on load to drop runtime-only bits

This patch masks the loaded state to BITMAP_STALE in md_bitmap_read_sb() so
any runtime-only bits like BITMAP_WRITE_ERROR are dropped. This fixes an
issue where an array refuses to assemble if it previously hit a transient
bitmap write error under an older kernel.

> diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
> index 0f02e2956398d..4dd3fc2a9e15b 100644
> --- a/drivers/md/md-bitmap.c
> +++ b/drivers/md/md-bitmap.c
> @@ -931,8 +931,13 @@ static int md_bitmap_read_sb(struct bitmap *bitmap)
>  		}
>  	}
>  
> -	/* assign fields using values from superblock */
> -	bitmap->flags |= le32_to_cpu(sb->state);
> +	/*
> +	 * Only BITMAP_STALE is meaningful to this loader from sb->state:
> +	 * WRITE_ERROR and DAEMON_BUSY are kernel-runtime signals (kernels
> +	 * before v4.15 persisted WRITE_ERROR), FIRST_USE/CLEAN are
> +	 * llbitmap-only, and HOSTENDIAN is derived from sb->version below.
> +	 */
> +	bitmap->flags |= le32_to_cpu(sb->state) & BIT(BITMAP_STALE);

[Severity: High]
This isn't a bug introduced by this patch, but does this bitwise OR cause
cluster superblock flags to leak across re_read loop iterations?

In md_bitmap_read_sb(), when nodes > 0, the code reads offset 0 initially and
accumulates flags into bitmap->flags. It then computes a new offset for its
own cluster_slot and jumps back to the re_read label, but bitmap->flags is
never cleared.

Could this cause a local node to spuriously inherit BITMAP_STALE or
BITMAP_HOSTENDIAN from node 0, incorrectly forcing a full array recovery?

[Severity: High]
This is a pre-existing issue, but does the lockless bitmap implementation in
drivers/md/md-llbitmap.c remain vulnerable to the exact same WRITE_ERROR bug
this patch fixes for standard bitmaps?

In llbitmap_read_sb():

    llbitmap->flags = le32_to_cpu(sb->state);

Since it reads the superblock state entirely unmasked, wouldn't a single
instance of on-disk corruption or transient error set BITMAP_WRITE_ERROR
permanently and force a full resync on every restart?

[Severity: High]
This is also a pre-existing issue, but do internal bitmap pages leak during
array teardown when CONFIG_MD_BITMAP_FILE is enabled?

In md_bitmap_file_unmap(), free_buffers() is called for each page:

drivers/md/md-bitmap.c:free_buffers() {
    ...
    if (!PagePrivate(page))
        return;
    ...
}

For internal bitmaps, reads and writes use sync_page_io() without buffer
heads, so PagePrivate is never set. Does this early return skip put_page()
and permanently leak all dynamically allocated metadata pages whenever the
array is stopped or the bitmap is resized?

>  	if (le32_to_cpu(sb->version) == BITMAP_MAJOR_HOSTENDIAN)
>  		set_bit(BITMAP_HOSTENDIAN, &bitmap->flags);
>  	bitmap->events_cleared = le64_to_cpu(sb->events_cleared);

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260719144409.940492-1-mykola@meshstor.io?part=1

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
  2026-07-19 15:06   ` sashiko-bot
@ 2026-07-20  1:54   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: yu kuai @ 2026-07-20  1:54 UTC (permalink / raw)
  To: Mykola Marzhan, Song Liu, Yu Kuai
  Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

Hi,

在 2026/7/19 22:44, Mykola Marzhan 写道:
> md_bitmap_read_sb() does `bitmap->flags |= le32_to_cpu(sb->state)`
> with no mask, so any bit set in the on-disk state field becomes a
> runtime flag.  With BITMAP_WRITE_ERROR (bit 2) set on disk,
> md_bitmap_create() fails with -EIO and the array refuses to assemble
> until the bitmap superblock is rewritten externally.
>
> That bit can genuinely be on disk: kernels before v4.15 wrote
> sb->state unmasked -- commit 97f0eb9f0fec ("md/bitmap: clear
> BITMAP_WRITE_ERROR bit before writing it to sb") added the
> write-side clear -- so an array that hit a transient bitmap write
> error under an older kernel carries the bit forever.  On-disk
> corruption or an external write plants it today.
>
> The write side has masked WRITE_ERROR out since v4.15; the load side
> is the missing half.  Mask the loaded state to BITMAP_STALE, the
> only bit this loader consumes: WRITE_ERROR and DAEMON_BUSY are
> kernel-runtime signals, FIRST_USE and CLEAN are used only by the
> llbitmap loader (a superblock this loader accepts cannot carry
> them, since versions above BITMAP_MAJOR_CLUSTERED are rejected),
> and HOSTENDIAN is derived from sb->version, not sb->state.
>
> Reproducer: write 0x04 into byte offset 48 (the state field) of the
> bitmap superblock on every member (a single forged member can be
> masked by another member's clean copy), then assemble: RUN_ARRAY
> fails with -EIO.  With this fix the array starts and sb->events
> advances normally.

This is not a reproducer, BITMAP_WRITE_ERROR indicate that bitmap to all member disks
failed, and bitmap no longer works for the array, so it's not safe to assemble with this
bitmap.

I think it's reasonable to assemble this array with bitmap=none.

>
> Fixes: 32a7627cf3a3 ("[PATCH] md: optimised resync using Bitmap based intent logging")
> Cc: stable@vger.kernel.org
> Assisted-by: Claude-Code:claude-opus-4-8
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
> ---
>   drivers/md/md-bitmap.c | 9 +++++++--
>   1 file changed, 7 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/md/md-bitmap.c b/drivers/md/md-bitmap.c
> index 0f02e2956398..4dd3fc2a9e15 100644
> --- a/drivers/md/md-bitmap.c
> +++ b/drivers/md/md-bitmap.c
> @@ -931,8 +931,13 @@ static int md_bitmap_read_sb(struct bitmap *bitmap)
>   		}
>   	}
>   
> -	/* assign fields using values from superblock */
> -	bitmap->flags |= le32_to_cpu(sb->state);
> +	/*
> +	 * Only BITMAP_STALE is meaningful to this loader from sb->state:
> +	 * WRITE_ERROR and DAEMON_BUSY are kernel-runtime signals (kernels
> +	 * before v4.15 persisted WRITE_ERROR), FIRST_USE/CLEAN are
> +	 * llbitmap-only, and HOSTENDIAN is derived from sb->version below.
> +	 */
> +	bitmap->flags |= le32_to_cpu(sb->state) & BIT(BITMAP_STALE);
>   	if (le32_to_cpu(sb->version) == BITMAP_MAJOR_HOSTENDIAN)
>   		set_bit(BITMAP_HOSTENDIAN, &bitmap->flags);
>   	bitmap->events_cleared = le64_to_cpu(sb->events_cleared);

-- 
Thanks,
Kuai

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 2/4] md/md-llbitmap: mask sb->state on load to drop runtime-only bits
  2026-07-19 14:44 ` [PATCH 2/4] md/md-llbitmap: " Mykola Marzhan
  2026-07-19 15:00   ` sashiko-bot
@ 2026-07-20  2:00   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: yu kuai @ 2026-07-20  2:00 UTC (permalink / raw)
  To: Mykola Marzhan, Song Liu, Yu Kuai
  Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

Hi,

在 2026/7/19 22:44, Mykola Marzhan 写道:
> llbitmap_read_sb() copies sb->state straight into llbitmap->flags
> with no mask, so any bit set in the on-disk state field becomes a
> runtime flag.  With BITMAP_WRITE_ERROR (bit 2) on disk,
> llbitmap_enabled() returns false for the array's entire lifetime:
> llbitmap_update_sb() early-returns, the state machine returns
> BitNone, the bits attribute reports "bitmap io error", and on-disk
> sb->events stops advancing.  The bitmap is silently disabled with no
> log line attributing the cause, and the next clean reassemble forces
> a full resync because sb->events is stale.

For llbitmap, it's using md_write_metadata(), if it's failed for one rdev,
md_error() is called directly for this rdev. So if BITMAP_WRITE_ERROR exist,
the array is already broken. And in this case, if array can still reassemble,
a full resync is the expected behavior.

>
> Runtime bits do reach disk: llbitmap_update_sb() writes the raw
> flags word, so BITMAP_DAEMON_BUSY is persisted whenever a superblock
> update coincides with a daemon-period overrun -- the condition that
> logs "daemon_work not finished".  WRITE_ERROR itself has no
> reachable write-side path today (its only setter is update_sb's own
> read-failure branch, which returns before writing), but on-disk
> corruption or an external write plants it all the same.
>
> Mask the loaded state to the bits this loader consumes (STALE,
> FIRST_USE and CLEAN), as the previous patch did for the classic
> loader.  Masking at load neutralises whatever is on disk and is the
> minimal backportable fix; masking the write side is left as a
> follow-up cleanup.
>
> Reproducer: write 0x04 into byte offset 48 (the state field) of the
> llbitmap superblock on every member (a forge on a single member can
> be masked by another member's clean copy), then assemble.  Without this
> fix the bits attribute reports "bitmap io error" and sb->events is
> frozen; with it the bitmap is alive and sb->events advances
> normally.
>
> Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap")
> Cc: stable@vger.kernel.org # v6.18+
> Assisted-by: Claude-Code:claude-opus-4-8
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
> ---
>   drivers/md/md-llbitmap.c | 10 +++++++++-
>   1 file changed, 9 insertions(+), 1 deletion(-)
>
> diff --git a/drivers/md/md-llbitmap.c b/drivers/md/md-llbitmap.c
> index e8f853e8461d..0b591124dc63 100644
> --- a/drivers/md/md-llbitmap.c
> +++ b/drivers/md/md-llbitmap.c
> @@ -981,7 +981,15 @@ static int llbitmap_read_sb(struct llbitmap *llbitmap)
>   		else
>   			mddev->bitmap_info.space = mddev->bitmap_info.default_space;
>   	}
> -	llbitmap->flags = le32_to_cpu(sb->state);
> +	/*
> +	 * Mask the loaded state to the bits this loader consumes.
> +	 * WRITE_ERROR and DAEMON_BUSY are kernel-runtime signals; a stray
> +	 * WRITE_ERROR trusted from disk silently disables the bitmap for
> +	 * the array's entire lifetime.
> +	 */
> +	llbitmap->flags = le32_to_cpu(sb->state) &
> +			  (BIT(BITMAP_STALE) | BIT(BITMAP_FIRST_USE) |
> +			   BIT(BITMAP_CLEAN));
>   	if (test_and_clear_bit(BITMAP_FIRST_USE, &llbitmap->flags)) {
>   		ret = llbitmap_init(llbitmap);
>   		goto out_put_page;

-- 
Thanks,
Kuai

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it
  2026-07-19 14:44 ` [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it Mykola Marzhan
  2026-07-19 14:57   ` sashiko-bot
@ 2026-07-20  2:02   ` yu kuai
  1 sibling, 0 replies; 12+ messages in thread
From: yu kuai @ 2026-07-20  2:02 UTC (permalink / raw)
  To: Mykola Marzhan, Song Liu, Yu Kuai
  Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

在 2026/7/19 22:44, Mykola Marzhan 写道:
> Every path in llbitmap_read_sb() that maps the superblock page ends
> at the out_put_page label, which frees the page and only then unmaps
> it:
>
> 	__free_page(sb_page);
> 	kunmap_local(sb);
>
> The page comes from alloc_page(GFP_KERNEL), so kunmap_local() is
> currently a no-op everywhere except CONFIG_DEBUG_KMAP_LOCAL_FORCE_MAP
> builds -- where the page really is freed while a live mapping still
> points at it.  Unmap before freeing.
>
> Fixes: 5ab829f1971d ("md/md-llbitmap: introduce new lockless bitmap")
> Assisted-by: Claude-Code:claude-opus-4-8
> Signed-off-by: Mykola Marzhan<mykola@meshstor.io>
> ---
>   drivers/md/md-llbitmap.c | 2 +-
>   1 file changed, 1 insertion(+), 1 deletion(-)
Reviewed-by: Yu Kuai <yukuai@fygo.io>

-- 
Thanks,
Kuai

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH 4/4] md: include events counters when kicking non-fresh device
  2026-07-19 14:44 ` [PATCH 4/4] md: include events counters when kicking non-fresh device Mykola Marzhan
@ 2026-07-20  2:14   ` yu kuai
  0 siblings, 0 replies; 12+ messages in thread
From: yu kuai @ 2026-07-20  2:14 UTC (permalink / raw)
  To: Mykola Marzhan, Song Liu, Yu Kuai
  Cc: Li Nan, Xiao Ni, linux-raid, linux-kernel

Hi,

在 2026/7/19 22:44, Mykola Marzhan 写道:
> When analyze_sbs() kicks a member with a stale events counter, the
> warning names the device but not the divergence that caused the
> kick, so judging whether the kicked member is salvageable means
> running mdadm --examine on every member.  Print the member's on-disk
> events counter and the freshest events counter in the warning
> itself.

If user space tools can do this already, then user space tools should be
considered preferred.

>
> rdev->sb_events cannot be used here: it is only assigned when a
> superblock is written and still reads 0 during assemble.  Read the
> counter from the superblock page instead -- md_event() for 0.90
> metadata, the little-endian events field for 1.x.
>
> The legacy substring "kicking non-fresh %pg from array!" is
> unchanged, so existing log parsers continue to match.
>
> Assisted-by: Claude-Code:claude-opus-4-8
> Signed-off-by: Mykola Marzhan <mykola@meshstor.io>
> ---
>   drivers/md/md.c | 25 +++++++++++++++++++++++--
>   1 file changed, 23 insertions(+), 2 deletions(-)
>
> diff --git a/drivers/md/md.c b/drivers/md/md.c
> index d60ea7aaca3a..9ec87375a717 100644
> --- a/drivers/md/md.c
> +++ b/drivers/md/md.c
> @@ -3934,8 +3934,29 @@ static int analyze_sbs(struct mddev *mddev)
>   		if (rdev != freshest) {
>   			if (super_types[mddev->major_version].
>   			    validate_super(mddev, freshest, rdev)) {
> -				pr_warn("md: kicking non-fresh %pg from array!\n",
> -					rdev->bdev);
> +				u64 rdev_events = 0;
> +
> +				/*
> +				 * rdev->sb_events is only assigned when
> +				 * writing a superblock and reads 0 during
> +				 * assemble; read events from the on-disk
> +				 * superblock instead.
> +				 */
> +				if (rdev->sb_page) {
> +					if (mddev->major_version == 1) {
> +						struct mdp_superblock_1 *sb =
> +							page_address(rdev->sb_page);
> +						rdev_events = le64_to_cpu(sb->events);
> +					} else if (mddev->major_version == 0) {
> +						mdp_super_t *sb =
> +							page_address(rdev->sb_page);
> +						rdev_events = md_event(sb);
> +					}
> +				}
> +				pr_warn("md: kicking non-fresh %pg from array! (events=%llu, freshest=%llu)\n",
> +					rdev->bdev,
> +					(unsigned long long)rdev_events,
> +					(unsigned long long)mddev->events);

And I don't like the above changes. If you really want such log, please add
it inside validate_super() methods.

>   				md_kick_rdev_from_array(rdev);
>   				continue;
>   			}

-- 
Thanks,
Kuai

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2026-07-20  2:14 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-19 14:44 [PATCH 0/4] md: bitmap superblock robustness and assemble diagnostics Mykola Marzhan
2026-07-19 14:44 ` [PATCH 1/4] md/md-bitmap: mask sb->state on load to drop runtime-only bits Mykola Marzhan
2026-07-19 15:06   ` sashiko-bot
2026-07-20  1:54   ` yu kuai
2026-07-19 14:44 ` [PATCH 2/4] md/md-llbitmap: " Mykola Marzhan
2026-07-19 15:00   ` sashiko-bot
2026-07-20  2:00   ` yu kuai
2026-07-19 14:44 ` [PATCH 3/4] md/md-llbitmap: unmap sb page before freeing it Mykola Marzhan
2026-07-19 14:57   ` sashiko-bot
2026-07-20  2:02   ` yu kuai
2026-07-19 14:44 ` [PATCH 4/4] md: include events counters when kicking non-fresh device Mykola Marzhan
2026-07-20  2:14   ` yu kuai

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.