[PATCH AUTOSEL 7.0-5.10] md/raid5: skip 2-failure compute when other disk is R5_LOCKED

From: Sasha Levin

Date: Mon Apr 20 2026 - 10:01:29 EST


From: FengWei Shih <dannyshih@xxxxxxxxxxxx>

[ Upstream commit 52e4324935be917f8f3267354b3cc06bb8ffcec1 ]

When skip_copy is enabled on a doubly-degraded RAID6, a device that is
being written to will be in R5_LOCKED state with R5_UPTODATE cleared.
If a new read triggers fetch_block() while the write is still in
flight, the 2-failure compute path may select this locked device as a
compute target because it is not R5_UPTODATE.

Because skip_copy makes the device page point directly to the bio page,
reconstructing data into it might be risky. Also, since the compute
marks the device R5_UPTODATE, it triggers WARN_ON in ops_run_io()
which checks that R5_SkipCopy and R5_UPTODATE are not both set.

This can be reproduced by running small-range concurrent read/write on
a doubly-degraded RAID6 with skip_copy enabled, for example:

mdadm -C /dev/md0 -l6 -n6 -R -f /dev/loop[0-3] missing missing
echo 1 > /sys/block/md0/md/skip_copy
fio --filename=/dev/md0 --rw=randrw --bs=4k --numjobs=8 \
--iodepth=32 --size=4M --runtime=30 --time_based --direct=1

Fix by checking R5_LOCKED before proceeding with the compute. The
compute will be retried once the lock is cleared on IO completion.

Signed-off-by: FengWei Shih <dannyshih@xxxxxxxxxxxx>
Reviewed-by: Yu Kuai <yukuai@xxxxxxxxx>
Link: https://lore.kernel.org/linux-raid/20260319053351.3676794-1-dannyshih@xxxxxxxxxxxx/
Signed-off-by: Yu Kuai <yukuai3@xxxxxxxxxx>
Signed-off-by: Sasha Levin <sashal@xxxxxxxxxx>
---

LLM Generated explanations, may be completely bogus:

Now I have all the information needed for my comprehensive analysis.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject Line
- **Subsystem**: md/raid5
- **Action verb**: "skip" — describing a fix that avoids proceeding with
a dangerous compute operation
- **Summary**: Skip 2-failure compute when the other disk is R5_LOCKED
- Record: [md/raid5] [skip/fix] [Prevent 2-failure compute from
selecting a locked device as target, avoiding data corruption and
WARN_ON triggers]

### Step 1.2: Tags
- **Signed-off-by**: FengWei Shih <dannyshih@xxxxxxxxxxxx> (author)
- **Reviewed-by**: Yu Kuai <yukuai@xxxxxxxxx> — **This is the MD
subsystem co-maintainer** (confirmed in MAINTAINERS)
- **Link**: https://lore.kernel.org/linux-
raid/20260319053351.3676794-1-dannyshih@xxxxxxxxxxxx/
- **Signed-off-by**: Yu Kuai <yukuai3@xxxxxxxxxx> — Applied by the
subsystem maintainer
- No Fixes: tag (expected for AUTOSEL candidates)
- No Reported-by: tag (but author provides precise reproduction steps)
- Record: Reviewed and applied by subsystem co-maintainer. Author
provides concrete repro.

### Step 1.3: Commit Body Analysis
- **Bug described**: On a doubly-degraded RAID6 with `skip_copy`
enabled, a concurrent read triggers `fetch_block()` during an in-
flight write. The 2-failure compute path selects the locked (being-
written-to) device as a compute target because it's not R5_UPTODATE.
- **Symptom**: WARN_ON in `ops_run_io()` at line 1271, which checks that
R5_SkipCopy and R5_UPTODATE are not both set. Additionally,
reconstructing data into the device page is risky because with
`skip_copy`, the device page points directly to the bio page —
corrupting user data.
- **Reproduction**: Concrete and reproducible with mdadm + fio commands
provided.
- **Root cause**: The 2-failure compute path in `fetch_block()` finds a
non-R5_UPTODATE disk and selects it as the "other" compute target
without checking if it's R5_LOCKED (i.e., has an I/O in flight).
- Record: Race between concurrent read and write on doubly-degraded
RAID6 with skip_copy. Triggers WARN_ON and potential data corruption.
Concrete reproduction steps provided.

