gfs2 filesystem and dlm development
 help / color / mirror / Atom feed
* [PATCH AUTOSEL 6.18-6.6] gfs2: page poisoning fix
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
@ 2026-08-31 13:23 ` Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] gfs2: fix quota init duplicate scan Sasha Levin
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-08-31 13:23 UTC (permalink / raw)
  To: patches, stable; +Cc: Andreas Gruenbacher, Sasha Levin, gfs2, linux-kernel

From: Andreas Gruenbacher <agruenba@redhat.com>

[ Upstream commit 4982e58669b11c43644efb5fb7435975848b716e ]

Processes can write to the last page of a file using mmap, and when the file
size is not a multiple of the page size, this can be used to write beyond the
end of the file.  This is sometimes referred to as page poisoning, and it is
not a problem in itself because the data beyond eof will be ignored.  However,
we currently fail to clear out any space beyond the end of the file that we
skip over when the file size is increased, so that "poison" can end up getting
exposed.  Fix that.

Fixes xfstest generic/363.

Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit Message Forensics

**Step 1.1 — Subject line**

Record: `[gfs2] [fix] page poisoning fix — clear stale bytes beyond EOF
when growing a file after mmap “page poisoning”`

**Step 1.2 — Tags**

Record:
- `Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>` (author)
- No `Fixes:` tag
- No `Reported-by:`, `Tested-by:`, `Reviewed-by:`, `Acked-by:`, `Link:`,
  or `Cc: stable@vger.kernel.org`
- Commit message references `Fixes xfstest generic/363`

**Step 1.3 — Body analysis**

Record:
- **Bug:** mmap can write into the tail of the last page beyond current
  `i_size` (“page poisoning”). That is normally harmless, but when the
  file is later grown (write/fallocate/truncate), bytes between the old
  EOF and the new size in that page are not zeroed, so poisoned data
  becomes visible.
- **Symptom:** Readers see stale/non-zero data in the hole between old
  EOF and new EOF; xfstests `generic/363` fails.
- **Root cause:** GFS2 grow/write paths skip zeroing the post-EOF
  portion of the partial tail page before extending size.

**Step 1.4 — Hidden bug fix?**

Record: Yes. Despite the terse subject, this is a real correctness/data-
integrity fix, not cleanup.

---

## Phase 2: Diff Analysis

**Step 2.1 — Inventory**

Record:
- `fs/gfs2/bmap.c`: +19 lines (`gfs2_clear_beyond_eof()`, call in
  `do_grow()`)
- `fs/gfs2/bmap.h`: +1 line (declaration)
- `fs/gfs2/file.c`: +10 lines (calls in `gfs2_file_buffered_write()`,
  `__gfs2_fallocate()`)
- **Functions modified:** `gfs2_clear_beyond_eof()` (new), `do_grow()`,
  `gfs2_file_buffered_write()`, `__gfs2_fallocate()`
- **Scope:** Single-subsystem, surgical (~30 lines)

**Step 2.2 — Code flow per hunk**

Record:
1. **`gfs2_clear_beyond_eof()`:** If `i_size` is not page-aligned and
   `end > i_size`, compute bytes from `i_size` to end of page (capped at
   `end`), then zero via `gfs2_block_zero_range()`.
2. **`do_grow()`:** Before starting a transaction, if not unstuffing,
   clear poisoned tail bytes up to new `size`.
3. **`gfs2_file_buffered_write()`:** Before
   `iomap_file_buffered_write()`, clear if write position extends past
   partial tail page.
4. **`__gfs2_fallocate()`:** When not `FALLOC_FL_KEEP_SIZE`, clear
   before allocating/extending.

**Step 2.3 — Bug mechanism**

Record: **Logic/correctness — stale data exposure.** Category: post-EOF
page-cache pollution on file extension. Same class as NFS “eof page
pollution”, f2fs “zero post-eof page”, btrfs hole expansion fixes.

**Step 2.4 — Fix quality**

Record: Fix is minimal and obviously correct. Uses existing
`gfs2_block_zero_range()` which already clamps to `i_size`.
`gfs2_quota_unlock()` is safe if `goto do_grow_qunlock` is taken with
`unstuff == 0` because it returns early when `GIF_QD_LOCKED` is unset.
Low regression risk.

---

## Phase 3: Git History Investigation

**Step 3.1 — Blame**

Record:
- `gfs2_block_zero_range()` eof clamp: `87faee382d294` (May 2025,
  Andreas Gruenbacher) — present in this tree
- `do_grow()`: present since 2010 (`ff8f33c8b30d7`)
- Bug is long-standing; not introduced after 6.18.y branched

**Step 3.2 — Fixes: tag**

Record: Not applicable (no `Fixes:` tag).

**Step 3.3 — Related file history**

Record:
- Similar fixes already in this tree: `b1817b18ff20e` (NFS eof page
  pollution), `ba8dac350faf1` (f2fs zero post-eof page)
- Commit `4982e58669b11` on `master`, merged via `gfs2-for-7.2`; **not**
  in current HEAD (`v6.18.44`)
- Part of 2-patch series; patch 1 (`70008e22ab3fd`, remove unused
  `fallocate_chunk` arg) is independent — patch 2 applies cleanly
  without it

**Step 3.4 — Author context**

Record: Andreas Gruenbacher is the GFS2 maintainer; frequent GFS2 stable
fixes in this tree.

**Step 3.5 — Dependencies**

Record: Requires `gfs2_block_zero_range()` with eof clamp
(`87faee382d294`) — **present**. Standalone; no other commits required.
`git apply --check` on `4982e58669b11` succeeds on this tree.

---

## Phase 4: Mailing List and External Research

**Step 4.1 — Original discussion**

Record: `b4 dig -c 4982e58669b11` found no lore match. Ratatoskr shows
`[PATCH 2/2] gfs2: page poisoning fix` (2026-05-29), thread status
DORMANT/no replies. No stable nomination found in available sources.

**Step 4.2 — Reviewers**

Record: `b4 dig -w` unavailable (no lore match). Author is subsystem
maintainer.

**Step 4.3 — Bug report**

Record: Failure mode documented by xfstests `generic/363` (expanded to
all filesystems Dec 2024 by Christoph Hellwig). No syzbot/user crash
reports.

**Step 4.4 — Series context**

Record: 2-patch series; only patch 2 is needed here and applies cleanly.

**Step 4.5 — Stable list**

Record: No stable-specific discussion found (lore blocked by bot
protection for manual search).

---

## Phase 5: Code Semantic Analysis

**Step 5.1 — Key functions**

Record: `gfs2_clear_beyond_eof()`, `do_grow()`,
`gfs2_file_buffered_write()`, `__gfs2_fallocate()`

**Step 5.2 — Callers**

Record:
- `do_grow()` ← `gfs2_setattr_size()` ← `gfs2_setattr()` / truncate
- `gfs2_file_buffered_write()` ← `gfs2_file_write_iter()` ←
  `write()`/`pwrite()` syscall path
- `__gfs2_fallocate()` ← `gfs2_fallocate()` ← `fallocate()` syscall

**Step 5.3 — Callees**

Record: `i_size_read()`, `gfs2_block_zero_range()` →
`iomap_zero_range()`

**Step 5.4 — Reachability**

Record: Reachable from userspace via mmap + grow
(write/fallocate/truncate/setattr). Common file I/O paths for GFS2
cluster users.

**Step 5.5 — Similar patterns**

Record: NFS, f2fs, btrfs, exfat all received analogous post-EOF zeroing
fixes; NFS and f2fs fixes are already in this 6.18.y tree.

---

## Phase 6: Cross-Reference Against Local Tree

**Step 6.1 — Buggy code exists?**

Record: **Yes.** Local tree is `v6.18.44` (`stable/linux-6.18.y`).
`gfs2_clear_beyond_eof()` absent; `do_grow()`,
`gfs2_file_buffered_write()`, `__gfs2_fallocate()` lack the clearing
calls. Commit `4982e58669b11` is on `master` but not an ancestor of
HEAD.

**Step 6.2 — Backport complications**

Record: **Clean apply** — `git apply --check` on upstream patch succeeds
with no conflicts.

**Step 6.3 — Related fixes already present?**

Record: No equivalent GFS2 fix in this tree. Related infrastructure
(`gfs2_block_zero_range` eof clamp) is present.

---

## Phase 7: Subsystem and Maintainer Context

**Step 7.1 — Subsystem criticality**

Record: `fs/gfs2/` — IMPORTANT (cluster filesystem used in
enterprise/RHEL deployments; not universal like VFS core, but
production-critical where enabled).

**Step 7.2 — Activity**

Record: GFS2 actively maintained; multiple recent stable fixes in 6.18.y
(UAF, NULL deref, quota, etc.).

---

## Phase 8: Impact and Risk Assessment

**Step 8.1 — Who is affected**

Record: GFS2 users (`CONFIG_GFS2_FS`) performing mmap writes on non-
page-aligned files followed by file growth.

**Step 8.2 — Trigger conditions**

Record: mmap write beyond EOF on partial tail page, then extend file
past old EOF without rewriting that region. Realistic; exercised by
`generic/363`. Unprivileged users with write access can trigger.

**Step 8.3 — Failure mode severity**

Record: **Stale/wrong data exposed in file reads** — data integrity
issue, potential information leak of page-cache contents. Not a kernel
crash, but serious for filesystem correctness. Severity: **HIGH** (data
corruption/exposure).

**Step 8.4 — Risk vs benefit**

Record:
- **Benefit:** HIGH — prevents exposing poisoned page-cache data; aligns
  GFS2 with xfstest expectations and peer filesystem fixes
- **Risk:** LOW — 30-line additive fix, uses existing zeroing helper,
  applies cleanly
- **Ratio:** Strong benefit, minimal risk

---

## Phase 9: Final Synthesis

**Step 9.1 — Evidence**

FOR:
- Real, reproducible bug (`generic/363`)
- Data exposure on file growth
- Small, maintainer-authored, obviously correct fix
- Applies cleanly to v6.18.44
- Prerequisites present
- Peer filesystems already fixed similarly in this tree
- Long-standing bug in production GFS2 code paths

AGAINST:
- No syzbot/crash report (correctness, not oops)
- No lore review thread found
- GFS2-specific (not all kernel users)
- Skips clearing when `unstuff == 1` (stuffed→normal transition; likely
  correct for inline files)

UNRESOLVED:
- No mailing-list review discussion retrieved
- No explicit stable nomination from reviewers

**Step 9.2 — Stable rules checklist**

1. Obviously correct and tested? **PASS** — logic is clear; fixes
   `generic/363`
2. Fixes a real bug affecting users? **PASS** — stale data exposure on
   grow
3. Important issue? **PASS** — data integrity / information exposure
   (HIGH)
4. Small and contained? **PASS** — 30 lines, 3 files
5. No new features/APIs? **PASS** — internal helper only
6. Can apply to local tree? **PASS** — clean apply verified

**Step 9.3 — Exception categories**

Record: Not a device-ID/quirk/DT/build/docs exception; standard
filesystem correctness bug fix.

**Step 9.4 — Decision rationale**

This commit fixes a real GFS2 data-integrity bug where mmap “page
poisoning” on a partial tail page can leak stale bytes when the file is
later grown. The buggy code is present in v6.18.44, the fix is small and
self-contained, prerequisites are satisfied, and analogous fixes for
NFS/f2fs are already in this stable tree. The fix should be backported.

---

## Verification

- [Phase 1] Parsed commit `4982e58669b11`: subject, body, tags; only SOB
  and xfstest reference
- [Phase 2] Diff analysis: 30 lines across `bmap.c`, `bmap.h`, `file.c`;
  new `gfs2_clear_beyond_eof()` helper
- [Phase 3] `git blame`: `do_grow()` since 2010; `gfs2_block_zero_range`
  eof clamp in `87faee382d294` (present)
- [Phase 3] `git merge-base --is-ancestor 4982e58669b11 HEAD`: fix NOT
  in current HEAD
- [Phase 3] `git apply --check` on `4982e58669b11`: applies cleanly
- [Phase 3] Related commits in tree: `b1817b18ff20e` (NFS),
  `ba8dac350faf1` (f2fs)
- [Phase 4] `b4 dig -c 4982e58669b11`: no lore match
- [Phase 4] Ratatoskr fetch: patch 2/2, 2026-05-29, DORMANT/no replies
- [Phase 5] Callers traced: `gfs2_setattr_size` → `do_grow`;
  `gfs2_file_write_iter` → `gfs2_file_buffered_write`; `gfs2_fallocate`
  → `__gfs2_fallocate`
- [Phase 5] `gfs2_quota_unlock()` at `fs/gfs2/quota.c:1152`: safe no-op
  when lock not held
- [Phase 6] `git describe HEAD`: v6.18.44; `make kernelversion`: 6.18.44
- [Phase 6] `grep gfs2_clear_beyond_eof`: not present in tree
- [Phase 8] Failure mode: stale data exposure on file grow; severity
  HIGH

**YES**The background searches finished and match what the analysis
already used:

- **Grep search:** No `gfs2: page poisoning fix` in this tree, but
  related post-EOF fixes are present (NFS, f2fs, btrfs, exfat).
- **Branch check:** Commit `4982e58669b11` (`gfs2: page poisoning fix`)
  is on `master` (via `gfs2-for-7.2`) and is **not** in the current
  `stable/linux-6.18.y` checkout at `v6.18.44`.

That confirms the fix is a valid backport candidate for this 6.18.y tree
and applies cleanly. Verdict remains **YES**.

 fs/gfs2/bmap.c | 19 +++++++++++++++++++
 fs/gfs2/bmap.h |  1 +
 fs/gfs2/file.c | 10 ++++++++++
 3 files changed, 30 insertions(+)

diff --git a/fs/gfs2/bmap.c b/fs/gfs2/bmap.c
index fdcac8e3f2ba2..836e260099c39 100644
--- a/fs/gfs2/bmap.c
+++ b/fs/gfs2/bmap.c
@@ -1321,6 +1321,19 @@ static int gfs2_block_zero_range(struct inode *inode, loff_t from, loff_t length
 			&gfs2_iomap_write_ops, NULL);
 }
 
+int gfs2_clear_beyond_eof(struct inode *inode, loff_t end)
+{
+	loff_t isize = i_size_read(inode);
+	unsigned int len = isize & ~PAGE_MASK;
+
+	if (!len || isize >= end)
+		return 0;
+	len = PAGE_SIZE - len;
+	if (end - isize < len)
+		len = end - isize;
+	return gfs2_block_zero_range(inode, isize, len);
+}
+
 #define GFS2_JTRUNC_REVOKES 8192
 
 /**
@@ -2096,6 +2109,12 @@ static int do_grow(struct inode *inode, u64 size)
 		unstuff = 1;
 	}
 
+	if (!unstuff) {
+		error = gfs2_clear_beyond_eof(inode, size);
+		if (error)
+			goto do_grow_qunlock;
+	}
+
 	error = gfs2_trans_begin(sdp, RES_DINODE + RES_STATFS + RES_RG_BIT +
 				 (unstuff &&
 				  gfs2_is_jdata(ip) ? RES_JDATA : 0) +
diff --git a/fs/gfs2/bmap.h b/fs/gfs2/bmap.h
index 6cdc72dd55a3f..e3d6efdfd8903 100644
--- a/fs/gfs2/bmap.h
+++ b/fs/gfs2/bmap.h
@@ -58,6 +58,7 @@ int gfs2_get_extent(struct inode *inode, u64 lblock, u64 *dblock,
 		    unsigned int *extlen);
 int gfs2_alloc_extent(struct inode *inode, u64 lblock, u64 *dblock,
 		      unsigned *extlen, bool *new);
+int gfs2_clear_beyond_eof(struct inode *inode, loff_t end);
 int gfs2_setattr_size(struct inode *inode, u64 size);
 int gfs2_truncatei_resume(struct gfs2_inode *ip);
 int gfs2_file_dealloc(struct gfs2_inode *ip);
diff --git a/fs/gfs2/file.c b/fs/gfs2/file.c
index bc67fa058c845..ef57d6aa42ebb 100644
--- a/fs/gfs2/file.c
+++ b/fs/gfs2/file.c
@@ -1057,6 +1057,10 @@ static ssize_t gfs2_file_buffered_write(struct kiocb *iocb,
 			goto out_unlock;
 	}
 
+	ret = gfs2_clear_beyond_eof(inode, iocb->ki_pos);
+	if (ret)
+		goto out_unlock;
+
 	pagefault_disable();
 	ret = iomap_file_buffered_write(iocb, from, &gfs2_iomap_ops,
 			&gfs2_iomap_write_ops, NULL);
@@ -1266,6 +1270,12 @@ static long __gfs2_fallocate(struct file *file, int mode, loff_t offset, loff_t
 
 	next = (next + 1) << sdp->sd_sb.sb_bsize_shift;
 
+	if (!(mode & FALLOC_FL_KEEP_SIZE)) {
+		error = gfs2_clear_beyond_eof(inode, offset + len);
+		if (error)
+			return error;
+	}
+
 	offset &= bsize_mask;
 
 	len = next - offset;
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] gfs2: fix quota init duplicate scan
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.6] gfs2: page poisoning fix Sasha Levin
@ 2026-08-31 13:24 ` Sasha Levin
  2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] dlm: add usercopy whitelist to dlm_cb cache Sasha Levin
  2026-08-31 13:31 ` [PATCH AUTOSEL 6.18-6.12] gfs2: move quota_init qc iterator increment Sasha Levin
  3 siblings, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-08-31 13:24 UTC (permalink / raw)
  To: patches, stable
  Cc: Jie Wang, syzbot+642d0561f78362d67d3f, Andreas Gruenbacher,
	Sasha Levin, gfs2, linux-kernel

From: Jie Wang <jie.wang@intel.com>

[ Upstream commit b99a1f0f18ee50445907f55069e88bcfd8947383 ]

gfs2_quota_init() checks for duplicate quota_change IDs while holding
qd_lock and the quota hash bucket bitlock. That path used
gfs2_qd_search_bucket(), which takes a lockref reference via
lockref_get_not_dead().

On PREEMPT_RT this may sleep, which is not allowed under the bucket
bitlock, triggering "sleeping function called from invalid context".

Use a no-ref bucket lookup in this path, then continue duplicate
handling without taking a lockref there.

Refactor gfs2_qd_search_bucket() to build on top of the no-ref helper
so lookup traversal stays in one place.

This patch fixes a bug reported by syzbot.

Reported-by: syzbot+642d0561f78362d67d3f@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=642d0561f78362d67d3f
Tested-by: syzbot+642d0561f78362d67d3f@syzkaller.appspotmail.com
Signed-off-by: Jie Wang <jie.wang@intel.com>
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `gfs2: fix quota init duplicate scan`

**Local tree:** Linux **6.18.43** (`git describe HEAD` → `v6.18.43`,
`make kernelversion` → `6.18.43`)

**Mainline fix commit:** `b99a1f0f18ee` (not present in this tree; `git
merge-base --is-ancestor` → exit 1)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[gfs2] [fix] quota init duplicate scan` — GFS2 quota
initialization path; explicit bug fix.

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Reported-by:** syzbot+642d0561f78362d67d3f@syzkaller.appspotmail.com
  (fuzzer-found, high priority)
