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

Sasha Levin <[email protected]>
Newsgroups gmane.linux.raid,gmane.linux.kernel.stable,gmane.linux.kernel
Message-ID <[email protected]>
From: FengWei Shih <[email protected]>

[ 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 <[email protected]>
Reviewed-by: Yu Kuai <[email protected]>
Link: https://lore.kernel.org/linux-raid/[email protected]/
Signed-off-by: Yu Kuai <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
---

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 <[email protected]> (author)
- **Reviewed-by**: Yu Kuai <[email protected]> — **This is the MD
  subsystem co-maintainer** (confirmed in MAINTAINERS)
- **Link**: https://lore.kernel.org/linux-
  raid/[email protected]/
- **Signed-off-by**: Yu Kuai <[email protected]> — 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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.