### Step 1.4: Hidden Bug Fix Detection
This is NOT a hidden fix — it's an explicit, well-described bug fix. The
commit clearly explains the bug mechanism, failure mode, and how to
reproduce it.

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
- **Files changed**: 1 (drivers/md/raid5.c)
- **Lines added**: 2
- **Function modified**: `fetch_block()`
- **Scope**: Single-file, single-function, 2-line surgical fix
- Record: Minimal change — 2 lines added in fetch_block() in raid5.c

### Step 2.2: Code Flow Change
**Before**: The 2-failure compute path finds the `other` disk that is
not R5_UPTODATE, then immediately proceeds with the compute operation
(setting R5_Wantcompute on both target disks).

**After**: After finding the `other` disk, the code first checks if it
has R5_LOCKED set. If so, it returns 0 (skip the compute), allowing the
compute to be retried after the lock clears on I/O completion.

The change is in the 2-failure compute branch of `fetch_block()`:

```3918:3919:drivers/md/raid5.c
BUG_ON(other < 0);
// NEW: if (test_bit(R5_LOCKED, &sh->dev[other].flags)) return 0;
pr_debug("Computing stripe %llu blocks %d,%d\n",
```

### Step 2.3: Bug Mechanism
This is a **race condition** combined with **potential data
corruption**:
1. Write path sets R5_SkipCopy on a device, pointing dev->page to the
bio page, and clears R5_UPTODATE (line 1961-1962).
2. The device is R5_LOCKED (I/O in flight).
3. A concurrent read triggers `fetch_block()` → enters the 2-failure
compute path.
4. The loop finds this device as `other` (because it's !R5_UPTODATE).
5. Compute is initiated, writing reconstructed data into `other->page`,
which is actually the user's bio page.
6. The compute then marks the device R5_UPTODATE via
`mark_target_uptodate()` (line 1506).
7. This triggers WARN_ON at line 1270-1271 because both R5_SkipCopy and
R5_UPTODATE are now set.
8. Data could be corrupted because the compute overwrites the bio page.

Record: Race condition causing WARN_ON trigger + potential data
corruption on RAID6 with skip_copy enabled.

### Step 2.4: Fix Quality
- **Obviously correct**: Yes — a device being written to (R5_LOCKED)
should not be selected as a compute target. The fix adds a simple
guard check.
- **Minimal**: 2 lines, surgical.
- **Regression risk**: Minimal. Returning 0 simply defers the compute
until the lock clears — this is the normal retry mechanism already
used elsewhere in the stripe handling.
- **No red flags**: No API changes, no lock changes, no architectural
impact.

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
- The 2-failure compute code in `fetch_block()` was introduced in commit
`5599becca4bee7` (2009-08-29, "md/raid6: asynchronous
handle_stripe_fill6"), which is from the v2.6.32 era.
- The `R5_SkipCopy` mechanism was introduced in commit `584acdd49cd24`
(2014-12-15, "md/raid5: activate raid6 rmw feature"), which landed in
v4.1.
- The bug exists since v4.1 when skip_copy was introduced — this created
the interaction where a device could be !R5_UPTODATE but R5_LOCKED
with page pointing to a bio page.

Record: Buggy interaction exists since ~v4.1 (2015). Present in all
active stable trees.

### Step 3.2: Fixes tag
No Fixes: tag present (expected for AUTOSEL). Based on analysis, the
proper Fixes: would point to `584acdd49cd24` where the skip_copy feature
introduced the problematic interaction.

### Step 3.3: File history
Recent changes to raid5.c show active development with fixes like IO
hang fixes, null-pointer deref fixes, etc. This is actively maintained
code.

### Step 3.4: Author
- FengWei Shih works at Synology (a major NAS/storage vendor that
heavily uses RAID6).
- Yu Kuai (reviewer and committer) is the MD subsystem co-maintainer per
MAINTAINERS.

### Step 3.5: Dependencies
- No dependencies. The fix is a standalone 2-line addition checking an
existing flag.
- Verified the code is identical in v5.15, v6.1, and v6.6 stable trees.

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1-4.5
Lore was not accessible due to Anubis anti-bot protection. However:
- The Link: tag in the commit points to the original submission on
linux-raid.
- The patch was reviewed by Yu Kuai (subsystem co-maintainer) and
applied by him.
- The author works at Synology, suggesting they encountered this in
production NAS workloads.

Record: Could not fetch lore discussion. But reviewer is subsystem co-
maintainer, author is from major storage vendor.

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Functions Modified
- `fetch_block()` — the sole function modified.

### Step 5.2: Callers
`fetch_block()` is called from `handle_stripe_fill()` (line 3973) in a
loop over all disks. `handle_stripe_fill()` is called from
`handle_stripe()`, which is the main stripe processing function in
RAID5/6 — called for every I/O operation.

### Step 5.3-5.4: Impact Surface
The call chain is: I/O request → handle_stripe() → handle_stripe_fill()
→ fetch_block(). This is a hot path for all RAID5/6 read operations
during degraded mode.

### Step 5.5: Similar Patterns
The single-failure compute path (the `if` branch above the modified
code, lines 3883-3905) doesn't have this problem because it only
triggers when `s->uptodate == disks - 1`, meaning only one disk is not
up-to-date, and it computes the requesting disk itself. The 2-failure
path is uniquely vulnerable because it selects a *second* disk as
compute target.

## PHASE 6: STABLE TREE ANALYSIS

### Step 6.1: Code Existence
Verified that the exact same 2-failure compute code block exists in
v5.15, v6.1, and v6.6 stable trees. The code is character-for-character
identical.

### Step 6.2: Backport Complications
**None.** The patch will apply cleanly to all stable trees. The
surrounding context lines match exactly.

### Step 6.3: No related fixes already in stable.

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: Subsystem
- **Subsystem**: MD/RAID (drivers/md/) — Software RAID
- **Criticality**: IMPORTANT — RAID6 is widely used in NAS, enterprise
storage, and data center systems. Data integrity issues in RAID are
critical.

### Step 7.2: Activity
Active subsystem with regular fixes and enhancements. Maintained by Song
Liu and Yu Kuai.

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Affected Users
All users running doubly-degraded RAID6 arrays with skip_copy enabled
during concurrent read/write. This is a realistic production scenario —
a RAID6 array losing two disks (which RAID6 is designed to survive)
while continuing to serve I/O.

### Step 8.2: Trigger Conditions
- Doubly-degraded RAID6 (two disks failed or missing)
- `skip_copy` enabled (configurable via sysfs, default off but commonly
enabled for performance)
- Concurrent read and write to overlapping stripe regions
- Reproducible with the fio command in the commit message

### Step 8.3: Failure Mode Severity
1. **WARN_ON trigger** in `ops_run_io()` — MEDIUM (kernel warning,
potential crash if panic_on_warn)
2. **Data corruption** — CRITICAL: The compute writes reconstructed data
into a bio page that is owned by the user write operation. This can
corrupt user data silently.
3. The commit says "reconstructing data into it might be risky" —
understatement given that the bio page belongs to user space.

**Severity: CRITICAL** (potential data corruption on RAID storage)

### Step 8.4: Risk-Benefit Ratio
- **BENEFIT**: Very high — prevents potential data corruption and
WARN_ON on RAID6 arrays
- **RISK**: Very low — 2-line fix that adds a simple guard check,
returns 0 to defer (existing retry mechanism), no side effects
- **Ratio**: Excellent — minimal risk, high benefit

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence Summary

**FOR backporting:**
- Fixes a real, reproducible race condition on doubly-degraded RAID6
with skip_copy
- Can lead to data corruption (compute writes into bio page)
- Triggers WARN_ON in ops_run_io() (system stability concern)
- 2-line surgical fix, obviously correct
- Reviewed and applied by subsystem co-maintainer (Yu Kuai)
- Author from Synology (major NAS vendor, real-world scenario)
- Concrete reproduction steps provided
- Code identical in all stable trees (v5.15, v6.1, v6.6) — clean apply
- Bug present since v4.1 (affects all active stable trees)
- No dependencies on other patches

**AGAINST backporting:**
- No explicit Fixes: tag (expected for AUTOSEL)
- Requires specific configuration (doubly-degraded + skip_copy +
concurrent I/O)
- No syzbot report (but has clear reproduction path)

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **YES** — reviewed by maintainer,
concrete repro
2. Fixes a real bug? **YES** — WARN_ON trigger + potential data
corruption
3. Important issue? **YES** — data corruption on RAID storage is
critical
4. Small and contained? **YES** — 2 lines in one function
5. No new features? **YES** — just a guard check
6. Applies to stable? **YES** — verified identical code in all stable
trees

### Step 9.3: Exception Categories
N/A — this is a standard bug fix, no exception needed.

### Step 9.4: Decision
Clear YES. This is a 2-line fix that prevents potential data corruption
and WARN_ON triggers on doubly-degraded RAID6 arrays. It was reviewed
and merged by the subsystem co-maintainer, is obviously correct, and
applies cleanly to all stable trees.

## Verification

- [Phase 1] Parsed tags: Reviewed-by Yu Kuai (MD co-maintainer), Link to
linux-raid
- [Phase 2] Diff analysis: 2 lines added in fetch_block(), adds
R5_LOCKED check before 2-failure compute
- [Phase 3] git blame: buggy interaction since v4.1 (commit
584acdd49cd24, 2014); 2-failure compute since v2.6.32 (commit
5599becca4bee7, 2009)
- [Phase 3] Verified identical code exists in v5.15 (line 3882), v6.1
(line 3984), v6.6 (line 3991)
- [Phase 3] Yu Kuai confirmed as MD subsystem co-maintainer in
MAINTAINERS file
- [Phase 4] Lore inaccessible (Anubis protection). UNVERIFIED: full
mailing list discussion. However, Reviewed-by from maintainer
mitigates this.
- [Phase 5] fetch_block() called from handle_stripe_fill() →
handle_stripe(), hot path for RAID I/O
- [Phase 5] Traced SkipCopy mechanism: set at line 1961 during write
prep, clears R5_UPTODATE, points dev->page to bio page
- [Phase 5] Traced compute completion: mark_target_uptodate() at line
1506 sets R5_UPTODATE, triggering WARN_ON at line 1270-1271
- [Phase 6] Code exists unchanged in all active stable trees (v5.15,
v6.1, v6.6) — patch applies cleanly
- [Phase 7] MD/RAID subsystem, IMPORTANT criticality, actively
maintained
- [Phase 8] Failure mode: data corruption (CRITICAL) + WARN_ON trigger
(MEDIUM); trigger requires doubly-degraded RAID6 + skip_copy +
concurrent I/O

**YES**

drivers/md/raid5.c | 2 ++
1 file changed, 2 insertions(+)

diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index a8e8d431071ba..6e9405a89bc4a 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -3916,6 +3916,8 @@ static int fetch_block(struct stripe_head *sh, struct stripe_head_state *s,
break;
}
BUG_ON(other < 0);
+ if (test_bit(R5_LOCKED, &sh->dev[other].flags))
+ return 0;
pr_debug("Computing stripe %llu blocks %d,%d\n",
(unsigned long long)sh->sector,
disk_idx, other);
--
2.53.0