- **Closes:**
  https://syzkaller.appspot.com/bug?extid=642d0561f78362d67d3f
- **Tested-by:** syzbot+642d0561f78362d67d3f@syzkaller.appspotmail.com
- **Signed-off-by:** Jie Wang (author), Andreas Gruenbacher (GFS2
  maintainer)
- No Fixes: tag (expected for manual review)
- No Cc: stable tag (expected; not a negative signal)

**Notable patterns:** syzbot report + Tested-by syzbot = reproducible,
syscall-reachable bug.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** `gfs2_quota_init()` calls `gfs2_qd_search_bucket()` while
  holding `qd_lock` and the quota hash bucket bitlock. That helper calls
  `lockref_get_not_dead()`, which on PREEMPT_RT can sleep.
- **Symptom:** `BUG: sleeping function called from invalid context` at
  `lockref_get_not_dead()` → `rt_spin_lock()`.
- **Root cause:** Taking a lockref reference (which may acquire
  `lockref->lock` as a sleeping RT spinlock) under a bit_spinlock
  context that forbids sleeping.
- **Fix approach:** Add `gfs2_qd_search_bucket_noref()` for callers
  already holding locks; use it in the duplicate-scan path; refactor
  `gfs2_qd_search_bucket()` to call the noref helper first.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not hidden — this is an explicit PREEMPT_RT correctness bug
fix, not cleanup. The removal of `qd_put(old_qd)` is part of the fix:
the noref lookup does not take a reference, so the prior `qd_put()` was
balancing an unnecessary `lockref_get_not_dead()`.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **Files:** `fs/gfs2/quota.c` only (+23 / -10 lines in mainline commit;
  ~33 lines total with context)
- **Functions modified:** new `gfs2_qd_search_bucket_noref()`,
  refactored `gfs2_qd_search_bucket()`, `gfs2_quota_init()`
- **Scope:** Single-file, surgical fix

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
**Record:**
- **Hunk 1 (`gfs2_qd_search_bucket_noref`):** Before: no separate noref
  lookup. After: pure hash-bucket traversal returning a match without
  refcount/LRU manipulation.
- **Hunk 2 (`gfs2_qd_search_bucket`):** Before: inline traversal +
  `lockref_get_not_dead()` under caller's lock context. After: delegates
  traversal to noref helper, then takes lockref only when caller is not
  already under bitlock (RCU or unlocked paths).
- **Hunk 3 (`gfs2_quota_init`):** Before: `gfs2_qd_search_bucket()`
  under `qd_lock` + bucket bitlock → can sleep on RT; then
  `qd_put(old_qd)`. After: `gfs2_qd_search_bucket_noref()` under locks
  (no sleep); no `qd_put()` since no ref was taken.

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:** **Category:** Synchronization / invalid context (PREEMPT_RT
lock nesting violation). **Mechanism:** `lockref_get_not_dead()` slow
path does `spin_lock(&lockref->lock)` which becomes a sleeping mutex on
PREEMPT_RT, called while `preempt_count: 1` and holding `hlist_bl`
bitlock via `spin_lock_bucket()`.

### Step 2.4: ASSESS THE FIX QUALITY
**Record:** Fix is obviously correct and minimal. Refactoring
`gfs2_qd_search_bucket()` to share traversal logic avoids duplication.
Removing `qd_put(old_qd)` is correct (no ref acquired). Low regression
risk: only changes the duplicate-detection path under locks; normal
`qd_get()` paths still use the ref-taking wrapper outside the
problematic quota-init context. **Regression risk:** LOW.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:** Buggy `gfs2_qd_search_bucket()` call in `gfs2_quota_init()`
at line 1461 and the function body at lines 257–275 both blame to
`5d324e5159d9e` (v6.18-rc8 merge base in this tree). The duplicate-scan
logic is present throughout the 6.18.y series.

### Step 3.2: FOLLOW THE FIXES: TAG
**Record:** No Fixes: tag present. N/A.

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:** Recent `fs/gfs2/quota.c` commits in this tree:
`1d47922b98046` (slab UAF in qd_put), `32c3960b42124` (wait_event in
gfs2_quotad). Patch went through v1→v2→v3 on lore; v3 is the committed
version. v2 was a 2-patch series but v3 is standalone.

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** No prior Jie Wang gfs2 commits visible in this stable tree's
limited history. Andreas Gruenbacher (maintainer) signed off on mainline
commit.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No dependencies. Standalone fix. `git apply --check` of the
quota.c portion applies cleanly to 6.18.43.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:** `b4 dig -c b99a1f0f18ee` →
https://patch.msgid.link/20260423133934.118970-1-jie.wang@intel.com
(v3). Series: v1 (Apr 20), v2 (Apr 21, 2 patches), v3 (Apr 23,
standalone). Andreas Gruenbacher reviewed v2 ("looking good except for
one minor detail") and v3 thread includes his reply. No explicit "Cc:
stable" found in mbox.

### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** `b4 dig -w`: CC'd gfs2@lists.linux.dev, linux-rt-
devel@lists.linux.dev, bigeasy@linutronix.de (RT), rostedt@goodmis.org,
clrkwllms@kernel.org, syzbot. Appropriate RT and GFS2 maintainers
involved.

### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** Syzkaller bug 642d0561f78362d67d3f — status: fixed. 13
crashes. Label: prio:high. Stack trace confirms:
- `gfs2_quota_init` → `gfs2_qd_search_bucket` → `lockref_get_not_dead` →
  `rt_spin_lock`
- Triggered during `mount()` of GFS2 on `PREEMPT_RT`
- Secondary `gfs2_assert_warn` in `gfs2_qd_dispose` after duplicate
  detection (from improper `qd_put` in broken path)
- Reproducer: crafted GFS2 image with duplicate quota_change identifiers

### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** v2 had a second patch ("move quota_init qc iterator
increment") — not needed; v3 is self-contained.

### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** lore.kernel.org stable search blocked by bot protection.
Could not verify stable-list discussion. Not a factor in the decision.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `gfs2_qd_search_bucket_noref()` (new),
`gfs2_qd_search_bucket()` (refactored), `gfs2_quota_init()` (call site
change).

### Step 5.2: TRACE CALLERS
**Record:**
- `gfs2_quota_init()` ← `gfs2_make_fs_rw()` ← `gfs2_fill_super()` ←
  mount syscall
- `gfs2_qd_search_bucket()` also called from `qd_get()` (lines 286, 298)
  — but `qd_get()`'s locked call at line 298 is a separate path; this
  fix targets only the quota-init duplicate-scan path as reported

### Step 5.3: TRACE CALLEES
**Record:** `gfs2_qd_search_bucket_noref()` → RCU hlist traversal only
(no locks). `gfs2_qd_search_bucket()` → noref helper +
`lockref_get_not_dead()` + `list_lru_del_obj()`.

### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** `mount()` → `gfs2_fill_super()` → `gfs2_make_fs_rw()` →
`gfs2_quota_init()` — reachable from userspace via mount syscall.
Requires `CONFIG_GFS2_FS` + `PREEMPT_RT` + duplicate quota_change
entries (corruption or crafted image).

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** `qd_get()` at line 298 also calls `gfs2_qd_search_bucket()`
under `spin_lock_bucket()`. Same theoretical RT issue, but not reported
by syzbot and not addressed by this patch. Out of scope for this
backport decision.

---

## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE

### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **YES.** Current `fs/gfs2/quota.c` at lines 1461 and 257–275
matches the pre-fix code exactly. Fix commit `b99a1f0f18ee` is **not**
an ancestor of HEAD.

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** **Clean apply expected.** `git apply --check` of the quota.c
diff from `b99a1f0f18ee` succeeded with no errors on 6.18.43.

### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** No prior fix for this syzbot bug found. Related recent fix
`1d47922b98046` (qd_put UAF) is separate.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **Subsystem:** fs/gfs2 (GFS2 cluster filesystem).
**Criticality:** IMPORTANT — affects GFS2/PREEMPT_RT users; mount path
is critical.

### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** Active in 6.18.y (recent quota fixes in this tree). GFS2 is
a production cluster filesystem used in RHEL and similar distributions.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** Users with `CONFIG_GFS2_FS` + `CONFIG_PREEMPT_RT` mounting
GFS2 filesystems where `gfs2_quota_init()` encounters duplicate
quota_change entries. Cluster/enterprise RT deployments are the primary
real-world audience.

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:** GFS2 mount on PREEMPT_RT kernel when quota_change file
contains duplicate identifiers. Syzbot crafts this condition; real-world
trigger is quota file corruption during mount/recovery. Unprivileged
users can trigger via `mount()` if permitted to mount crafted images.

### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** `BUG: sleeping function called from invalid context` —
kernel WARN/BUG on RT. Mount may fail or leave quota subsystem in
inconsistent state (secondary assertion in `gfs2_qd_dispose`).
**Severity: HIGH** (invalid context bug, mount failure, potential
follow-on corruption).

### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH for GFS2+RT users — prevents mount-time kernel bug
  on corruption recovery path; syzbot-verified
- **Risk:** LOW — ~33 lines, single file, applies cleanly, maintainer-
  reviewed, no API changes
- **Ratio:** Strong benefit, minimal risk → backport warranted

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backporting:**
- Syzbot-reported, reproducible, Tested-by syzbot
- Real PREEMPT_RT invalid-context bug (sleeping under bitlock)
- Mount-path reachable from userspace
- Buggy code confirmed present in 6.18.43; fix not yet applied
- Small, surgical, applies cleanly
- GFS2 maintainer (Gruenbacher) signed off
- Fixes kernel BUG/WARN and prevents mount failures on RT

**AGAINST backporting:**
- Requires PREEMPT_RT + GFS2 (narrower than universal)
- Duplicate quota entries needed to hit the exact path
  (corruption/crafted FS)
- `qd_get()` has a similar pattern not fixed here (pre-existing,
  separate issue)

**UNRESOLVED:**
- Exact commit that introduced duplicate-scan logic (shallow stable
  history)
- Stable mailing list discussion (lore blocked)

Neither unresolved item affects the YES decision.

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — syzbot Tested-by; maintainer
   SOB; clear mechanism
2. Fixes a real bug affecting users? **PASS** — syzbot, 13 crashes,
   mount path
3. Important issue? **PASS** — invalid context BUG on PREEMPT_RT, mount
   failure (HIGH)
4. Small and contained? **PASS** — 1 file, ~33 lines
5. No new features or APIs? **PASS** — internal refactor + call-site fix
   only
6. Can apply to local tree? **PASS** — `git apply --check` succeeded

### Step 9.3: EXCEPTION CATEGORIES
**Record:** None applicable (not device ID, quirk, DT, build, or docs).
Standard bug fix.

### Step 9.4: DECISION RATIONALE

For **Linux 6.18.43**, this commit should be backported. The buggy code
is present, the fix is absent, and the failure is a verified PREEMPT_RT
invalid-context bug in the GFS2 mount path. The patch is small, self-
contained, applies cleanly, and meets all stable-kernel criteria.

---

## Verification

- [Phase 1] Parsed subject, tags: syzbot Reported-by, Closes, Tested-by,
  author + maintainer SOB
- [Phase 1] Commit body describes PREEMPT_RT sleep-under-bitlock bug in
  `gfs2_quota_init()`
- [Phase 2] Diff: 1 file, 3 hunks, new `gfs2_qd_search_bucket_noref()`,
  refactored search, quota_init call change, removed `qd_put(old_qd)`
- [Phase 2] Read `lib/lockref.c:143-161`: `lockref_get_not_dead()` uses
  `spin_lock(&lockref->lock)` on slow path
- [Phase 3] `git describe HEAD` → v6.18.43; `make kernelversion` →
  6.18.43
- [Phase 3] `git blame` lines 1461, 257-275 → present since v6.18-rc8
  merge in this tree
- [Phase 3] `git merge-base --is-ancestor b99a1f0f18ee HEAD` → exit 1
  (fix NOT in tree)
- [Phase 3] `git apply --check` of fix diff → success (clean apply)
- [Phase 4] `b4 dig -c b99a1f0f18ee` → v3 lore thread found
- [Phase 4] `b4 dig -a` → v1/v2/v3 series; v3 is final standalone
  version
- [Phase 4] `b4 dig -w` → gfs2, linux-rt-devel, RT maintainers CC'd
- [Phase 4] `/tmp/gfs2_quota_fix.mbox`: Gruenbacher reviewed v2
  ("looking good")
- [Phase 4] Syzkaller bug page: 13 crashes, prio:high, fix commit
  b99a1f0f18ee, PREEMPT_RT stack trace
- [Phase 5] `grep gfs2_quota_init`: called from `fs/gfs2/super.c:149`
  during `gfs2_make_fs_rw()`
- [Phase 5] `grep gfs2_qd_search_bucket`: 4 call sites; fix changes only
  quota_init duplicate path
- [Phase 6] Grep confirmed buggy code at lines 1461, 269-271 in current
  tree
- [Phase 6] `git diff HEAD b99a1f0f18ee -- fs/gfs2/quota.c`: only quota-
  fix portion needed for backport (other mainline diffs are unrelated)
- [Phase 7] PREEMPT_RT exists in tree (`kernel/Kconfig.preempt:
  PREEMPT_RT`)
- [Phase 8] Failure mode: BUG sleeping-in-invalid-context, severity HIGH
- UNVERIFIED: Exact introduction commit of duplicate-scan logic (shallow
  history)
- UNVERIFIED: Stable mailing list discussion (lore blocked by bot
  protection)

**YES**The background check finished: `git merge-base --is-ancestor
b99a1f0f18ee HEAD` returned exit code 1, so commit `b99a1f0f18ee`
("gfs2: fix quota init duplicate scan") is **not** in this 6.18.43 tree.
The buggy code is still present at `fs/gfs2/quota.c` line 1461.

The backport analysis stands: **YES** — this fix should be backported to
this tree.

 fs/gfs2/quota.c | 33 +++++++++++++++++++++++----------
 1 file changed, 23 insertions(+), 10 deletions(-)

diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index 898fc3937b449..7a36a3fe5e6e6 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -254,9 +254,13 @@ static struct gfs2_quota_data *qd_alloc(unsigned hash, struct gfs2_sbd *sdp, str
 	return NULL;
 }
 
-static struct gfs2_quota_data *gfs2_qd_search_bucket(unsigned int hash,
-						     const struct gfs2_sbd *sdp,
-						     struct kqid qid)
+/*
+ * Lookup variant for callers which already hold qd_lock + bucket lock.
+ */
+static struct gfs2_quota_data *
+gfs2_qd_search_bucket_noref(unsigned int hash,
+			    const struct gfs2_sbd *sdp,
+			    struct kqid qid)
 {
 	struct gfs2_quota_data *qd;
 	struct hlist_bl_node *h;
@@ -264,12 +268,22 @@ static struct gfs2_quota_data *gfs2_qd_search_bucket(unsigned int hash,
 	hlist_bl_for_each_entry_rcu(qd, h, &qd_hash_table[hash], qd_hlist) {
 		if (!qid_eq(qd->qd_id, qid))
 			continue;
-		if (qd->qd_sbd != sdp)
-			continue;
-		if (lockref_get_not_dead(&qd->qd_lockref)) {
-			list_lru_del_obj(&gfs2_qd_lru, &qd->qd_lru);
+		if (qd->qd_sbd == sdp)
 			return qd;
-		}
+	}
+
+	return NULL;
+}
+
+static struct gfs2_quota_data *
+gfs2_qd_search_bucket(unsigned int hash, const struct gfs2_sbd *sdp, struct kqid qid)
+{
+	struct gfs2_quota_data *qd;
+
+	qd = gfs2_qd_search_bucket_noref(hash, sdp, qid);
+	if (qd && lockref_get_not_dead(&qd->qd_lockref)) {
+		list_lru_del_obj(&gfs2_qd_lru, &qd->qd_lru);
+		return qd;
 	}
 
 	return NULL;
@@ -1458,7 +1472,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp)
 
 			spin_lock(&qd_lock);
 			spin_lock_bucket(hash);
-			old_qd = gfs2_qd_search_bucket(hash, sdp, qc_id);
+			old_qd = gfs2_qd_search_bucket_noref(hash, sdp, qc_id);
 			if (old_qd) {
 				fs_err(sdp, "Corruption found in quota_change%u"
 					    "file: duplicate identifier in "
@@ -1467,7 +1481,6 @@ int gfs2_quota_init(struct gfs2_sbd *sdp)
 
 				spin_unlock_bucket(hash);
 				spin_unlock(&qd_lock);
-				qd_put(old_qd);
 
 				gfs2_glock_put(qd->qd_gl);
 				kmem_cache_free(gfs2_quotad_cachep, qd);
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] dlm: add usercopy whitelist to dlm_cb cache
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.6] gfs2: page poisoning fix Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] gfs2: fix quota init duplicate scan Sasha Levin
@ 2026-08-31 13:26 ` Sasha Levin
  2026-08-31 13:31 ` [PATCH AUTOSEL 6.18-6.12] gfs2: move quota_init qc iterator increment Sasha Levin
  3 siblings, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-08-31 13:26 UTC (permalink / raw)
  To: patches, stable
  Cc: Ziyi Guo, Alexander Aring, David Teigland, Sasha Levin, gfs2,
	linux-kernel

From: Ziyi Guo <n7l8m4@u.northwestern.edu>

[ Upstream commit b5314f2c6654a3616fd72777deb1ca766cc50618 ]

The dlm_cb slab cache is created with kmem_cache_create(), which
provides no usercopy whitelist. When a callback carries LVB data,
dlm_user_add_ast() copies the LVB into the inline lvbptr[] array within
the slab-allocated struct dlm_callback and redirects ua->lksb.sb_lvbptr
to point to it. copy_result_to_user() then calls copy_to_user() with
this pointer. With CONFIG_HARDENED_USERCOPY enabled, this triggers
usercopy_abort().

Switch to kmem_cache_create_usercopy() with a whitelist covering the
lvbptr field.

Signed-off-by: Ziyi Guo <n7l8m4@u.northwestern.edu>
Acked-by: Alexander Aring <aahringo@redhat.com>
Signed-off-by: Alexander Aring <aahringo@redhat.com>
Signed-off-by: David Teigland <teigland@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `dlm: add usercopy whitelist to dlm_cb
cache`

**Local tree:** `v6.18.43-1-gc7f0dac02d232` (kernel version **6.18.43**)
**Upstream commit:** `b5314f2c6654a`
**Stable-prepared commit (not in HEAD):** `82278e846311c`

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Parse the subject line
**Record:** `[dlm]` `[add]` — Add a usercopy whitelist to the `dlm_cb`
slab cache so `copy_to_user()` from inline LVB data in slab-allocated
`struct dlm_callback` objects is permitted under
`CONFIG_HARDENED_USERCOPY`.

### Step 1.2: Parse all commit message tags
**Record:**
- **Fixes:** — not present (expected for manual review)
- **Reported-by:** — none
- **Tested-by:** — none
- **Reviewed-by:** — none
- **Acked-by:** Alexander Aring `<aahringo@redhat.com>` (DLM maintainer)
- **Link:** — none
- **Cc: stable:** — none
- **Signed-off-by:** Ziyi Guo (author), Alexander Aring, David Teigland;
  ignore pipeline-added Sasha Levin SOB

Notable: maintainer Acked-by, no syzbot/fuzzer report.

### Step 1.3: Analyze commit body
**Record:**
- **Bug:** `dlm_cb` cache created with `kmem_cache_create()` has no
  usercopy whitelist. When an AST callback carries LVB data,
  `dlm_user_add_ast()` copies LVB into inline `lvbptr[]` inside a slab
  `struct dlm_callback` and repoints `ua->lksb.sb_lvbptr` to it.
  `copy_result_to_user()` then `copy_to_user()`s from that pointer.
- **Symptom:** With `CONFIG_HARDENED_USERCOPY`, this triggers
  `usercopy_abort()`.
- **Root cause:** Slab object pointer used for userspace copy without
  declaring a usercopy-whitelisted region at cache creation time.
- **Version info:** none in message.

### Step 1.4: Detect hidden bug fixes
**Record:** Not disguised — this is an explicit bug fix. The failure
mode (`usercopy_abort()` → `BUG()`) is a kernel panic, not a cosmetic
cleanup.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory the changes
**Record:**
- **Files:** `fs/dlm/memory.c` (+3 / -1)
- **Function:** `dlm_memory_init()`
- **Scope:** Single-file, surgical fix (4 net lines)

### Step 2.2: Code flow change per hunk
**Record:**
- **Before:** `cb_cache = kmem_cache_create("dlm_cb", ...)` — no
  usercopy region declared.
- **After:** `cb_cache = kmem_cache_create_usercopy("dlm_cb", ...,
  offsetof(struct dlm_callback, lvbptr), sizeof_field(struct
  dlm_callback, lvbptr), ...)` — whitelists only the `lvbptr[]` field
  for userspace copies.
- **Path affected:** Init-time cache creation; runtime path is userspace
  DLM AST delivery with LVB copy.

### Step 2.3: Bug mechanism
**Record:** **Category:** Memory safety / hardened usercopy enforcement.
**Mechanism:** `copy_to_user()` from `cb->lvbptr` (inside SLUB object)
fails `__check_object_size()` in `mm/slub.c` because the cache has no
`useroffset`/`usersize`, leading to `usercopy_abort("SLUB object",
"dlm_cb", ...)`.

Verified call chain:
1. `dlm_add_cb()` → `dlm_user_add_ast()` (user locks)
2. `dlm_user_add_ast()` sets `cb->lkb_lksb->sb_lvbptr = cb->lvbptr` when
   `copy_lvb` is true
3. `device_read()` → `copy_result_to_user()` → `copy_to_user(buf+len,
   ua->lksb.sb_lvbptr, DLM_USER_LVB_LEN)`

### Step 2.4: Fix quality assessment
**Record:** Fix is obviously correct and minimal. Whitelisting only
`lvbptr[]` is the established kernel pattern (same author fixed orangefs
identically). Low regression risk — only expands permitted copy region
for the exact field intentionally copied to userspace. No lock-order or
API changes.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame the changed lines
**Record:** Current `cb_cache` creation at lines 51–53 blame to
`5d324e5159d9e` (v6.18-rc8 import). Inline `lvbptr[DLM_USER_LVB_LEN]` in
`struct dlm_callback` and the `copy_lvb` redirect in
`dlm_user_add_ast()` are present in this tree at the same baseline.
Exact introduction commit of inline `lvbptr` not recoverable from this
tree's shallow `fs/dlm/` history.

### Step 3.2: Follow Fixes: tag
**Record:** N/A — no `Fixes:` tag.

### Step 3.3: Related file history
**Record:** Fix commit `82278e846311c` exists in repo but is **not** an
ancestor of HEAD. Upstream mainline commit `b5314f2c6654a`. Nearby
upstream DLM commit `a1ed04430f805` (SRCU list change) is unrelated.
This usercopy fix is standalone within its 2/4 series position.

### Step 3.4: Author's other commits
**Record:** Ziyi Guo authored the same class of fix for orangefs
(`f855f4ab123b2`). Alexander Aring (Acked-by) is DLM maintainer who
resent the patch in v7.1-rc1 series.

### Step 3.5: Dependencies
**Record:** No dependencies. Patch is 2/4 in a series but only touches
`memory.c` cache creation; does not require patch 1/4 (SRCU hlist
change). `git apply --check` on the diff against current tree:
**APPLY_OK**.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original patch discussion
**Record:** `b4 dig -c 82278e846311c` →
https://patch.msgid.link/20260427155935.2415989-3-aahringo@redhat.com
**Series revisions:** v1 from Ziyi Guo (2026-02-12); v7.1-rc1 resend
(2026-04-27, patch 2/4).
**Reviewer feedback:** No replies in saved mbox with stable nominations,
NAKs, or Tested-by.

### Step 4.2: Reviewers from b4 dig -w
**Record:** CC'd: Alexander Aring, teigland@redhat.com,
gfs2@lists.linux.dev. Appropriate DLM/GFS2 audience; maintainer Acked-by
present.

### Step 4.3: Bug report search
**Record:** No Reported-by, syzbot, or bugzilla links. Bug is logically
reproducible: any DLM userspace client reading AST results with LVB on a
`CONFIG_HARDENED_USERCOPY=y` kernel.

### Step 4.4: Related patches / series
**Record:** Part of 4-patch DLM series; this patch is self-contained.
Same bug class as orangefs usercopy whitelist fix by same author.

### Step 4.5: Stable mailing list
**Record:** No stable-list discussion found in saved mbox thread.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key functions
**Record:** `dlm_memory_init()` (modified); affected runtime:
`dlm_allocate_cb()`, `dlm_user_add_ast()`, `copy_result_to_user()`,
`device_read()`.

### Step 5.2: Callers
**Record:**
- `dlm_allocate_cb()` ← `dlm_get_cb()` ← `dlm_user_add_ast()`
- `dlm_user_add_ast()` ← `dlm_add_cb()` when `DLM_DFL_USER_BIT` set
  (userspace locks)
- `device_read()` ← DLM char device `read` ioctl path (`/dev/dlm_*`),
  userspace syscall

### Step 5.3: Callees
**Record:** `kmem_cache_create_usercopy()`, `kmem_cache_alloc()`,
`copy_to_user()`, `memcpy()`.

### Step 5.4: Call chain / reachability
**Record:** Userspace opens DLM device → lock with `DLM_LKF_VALBLK` →
AST completion with LVB copy needed (`dlm_may_skip_callback()` sets
`copy_lvb=1` for user locks) → userspace `read()` on device →
`copy_to_user()` from slab `lvbptr`. **Reachable from userspace** via
DLM device read on clusters using GFS2/DLM userspace (CONFIG_DLM=m/y).

### Step 5.5: Similar patterns
**Record:** Identical pattern in `fs/orangefs/orangefs-cache.c`,
`net/core/skbuff.c`, `kernel/fork.c`, etc. Well-established hardened-
usercopy fix approach.

---

## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE (6.18.43)

### Step 6.1: Does buggy code exist?
**Record:** **YES.** `fs/dlm/memory.c` lines 51–53 still use
`kmem_cache_create()`. `struct dlm_callback` has `unsigned char
lvbptr[DLM_USER_LVB_LEN]` at `dlm_internal.h:236`. `dlm_user_add_ast()`
redirect at `user.c:223–226`. Fix commit `82278e846311c` is **not** in
HEAD.

### Step 6.2: Backport complications
**Record:** **Clean apply** — verified with `git apply --check`. No
conflicting changes in `fs/dlm/memory.c`.

### Step 6.3: Related fixes already present?
**Record:** None. `git log --grep='usercopy whitelist' -- fs/dlm/`
returns nothing on HEAD.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: Subsystem criticality
**Record:** **fs/dlm** — Distributed Lock Manager. **IMPORTANT** for
cluster filesystem users (GFS2, OCFS2, corosync/pacemaker stacks). Not
universal like mm/net core, but critical for cluster deployments.

### Step 7.2: Subsystem activity
**Record:** DLM actively maintained (Red Hat/David Teigland tree).
Recent upstream usercopy fix in 2026.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who is affected
**Record:** Users of DLM userspace API (`CONFIG_DLM`) on kernels built
with `CONFIG_HARDENED_USERCOPY=y`. Affects cluster nodes running GFS2
and similar workloads using value-block (LVB) locks.

### Step 8.2: Trigger conditions
**Record:**
- `CONFIG_DLM` enabled
- `CONFIG_HARDENED_USERCOPY` enabled (present in multiple arch
  defconfigs; default-on when `HARDENED_USERCOPY_DEFAULT_ON` is set)
- Userspace lock operation with LVB (`DLM_LKF_VALBLK`)
- AST completion where LVB must be copied back (`copy_lvb=1`)
- Userspace `read()` on DLM device to receive AST

**Likelihood:** Real for hardened cluster kernels using LVB locks — not
theoretical.

### Step 8.3: Failure mode severity
**Record:** `usercopy_abort()` in `mm/usercopy.c:86–102` calls `BUG()` —
**kernel panic**. **Severity: CRITICAL.**

### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** HIGH — prevents kernel panic on legitimate DLM userspace
  operation
- **Risk:** VERY LOW — 3-line whitelist addition, field-scoped,
  maintainer-acked, proven pattern
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence compile

**FOR backport:**
- Real bug with kernel panic (`BUG()` via `usercopy_abort`)
- Userspace-reachable via DLM device read
- Small, surgical, maintainer-acked fix
- Applies cleanly to 6.18.43
- Buggy code confirmed present; fix not yet applied
- Same fix class already accepted upstream (orangefs precedent)

**AGAINST backport:**
- Requires `CONFIG_DLM` + `CONFIG_HARDENED_USERCOPY` (not all kernels)
- No explicit user/syzbot report in commit message
- Part of a 4-patch series (but this patch is standalone)

**Unresolved:** Exact kernel version that introduced inline `lvbptr`
(not needed for decision — code is in 6.18.43).

### Step 9.2: Stable rules checklist
1. Obviously correct and tested? **PASS** — mechanism clear; maintainer
   Acked-by; no Tested-by
2. Fixes real bug? **PASS** — hardened usercopy abort on valid DLM
   operation
3. Important issue? **PASS** — kernel panic (CRITICAL)
4. Small and contained? **PASS** — 4 lines, 1 file
5. No new features/APIs? **PASS** — internal cache metadata only
6. Can apply to local tree? **PASS** — clean apply verified

### Step 9.3: Exception categories
**Record:** None directly, but this is a hardened-usercopy compatibility
fix analogous to accepted build/runtime fixes for
`CONFIG_HARDENED_USERCOPY` users.

### Step 9.4: Decision rationale
For **6.18.43**, the buggy code path exists, the failure mode is a
kernel panic on a legitimate userspace DLM operation when hardened
usercopy is enabled, and the fix is minimal, maintainer-reviewed, and
applies cleanly. This meets all stable-kernel criteria.

---

## Verification

- **[Phase 1]** Parsed commit message and tags from user query and `git
  show b5314f2c6654a`
- **[Phase 2]** Read diff; traced `dlm_user_add_ast()` →
  `copy_result_to_user()` in `fs/dlm/user.c`
- **[Phase 2]** Confirmed `usercopy_abort()` calls `BUG()` in
  `mm/usercopy.c:86–102`
- **[Phase 2]** Confirmed SLUB usercopy check in `mm/slub.c:8275–8281`
- **[Phase 3]** `git describe HEAD` → v6.18.43; fix `82278e846311c` NOT
  ancestor of HEAD
- **[Phase 3]** `git apply --check` on patch → APPLY_OK
- **[Phase 3]** `git blame` on `lvbptr` field and copy path — present in
  tree
- **[Phase 4]** `b4 dig -c 82278e846311c` → lore URL found
- **[Phase 4]** `b4 dig -a` → v1 and v7.1-rc1 revisions
- **[Phase 4]** `b4 dig -w` → maintainers CC'd
- **[Phase 4]** Saved mbox `/tmp/dlm_usercopy.mbox` — no stable/Tested-
  by/NAK replies
- **[Phase 5]** Traced callers: `dlm_add_cb` → `dlm_user_add_ast`;
  `device_read` → `copy_result_to_user`
- **[Phase 5]** Verified `copy_lvb` set in `dlm_may_skip_callback()` for
  user LVB casts (`ast.c:101–108`)
- **[Phase 6]** `fs/dlm/memory.c` still uses `kmem_cache_create()`
  without usercopy whitelist
- **[Phase 6]** `struct dlm_callback.lvbptr[DLM_USER_LVB_LEN]` at
  `dlm_internal.h:236`
- **[Phase 7]** `CONFIG_DLM` in `fs/dlm/Kconfig`; `DLM_USER_LVB_LEN` =
  32 in `include/uapi/linux/dlm_device.h`
- **[Phase 8]** `CONFIG_HARDENED_USERCOPY` documented in
  `security/Kconfig.hardening`; enabled in multiple arch defconfigs
- **UNVERIFIED:** Exact commit that introduced inline `lvbptr` (shallow
  `fs/dlm/` history in this tree)

**YES****Verdict: YES** — backport to this tree (6.18.43).

The DLM `dlm_cb` slab cache lacks a usercopy whitelist. When userspace
AST callbacks copy LVB data from the inline `lvbptr[]` field in a slab-
allocated `struct dlm_callback`, `copy_to_user()` under
`CONFIG_HARDENED_USERCOPY` triggers `usercopy_abort()` → `BUG()` (kernel
panic). The fix switches to `kmem_cache_create_usercopy()` whitelisting
only `lvbptr[]` — a 4-line, maintainer-acked change that applies cleanly
and is not yet in HEAD.The check confirmed commit `82278e846311c` is
**not** in the current 6.18.43 tree — the fix still needs to be
backported. That supports the **YES** verdict from the earlier analysis.

 fs/dlm/memory.c | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/fs/dlm/memory.c b/fs/dlm/memory.c
index 5c35cc67aca4c..ee55994ce90d8 100644
--- a/fs/dlm/memory.c
+++ b/fs/dlm/memory.c
@@ -48,8 +48,10 @@ int __init dlm_memory_init(void)
 	if (!rsb_cache)
 		goto rsb;
 
-	cb_cache = kmem_cache_create("dlm_cb", sizeof(struct dlm_callback),
+	cb_cache = kmem_cache_create_usercopy("dlm_cb", sizeof(struct dlm_callback),
 				     __alignof__(struct dlm_callback), 0,
+					 offsetof(struct dlm_callback, lvbptr),
+					 sizeof_field(struct dlm_callback, lvbptr),
 				     NULL);
 	if (!cb_cache)
 		goto cb;
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] gfs2: move quota_init qc iterator increment
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (2 preceding siblings ...)
  2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] dlm: add usercopy whitelist to dlm_cb cache Sasha Levin
@ 2026-08-31 13:31 ` Sasha Levin
  3 siblings, 0 replies; 4+ messages in thread
From: Sasha Levin @ 2026-08-31 13:31 UTC (permalink / raw)
  To: patches, stable
  Cc: Jie Wang, Andreas Gruenbacher, Sasha Levin, gfs2, linux-kernel

From: Jie Wang <jie.wang@intel.com>

[ Upstream commit 942202677f8f2ee448a6a2feb06aeeaf520342e3 ]

Move qc++ from the loop body into the for-loop increment
expression in gfs2_quota_init().

This keeps iterator progression explicit and avoids mixing pointer
advance with duplicate-slot handling in the loop body.

Signed-off-by: Jie Wang <jie.wang@intel.com>
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

The background `git log -S` search between v6.10 and v6.11 finished
(~75s) but only surfaced merge/release commits, not the actual GFS2
change — typical for this shallow stable checkout.

What we could pin down: duplicate-slot zeroing first appears in
**v6.11** (absent in v6.10, present in v6.11), so the `qc++` /
`memset(qc)` ordering bug has been in 6.18.y since then. That’s enough
context for the **YES** backport call; the exact introducing SHA isn’t
available from this tree’s history.

 fs/gfs2/quota.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/fs/gfs2/quota.c b/fs/gfs2/quota.c
index 7a36a3fe5e6e6..4620f586411a6 100644
--- a/fs/gfs2/quota.c
+++ b/fs/gfs2/quota.c
@@ -1447,7 +1447,7 @@ int gfs2_quota_init(struct gfs2_sbd *sdp)
 
 		qc = (struct gfs2_quota_change *)(bh->b_data + sizeof(struct gfs2_meta_header));
 		for (y = 0; y < sdp->sd_qc_per_block && slot < sdp->sd_quota_slots;
-		     y++, slot++) {
+		     y++, slot++, qc++) {
 			struct gfs2_quota_data *old_qd, *qd;
 			s64 qc_change = be64_to_cpu(qc->qc_change);
 			u32 qc_flags = be32_to_cpu(qc->qc_flags);
@@ -1455,7 +1455,6 @@ int gfs2_quota_init(struct gfs2_sbd *sdp)
 						USRQUOTA : GRPQUOTA;
 			struct kqid qc_id = make_kqid(&init_user_ns, qtype,
 						      be32_to_cpu(qc->qc_id));
-			qc++;
 			if (!qc_change)
 				continue;
 
-- 
2.53.0


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

end of thread, other threads:[~2026-08-31 13:52 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260831133314.4125787-1-sashal@kernel.org>
2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.6] gfs2: page poisoning fix Sasha Levin
2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] gfs2: fix quota init duplicate scan Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] dlm: add usercopy whitelist to dlm_cb cache Sasha Levin
2026-08-31 13:31 ` [PATCH AUTOSEL 6.18-6.12] gfs2: move quota_init qc iterator increment Sasha Levin

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox