* [PATCH AUTOSEL 6.18-5.10] hfs: rework hfsplus_readdir() logic
[not found] <20260831133314.4125787-1-sashal@kernel.org>
@ 2026-08-31 13:20 ` Sasha Levin
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-6.12] virtio-fs: avoid double-free on failed queue setup Sasha Levin
` (10 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:20 UTC (permalink / raw)
To: patches, stable
Cc: Viacheslav Dubeyko, John Paul Adrian Glaubitz, Yangtao Li,
linux-fsdevel, Sasha Levin, linux-kernel
From: Viacheslav Dubeyko <slava@dubeyko.com>
[ Upstream commit 7fde7e806657fbe0d33f489521b488eed94f9b39 ]
The xfstests' test-case generic/637 fails with error:
FSTYP -- hfs
PLATFORM -- Linux/x86_64 kvm-xfstests 6.15.0-rc4-xfstests-g00b827f0cffa #1 SMP PREEMPT_DYNAMIC Fri May 25
MKFS_OPTIONS -- /dev/vdc
MOUNT_OPTIONS -- /dev/vdc /vdc
QA output created by 637
entries 7 and 8 have duplicate d_off 8
Found unlinked files in open dir (see xfstests-dev/results//generic/637.full for details)
Likewise HFS+, currently, HFS has very complicated and
fragile logic of rd->file->f_pos correction in hfs_delete_cat().
This patch removes this logic and it stores the current
pos into hfs_readdir_data. Finally, if rd->pos == ctx->pos
then hfs_readdir() tries to find the position in
b-tree's node by means of hfs_cat_key. This position is
used to re-start the folder's content traversal.
sudo ./check generic/637
FSTYP -- hfs
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.1.0-rc1+ #55 SMP PREEMPT_DYNAMIC Tue May 19 15:18:02 PDT 2026
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
generic/637 32s ... 31s
Ran: generic/637
Passed all 1 tests
Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/65
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Link: https://lore.kernel.org/r/20260519222811.1311071-2-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Stable Backport Analysis: `hfs: rework hfsplus_readdir() logic`
**Local tree:** `v6.18.44` (Makefile: VERSION=6, PATCHLEVEL=18,
SUBLEVEL=44)
---
## PHASE 1: COMMIT MESSAGE FORENSICS
### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[hfs] [rework] Rework directory readdir resume logic
(subject incorrectly references hfsplus_readdir; diff is HFS-only)`
### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Fixes:** — absent (expected for manual review)
- **Reported-by:** — absent
- **Tested-by:** — absent
- **Reviewed-by / Acked-by:** — absent
- **Link:**
https://lore.kernel.org/r/20260519222811.1311071-2-slava@dubeyko.com
- **Closes:** https://github.com/hfs-linux-kernel/hfs-linux-
kernel/issues/65
- **cc:** John Paul Adrian Glaubitz, Yangtao Li, linux-
fsdevel@vger.kernel.org
- **Signed-off-by:** Viacheslav Dubeyko (author)
- **Notable:** Message-ID suffix `-2-` indicates patch 2 of a series;
companion patch 1 fixes HFS+ separately. No syzbot, no Cc: stable tag.
### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** xfstests `generic/637` fails on HFS with `entries 7 and 8
have duplicate d_off 8` and `Found unlinked files in open dir`.
- **Symptom:** Incorrect `getdents`/`readdir` results — duplicate
directory offsets and deleted entries visible in an open directory.
- **Root cause (author):** Fragile `rd->file->f_pos--` correction in
`hfs_cat_delete()` when entries are removed while a directory is open
for reading.
- **Fix approach:** Store `ctx->pos` and catalog key in
`hfs_readdir_data`; on resume, if `rd->pos == ctx->pos`, locate
position via `hfs_cat_key` instead of positional `hfs_brec_goto()`.
- **Testing:** Author reports `generic/637` passes after fix.
### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — this is an explicit correctness fix for
directory enumeration, though the subject says "rework" rather than
"fix".
---
## PHASE 2: DIFF ANALYSIS
### Step 2.1: INVENTORY THE CHANGES
**Record:**
| File | Change |
|------|--------|
| `fs/hfs/catalog.c` | -9 lines (remove f_pos correction loop) |
| `fs/hfs/dir.c` | ~28 lines changed (key-based resume, simplify
release) |
| `fs/hfs/hfs.h` | struct `hfs_readdir_data` simplified |
| `fs/hfs/hfs_fs.h` | remove `open_dir_list`, `open_dir_lock` |
| `fs/hfs/inode.c` | -4 lines (remove list/lock init) |
**Functions modified:** `hfs_cat_delete()`, `hfs_readdir()`,
`hfs_dir_release()`, `hfs_new_inode()`, `hfs_read_inode()`
**Scope:** Single-subsystem, 5 files, net -22 lines (12 insertions, 34
deletions). Surgical refactor that fixes a bug.
### Step 2.2: CODE FLOW CHANGE
**Record:**
**Hunk 1 — `hfs_cat_delete()`:** BEFORE: on delete, iterate all open
readdir handles and decrement `f_pos` for entries after the deleted key.
AFTER: no f_pos manipulation.
**Hunk 2 — `hfs_readdir()` resume:** BEFORE: always `hfs_brec_goto(&fd,
ctx->pos - 1)`. AFTER: if saved `rd->pos == ctx->pos`, use stored
`hfs_cat_key` with `hfs_brec_find()` (fallback `hfs_brec_goto(&fd, 1)`
on `-ENOENT`); else positional goto.
**Hunk 3 — `hfs_readdir()` state save:** BEFORE: track open dirs in per-
inode linked list with spinlock; save only key. AFTER: save `rd->pos =
ctx->pos` and key in per-file `private_data`.
**Hunk 4 — `hfs_dir_release()`:** BEFORE: remove from linked list under
spinlock, then kfree. AFTER: simple kfree.
**Hunk 5 — struct cleanup:** Remove `list`, `file` from
`hfs_readdir_data`; add `loff_t pos`. Remove
`open_dir_list`/`open_dir_lock` from `hfs_inode_info`.
### Step 2.3: BUG MECHANISM
**Record:** **Category:** Logic/correctness fix in directory
enumeration.
**Mechanism:** When `readdir` is interrupted mid-directory (e.g., small
userspace buffer), the saved position and the catalog key can diverge
from a naïve positional index after concurrent unlinks. The old
`f_pos--` hack in `hfs_cat_delete()` fails to maintain consistency,
causing:
1. Duplicate `d_off` values returned to userspace
2. Deleted ("unlinked") files appearing in directory listings
The companion HFS+ patch (`fc30ae43b8b5b`) documents the exact failure
sequence with debug output confirming this mechanism.
### Step 2.4: FIX QUALITY
**Record:**
- **Obviously correct:** Yes — key-based resume is the standard
approach; removes complex cross-file f_pos tracking.
- **Minimal:** Yes — net code reduction, no unrelated changes.
- **Regression risk:** Low — simplifies locking (removes spinlock/list
entirely for this path). The `-ENOENT` → `hfs_brec_goto(&fd, 1)`
fallback handles deleted-key edge case.
---
## PHASE 3: GIT HISTORY INVESTIGATION
### Step 3.1: BLAME THE CHANGED LINES
**Record:**
- `f_pos--` logic in `hfs_cat_delete()`: original git import
(`1da177e4c3f4`, 2005)
- `open_dir_lock`/`list_for_each_entry`: Al Viro, `9717a91b01feda`
("hfs: switch to ->iterate_shared()", 2016)
- Bug has been present essentially since HFS support was added; not a
recent regression.
### Step 3.2: FOLLOW THE FIXES: TAG
**Record:** No `Fixes:` tag present — N/A.
### Step 3.3: FILE HISTORY FOR RELATED CHANGES
**Record:**
- `eec11535ca3d3` — prior `hfs: fix hfs_readdir()` (memcpy bug in key
save, reviewed by Dubeyko)
- `9717a91b01feda` — iterate_shared conversion added open_dir_list
mechanism
- `956b1d8051cfa`, `54694417d4384` — same author's HFS+ xfstests fixes
already in this 6.18.y tree
- Fix commit `7fde7e806657f` exists locally on `autosel` branch but is
**not** an ancestor of HEAD (6.18.44)
### Step 3.4: AUTHOR'S OTHER COMMITS
**Record:** Viacheslav Dubeyko is an active HFS/HFS+ maintainer with
multiple xfstests-driven fixes backported to stable (generic/498,
generic/480, generic/101, etc.).
### Step 3.5: DEPENDENT/PREREQUISITE COMMITS
**Record:** Standalone for HFS. Companion `hfsplus: rework
hfsplus_readdir() logic` is a separate commit for HFS+; this HFS patch
does not depend on it. Applies cleanly to current tree (`git apply
--check` passed).
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
### Step 4.1: ORIGINAL PATCH DISCUSSION
**Record:**
- **b4 dig -c 7fde7e806657f:**
https://patch.msgid.link/20260519222811.1311071-2-slava@dubeyko.com
- **Series revisions (b4 dig -a):** v1 only (single revision)
- **Review feedback:** Mbox contains only the patch submission — no
replies, no stable nominations, no NAKs in thread
### Step 4.2: WHO REVIEWED
**Record (b4 dig -w):** CC'd: Viacheslav Dubeyko, glaubitz@physik.fu-
berlin.de, linux-fsdevel@vger.kernel.org, frank.li@vivo.com,
Slava.Dubeyko@ibm.com. No explicit Reviewed-by in commit or thread.
### Step 4.3: BUG REPORT
**Record:**
- **GitHub issue #65:** 100% failure rate on `generic/637` for HFS (5/5
runs), kernel 6.15.0-rc4-xfstests. Closed after fix reference.
- **Failure:** duplicate d_off, unlinked files in open directory —
reproducible, concrete.
### Step 4.4: RELATED PATCHES AND SERIES
**Record:** 2-patch series sent separately:
1. `hfsplus: rework hfsplus_readdir() logic` (upstream `4b04964328446`)
2. `hfs: rework hfsplus_readdir() logic` (upstream `7fde7e806657f`) —
**this commit**
Each is self-contained for its respective filesystem.
### Step 4.5: STABLE MAILING LIST HISTORY
**Record:** Lore fetch blocked by bot protection; no stable-list
discussion found via b4 or GitHub.
---
## PHASE 5: CODE SEMANTIC ANALYSIS
### Step 5.1: KEY FUNCTIONS
**Record:** `hfs_readdir()`, `hfs_cat_delete()`, `hfs_dir_release()`
### Step 5.2: TRACE CALLERS
**Record:**
- `hfs_readdir()` — VFS `iterate_shared` callback; reachable from
`getdents`/`readdir` syscalls on HFS mounts
- `hfs_cat_delete()` — called from `hfs_remove()` (unlink/rmdir),
reachable from `unlink`/`rmdir` syscalls
- **Trigger path:** open directory → partial readdir → concurrent unlink
→ resume readdir
### Step 5.3: TRACE CALLEES
**Record:** `hfs_brec_goto()`, `hfs_brec_find()`, `hfs_brec_remove()`,
`dir_emit()`, `kmalloc()`, `kfree()`, `hfs_find_init()/exit()`
### Step 5.4: CALL CHAIN / REACHABILITY
**Record:** Fully reachable from userspace via standard VFS syscalls on
`CONFIG_HFS_FS` mounts. Not init-only or obscure kernel-internal path.
### Step 5.5: SIMILAR PATTERNS
**Record:** Identical `f_pos--` pattern exists in `fs/hfsplus/catalog.c`
(lines 394-402) — fixed by companion patch. Same structural bug in both
filesystems.
---
## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE
### Step 6.1: DOES THE BUGGY CODE EXIST?
**Record:** **YES.** Current 6.18.44 tree has:
- `f_pos--` loop in `hfs_cat_delete()` at `fs/hfs/catalog.c:369-375`
- `open_dir_list`/`open_dir_lock` in `hfs_inode_info`
- Positional-only resume in `hfs_readdir()` at `fs/hfs/dir.c:100`
Bug present since original HFS code (~2005).
### Step 6.2: BACKPORT COMPLICATIONS
**Record:** **Clean apply expected.** `git apply --check` against
`7470b727ac4b2` diff succeeded with no conflicts. Uses
`kmalloc(sizeof(...))` matching current tree (not `kmalloc_obj` from the
candidate diff text).
### Step 6.3: RELATED FIXES ALREADY PRESENT?
**Record:** No — fix not in 6.18.44. Related HFS+ xfstests fixes from
same author (generic/498, etc.) are present, establishing precedent for
this class of fix.
---
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
### Step 7.1: SUBSYSTEM AND CRITICALITY
**Record:** **Filesystem (HFS)** — IMPORTANT for HFS users; PERIPHERAL
in overall kernel scope (legacy Mac filesystem, niche but real user
base).
### Step 7.2: SUBSYSTEM ACTIVITY
**Record:** Active maintenance by Dubeyko — multiple recent xfstests-
driven fixes in 6.18.y.
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
### Step 8.1: WHO IS AFFECTED
**Record:** Users with HFS filesystems mounted (`CONFIG_HFS_FS`).
Includes legacy media, cross-platform data exchange, testing
environments.
### Step 8.2: TRIGGER CONDITIONS
**Record:**
- Directory open for reading
- Partial `readdir`/`getdents` (buffer fills before directory exhausted)
- Concurrent file deletion in same directory
- **Likelihood:** Moderate for backup tools, file managers, `find`-like
utilities
- **Unprivileged trigger:** Yes — any user with directory access
### Step 8.3: FAILURE MODE SEVERITY
**Record:**
- **Failure:** Wrong directory entries (duplicate offsets, deleted files
visible)
- **Severity:** **HIGH** for filesystem semantics — not a kernel oops,
but violates POSIX directory consistency expectations; can cause
application-level data handling errors
- Comparable to other xfstests generic/ fixes backported from this
subsystem
### Step 8.4: RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH for HFS users — fixes reproducible xfstests failure
and real directory listing corruption
- **Risk:** LOW — net code simplification, removes locking, tested with
xfstests
- **Ratio:** Strongly favorable
**Minor note:** Full patch with context is ~137 lines; stable rules
mention 100-line guideline, but actual changed lines are only 46 with
net reduction.
---
## PHASE 9: FINAL SYNTHESIS
### Step 9.1: EVIDENCE COMPILED
**FOR backport:**
- Reproducible xfstests `generic/637` failure (100% repro rate in GitHub
issue)
- Real user-visible directory listing bug (duplicate d_off, ghost
entries)
- Bug present in 6.18.44 tree since ~2005
- Small, obviously correct fix (key-based resume)
- Applies cleanly to local tree
- Tested by author with xfstests
- Same author's similar HFS+ xfstests fixes already in 6.18.y
- Reachable from userspace syscalls
- Net code simplification reduces regression surface
**AGAINST backport:**
- HFS is niche (limited user base)
- No formal Reviewed-by or stable nomination in mailing list thread
- Not a crash/oops — semantics bug rather than kernel panic
- Patch context slightly exceeds 100-line stable guideline (borderline)
- HFS+ companion patch needed separately for full generic/637 coverage
on HFS+
**UNRESOLVED:**
- No mailing list review replies found (thread had no responses in saved
mbox)
- Could not fetch lore directly (bot protection)
### Step 9.2: STABLE RULES CHECKLIST
| Criterion | Result |
|-----------|--------|
| 1. Obviously correct and tested? | **PASS** — xfstests generic/637,
clear mechanism |
| 2. Fixes real bug affecting users? | **PASS** — reproducible directory
listing corruption |
| 3. Important issue? | **PASS** — filesystem correctness, HIGH severity
for HFS users |
| 4. Small and contained? | **PASS** — 46 lines changed, 5 files, net
-22 lines |
| 5. No new features/APIs? | **PASS** — internal restructuring only |
| 6. Can apply to local tree? | **PASS** — clean apply verified |
### Step 9.3: EXCEPTION CATEGORIES
**Record:** None — standard bug fix, not device ID/quirk/build fix.
### Step 9.4: DECISION RATIONALE
For **6.18.44**, this commit fixes a long-standing, reproducible
directory enumeration bug in the HFS driver. The failure mode —
duplicate `d_off` values and deleted files appearing in open directory
listings — is a real filesystem correctness issue validated by xfstests
and a tracked GitHub issue. The fix is surgical, simplifies the code by
removing fragile `f_pos` manipulation, applies cleanly, and follows the
established pattern of xfstests-driven HFS/HFS+ fixes from this
maintainer already present in the 6.18.y tree.
---
## Verification
- **[Phase 1]** Parsed commit message: subject, Link, Closes, cc tags;
no Fixes:/Reported-by:/Reviewed-by:
- **[Phase 1]** GitHub issue #65 fetched: 100% generic/637 failure on
HFS, duplicate d_off 8, unlinked files
- **[Phase 2]** Read current `fs/hfs/dir.c`, `fs/hfs/catalog.c`,
`fs/hfs/hfs.h`, `fs/hfs/hfs_fs.h` — confirmed buggy code
- **[Phase 2]** Diff analysis: 5 files, 12+/34-, functions identified
- **[Phase 3]** `git describe HEAD`: v6.18.44
- **[Phase 3]** `git blame fs/hfs/catalog.c:369-375`: f_pos-- from 2005,
lock from Al Viro 2016
- **[Phase 3]** `git log --oneline -30 -- fs/hfs/dir.c`: recent history
reviewed
- **[Phase 3]** `git merge-base --is-ancestor 7470b727ac4b2 HEAD`: exit
1 — fix NOT in tree
- **[Phase 3]** `git show 956b1d8051cfa`: prior xfstests fix from same
author in tree
- **[Phase 4]** `b4 dig -c 7fde7e806657f`: found lore thread, v1 only
- **[Phase 4]** `b4 dig -c 7fde7e806657f -w`: recipients listed, no
Reviewed-by
- **[Phase 4]** `b4 dig -c 7fde7e806657f -m /tmp/hfs637.mbox`: saved, no
review replies
- **[Phase 4]** Lore.kernel.org WebFetch: blocked by bot protection
- **[Phase 5]** `grep hfs_cat_delete/hfs_readdir`: callers traced to VFS
syscall paths
- **[Phase 5]** Read `fs/hfsplus/catalog.c:394-402`: confirmed identical
bug pattern in HFS+
- **[Phase 6]** `grep open_dir_list` in fs/hfs/: buggy code confirmed
present
- **[Phase 6]** `git apply --check` against fix diff: **clean apply**
- **[Phase 6]** `git show 7470b727ac4b2:fs/hfs/dir.c | rg kmalloc`: uses
`kmalloc(sizeof(...))` matching tree
- **[Phase 8]** Failure mode: directory listing corruption, HIGH
severity for HFS users
**YES**The background search finished successfully. It found the fix on
the `autosel` branch (`7470b727ac4b2` for HFS, `fc30ae43b8b5b` for
HFS+), but neither commit is in the current **6.18.44** tree — the buggy
`f_pos--` logic is still present there.
The analysis stands: **YES** for backporting this HFS commit to 6.18.y.
It fixes a real `generic/637` directory listing bug, applies cleanly,
and the HFS+ companion patch would need a separate backport decision.
fs/hfs/catalog.c | 9 ---------
fs/hfs/dir.c | 28 +++++++++++-----------------
fs/hfs/hfs.h | 3 +--
fs/hfs/hfs_fs.h | 2 --
fs/hfs/inode.c | 4 ----
5 files changed, 12 insertions(+), 34 deletions(-)
diff --git a/fs/hfs/catalog.c b/fs/hfs/catalog.c
index b80ba40e38776..ccdbbffaaf7c1 100644
--- a/fs/hfs/catalog.c
+++ b/fs/hfs/catalog.c
@@ -340,7 +340,6 @@ int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str)
{
struct super_block *sb;
struct hfs_find_data fd;
- struct hfs_readdir_data *rd;
int res, type;
hfs_dbg("name %s, cnid %u\n", str ? str->name : NULL, cnid);
@@ -366,14 +365,6 @@ int hfs_cat_delete(u32 cnid, struct inode *dir, const struct qstr *str)
}
}
- /* we only need to take spinlock for exclusion with ->release() */
- spin_lock(&HFS_I(dir)->open_dir_lock);
- list_for_each_entry(rd, &HFS_I(dir)->open_dir_list, list) {
- if (fd.tree->keycmp(fd.search_key, (void *)&rd->key) < 0)
- rd->file->f_pos--;
- }
- spin_unlock(&HFS_I(dir)->open_dir_lock);
-
res = hfs_brec_remove(&fd);
if (res)
goto out;
diff --git a/fs/hfs/dir.c b/fs/hfs/dir.c
index 86a6b317b474a..130c2f3a417f0 100644
--- a/fs/hfs/dir.c
+++ b/fs/hfs/dir.c
@@ -97,7 +97,15 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx)
}
if (ctx->pos >= inode->i_size)
goto out;
- err = hfs_brec_goto(&fd, ctx->pos - 1);
+ rd = file->private_data;
+ if (rd && rd->pos == ctx->pos) {
+ memcpy(fd.search_key, &rd->key, sizeof(struct hfs_cat_key));
+ err = hfs_brec_find(&fd);
+ if (err == -ENOENT)
+ err = hfs_brec_goto(&fd, 1);
+ } else {
+ err = hfs_brec_goto(&fd, ctx->pos - 1);
+ }
if (err)
goto out;
@@ -146,7 +154,6 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx)
if (err)
goto out;
}
- rd = file->private_data;
if (!rd) {
rd = kmalloc(sizeof(struct hfs_readdir_data), GFP_KERNEL);
if (!rd) {
@@ -154,15 +161,8 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx)
goto out;
}
file->private_data = rd;
- rd->file = file;
- spin_lock(&HFS_I(inode)->open_dir_lock);
- list_add(&rd->list, &HFS_I(inode)->open_dir_list);
- spin_unlock(&HFS_I(inode)->open_dir_lock);
}
- /*
- * Can be done after the list insertion; exclusion with
- * hfs_delete_cat() is provided by directory lock.
- */
+ rd->pos = ctx->pos;
memcpy(&rd->key, &fd.key->cat, sizeof(struct hfs_cat_key));
out:
hfs_find_exit(&fd);
@@ -171,13 +171,7 @@ static int hfs_readdir(struct file *file, struct dir_context *ctx)
static int hfs_dir_release(struct inode *inode, struct file *file)
{
- struct hfs_readdir_data *rd = file->private_data;
- if (rd) {
- spin_lock(&HFS_I(inode)->open_dir_lock);
- list_del(&rd->list);
- spin_unlock(&HFS_I(inode)->open_dir_lock);
- kfree(rd);
- }
+ kfree(file->private_data);
return 0;
}
diff --git a/fs/hfs/hfs.h b/fs/hfs/hfs.h
index 6f194d0768b6f..f46d12ce04a3f 100644
--- a/fs/hfs/hfs.h
+++ b/fs/hfs/hfs.h
@@ -281,8 +281,7 @@ struct hfs_mdb {
/*======== Data structures kept in memory ========*/
struct hfs_readdir_data {
- struct list_head list;
- struct file *file;
+ loff_t pos;
struct hfs_cat_key key;
};
diff --git a/fs/hfs/hfs_fs.h b/fs/hfs/hfs_fs.h
index 38854df4c1b42..d9665226f5474 100644
--- a/fs/hfs/hfs_fs.h
+++ b/fs/hfs/hfs_fs.h
@@ -37,8 +37,6 @@ struct hfs_inode_info {
struct hfs_cat_key cat_key;
- struct list_head open_dir_list;
- spinlock_t open_dir_lock;
struct inode *rsrc_inode;
struct mutex extents_lock;
diff --git a/fs/hfs/inode.c b/fs/hfs/inode.c
index cd43eff72d13d..cd3dd5bd456e0 100644
--- a/fs/hfs/inode.c
+++ b/fs/hfs/inode.c
@@ -192,8 +192,6 @@ struct inode *hfs_new_inode(struct inode *dir, const struct qstr *name, umode_t
return NULL;
mutex_init(&HFS_I(inode)->extents_lock);
- INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list);
- spin_lock_init(&HFS_I(inode)->open_dir_lock);
hfs_cat_build_key(sb, (btree_key *)&HFS_I(inode)->cat_key, dir->i_ino, name);
next_id = atomic64_inc_return(&HFS_SB(sb)->next_id);
BUG_ON(next_id > U32_MAX);
@@ -334,8 +332,6 @@ static int hfs_read_inode(struct inode *inode, void *data)
HFS_I(inode)->flags = 0;
HFS_I(inode)->rsrc_inode = NULL;
mutex_init(&HFS_I(inode)->extents_lock);
- INIT_LIST_HEAD(&HFS_I(inode)->open_dir_list);
- spin_lock_init(&HFS_I(inode)->open_dir_lock);
/* Initialize the inode */
inode->i_uid = hsb->s_uid;
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-6.12] virtio-fs: avoid double-free on failed queue setup
[not found] <20260831133314.4125787-1-sashal@kernel.org>
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.10] hfs: rework hfsplus_readdir() logic Sasha Levin
@ 2026-08-31 13:20 ` Sasha Levin
2026-08-31 13:21 ` [PATCH AUTOSEL 6.18-5.10] affs: handle set_blocksize failures Sasha Levin
` (9 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:20 UTC (permalink / raw)
To: patches, stable
Cc: Yung-Tse Cheng, Miklos Szeredi, Sasha Levin, gmaglione, vgoyal,
stefanha, miklos, virtualization, linux-fsdevel, fuse-devel,
linux-kernel
From: Yung-Tse Cheng <mes900903@gmail.com>
[ Upstream commit 6af3330ec5d5fb8c06c04eb520a71cf73ea5a765 ]
virtio_fs_setup_vqs() allocates fs->vqs and fs->mq_map before calling
virtio_find_vqs(). If virtio_find_vqs() fails, the error path frees both
pointers and returns an error to virtio_fs_probe().
virtio_fs_probe() then drops the last kobject reference, and
virtio_fs_ktype_release() frees fs->vqs and fs->mq_map again. This leaves
dangling pointers in struct virtio_fs and can trigger a double-free during
probe failure cleanup.
Set fs->vqs and fs->mq_map to NULL immediately after kfree() in the
virtio_fs_setup_vqs() error path so that the later kobject release sees an
uninitialized state and kfree(NULL) becomes harmless.
This can be reproduced when a broken virtio-fs device advertises more
request queues than the transport actually provides. In that case
virtio_find_vqs() fails while setting up the extra queue, and the probe
path reaches the double-free cleanup sequence.
Signed-off-by: Yung-Tse Cheng <mes900903@gmail.com>
Signed-off-by: Miklos Szeredi <mszeredi@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:** `[virtio-fs] [avoid] double-free on failed queue setup` —
explicit bug fix in the virtio-fs driver error path during virtqueue
setup.
### Step 1.2: Tags
**Record:**
- **Signed-off-by:** Yung-Tse Cheng `<mes900903@gmail.com>` (author)
- **Signed-off-by:** Miklos Szeredi `<mszeredi@redhat.com>`
(FUSE/virtio-fs maintainer)
- No Fixes:, Reported-by:, Tested-by:, Reviewed-by:, Acked-by:, Link:,
or Cc: stable tags
Notable: maintainer Signed-off-by is a strong quality signal.
### Step 1.3: Body Analysis
**Record:**
- **Bug:** On `virtio_find_vqs()` failure in `virtio_fs_setup_vqs()`,
the error path frees `fs->vqs` and `fs->mq_map`, but
`virtio_fs_probe()` then calls `kobject_put()`, which runs
`virtio_fs_ktype_release()` and frees the same pointers again.
- **Symptom:** Double-free and dangling pointers during probe-failure
cleanup; potential kernel crash / memory corruption.
- **Trigger:** Broken virtio-fs device advertising more request queues
than the transport actually provides.
- **Root cause:** Missing NULL assignment after `kfree()` in the setup
error path, so the kobject release path cannot tell memory was already
freed.
### Step 1.4: Hidden Bug Fix?
**Record:** No — this is an explicit, clearly described double-free fix,
not disguised cleanup.
---
## Phase 2: Diff Analysis
### Step 2.1: Inventory
**Record:**
- **File:** `fs/fuse/virtio_fs.c` (+2 lines, 0 removed)
- **Function:** `virtio_fs_setup_vqs()`
- **Scope:** Single-file, surgical fix (2 lines)
### Step 2.2: Code Flow Change
**Record:**
- **Hunk (error path in `virtio_fs_setup_vqs()`):**
- **Before:** On failure (`ret != 0`), `kfree(fs->vqs)` and
`kfree(fs->mq_map)` leave dangling pointers in `struct virtio_fs`.
- **After:** Same frees, then `fs->vqs = NULL` and `fs->mq_map =
NULL`, so later `virtio_fs_ktype_release()` does harmless
`kfree(NULL)`.
### Step 2.3: Bug Mechanism
**Record:** **Category:** Double-free / dangling pointer on error path.
**Mechanism:** `virtio_fs_setup_vqs()` and `virtio_fs_ktype_release()`
both free the same allocations without coordinating ownership transfer.
### Step 2.4: Fix Quality
**Record:** Obviously correct, minimal, standard kernel pattern.
Regression risk is very low — only affects the failure path and makes
cleanup idempotent.
---
## Phase 3: Git History Investigation
### Step 3.1: Blame
**Record:**
- `kfree(fs->vqs)` in error path: Stefan Hajnoczi, 2018-06-12
(`a62a8ef9d97da2`)
- `if (ret) { ... kfree(fs->mq_map); }` wrapper: Peter-Jan Gootzen,
2024-05-01 (`529395d2ae6456`, "virtio-fs: add multi-queue support")
- The **double-free mechanism** was introduced when kobject lifecycle
landed in `virtio_fs_ktype_release()` — commit `a8f62f50b4e4e`
(2024-02-12, "virtiofs: export filesystem tags through sysfs"). That
commit is an ancestor of this tree and of `v6.18`.
### Step 3.2: Fixes: Tag
**Record:** N/A — no Fixes: tag present.
### Step 3.3: Related File History
**Record:** Recent `virtio_fs.c` activity includes other probe/cleanup
fixes (e.g. `c014021253d77` incorrect fsvq kobj check). No related fix
for this double-free is present. The candidate fix is not yet in this
tree.
### Step 3.4: Author Context
**Record:** Yung-Tse Cheng has no prior commits in this checkout. Miklos
Szeredi is the FUSE maintainer and signed off on the patch.
### Step 3.5: Dependencies
**Record:** Standalone, 2-line fix. No series dependencies. `git apply
--check` succeeds cleanly against the local tree.
---
## Phase 4: Mailing List and External Research
### Step 4.1: Original Discussion
**Record:** `b4 dig -c` failed (commit not in local tree). Web search
found the patch at [mail-archive.com](https://www.mail-
archive.com/linux-kernel@vger.kernel.org/msg2622149.html) and [Patchew](
https://patchew.org/linux/20260405193039.178506-1-mes900903@gmail.com/).
Posted 2026-04-06 by Yung-Tse Cheng. Standalone 1-patch series. Lore
fetch timed out; no review-thread details retrieved.
### Step 4.2: Reviewers
**Record:** From Spinics archive: To: virtio-fs maintainers (gmaglione,
vgoyal, stefanha, miklos). Cc: virtualization@, linux-fsdevel@, linux-
kernel@. Appropriate maintainers were included.
### Step 4.3: Bug Report
**Record:** No external bug report or syzbot link. Author describes
reproducible scenario with a misconfigured/broken virtio-fs device.
### Step 4.4: Related Patches
**Record:** Standalone fix, not part of a multi-patch series.
### Step 4.5: Stable List History
**Record:** No stable-list discussion found. UNVERIFIED due to lore
access failure.
---
## Phase 5: Code Semantic Analysis
### Step 5.1: Key Functions
**Record:** `virtio_fs_setup_vqs()`, `virtio_fs_ktype_release()`,
`virtio_fs_probe()`
### Step 5.2: Callers
**Record:**
- `virtio_fs_setup_vqs()` — called only from `virtio_fs_probe()` (line
1133)
- `virtio_fs_ktype_release()` — kobject `.release` callback, invoked via
`kobject_put()` from `virtio_fs_probe()` error path (line 1160) and
normal teardown paths
### Step 5.3: Callees
**Record:** `kcalloc()`, `virtio_find_vqs()`, `kfree()`, `kobject_put()`
— standard probe allocation/cleanup.
### Step 5.4: Reachability
**Record:**
```
virtio device probe → virtio_fs_probe()
→ virtio_fs_setup_vqs() [fails]
→ error path kfree(vqs, mq_map)
→ out: kobject_put()
→ virtio_fs_ktype_release() [double-free without fix]
```
Reachable during virtio-fs device enumeration when queue setup fails
(broken device, ENOMEM, or `virtio_find_vqs()` failure). Not a syscall
path directly, but triggered during driver probe on systems with virtio-
fs enabled.
### Step 5.5: Similar Patterns
**Record:** No `fs->vqs = NULL` or `fs->mq_map = NULL` anywhere in
current `virtio_fs.c`. The dangling-pointer pattern is unique to this
error path.
---
## Phase 6: Cross-Reference Against Local Tree
### Step 6.1: Buggy Code Present?
**Record:** **YES.** Local tree is **v6.18.44** (`git describe HEAD` →
`v6.18.44-1-g2736c32da98b9`). Current code at lines 989–992 frees
without NULLing:
```989:992:fs/fuse/virtio_fs.c
if (ret) {
kfree(fs->vqs);
kfree(fs->mq_map);
}
```
And `virtio_fs_ktype_release()` at lines 195–196 frees the same pointers
again. Fix is not yet applied.
### Step 6.2: Backport Complications
**Record:** Clean apply — `git apply --check` passed with exit code 0.
No conflicts expected.
### Step 6.3: Related Fixes Already Present?
**Record:** None. `git log -S 'fs->mq_map = NULL'` returned no results.
No grep matches for NULL assignments.
---
## Phase 7: Subsystem Context
### Step 7.1: Subsystem
**Record:** `fs/fuse/virtio_fs.c` — virtio-fs driver (FUSE over virtio).
**Criticality: IMPORTANT** — affects virtualization/virtio-fs users, not
universal core kernel, but probe failures can crash the host/VM.
### Step 7.2: Activity
**Record:** Actively maintained; recent virtio-fs and fuse fixes in this
tree.
---
## Phase 8: Impact and Risk Assessment
### Step 8.1: Who Is Affected
**Record:** Systems with `CONFIG_VIRTIO_FS` enabled (module or built-in)
where virtio-fs device probe fails during queue setup — VMs with virtio-
fs, hosts exporting virtio-fs, or broken/malicious virtio device
configurations.
### Step 8.2: Trigger Conditions
**Record:**
- `virtio_find_vqs()` failure (e.g. device advertises more queues than
transport supports)
- Also any error path through `out:` label with `ret != 0` after
`fs->vqs`/`fs->mq_map` were allocated (including ENOMEM)
- Not everyday, but reproducible on probe failure; privileged entity
controlling virtio device configuration can trigger it
### Step 8.3: Failure Mode Severity
**Record:** **Double-free** → kernel oops, possible memory corruption.
**Severity: HIGH** (crash / potential security impact from heap
corruption on probe failure).
### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents crash on legitimate probe failure paths
- **Risk:** VERY LOW — 2 lines, error-path only, idempotent cleanup
- **Ratio:** Strongly favors backport
---
## Phase 9: Final Synthesis
### Step 9.1: Evidence Summary
**FOR backport:**
- Real double-free bug with clear mechanism
- Reproducible trigger described (broken virtio-fs queue advertisement)
- HIGH severity (kernel crash / memory corruption)
- Minimal 2-line fix, applies cleanly
- FUSE maintainer (Miklos Szeredi) Signed-off-by
- Bug present in this 6.18.44 tree since kobject lifecycle (Feb 2024);
mq_map added second vector (May 2024)
- Standard NULL-after-kfree pattern
**AGAINST backport:**
- Only triggered on probe failure, not hot path
- No syzbot report or CVE
- Lore review thread not fully retrieved
**UNRESOLVED:**
- Whether reviewers explicitly nominated for stable (lore fetch failed)
- Whether patch has landed in mainline yet (not in this checkout)
### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — standard pattern; maintainer
SOB; author describes reproduction
2. Fixes a real bug? **PASS** — verified double-free in local code
3. Important issue? **PASS** — double-free on probe failure (HIGH)
4. Small and contained? **PASS** — 2 lines, 1 file
5. No new features/APIs? **PASS**
6. Can apply to local tree? **PASS** — clean apply verified
### Step 9.3: Exception Categories
**Record:** None — this is a straight bug fix, not a
quirk/ID/DT/build/doc exception.
### Step 9.4: Decision Rationale
This tree (6.18.44) contains the buggy code: `virtio_fs_setup_vqs()`
frees `fs->vqs` and `fs->mq_map` on error without NULLing them, and
`virtio_fs_probe()` unconditionally calls `kobject_put()` which re-frees
them in `virtio_fs_ktype_release()`. The fix is minimal, obviously
correct, and prevents a kernel crash on a realistic probe-failure path.
It fully meets stable kernel criteria.
---
## Verification
- [Phase 1] Parsed subject, tags, body from provided commit message
- [Phase 1] Confirmed maintainer Signed-off-by: Miklos Szeredi
- [Phase 2] Read current `virtio_fs_setup_vqs()` error path (lines
986–993): missing NULL assignments
- [Phase 2] Read `virtio_fs_ktype_release()` (lines 191–197): frees same
pointers
- [Phase 2] Read `virtio_fs_probe()` error path (lines 1158–1161):
`kobject_put()` on all failures
- [Phase 3] `git describe HEAD`: v6.18.44-1-g2736c32da98b9
- [Phase 3] `git blame -L 989,992`: error-path code from 2018 (vqs) and
2024 (mq_map wrapper)
- [Phase 3] `git show 529395d2ae6456`: mq_map kfree added to error path
without NULL
- [Phase 3] `git show a8f62f50b4e4e`: kobject lifecycle introduced
double-free mechanism
- [Phase 3] `git merge-base --is-ancestor a8f62f50b4e4e HEAD`: confirmed
in tree
- [Phase 3] `git merge-base --is-ancestor 529395d2ae6456 HEAD`:
confirmed in tree
- [Phase 3] `git log -S 'fs->mq_map = NULL'`: no existing fix
- [Phase 4] `b4 dig -c df25d4faca41c`: failed (commit not in tree)
- [Phase 4] Web search: found patch submission at mail-archive.com and
Patchew (2026-04-06)
- [Phase 4] Lore direct fetch: timed out — review comments UNVERIFIED
- [Phase 5] `grep virtio_fs_setup_vqs`: single caller in
`virtio_fs_probe()`
- [Phase 5] Traced probe → setup_vqs error → kobject_put → ktype_release
call chain
- [Phase 6] Confirmed buggy code present at lines 989–992; fix absent
(no NULL assignments)
- [Phase 6] `git apply --check`: patch applies cleanly (exit 0)
- [Phase 7] Read `fs/fuse/Kconfig`: VIRTIO_FS depends on FUSE_FS +
VIRTIO
- [Phase 8] Assessed severity: double-free on probe failure = HIGH
**YES**Those background searches finished and line up with the earlier
analysis:
- **Author lookup** — No commits from Yung-Tse Cheng / `mes900903` in
this 6.18.44 tree; the patch is still a candidate, not merged here.
- **Commit message search** — Nothing matching “avoid double-free on
failed queue setup” in local history.
- **Alternate-fix search** — No other commit in `fs/fuse/` that already
fixes this.
Together that confirms the bug is present locally and the fix isn’t
applied yet. The backport recommendation remains **YES**.
fs/fuse/virtio_fs.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/fuse/virtio_fs.c b/fs/fuse/virtio_fs.c
index b2f6486fe1d56..2b3daa93e299a 100644
--- a/fs/fuse/virtio_fs.c
+++ b/fs/fuse/virtio_fs.c
@@ -988,7 +988,9 @@ static int virtio_fs_setup_vqs(struct virtio_device *vdev,
kfree(vqs);
if (ret) {
kfree(fs->vqs);
+ fs->vqs = NULL;
kfree(fs->mq_map);
+ fs->mq_map = NULL;
}
return ret;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-5.10] affs: handle set_blocksize failures
[not found] <20260831133314.4125787-1-sashal@kernel.org>
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.10] hfs: rework hfsplus_readdir() logic Sasha Levin
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-6.12] virtio-fs: avoid double-free on failed queue setup Sasha Levin
@ 2026-08-31 13:21 ` Sasha Levin
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18] netfs: Fix DIO write retry for filesystems without a ->prepare_write() Sasha Levin
` (8 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:21 UTC (permalink / raw)
To: patches, stable
Cc: Christoph Hellwig, Christian Brauner (Amutable), Sasha Levin,
dsterba, linux-fsdevel, linux-kernel
From: Christoph Hellwig <hch@lst.de>
[ Upstream commit 0861182af5983a39bd2a891966436c5679b74a45 ]
affs uses buffer_heads, which don't handle block size > PAGE_SIZE well.
Without this, mounting we will hit the
BUG_ON(offset >= folio_size(folio));
in folio_set_bh on the first __bread_gfp call.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260511071701.2456211-7-hch@lst.de
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
## Phase 1: Commit Message Forensics
**Step 1.1 — Subject:** `[affs]` `[handle]` — handle `set_blocksize`
failures in AFFS mount path.
**Step 1.2 — Tags:**
- Signed-off-by: Christoph Hellwig \<hch@lst.de\>
- Link: https://patch.msgid.link/20260511071701.2456211-7-hch@lst.de
- Signed-off-by: Christian Brauner \<brauner@kernel.org\>
- No Fixes:, Reported-by:, Tested-by:, Cc: stable, or syzbot tags
- Mailing list: Acked-by: David Sterba \<dsterba@suse.com\> (from
thread)
**Step 1.3 — Body:** AFFS uses buffer_heads, which cannot safely use
block sizes larger than `PAGE_SIZE`. If `sb_set_blocksize()` fails and
the code continues, the first `__bread_gfp()` call hits `BUG_ON(offset
>= folio_size(folio))` in `folio_set_bh()`. Symptom: kernel BUG/panic
during mount (including filesystem auto-probe).
**Step 1.4 — Hidden bug fix?** No — this is an explicit mount-path bug
fix, not disguised cleanup.
---
## Phase 2: Diff Analysis
**Step 2.1 — Inventory:**
- `fs/affs/affs.h`: −5 lines (removes `affs_set_blocksize()` wrapper)
- `fs/affs/super.c`: +4/−2 lines
- Functions: `affs_fill_super()` only
- Scope: single-subsystem, surgical (~11 lines net)
**Step 2.2 — Code flow:**
- **Before:** `affs_set_blocksize()` called `sb_set_blocksize()` and
ignored its return value.
- **After:** Direct `sb_set_blocksize()` calls with failure checks;
mount returns `-EINVAL` on failure at both the initial `PAGE_SIZE`
setup and each blocksize-probe iteration.
**Step 2.3 — Bug mechanism:** Missing error-path handling. When
`sb_set_blocksize()` returns 0 (failure — e.g. requested size >
`PAGE_SIZE` on a non-`FS_LBS` filesystem, or `set_blocksize()` failure
on an incompatible block device), mount continued and issued buffer-head
I/O that triggers `folio_set_bh()`'s `BUG_ON`.
**Step 2.4 — Fix quality:** Obviously correct; mirrors patterns already
used in this tree by ext4, ufs, udf, minix (initial call), etc. Minimal
regression risk.
---
## Phase 3: Git History Investigation
**Step 3.1 — Blame:** Buggy ignore-return-value pattern dates to Linux
2.6.12 (`1da177e4c3f41`). Present throughout AFFS history in this tree.
**Step 3.2 — Fixes: tag:** Not present (expected for manual review).
**Step 3.3 — Related commits:**
- `a64e5a596067b` (2025-03-07): re-added `PAGE_SIZE` validation to
`sb_set_blocksize()` — **in this tree**
- `465e5e6a1698f` (2023): added `folio_set_bh()` with `BUG_ON` — **in
this tree**
- Mainline commit: `0861182af5983` — **NOT in this tree**
- Part of 10-patch series merged as `d90e60ced4c3c` ("fix crashes when
mounting legacy file system with sector size > PAGE_SIZE")
**Step 3.4 — Author:** Christoph Hellwig; series merged by VFS
maintainer Christian Brauner.
**Step 3.5 — Dependencies:** Standalone; patch 6/10 in series but self-
contained for AFFS. No prerequisite commits required beyond code already
in 6.18.y.
---
## Phase 4: Mailing List and External Research
**Step 4.1 — Thread:**
https://patch.msgid.link/20260511071701.2456211-7-hch@lst.de (b4 dig
confirmed). Series v1, 10 patches.
**Step 4.2 — Reviewers:** CC'd to linux-fsdevel, Alexander Viro,
Christian Brauner, filesystem maintainers. David Sterba Acked-by on affs
patch.
**Step 4.3 — Bug report:** Cover letter (`2456211-1-hch@lst.de`): author
reproduced crashes probing built-in filesystems on a 64K-sector loop
device; affs was among filesystems that actually crashed.
**Step 4.4 — Series context:** 10 filesystems fixed with same pattern;
affs patch is independent of the others.
**Step 4.5 — Stable list:** No stable-specific discussion found; not a
negative signal.
---
## Phase 5: Code Semantic Analysis
**Step 5.1 — Functions:** `affs_fill_super()`, inline `affs_bread()`
(unchanged).
**Step 5.2 — Callers:** `affs_fill_super()` called from
`get_tree_bdev()` during `mount(2)` / filesystem probe.
**Step 5.3 — Callees:** `sb_set_blocksize()` → `set_blocksize()`; on
success, `affs_bread()` → `sb_bread()` → `__bread_gfp()` →
`folio_alloc_buffers()` → `folio_set_bh()`.
**Step 5.4 — Reachability:** Reachable from userspace mount and blkid-
style filesystem probing on block devices with large logical sector
sizes or on systems where `PAGE_SIZE` > AFFS's supported 4K blocks.
**Step 5.5 — Similar patterns:** 15+ filesystems in this tree already
check `sb_set_blocksize()` return value; AFFS is an outlier.
---
## Phase 6: Cross-Reference Against Local Tree
**Tree:** `v6.18.44` (6.18.y stable)
**Step 6.1 — Buggy code exists:** Yes — `fs/affs/super.c` lines 360 and
376 still call `affs_set_blocksize()` without checking return value.
**Step 6.2 — Backport difficulty:** Clean apply expected; no conflicting
changes in this file.
**Step 6.3 — Related fixes already present:** No equivalent AFFS fix.
Prerequisites (`folio_set_bh` BUG_ON, `sb_set_blocksize` PAGE_SIZE
check) are both present, making the bug reachable.
---
## Phase 7: Subsystem Context
**Step 7.1:** AFFS filesystem (`fs/affs/`). Criticality: PERIPHERAL
(Amiga/legacy FS), but mount path is security-relevant (unprivileged
mount with privileges).
**Step 7.2:** Low recent churn; mature legacy driver.
---
## Phase 8: Impact and Risk Assessment
**Step 8.1 — Affected users:** Users with `CONFIG_AFFS_FS` who mount or
auto-probe AFFS on block devices where `sb_set_blocksize()` fails.
**Step 8.2 — Trigger:** Mount/probe on devices with sector size >
`PAGE_SIZE`, or blocksize/LBA mismatches; more likely on large-page
architectures or during multi-FS probe on unusual block devices.
**Step 8.3 — Failure mode:** Kernel `BUG()` in `folio_set_bh()` —
**CRITICAL** severity when triggered.
**Step 8.4 — Risk/benefit:**
- Benefit: Prevents kernel panic; converts crash to clean `-EINVAL`
mount failure
- Risk: Very low (2 error checks + wrapper removal)
- Ratio: Strongly favorable
---
## Phase 9: Final Synthesis
**Evidence FOR:**
- Real, reproduced crash (author's 64K loop-device probe testing)
- Kernel BUG/panic on mount
- Small, obviously correct fix
- Matches established patterns in this tree
- Prerequisites present in 6.18.y
- Reviewed (Acked-by David Sterba) and merged to mainline
- Bug present since 2.6 in code that exists in this tree
**Evidence AGAINST:**
- AFFS is rarely used (niche legacy FS)
- Trigger conditions are uncommon on typical 4K-page desktops
- Not a security CVE, but mount-triggered BUG is still serious
**Stable rules checklist:**
1. Obviously correct and tested? **PASS** — author tested; reviewer
acked
2. Fixes real bug affecting users? **PASS** — confirmed crash on mount
probe
3. Important issue? **PASS** — kernel BUG/panic (CRITICAL)
4. Small and contained? **PASS** — ~11 lines, 2 files
5. No new features/APIs? **PASS**
6. Can apply to local tree? **PASS** — clean apply expected
**Exception category:** None (standard bug fix).
---
## Verification
- [Phase 1] Parsed commit message and tags from provided diff and `git
show 0861182af5983`
- [Phase 2] Diff analysis: 2 files, `affs_fill_super()` only; adds
`sb_set_blocksize()` failure checks
- [Phase 3] `git describe HEAD`: v6.18.44
- [Phase 3] `git blame` lines 360–376: bug since `1da177e4c3f41`
(2.6.12)
- [Phase 3] `git merge-base --is-ancestor 0861182af5983 HEAD`: NOT in
tree
- [Phase 3] `git merge-base --is-ancestor a64e5a596067b HEAD`: PAGE_SIZE
check present
- [Phase 3] `git merge-base --is-ancestor 465e5e6a1698f HEAD`:
`folio_set_bh` BUG_ON present
- [Phase 3] Read `block/bdev.c:220-230`: `sb_set_blocksize()` returns 0
on failure
- [Phase 3] Read `fs/buffer.c:1578-1582`: `folio_set_bh()` BUG_ON
confirmed
- [Phase 4] `b4 dig -c 0861182af5983`: thread at
patch.msgid.link/20260511071701.2456211-7-hch@lst.de
- [Phase 4] `b4 dig -a`: v1 series, patch 6/10
- [Phase 4] `b4 dig -w`: Viro, Brauner, fsdevel CC'd
- [Phase 4] Mbox: cover letter confirms 64K loop-device crash during FS
probe; David Sterba Acked-by
- [Phase 5] `affs_fill_super` called from `get_tree_bdev()` at
`fs/affs/super.c:583`
- [Phase 5] `sb_bread` → `__bread_gfp` uses `sb->s_blocksize` per
`include/linux/buffer_head.h:344-346`
- [Phase 6] Buggy `affs_set_blocksize()` wrapper confirmed at
`fs/affs/affs.h:228-232`
- [Phase 6] AFFS lacks `FS_LBS` flag (`fs/affs/super.c:651`)
- [Phase 8] Failure mode: BUG_ON in `folio_set_bh` — CRITICAL
This fix prevents a kernel BUG during AFFS mount when
`sb_set_blocksize()` fails — a scenario the author reproduced during
filesystem probing on a 64K-sector block device. The change is minimal,
matches patterns already used by other filesystems in 6.18.y, and all
prerequisites are present in this tree.
**YES**The background search finished while the AFFS backport analysis
was already done. It didn’t turn up the `affs: handle set_blocksize
failures` commit in this tree (expected — it’s only on mainline as
`0861182af5983`), and the mbox search didn’t find a local copy of the
patch thread.
**Verdict from the analysis: YES** — the fix should be backported to
this 6.18.y tree. It’s a small, low-risk change that prevents a kernel
BUG during AFFS mount when `sb_set_blocksize()` fails, and the buggy
code is still present here.
fs/affs/affs.h | 5 -----
fs/affs/super.c | 6 ++++--
2 files changed, 4 insertions(+), 7 deletions(-)
diff --git a/fs/affs/affs.h b/fs/affs/affs.h
index ac4e9a02910b7..99e54e3453ac1 100644
--- a/fs/affs/affs.h
+++ b/fs/affs/affs.h
@@ -225,11 +225,6 @@ static inline bool affs_validblock(struct super_block *sb, int block)
block < AFFS_SB(sb)->s_partition_size);
}
-static inline void
-affs_set_blocksize(struct super_block *sb, int size)
-{
- sb_set_blocksize(sb, size);
-}
static inline struct buffer_head *
affs_bread(struct super_block *sb, int block)
{
diff --git a/fs/affs/super.c b/fs/affs/super.c
index 44f8aa883100e..994f1199beaf1 100644
--- a/fs/affs/super.c
+++ b/fs/affs/super.c
@@ -357,7 +357,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc)
size = bdev_nr_sectors(sb->s_bdev);
pr_debug("initial blocksize=%d, #blocks=%d\n", 512, size);
- affs_set_blocksize(sb, PAGE_SIZE);
+ if (!sb_set_blocksize(sb, PAGE_SIZE))
+ return -EINVAL;
/* Try to find root block. Its location depends on the block size. */
i = bdev_logical_block_size(sb->s_bdev);
@@ -373,7 +374,8 @@ static int affs_fill_super(struct super_block *sb, struct fs_context *fc)
if (ctx->root_block < 0)
sbi->s_root_block = (ctx->reserved + size - 1) / 2;
pr_debug("setting blocksize to %d\n", blocksize);
- affs_set_blocksize(sb, blocksize);
+ if (!sb_set_blocksize(sb, blocksize))
+ return -EINVAL;
sbi->s_partition_size = size;
/* The root block location that was calculated above is not
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18] netfs: Fix DIO write retry for filesystems without a ->prepare_write()
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (2 preceding siblings ...)
2026-08-31 13:21 ` [PATCH AUTOSEL 6.18-5.10] affs: handle set_blocksize failures Sasha Levin
@ 2026-08-31 13:25 ` Sasha Levin
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] isofs: handle set_blocksize failures Sasha Levin
` (7 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:25 UTC (permalink / raw)
To: patches, stable
Cc: David Howells, syzbot+3c74b1f0c372e98efc32, Paulo Alcantara,
hongao, ChenXiaoSong, netfs, linux-fsdevel,
Christian Brauner (Amutable), Sasha Levin, linux-kernel
From: David Howells <dhowells@redhat.com>
[ Upstream commit 64f04f9789237728be4e1836151848af350d1374 ]
Fix netfs_unbuffered_write() so that it doesn't re-issue a write twice when
the filesystem doesn't have a ->prepare_write(). The resetting of the
iterator and the call to netfs_reissue_write() should just be removed as
almost everything it does is done again when the loop it's in goes back to
the top.
It does, however, still need the IN_PROGRESS flag setting, so that (and the
stat inc) are moved out of the if-statement.
Further, the MADE_PROGRESS flags should be cleared and wreq->transferred
should be updated, so fix those too.
Reported-by: syzbot+3c74b1f0c372e98efc32@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=3c74b1f0c372e98efc32
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/20260625140640.3116900-16-dhowells@redhat.com
cc: Paulo Alcantara <pc@manguebit.org>
cc: hongao <hongao@uniontech.com>
cc: ChenXiaoSong <chenxiaosong@chenxiaosong.com>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Backport Analysis: `netfs: Fix DIO write retry for filesystems without
a ->prepare_write()`
**Local tree:** `v6.18.44` (6.18.y stable) — `VERSION = 6`, `PATCHLEVEL
= 18`, `SUBLEVEL = 44`
---
## PHASE 1: COMMIT MESSAGE FORENSICS
### Step 1.1: Parse the subject line
**Record:** `[netfs]` `[Fix]` — Correct DIO/unbuffered write retry when
the filesystem has no `->prepare_write()` hook.
### Step 1.2: Parse all commit message tags
**Record:**
| Tag | Value |
|-----|-------|
| Reported-by | syzbot+3c74b1f0c372e98efc32@syzkaller.appspotmail.com |
| Closes | https://syzkaller.appspot.com/bug?extid=3c74b1f0c372e98efc32
|
| Signed-off-by | David Howells \<dhowells@redhat.com\> |
| Link |
https://patch.msgid.link/20260625140640.3116900-16-dhowells@redhat.com |
| cc | Paulo Alcantara, hongao, ChenXiaoSong, netfs@lists.linux.dev,
linux-fsdevel@vger.kernel.org |
| Signed-off-by | Christian Brauner (Amutable) \<brauner@kernel.org\> |
**Notable patterns:** syzbot report (strong YES signal). No `Fixes:` tag
(expected for manual review). No `Cc: stable` tag (not a negative
signal).
### Step 1.3: Analyze commit body
**Record:**
- **Bug:** On retry in `netfs_unbuffered_write()`, when
`stream->prepare_write` is NULL, the code calls
`netfs_reissue_write()` and then the loop iterates again and issues
the write a second time.
- **Symptom:** Double write issuance, incorrect progress accounting
(`wreq->transferred` not updated on partial retry), stale
`NETFS_SREQ_MADE_PROGRESS` flag.
- **Root cause:** The retry path incorrectly mirrored `write_retry.c`’s
`netfs_reissue_write()` pattern, but `netfs_unbuffered_write()`’s loop
already re-issues at the top on the next iteration.
- **Version info:** None explicit; bug is tied to code introduced in
6.18.y backports.
### Step 1.4: Detect hidden bug fixes
**Record:** Yes — despite “fix retry logic” wording, this is a real
memory-safety and correctness bug: syzbot reports KASAN slab-use-after-
free in `netfs_unbuffered_write()`, reachable from userspace `write()`
via 9p.
---
## PHASE 2: DIFF ANALYSIS
### Step 2.1: Inventory changes
**Record:**
- **Files:** `fs/netfs/direct_write.c` only (+6 / -10 lines, net −4)
- **Function:** `netfs_unbuffered_write()`
- **Scope:** Single-file surgical fix in retry path
### Step 2.2: Code flow change per hunk
**Record:**
| Hunk | Before → After |
|------|----------------|
| Partial transfer | `iov_iter_advance()` only → also `wreq->transferred
+= subreq->transferred` |
| Flag clearing | No `MADE_PROGRESS` clear →
`__clear_bit(NETFS_SREQ_MADE_PROGRESS, ...)` added |
| prepare_write branch | `if/else`: else calls `netfs_reset_iter()` +
`netfs_reissue_write()` → unified path: optional `prepare_write()`,
always set `IN_PROGRESS` + stat |
**Affected path:** Retry branch when `NETFS_SREQ_NEED_RETRY` is set
(error recovery during unbuffered/DIO writes).
### Step 2.3: Bug mechanism
**Record:**
- **Category:** Logic/correctness bug with memory safety consequences
(UAF); also reference-counting/lifecycle corruption from double issue.
- **Mechanism:** `netfs_reissue_write()` calls `netfs_do_issue_write()`
→ `stream->issue_write()`. The loop then continues with `subreq` still
non-NULL, skips `netfs_prepare_write()`, and calls
`stream->issue_write(subreq)` again at line 134. This corrupts
subrequest lifecycle and can free the subrequest while the loop still
holds a pointer to it (matching syzbot’s alloc/free/read pattern).
### Step 2.4: Fix quality
**Record:** Obviously correct. The `prepare_write` path already worked
this way (set up state, loop back, issue once). The fix unifies the
no-`prepare_write` path to match. Minimal regression risk; no new APIs
or locking changes.
---
## PHASE 3: GIT HISTORY INVESTIGATION
### Step 3.1: Blame changed lines
**Record:**
- Retry infrastructure: `72d08d2839649` (upstream `a0b4c7a49137`, Feb
2026) — “Fix unbuffered/DIO writes to dispatch subrequests in strict
sequence”
- Buggy `else { netfs_reissue_write() }` branch: `a4d1b4ba9754b`
(upstream `e9075e420a1e`, Mar 2026) — “Fix NULL pointer dereference in
netfs_unbuffered_write() on retry”
- Both commits are ancestors of HEAD in this tree.
### Step 3.2: Follow Fixes: tag
**Record:** N/A — no `Fixes:` tag in commit message. The bug was
introduced by `a4d1b4ba9754b`, which attempted to fix an earlier NULL
deref (syzbot `7227db0f`) but introduced the double-issue/UAF.
### Step 3.3: File history for related changes
**Record:** Recent `direct_write.c` history in this tree:
- `f0035858dfb23` — stream->front removal
- `a4d1b4ba9754b` — NULL deref fix (introduced this bug)
- `72d08d2839649` — sequential DIO write dispatch
Standalone fix; not part of a multi-commit dependency chain for this
tree.
### Step 3.4: Author's other commits
**Record:** David Howells is the netfs subsystem author. He authored
`72d08d2839649` (the retry loop) and this follow-up fix. Deepanshu
Kartikey authored the incomplete `a4d1b4ba9754b` fix.
### Step 3.5: Prerequisites
**Record:**
- **Required in tree:** `72d08d2839649` (retry loop) and `a4d1b4ba9754b`
(if/else structure) — both present.
- **Fix commit `64f04f978923`:** NOT in HEAD.
- **Standalone:** Yes — only modifies existing retry path; cherry-pick
applies cleanly.
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
### Step 4.1: Original patch discussion
**Record:**
- `b4 dig -c 64f04f978923`: found at
https://patch.msgid.link/20260625140640.3116900-16-dhowells@redhat.com
- Subject: `[PATCH v3 15/15] netfs: Fix DIO write retry for filesystems
without a ->prepare_write()`
- Part of a 15-patch netfs series; this patch is self-contained in
`direct_write.c`.
- Lore page blocked by bot protection; could not read thread body.
### Step 4.2: Reviewers
**Record:** `b4 dig -w` CC list includes David Howells, Christian
Brauner, Paulo Alcantara, Christoph Hellwig, netfs@lists.linux.dev,
linux-fsdevel@vger.kernel.org, syzbot address. Appropriate subsystem
coverage.
### Step 4.3: Bug report
**Record:** https://syzkaller.appspot.com/bug?extid=3c74b1f0c372e98efc32
- **Type:** KASAN: slab-use-after-free Read in `netfs_unbuffered_write`
- **Status:** Fixed upstream 2026/07/29
- **Priority:** high
- **Trigger:** `ksys_write` → `v9fs_file_write_iter` →
`netfs_unbuffered_write_iter` → `netfs_unbuffered_write`
- **AI assessment:** Exploitable, unprivileged, userspace-triggerable
- **8 crashes** over ~75 days
### Step 4.4: Related patches/series
**Record:** Patch 15/15 of v3 netfs series. Other series patches (e.g.,
“Fix oops in write-retry from mis-resetting the subreq iterator”) are
NOT in this tree, but this patch does not depend on them — verified by
clean cherry-pick.
### Step 4.5: Stable mailing list
**Record:** Could not search lore stable list (bot protection). No
evidence against stable nomination.
---
## PHASE 5: CODE SEMANTIC ANALYSIS
### Step 5.1: Key functions
**Record:** `netfs_unbuffered_write()` (modified),
`netfs_reissue_write()` (no longer called from here on retry)
### Step 5.2: Callers
**Record:**
- `netfs_unbuffered_write_iter_locked()` ←
`netfs_unbuffered_write_iter()`
- Callers of `netfs_unbuffered_write_iter()`:
- `fs/9p/vfs_file.c` (no `prepare_write` — **affected**)
- `fs/smb/client/file.c` (has `cifs_prepare_write` — uses
`prepare_write` path, not affected by this specific bug)
- `fs/netfs/buffered_write.c` (fallback path)
### Step 5.3: Callees in retry path
**Record:** `iov_iter_advance`, `retry_request` op, flag bit ops,
`netfs_get_subrequest`, optional `prepare_write`, then loop-top
`stream->issue_write()`.
### Step 5.4: Call chain / reachability
**Record:** `write(2)` → VFS → `v9fs_file_write_iter` →
`netfs_unbuffered_write_iter` → `netfs_unbuffered_write`. **Userspace-
reachable** on 9p mounts with O_DIRECT or unbuffered write paths.
### Step 5.5: Similar patterns
**Record:** `write_retry.c` correctly uses `netfs_reissue_write()`
outside a re-issue loop. `netfs_unbuffered_write()` has its own issue-
at-loop-top pattern — the bug was copying the wrong pattern.
---
## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE
### Step 6.1: Does buggy code exist?
**Record:** **YES.** Lines 189–199 in `fs/netfs/direct_write.c` contain
the buggy `else { netfs_reissue_write(); }` branch. Introduced by
`a4d1b4ba9754b`, which is in this tree.
### Step 6.2: Backport complications
**Record:** **Clean apply.** `git cherry-pick --no-commit 64f04f978923`
succeeded with auto-merge on `fs/netfs/direct_write.c`.
### Step 6.3: Related fixes already present?
**Record:** `a4d1b4ba9754b` (incomplete NULL-deref fix) is present. Fix
`64f04f978923` is NOT present (`git merge-base --is-ancestor` returns
failure). No duplicate fix found.
---
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
### Step 7.1: Subsystem criticality
**Record:** `fs/netfs/` — IMPORTANT. Shared library for network
filesystems (9p, CIFS, AFS, Ceph). Write path affects data integrity.
### Step 7.2: Subsystem activity
**Record:** Actively maintained in 6.18.y — multiple netfs fixes already
backported (UAF, deadlock, writeback fixes visible in recent log).
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
### Step 8.1: Who is affected
**Record:** Users of network filesystems without `prepare_write` on the
upload stream — primarily **9p**. Config-dependent (9p + unbuffered/DIO
write + write retry).
### Step 8.2: Trigger conditions
**Record:** Write subrequest marked `NETFS_SREQ_NEED_RETRY` during
unbuffered/DIO write when `stream->prepare_write == NULL`. Syzbot
reproduces via `write()` syscall. Unprivileged users can trigger on
accessible 9p mounts.
### Step 8.3: Failure mode severity
**Record:**
- KASAN slab-use-after-free (syzbot-confirmed) — **CRITICAL** (crash,
potential security)
- Double write issuance — **CRITICAL** (data corruption risk)
- Incorrect `wreq->transferred` — **HIGH** (wrong offsets, potential
corruption)
### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** HIGH — fixes syzbot UAF, prevents double-write and
progress accounting errors on a common netfs code path.
- **Risk:** VERY LOW — ~10-line change, mirrors existing `prepare_write`
path behavior.
- **Ratio:** Strongly favors backport.
---
## PHASE 9: FINAL SYNTHESIS
### Step 9.1: Evidence summary
**FOR backport:**
- syzbot-reported, reproducible KASAN UAF
- Userspace-triggerable via `write()` on 9p
- High priority, assessed exploitable
- Small, surgical, obviously correct fix
- Bug exists in this 6.18.44 tree (introduced by prior stable backport
`a4d1b4ba9754b`)
- Applies cleanly
- Fixes real data-integrity and crash bugs
**AGAINST backport:**
- Part of a 15-patch series (but this patch is self-contained)
- Only affects filesystems without `prepare_write` (primarily 9p, not
CIFS/AFS)
**Unresolved:** Could not read full lore review thread (bot protection).
Does not affect decision — syzbot evidence is sufficient.
### Step 9.2: Stable rules checklist
| Criterion | Result |
|-----------|--------|
| 1. Obviously correct and tested? | **PASS** — syzbot tested prior
NULL-deref fix; this is author's correction of that approach |
| 2. Fixes real bug affecting users? | **PASS** — syzbot UAF, 8 crashes
|
| 3. Important issue? | **PASS** — CRITICAL (UAF, data corruption) |
| 4. Small and contained? | **PASS** — single file, ~10 lines |
| 5. No new features/APIs? | **PASS** |
| 6. Can apply to local tree? | **PASS** — verified clean cherry-pick |
### Step 9.3: Exception categories
**Record:** N/A — standard bug fix, not a quirk/ID/DT/doc exception.
### Step 9.4: Decision rationale
This 6.18.y tree contains the buggy retry path from `a4d1b4ba9754b` but
not the corrective fix. The bug causes slab-use-after-free (syzbot-
confirmed, high priority, userspace-triggerable on 9p) and double write
issuance. The fix is minimal, applies cleanly, and corrects a regression
introduced by a prior stable backport. This is exactly the kind of
follow-up fix stable trees need.
---
## Verification
- **[Phase 1]** Parsed commit message and tags; confirmed syzbot
Reported-by and Closes link
- **[Phase 2]** Read `fs/netfs/direct_write.c` lines 94–205; confirmed
retry loop double-issue path
- **[Phase 2]** Read `netfs_reissue_write()` in
`fs/netfs/write_issue.c:240-257`; confirms it calls
`netfs_do_issue_write()`
- **[Phase 3]** `git describe HEAD`: v6.18.44
- **[Phase 3]** `git blame -L 163,200`: retry code from `72d08d2839649`,
buggy else from `a4d1b4ba9754b`
- **[Phase 3]** `git show a4d1b4ba9754b`: introduced
`netfs_reissue_write()` in else branch
- **[Phase 3]** `git show 72d08d2839649`: introduced sequential DIO
write retry loop
- **[Phase 3]** `git merge-base --is-ancestor`: both prerequisites in
HEAD; fix `64f04f978923` NOT in HEAD
- **[Phase 4]** `b4 dig -c 64f04f978923`: found lore URL, patch v3 15/15
- **[Phase 4]** `b4 dig -w`: confirmed maintainer CC list
- **[Phase 4]** WebFetch syzbot bug page: KASAN UAF, high priority,
exploitable, 8 crashes, v9p trigger via `ksys_write`
- **[Phase 5]** `grep netfs_unbuffered_write_iter`: callers in 9p, SMB,
netfs buffered_write
- **[Phase 5]** `grep prepare_write fs/9p`: no matches — 9p lacks
`prepare_write`
- **[Phase 5]** `grep prepare_write fs/smb`, `fs/afs`: both have
`prepare_write` set
- **[Phase 6]** Confirmed buggy else branch at `direct_write.c:189-199`
- **[Phase 6]** `git cherry-pick --no-commit 64f04f978923`: clean apply
(exit 0)
- **[Phase 8]** Syzbot stack trace: UAF at `netfs_unbuffered_write` via
`v9fs_file_write_iter` + `ksys_write`
**YES**
fs/netfs/direct_write.c | 18 +++++++-----------
1 file changed, 7 insertions(+), 11 deletions(-)
diff --git a/fs/netfs/direct_write.c b/fs/netfs/direct_write.c
index f9ab69de3e298..c2873ecbeceb6 100644
--- a/fs/netfs/direct_write.c
+++ b/fs/netfs/direct_write.c
@@ -166,13 +166,16 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
*/
subreq->error = -EAGAIN;
trace_netfs_sreq(subreq, netfs_sreq_trace_retry);
- if (subreq->transferred > 0)
+ if (subreq->transferred > 0) {
iov_iter_advance(&wreq->buffer.iter, subreq->transferred);
+ wreq->transferred += subreq->transferred;
+ }
if (stream->source == NETFS_UPLOAD_TO_SERVER &&
wreq->netfs_ops->retry_request)
wreq->netfs_ops->retry_request(wreq, stream);
+ __clear_bit(NETFS_SREQ_MADE_PROGRESS, &subreq->flags);
__clear_bit(NETFS_SREQ_NEED_RETRY, &subreq->flags);
__clear_bit(NETFS_SREQ_BOUNDARY, &subreq->flags);
__clear_bit(NETFS_SREQ_FAILED, &subreq->flags);
@@ -186,17 +189,10 @@ static int netfs_unbuffered_write(struct netfs_io_request *wreq)
netfs_get_subrequest(subreq, netfs_sreq_trace_get_resubmit);
- if (stream->prepare_write) {
+ if (stream->prepare_write)
stream->prepare_write(subreq);
- __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
- netfs_stat(&netfs_n_wh_retry_write_subreq);
- } else {
- struct iov_iter source;
-
- netfs_reset_iter(subreq);
- source = subreq->io_iter;
- netfs_reissue_write(stream, subreq, &source);
- }
+ __set_bit(NETFS_SREQ_IN_PROGRESS, &subreq->flags);
+ netfs_stat(&netfs_n_wh_retry_write_subreq);
}
netfs_unbuffered_write_done(wreq);
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-5.10] isofs: handle set_blocksize failures
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (3 preceding siblings ...)
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18] netfs: Fix DIO write retry for filesystems without a ->prepare_write() Sasha Levin
@ 2026-08-31 13:25 ` Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: rework hfsplus_readdir() logic Sasha Levin
` (6 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:25 UTC (permalink / raw)
To: patches, stable
Cc: Christoph Hellwig, Christian Brauner (Amutable), Sasha Levin,
jack, linux-fsdevel, linux-kernel
From: Christoph Hellwig <hch@lst.de>
[ Upstream commit 25ef4c4d9f0e96fb89c0ae0d7127c3f12a31bc32 ]
isofs uses buffer_heads, which don't handle block size > PAGE_SIZE well.
Without this, mounting we will hit the
BUG_ON(offset >= folio_size(folio));
in folio_set_bh on the first __bread_gfp call.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260511071701.2456211-8-hch@lst.de
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Stable Backport Analysis: `isofs: handle set_blocksize failures`
**Local tree:** `v6.18.44` (Linux 6.18.44)
**Commit under review:** `25ef4c4d9f0e9` (on `master`, not yet in this
tree)
**Patch series:** [PATCH 07/10] of “fix crashes when mounting legacy
file system with sector size > PAGE_SIZE”
---
## PHASE 1: COMMIT MESSAGE FORENSICS
**Step 1.1 — Subject line**
Record: `[isofs] [handle] — check return value of sb_set_blocksize()
during mount and fail cleanly instead of continuing`
**Step 1.2 — Tags**
Record:
- `Signed-off-by: Christoph Hellwig <hch@lst.de>` (author)
- `Link: https://patch.msgid.link/20260511071701.2456211-8-hch@lst.de`
- `Signed-off-by: Christian Brauner <brauner@kernel.org>` (committer)
- No `Fixes:`, `Reported-by:`, `Cc: stable@vger.kernel.org`, `Reviewed-
by:` in the committed message (Jan Kara reviewed on-list; see Phase 4)
- No syzbot report
**Step 1.3 — Body analysis**
Record:
- **Bug:** `isofs` uses buffer heads, which cannot handle block sizes >
`PAGE_SIZE`. If `sb_set_blocksize()` fails and mount continues, the
first `__bread_gfp` path hits `BUG_ON(offset >= folio_size(folio))` in
`folio_set_bh`.
- **Symptom:** Kernel `BUG()` during ISO9660 mount.
- **Root cause (author):** Ignored `sb_set_blocksize()` failure leaves
inconsistent block geometry; buffer-head setup then triggers the folio
assertion.
**Step 1.4 — Hidden bug fix?**
Record: **Yes.** Although the subject says “handle failures,” this is a
real crash fix on the mount path, not cosmetic cleanup.
---
## PHASE 2: DIFF ANALYSIS
**Step 2.1 — Inventory**
Record:
- **Files:** `fs/isofs/inode.c` (+2 / -1 lines)
- **Function:** `isofs_fill_super()`
- **Scope:** Single-file, surgical mount-path fix
**Step 2.2 — Code flow change**
Record:
- **Before:** `sb_set_blocksize(s, orig_zonesize);` — return value
ignored; mount continues.
- **After:** `if (!sb_set_blocksize(s, orig_zonesize)) goto
out_freesbi;` — mount aborts and frees `sbi`.
- **Path affected:** Normal mount success path in `isofs_fill_super()`,
after volume-descriptor parsing and before root inode read
(`isofs_iget()` → `sb_bread()`).
**Step 2.3 — Bug mechanism**
Record:
- **Category:** Logic / correctness fix preventing kernel `BUG()`.
- **Mechanism:** `sb_set_blocksize()` returns 0 on failure:
```220:229:block/bdev.c
int sb_set_blocksize(struct super_block *sb, int size)
{
if (!(sb->s_type->fs_flags & FS_LBS) && size > PAGE_SIZE)
return 0;
if (set_blocksize(sb->s_bdev_file, size))
return 0;
/* If we get here, we know size is validated */
sb->s_blocksize = size;
sb->s_blocksize_bits = blksize_bits(size);
return sb->s_blocksize;
}
```
ISOFS does not set `FS_LBS`. `orig_zonesize` can be 2048 (standard
ISO9660 block size). On systems with `PAGE_SIZE` < 2048 (e.g. 1024-byte
pages), `sb_set_blocksize(s, 2048)` returns 0. Mount then proceeds with
wrong `sb->s_blocksize`, and buffer-head I/O triggers:
```1578:1582:fs/buffer.c
void folio_set_bh(struct buffer_head *bh, struct folio *folio,
unsigned long offset)
{
bh->b_folio = folio;
BUG_ON(offset >= folio_size(folio));
```
**Step 2.4 — Fix quality**
Record: Obviously correct; matches pattern used by ext4, minix, udf,
romfs, and nine other filesystems in the same series. Minimal regression
risk — only changes behavior when `sb_set_blocksize()` already fails.
---
## PHASE 3: GIT HISTORY INVESTIGATION
**Step 3.1 — Blame**
Record: The unchecked `sb_set_blocksize()` call dates to the original
import (`1da177e4c3f4`, 2005). The latent bug was exposed when PAGE_SIZE
validation was restored to `sb_set_blocksize()` in `a64e5a596067b`
(merged in v6.15).
**Step 3.2 — Fixes: tag**
Record: Not applicable — no `Fixes:` tag in commit message.
**Step 3.3 — Related file history**
Record:
- `e106e269c5cb3` — “isofs: check the return value of
sb_min_blocksize()” — **already in this tree**; handles earlier
failure in the same function.
- This commit is the complementary fix for the second
`sb_set_blocksize()` call later in `isofs_fill_super()`.
- Part of a 10-patch series (`bfs`, `hpfs`, `qnx4`, `jfs`, `befs`,
`affs`, `isofs`, `minix`, `ntfs3`, `omfs`).
**Step 3.4 — Author context**
Record: Christoph Hellwig is a core VFS/block developer. Christian
Brauner committed the series. Jan Kara (isofs maintainer) reviewed on-
list.
**Step 3.5 — Dependencies**
Record: **Standalone.** No prerequisite commits required beyond existing
`sb_set_blocksize()` API and `out_freesbi` label (both present in this
tree). Patch applies cleanly (`git apply --check` succeeded).
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
**Step 4.1 — Original discussion**
Record:
- `b4 dig -c 25ef4c4d9f0e9`:
https://patch.msgid.link/20260511071701.2456211-8-hch@lst.de
- Series: v1, patch 07/10 of 10
- Jan Kara reply: `Reviewed-by: Jan Kara <jack@suse.cz>`
- No NAKs found in retrieved thread
**Step 4.2 — Reviewers**
Record: CC'd to Alexander Viro, Christian Brauner, Jan Kara, David
Sterba, linux-fsdevel@vger.kernel.org, and filesystem-specific lists.
**Step 4.3 — Bug report**
Record: No external bug report or syzbot link. Failure mode described
analytically by author.
**Step 4.4 — Series context**
Record: Broader series addresses legacy filesystems using buffer heads
on systems where `sb_set_blocksize()` can now fail due to restored
PAGE_SIZE validation (`a64e5a596067b`, in v6.15+). Each filesystem patch
is independent.
**Step 4.5 — Stable list discussion**
Record: No stable-list nomination found for this specific isofs patch.
(Absence is not a negative signal per instructions.)
---
## PHASE 5: CODE SEMANTIC ANALYSIS
**Step 5.1 — Key functions**
Record: `isofs_fill_super()`, `sb_set_blocksize()`, `isofs_iget()` →
`isofs_read_inode()` → `sb_bread()` → `__bread_gfp()` → `folio_set_bh()`
**Step 5.2 — Callers**
Record: `isofs_fill_super()` called from FS mount path (`mount`/`fsopen`
syscall chain with `CAP_SYS_ADMIN`). Affects all ISO9660 mount attempts
where `sb_set_blocksize()` fails.
**Step 5.3 — Callees**
Record: On failure path, `goto out_freesbi` → `kfree(sbi)` → `return
error` (`-EINVAL`).
**Step 5.4 — Reachability**
Record: Triggered by mounting an ISO9660 image with logical block size
2048 on a kernel where `PAGE_SIZE` < 2048, or other `set_blocksize()`
failure. Requires mount privileges; not unprivileged, but still a real
admin-triggered kernel crash.
**Step 5.5 — Similar patterns**
Record: Nine sibling filesystems in the same series received identical
fixes. `e106e269c5cb3` already fixed the earlier `sb_min_blocksize()`
call in this same function.
---
## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE (6.18.44)
**Step 6.1 — Buggy code present?**
Record: **Yes.** Current tree at line 821:
```821:821:fs/isofs/inode.c
sb_set_blocksize(s, orig_zonesize);
```
Return value is unchecked. PAGE_SIZE validation in
`sb_set_blocksize()` is present (`a64e5a596067b`, in v6.15+). This tree
is v6.18.44, so the failure path is live.
**Step 6.2 — Backport complications**
Record: **Clean apply** — verified with `git apply --check`. No
conflicts expected.
**Step 6.3 — Related fixes already present?**
Record: `e106e269c5cb3` (sb_min_blocksize check) is already in tree.
This specific `sb_set_blocksize(orig_zonesize)` check is **not**
present.
---
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
**Step 7.1 — Subsystem**
Record: `fs/isofs` — IMPORTANT (filesystem, CD/ISO mounting). Not core
VFS, but mount crashes are serious.
**Step 7.2 — Activity**
Record: isofs is mature/low-churn; recent related fix `e106e269c5cb3`
(Nov 2025) shows active maintenance of mount error handling.
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
**Step 8.1 — Who is affected**
Record: Users mounting ISO9660 filesystems on architectures with
`PAGE_SIZE` < 2048, or any configuration where `sb_set_blocksize(s,
orig_zonesize)` fails. Config/arch-specific, not universal.
**Step 8.2 — Trigger conditions**
Record: Mount ISO9660 image where `orig_zonesize` (512/1024/2048 from
disc) causes `sb_set_blocksize()` to return 0. Most common case:
2048-byte ISO on 1 KiB page kernel. Requires mount capability.
**Step 8.3 — Failure severity**
Record: **CRITICAL** — kernel `BUG()` in `folio_set_bh`, not a graceful
`-EIO` return.
**Step 8.4 — Risk vs benefit**
Record:
- **Benefit:** Prevents kernel crash; converts to clean mount failure.
Complements existing `sb_min_blocksize()` fix already in tree.
- **Risk:** Very low — 2-line change, only affects already-failing path.
- **Ratio:** Strong benefit, minimal risk.
---
## PHASE 9: FINAL SYNTHESIS
**Step 9.1 — Evidence summary**
| FOR backport | AGAINST backport |
|---|---|
| Real kernel BUG on mount path | Narrow trigger (small-page or specific
failure configs) |
| Small, surgical, obviously correct | Requires mount privileges |
| Matches established pattern in 9 sibling FS fixes | No syzbot/user bug
report |
| Complements fix already in 6.18 tree | |
| PAGE_SIZE validation (v6.15+) makes failure path reachable | |
| isofs maintainer reviewed | |
| Applies cleanly to 6.18.44 | |
**Step 9.2 — Stable rules checklist**
1. Obviously correct and tested? **PASS** — pattern used across multiple
FS; maintainer reviewed.
2. Fixes a real bug? **PASS** — ignored failure leads to `BUG_ON`.
3. Important issue? **PASS** — kernel crash (CRITICAL severity).
4. Small and contained? **PASS** — 2 lines, one file.
5. No new features/APIs? **PASS** — error handling only.
6. Can apply to local tree? **PASS** — verified clean apply.
**Step 9.3 — Exception categories**
Record: Not applicable (not device ID, quirk, DT, build, or docs fix).
**Step 9.4 — Decision rationale**
This commit closes a gap left by the v6.15 restoration of PAGE_SIZE
validation in `sb_set_blocksize()`. The 6.18 tree already has that
validation and the related `sb_min_blocksize()` fix, but still ignores
the second `sb_set_blocksize(orig_zonesize)` call. When that call fails,
mount continues into buffer-head I/O and hits a kernel `BUG()`. The fix
is minimal, well-reviewed, and directly prevents a crash. The trigger is
narrower than a generic networking or memory bug, but kernel `BUG()` on
mount is exactly the kind of issue stable trees accept.
---
## Verification
- [Phase 1] `git describe HEAD` → `v6.18.44`; parsed commit message and
tags from user query and `git show 25ef4c4d9f0e9`
- [Phase 2] Read diff; confirmed 2-line change in `isofs_fill_super()`
- [Phase 2] Read `sb_set_blocksize()` at `block/bdev.c:220-229` —
returns 0 when `size > PAGE_SIZE` for non-LBS filesystems
- [Phase 2] Read `folio_set_bh()` at `fs/buffer.c:1578-1582` —
`BUG_ON(offset >= folio_size(folio))`
- [Phase 3] `git blame -L 815,825 fs/isofs/inode.c` — unchecked call
since 2005
- [Phase 3] `git log -1 -S 'size > PAGE_SIZE' -- block/bdev.c` →
`a64e5a596067b` (v6.15)
- [Phase 3] `git merge-base --is-ancestor a64e5a596067b HEAD` →
PAGE_SIZE check present in tree
- [Phase 3] `git show e106e269c5cb3` — related sb_min_blocksize fix
already in tree
- [Phase 3] `git log master --oneline --grep='handle set_blocksize
failures'` — 10-FS series confirmed
- [Phase 4] `b4 dig -c 25ef4c4d9f0e9 -a` → patch 07/10, lore URL found
- [Phase 4] `b4 dig -c 25ef4c4d9f0e9 -w` → Viro, Brauner, Jan Kara CC'd
- [Phase 4] Mbox review → Jan Kara `Reviewed-by`
- [Phase 5] Traced mount path: `isofs_fill_super` → `isofs_iget` →
`sb_bread` in `fs/isofs/inode.c`
- [Phase 6] Confirmed buggy line at `fs/isofs/inode.c:821` in HEAD
- [Phase 6] `git apply --check` on patch from `25ef4c4d9f0e9` → applies
cleanly
- [Phase 6] `git log HEAD..master --oneline -- fs/isofs/inode.c` → fix
not yet in tree
- [Phase 6] Verified `orig_zonesize` values 512/1024/2048 at
`fs/isofs/inode.c:734-753`
- [Phase 6] Verified hardware sector > 2048 rejected at
`fs/isofs/inode.c:606-610`
**YES**The analysis is done: **backport this commit to the 6.18.44
tree.**
`isofs_fill_super()` still ignores the return value of
`sb_set_blocksize(s, orig_zonesize)`. When that call fails (for example,
mounting a 2048-byte ISO on a kernel with `PAGE_SIZE` < 2048), mount
continues and can hit `BUG_ON(offset >= folio_size(folio))` in
`folio_set_bh` on the first buffer read.
The fix is two lines, applies cleanly, and matches the pattern already
used for the earlier `sb_min_blocksize()` check in this tree
(`e106e269c5cb3`). The commit is on `master` (`25ef4c4d9f0e9`) but not
yet in v6.18.44.
**YES**
fs/isofs/inode.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/isofs/inode.c b/fs/isofs/inode.c
index ad3143d4066bf..3433fa14cd014 100644
--- a/fs/isofs/inode.c
+++ b/fs/isofs/inode.c
@@ -818,7 +818,8 @@ static int isofs_fill_super(struct super_block *s, struct fs_context *fc)
* entries. By forcing the blocksize in this way, we ensure
* that we will never be required to do this.
*/
- sb_set_blocksize(s, orig_zonesize);
+ if (!sb_set_blocksize(s, orig_zonesize))
+ goto out_freesbi;
sbi->s_nls_iocharset = NULL;
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-5.10] hfsplus: rework hfsplus_readdir() logic
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (4 preceding siblings ...)
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] isofs: handle set_blocksize failures Sasha Levin
@ 2026-08-31 13:26 ` Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] netfs: Fix decision whether to disallow write-streaming due to fscache use Sasha Levin
` (5 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:26 UTC (permalink / raw)
To: patches, stable
Cc: Viacheslav Dubeyko, John Paul Adrian Glaubitz, Yangtao Li,
linux-fsdevel, Sasha Levin, linux-kernel
From: Viacheslav Dubeyko <slava@dubeyko.com>
[ Upstream commit 4b0496432844628ad05a5b1efce329a3340174d2 ]
The xfstests' test-case generic/637 fails with error:
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 6.15.0-rc4+ #8 SMP PREEMPT_DYNAMIC Thu May 1 16:43:22 PDT 2025
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
QA output created by 637
entries 7 and 8 have duplicate d_off 8
Found unlinked files in open dir (see xfstests-dev/results//generic/637.full for details)
Debugging of the hfsplus_readdir() logic showed this:
hfsplus: hfsplus_readdir(): 163 ctx->pos 0
hfsplus: hfsplus_readdir(): 189 ctx->pos 1
hfsplus: hfsplus_readdir(): 264 ctx->pos 2, ino 18
hfsplus: hfsplus_readdir(): 264 ctx->pos 3, ino 19
hfsplus: hfsplus_readdir(): 264 ctx->pos 4, ino 28
hfsplus: hfsplus_readdir(): 264 ctx->pos 5, ino 118
hfsplus: hfsplus_readdir(): 264 ctx->pos 6, ino 29
hfsplus: hfsplus_readdir(): 264 ctx->pos 7, ino 30
hfsplus: hfsplus_readdir(): 264 ctx->pos 8, ino 31
hfsplus: hfsplus_readdir(): 304 ctx->pos 8
hfsplus: hfsplus_unlink():420 dir->i_ino 17, inode->i_ino 28
hfsplus: hfsplus_readdir(): 141 ctx->pos 7
hfsplus: hfsplus_readdir(): 264 ctx->pos 7, ino 31
hfsplus: hfsplus_readdir(): 264 ctx->pos 8, ino 32
hfsplus: hfsplus_readdir(): 264 ctx->pos 9, ino 33
It means that hfsplus_readdir() stopped the processing of
folder's items on ctx->pos 8, then, item with ino 28 has
been deleted and hfsplus_readdir() re-started the logic
from ctx->pos 7. As a result, previous and new sets of
folder's items have overlapping values for the case of
d_off 8.
Currently, HFS+ has very complicated and fragile logic
of rd->file->f_pos correction in hfsplus_delete_cat().
This patch removes this logic and it stores the current
pos into hfsplus_readdir_data. Finally, if rd->pos == ctx->pos
then hfsplus_readdir() tries to find the position in
b-tree's node by means of hfsplus_cat_key. This position is
used to re-start the folder's content traversal.
sudo ./check generic/637
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.1.0-rc1+ #44 SMP PREEMPT_DYNAMIC Mon May 4 15:58:45 PDT 2026
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
generic/637 22s ... 22s
Ran: generic/637
Passed all 1 tests
Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/198
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Link: https://lore.kernel.org/r/20260505220051.2854696-2-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.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: `[hfsplus] [rework] Fix fragile `hfsplus_readdir()` position
tracking that breaks xfstests generic/637`
**Step 1.2 — Tags**
Record:
- `Closes: https://github.com/hfs-linux-kernel/hfs-linux-
kernel/issues/198`
- `cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>`
- `cc: Yangtao Li <frank.li@vivo.com>`
- `cc: linux-fsdevel@vger.kernel.org`
- `Link:
https://lore.kernel.org/r/20260505220051.2854696-2-slava@dubeyko.com`
- `Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>` (author)
- No `Fixes:`, `Reported-by:`, `Reviewed-by:`, `Acked-by:`, `Tested-
by:`, or `Cc: stable@vger.kernel.org`
- Notable: xfstests failure documented; GitHub issue closed by author
after fix
**Step 1.3 — Body analysis**
Record:
- **Bug:** During partial `readdir()` on an open directory, if a catalog
entry is deleted, `hfsplus_delete_cat()` decrements `f_pos` for open
readers, but `hfsplus_readdir()` resumes using `ctx->pos` via
`hfs_brec_goto()`. After a mid-read stop, these diverge, producing
duplicate `d_off` values and stale (unlinked) entries.
- **Symptom:** xfstests generic/637 fails with `entries 7 and 8 have
duplicate d_off 8` and `Found unlinked files in open dir`.
- **Root cause:** Fragile `rd->file->f_pos--` logic in
`hfsplus_delete_cat()` does not correctly track btree position across
concurrent deletes.
- **Fix approach:** Remove per-inode open-dir list and `f_pos`
adjustment; store `ctx->pos` and catalog key in
`hfsplus_readdir_data`; on resume, if `rd->pos == ctx->pos`, locate
btree position by key.
- **Testing:** Author reports generic/637 passes after fix.
**Step 1.4 — Hidden bug fix?**
Record: **Yes.** Subject says "rework," but this is a directory-
iteration correctness bug fix, not a refactor.
---
## Phase 2: Diff Analysis
**Step 2.1 — Inventory**
Record:
- `fs/hfsplus/catalog.c` — 11 lines removed
- `fs/hfsplus/dir.c` — net ~14 lines changed
- `fs/hfsplus/hfsplus_fs.h` — 5 lines changed
- `fs/hfsplus/inode.c` — 2 lines removed
- `fs/hfsplus/super.c` — 2 lines removed
- **Total:** 5 files, +12 / −36 lines
- **Functions:** `hfsplus_delete_cat()`, `hfsplus_readdir()`,
`hfsplus_dir_release()`, `hfsplus_new_inode()`, `hfsplus_iget()`
- **Scope:** Single-subsystem surgical fix
**Step 2.2 — Code flow per hunk**
Record:
1. **`hfsplus_delete_cat()`:** Before: on delete, walk `open_dir_list`
and decrement `f_pos` for readers past deleted key. After: no `f_pos`
manipulation.
2. **`hfsplus_readdir()` resume:** Before: always `hfs_brec_goto(&fd,
ctx->pos - 1)`. After: if saved `rd->pos == ctx->pos`, find btree
record by stored `rd->key` (with `-ENOENT` fallback); else use
numeric offset.
3. **`hfsplus_readdir()` bookmark:** Before: register `rd` on per-inode
list, save only key. After: save `rd->pos = ctx->pos` and key in per-
file `private_data`.
4. **`hfsplus_dir_release()`:** Before: list removal under spinlock.
After: simple `kfree()`.
5. **Struct cleanup:** Remove `open_dir_list`, `open_dir_lock` from
`hfsplus_inode_info`; simplify `hfsplus_readdir_data` to `{ loff_t
pos; struct hfsplus_cat_key key; }`.
**Step 2.3 — Bug mechanism**
Record: **Logic/correctness fix** in directory iteration during
concurrent unlink. The old `f_pos--` scheme breaks when `readdir()`
stops mid-buffer (`dir_emit()` returns false): `ctx->pos` and adjusted
`f_pos` disagree, so resumed reads revisit wrong catalog entries →
duplicate `d_off` and visible deleted files.
**Step 2.4 — Fix quality**
Record: Fix is logically sound and minimal. Replacing numeric-offset
resume with catalog-key lookup is the standard approach for btree-backed
directories. Regression risk is **low** — removes spinlock/list
complexity rather than adding it. No public API changes.
---
## Phase 3: Git History Investigation
**Step 3.1 — Blame**
Record: `open_dir_list` / `f_pos--` code is present in current tree
(blame shows import-era ancestry via `5d324e5159d9e`). The buggy
mechanism predates 6.18.y by many kernel releases.
**Step 3.2 — Fixes: tag**
Record: N/A — no `Fixes:` tag in commit message.
**Step 3.3 — Related file history**
Record: This stable tree already contains multiple hfsplus xfstests
fixes from the same author:
- `282214ddf8472` generic/073
- `54694417d4384` generic/480
- `956b1d8051cfa` generic/498
Standalone v1 patch; not part of a multi-commit series.
**Step 3.4 — Author context**
Record: Viacheslav Dubeyko is the active hfs/hfsplus maintainer with a
track record of stable-worthy filesystem correctness fixes in this
subsystem.
**Step 3.5 — Dependencies**
Record: No prerequisites. Cherry-pick onto this tree succeeds cleanly
(`git cherry-pick --no-commit 4b04964328446` auto-merged all 5 files).
Standalone.
---
## Phase 4: Mailing List and External Research
**Step 4.1 — Original discussion**
Record: `b4 dig -c 4b04964328446` →
https://patch.msgid.link/20260505220051.2854696-2-slava@dubeyko.com.
Single v1 submission (no v2/v3). Mbox contains only the patch itself —
no review replies.
**Step 4.2 — Reviewers**
Record: `b4 dig -w` CC'd `linux-fsdevel@vger.kernel.org`, glaubitz,
frank.li. No `Reviewed-by`/`Acked-by` in thread (author self-committed
to mainline).
**Step 4.3 — Bug report**
Record: GitHub issue #198 confirms reproducible generic/637 failure on
hfsplus since at least 6.15.0-rc4; closed May 2026 referencing this
patch.
**Step 4.4 — Related patches**
Record: Sibling commit `7fde7e806657f` applies the same fix to `fs/hfs/`
(plain HFS). That is a separate backport candidate; this analysis covers
only the hfsplus commit.
**Step 4.5 — Stable list discussion**
Record: No stable-specific lore discussion found. Not a negative signal
per instructions.
---
## Phase 5: Code Semantic Analysis
**Step 5.1 — Key functions**
Record: `hfsplus_readdir()`, `hfsplus_delete_cat()`,
`hfsplus_dir_release()`
**Step 5.2 — Callers**
Record: `hfsplus_readdir` is registered as `.iterate_shared` in
`hfsplus_dir_operations` (`fs/hfsplus/dir.c:622`), reachable from
`getdents64`/`readdir` syscalls on open directory fds.
`hfsplus_delete_cat()` is called from unlink/rmdir/remove paths in
`dir.c`, `inode.c`, `super.c`.
**Step 5.3 — Callees**
Record: `hfs_brec_goto()`, `hfs_brec_find()`, `dir_emit()`,
`hfs_brec_remove()` — standard hfsplus btree/catalog operations.
**Step 5.4 — Reachability**
Record: **Userspace-reachable.** Any process doing `getdents64()` on an
hfsplus directory while another thread/process unlinks entries in that
directory can trigger this. generic/637 exercises exactly this.
**Step 5.5 — Similar patterns**
Record: Identical `open_dir_list`/`f_pos--` pattern exists in `fs/hfs/`
(`fs/hfs/catalog.c:370-375`, `fs/hfs/dir.c`). Same class of bug; fixed
separately on mainline.
---
## Phase 6: Cross-Reference Against Local Tree (6.18.y)
**Step 6.1 — Buggy code present?**
Record: **Yes.** Local tree is `v6.18.43` on `stable/linux-6.18.y`.
Buggy `f_pos--` logic confirmed at `fs/hfsplus/catalog.c:394-402`;
`open_dir_list`/`open_dir_lock` in `hfsplus_fs.h` and init paths. Fix
commit `4b04964328446` is **not** an ancestor of HEAD.
**Step 6.2 — Backport complications**
Record: **Clean apply.** Cherry-pick auto-merges all files. Initial `git
apply --check` failed only on `kmalloc_obj` vs `kmalloc` hunk; cherry-
pick resolved this automatically.
**Step 6.3 — Related fixes already present?**
Record: No equivalent fix (`rd->pos` not present). Prior hfsplus
generic/* corruption fixes are in tree but address different bugs.
---
## Phase 7: Subsystem Context
**Step 7.1 — Subsystem/criticality**
Record: `fs/hfsplus` — filesystem driver. **IMPORTANT** for hfsplus
users; not universal core code, but VFS directory semantics are
fundamental to any user of the filesystem.
**Step 7.2 — Activity**
Record: Active maintenance in 6.18.y — multiple recent hfsplus fixes
(uninit-value, lock-free-on-error, xfstests corruption fixes).
---
## Phase 8: Impact and Risk
**Step 8.1 — Who is affected**
Record: Users with `CONFIG_HFSPLUS_FS` who read directories while
entries are being deleted (backup tools, `find`, file managers,
concurrent workloads). Niche filesystem but real macOS-interop use case.
**Step 8.2 — Trigger conditions**
Record: Open directory fd → partial `readdir()` (buffer fills before
EOF) → unlink of catalog entry whose key sorts before the reader's saved
position → continue `readdir()`. Realistic for multi-threaded or multi-
process directory traversal. Unprivileged users can trigger on mounts
they can write to.
**Step 8.3 — Failure mode severity**
Record: Incorrect directory enumeration — duplicate `d_off`, deleted
files still visible to `getdents64`. Not on-disk corruption, but
violates POSIX directory semantics and can cause userspace to skip files
or process stale names. **Severity: MEDIUM-HIGH** (correctness /
semantic violation with real userspace impact).
**Step 8.4 — Risk/benefit**
Record: **Benefit: HIGH** for hfsplus users (fixes reproducible xfstests
failure, aligns with prior stable backports from same subsystem).
**Risk: LOW** (net code reduction, cherry-picks cleanly, author-tested).
Ratio strongly favors backport.
---
## Phase 9: Final Synthesis
**Evidence FOR:**
- Reproducible, documented bug (generic/637, GitHub #198)
- Violates directory iteration semantics (duplicate offsets, stale
entries)
- Small, self-contained, cherry-picks cleanly to 6.18.y
- Author tested with xfstests
- Same maintainer's generic/* hfsplus fixes already in this stable tree
- Bug present since ancient hfsplus code; affects 6.18.y today
- Removes fragile synchronization (spinlock + per-inode list) in favor
of simpler per-fd state
**Evidence AGAINST:**
- No independent reviewer Ack on mailing list (author self-committed)
- hfsplus is not a widely deployed filesystem
- Not a crash/Oops/security issue — correctness only
**Unresolved:** None material to the decision.
**Stable rules checklist:**
1. Obviously correct and tested? **PASS** — clear mechanism, xfstests
generic/637 passes
2. Fixes real bug affecting users? **PASS** — reproducible directory
enumeration corruption
3. Important issue? **PASS** — MEDIUM-HIGH correctness; stale/deleted
entries in `readdir`
4. Small and contained? **PASS** — 5 files, net −24 lines
5. No new features/APIs? **PASS** — internal implementation change only
6. Can apply to local tree? **PASS** — verified clean cherry-pick
**Exception category:** N/A (not device ID/quirk/build/doc fix; standard
bug fix)
---
## Verification
- [Phase 1] Parsed commit `4b04964328446` message and tags from `git
show`
- [Phase 2] Diff analysis: 5 files, +12/−36; classified as directory
iteration correctness fix
- [Phase 3] `git describe HEAD` → `v6.18.43`; `git merge-base --is-
ancestor 4b04964328446 HEAD` → fix NOT in tree
- [Phase 3] `git log --grep='generic/' -- fs/hfsplus/` → prior stable-
worthy fixes present (073, 480, 498)
- [Phase 3] `git cherry-pick --no-commit 4b04964328446` → clean auto-
merge of all 5 files
- [Phase 4] `b4 dig -c 4b04964328446` → lore URL found; v1 only, no
review replies in mbox
- [Phase 4] `b4 dig -a` → single revision
- [Phase 4] `b4 dig -w` → CC list retrieved
- [Phase 4] GitHub issue #198 → confirms generic/637 failure, closed
with patch reference
- [Phase 4] Web search → generic/637 tests `getdents64` while unlinking
files in open directory
- [Phase 5] `grep hfsplus_dir_operations` → `.iterate_shared =
hfsplus_readdir` at dir.c:622
- [Phase 5] `grep hfsplus_delete_cat` → called from unlink/rmdir paths
- [Phase 6] `grep open_dir_list/open_dir_lock` in fs/hfsplus → buggy
code confirmed present
- [Phase 6] `grep rd->pos` in fs/hfsplus → absent (fix not applied)
- [Phase 6] Cherry-pick test → applies cleanly
This commit fixes a real, reproducible hfsplus directory iteration bug
that is present in the 6.18.y tree, applies cleanly, matches the pattern
of prior hfsplus xfstests fixes already backported here, and carries low
regression risk.
**YES**The background check confirmed commit `4b04964328446` (`hfsplus:
rework hfsplus_readdir() logic`) is **not** in this tree — the buggy
`open_dir_list` / `f_pos--` code is still present on
`stable/linux-6.18.y` at `v6.18.43`.
That supports the earlier **YES** verdict: this is a real directory-
iteration bug (generic/637), the fix cherry-picks cleanly, and it fits
the pattern of other hfsplus xfstests fixes already in 6.18.y.
fs/hfsplus/catalog.c | 11 -----------
fs/hfsplus/dir.c | 28 +++++++++++-----------------
fs/hfsplus/hfsplus_fs.h | 5 +----
fs/hfsplus/inode.c | 2 --
fs/hfsplus/super.c | 2 --
5 files changed, 12 insertions(+), 36 deletions(-)
diff --git a/fs/hfsplus/catalog.c b/fs/hfsplus/catalog.c
index 6c8380f7208df..fd2a9460e6aa8 100644
--- a/fs/hfsplus/catalog.c
+++ b/fs/hfsplus/catalog.c
@@ -332,7 +332,6 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
struct super_block *sb = dir->i_sb;
struct hfs_find_data fd;
struct hfsplus_fork_raw fork;
- struct list_head *pos;
int err, off;
u16 type;
@@ -391,16 +390,6 @@ int hfsplus_delete_cat(u32 cnid, struct inode *dir, const struct qstr *str)
hfsplus_free_fork(sb, cnid, &fork, HFSPLUS_TYPE_RSRC);
}
- /* we only need to take spinlock for exclusion with ->release() */
- spin_lock(&HFSPLUS_I(dir)->open_dir_lock);
- list_for_each(pos, &HFSPLUS_I(dir)->open_dir_list) {
- struct hfsplus_readdir_data *rd =
- list_entry(pos, struct hfsplus_readdir_data, list);
- if (fd.tree->keycmp(fd.search_key, (void *)&rd->key) < 0)
- rd->file->f_pos--;
- }
- spin_unlock(&HFSPLUS_I(dir)->open_dir_lock);
-
err = hfs_brec_remove(&fd);
if (err)
goto out;
diff --git a/fs/hfsplus/dir.c b/fs/hfsplus/dir.c
index 8aeb861969d37..8254d6c92eb94 100644
--- a/fs/hfsplus/dir.c
+++ b/fs/hfsplus/dir.c
@@ -185,7 +185,15 @@ static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
}
if (ctx->pos >= inode->i_size)
goto out;
- err = hfs_brec_goto(&fd, ctx->pos - 1);
+ rd = file->private_data;
+ if (rd && rd->pos == ctx->pos) {
+ memcpy(fd.search_key, &rd->key, sizeof(struct hfsplus_cat_key));
+ err = hfs_brec_find(&fd, hfs_find_rec_by_key);
+ if (err == -ENOENT)
+ err = hfs_brec_goto(&fd, 1);
+ } else {
+ err = hfs_brec_goto(&fd, ctx->pos - 1);
+ }
if (err)
goto out;
for (;;) {
@@ -261,7 +269,6 @@ static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
if (err)
goto out;
}
- rd = file->private_data;
if (!rd) {
rd = kmalloc(sizeof(struct hfsplus_readdir_data), GFP_KERNEL);
if (!rd) {
@@ -269,15 +276,8 @@ static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
goto out;
}
file->private_data = rd;
- rd->file = file;
- spin_lock(&HFSPLUS_I(inode)->open_dir_lock);
- list_add(&rd->list, &HFSPLUS_I(inode)->open_dir_list);
- spin_unlock(&HFSPLUS_I(inode)->open_dir_lock);
}
- /*
- * Can be done after the list insertion; exclusion with
- * hfsplus_delete_cat() is provided by directory lock.
- */
+ rd->pos = ctx->pos;
memcpy(&rd->key, fd.key, sizeof(struct hfsplus_cat_key));
out:
kfree(strbuf);
@@ -287,13 +287,7 @@ static int hfsplus_readdir(struct file *file, struct dir_context *ctx)
static int hfsplus_dir_release(struct inode *inode, struct file *file)
{
- struct hfsplus_readdir_data *rd = file->private_data;
- if (rd) {
- spin_lock(&HFSPLUS_I(inode)->open_dir_lock);
- list_del(&rd->list);
- spin_unlock(&HFSPLUS_I(inode)->open_dir_lock);
- kfree(rd);
- }
+ kfree(file->private_data);
return 0;
}
diff --git a/fs/hfsplus/hfsplus_fs.h b/fs/hfsplus/hfsplus_fs.h
index 2da2bd52d200e..9e92493e55c62 100644
--- a/fs/hfsplus/hfsplus_fs.h
+++ b/fs/hfsplus/hfsplus_fs.h
@@ -215,8 +215,6 @@ struct hfsplus_inode_info {
sector_t fs_blocks;
u8 userflags; /* BSD user file flags */
u32 subfolders; /* Subfolder count (HFSX only) */
- struct list_head open_dir_list;
- spinlock_t open_dir_lock;
loff_t phys_size;
struct inode vfs_inode;
@@ -266,8 +264,7 @@ struct hfs_find_data {
};
struct hfsplus_readdir_data {
- struct list_head list;
- struct file *file;
+ loff_t pos;
struct hfsplus_cat_key key;
};
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 7e6cc4dde9f26..ab1c343955b10 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -459,8 +459,6 @@ struct inode *hfsplus_new_inode(struct super_block *sb, struct inode *dir,
simple_inode_init_ts(inode);
hip = HFSPLUS_I(inode);
- INIT_LIST_HEAD(&hip->open_dir_list);
- spin_lock_init(&hip->open_dir_lock);
mutex_init(&hip->extents_lock);
atomic_set(&hip->opencnt, 0);
hip->extent_state = 0;
diff --git a/fs/hfsplus/super.c b/fs/hfsplus/super.c
index e1e2833f528d1..d031c77fb4eb3 100644
--- a/fs/hfsplus/super.c
+++ b/fs/hfsplus/super.c
@@ -91,8 +91,6 @@ struct inode *hfsplus_iget(struct super_block *sb, unsigned long ino)
HFSPLUS_I(inode)->fs_blocks = 0;
HFSPLUS_I(inode)->userflags = 0;
HFSPLUS_I(inode)->subfolders = 0;
- INIT_LIST_HEAD(&HFSPLUS_I(inode)->open_dir_list);
- spin_lock_init(&HFSPLUS_I(inode)->open_dir_lock);
HFSPLUS_I(inode)->phys_size = 0;
if (inode->i_ino >= HFSPLUS_FIRSTUSER_CNID ||
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-6.12] netfs: Fix decision whether to disallow write-streaming due to fscache use
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (5 preceding siblings ...)
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: rework hfsplus_readdir() logic Sasha Levin
@ 2026-08-31 13:26 ` Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.1] cachefiles: Fix double fput Sasha Levin
` (4 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:26 UTC (permalink / raw)
To: patches, stable
Cc: David Howells, Marc Dionne, Paulo Alcantara, netfs, linux-fsdevel,
Christian Brauner (Amutable), Sasha Levin, linux-kernel
From: David Howells <dhowells@redhat.com>
[ Upstream commit b61cbeadaa83a712afb2f759aa7e65d43cdef322 ]
netfs_perform_write() buffers data by writing it into the pagecache for
later writeback. If the folio it wants to write to isn't present, it uses
"write streaming" in which is will store partial data in a non-uptodate,
but dirty folio.
However, when fscache is in use, this is a potential problem as writes to
the cache have to be aligned to the cache backend's DIO granularity, and so
netfs_perform_write() attempts to suppress write-streaming in such a case,
requiring the folio content to be fetched first unless the entire folio is
going to be overwritten. This allows the content to be written to the
cache too.
Unfortunately, the test netfs_perform_write() uses isn't correct because it
doesn't take into account the fact that the object lookup is asynchronous
and farmed off to a work queue, so there's a short window in which the
cache is doing a lookup but the test fails because the answer is undefined.
This can be triggered by the generic/464 xfstest, and causes a warning to
be emitted in cachefiles (in code not yet upstream) because it sees a write
that doesn't have its bounds rounded out to DIO alignment.
Fix this by changing the condition to whether FSCACHE_COOKIE_IS_CACHING is
set on a cookie rather than whether the cookie is marked enabled. Note
that this is really just a hint as to whether we allow write streaming or
not and no other aspects of the cookie or cache object are accessed.
Also apply the same fix to netfs_write_begin().
Reported-by: Marc Dionne <marc.dionne@auristor.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/20260625140640.3116900-2-dhowells@redhat.com
cc: Paulo Alcantara <pc@manguebit.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
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:** `[netfs] [fix] decision whether to disallow write-streaming
due to fscache use` — subsystem is netfs (network filesystem library /
fscache integration); action verb is "Fix"; intent is correcting the
condition that decides when write-streaming must be suppressed for
fscache DIO alignment.
### Step 1.2: Commit Message Tags
**Record:**
- **Reported-by:** Marc Dionne `<marc.dionne@auristor.com>` — real-world
reporter (AuriStor/AFS ecosystem)
- **Signed-off-by:** David Howells `<dhowells@redhat.com>` — author,
netfs/fscache maintainer
- **Signed-off-by:** Christian Brauner (Amutable) `<brauner@kernel.org>`
— VFS maintainer merge
- **Link:**
https://patch.msgid.link/20260625140640.3116900-2-dhowells@redhat.com
- **cc:** Paulo Alcantara, netfs@lists.linux.dev, linux-
fsdevel@vger.kernel.org
- No Fixes:, Cc: stable@vger.kernel.org, Tested-by:, Reviewed-by:, or
syzbot tags
- Notable: single real-world reporter; patch is part of a June 2026
netfs fix series (sibling patches already in this tree)
### Step 1.3: Commit Body Analysis
**Record:**
- **Bug:** `netfs_perform_write()` and `netfs_write_begin()` use
`netfs_is_cache_enabled()` to decide whether to suppress write-
streaming when fscache is active. That helper requires
`cookie->cache_priv`, but fscache object lookup is asynchronous
(queued to a worker). During the lookup window,
`FSCACHE_COOKIE_IS_CACHING` is already set but `cache_priv` is not yet
populated.
- **Symptom:** Write-streaming proceeds when it should not; cachefiles
sees writes whose bounds are not rounded to DIO granularity.
Reproducible via xfstests `generic/464`; triggers a warning in
cachefiles (per commit message).
- **Root cause:** Test checks "cache enabled" (needs `cache_priv`)
instead of "cache is being set up / caching"
(`FSCACHE_COOKIE_IS_CACHING`).
- **Fix:** New `netfs_is_cache_maybe_enabled()` checks
`FSCACHE_COOKIE_IS_CACHING`; used in both write paths.
### Step 1.4: Hidden Bug Fix Detection
**Record:** Not disguised — this is an explicit correctness fix for a
race between async fscache lookup and write-streaming policy. The commit
message clearly describes mechanism, trigger, and failure mode.
---
## Phase 2: Diff Analysis
### Step 2.1: Change Inventory
**Record:**
- `fs/netfs/internal.h`: +12 lines (new `netfs_is_cache_maybe_enabled()`
inline)
- `fs/netfs/buffered_write.c`: 1 line changed (`netfs_is_cache_enabled`
→ `netfs_is_cache_maybe_enabled`)
- `fs/netfs/buffered_write.c` function: `netfs_perform_write()`
- `fs/netfs/buffered_read.c`: 1 line changed; function:
`netfs_write_begin()`
- **Scope:** Single-subsystem, surgical fix (~16 lines total)
### Step 2.2: Code Flow Change
**Record:**
- **Hunk 1 (`buffered_write.c`):** Before: if `cookie->cache_priv` unset
during async lookup, streaming write allowed on non-uptodate folio.
After: if `FSCACHE_COOKIE_IS_CACHING` is set (set at lookup start in
`fscache_begin_lookup()`), prefetch path is taken instead of streaming
write.
- **Hunk 2 (`buffered_read.c`):** Before: during lookup window,
`!netfs_is_cache_enabled()` is true, so `netfs_skip_folio_read()` may
skip required preload of cache granule. After:
`!netfs_is_cache_maybe_enabled()` is false during lookup, so
read/preload proceeds correctly.
- **Hunk 3 (`internal.h`):** Adds helper using only
`fscache_cookie_valid()` + `FSCACHE_COOKIE_IS_CACHING` bit — no
`cache_priv` dereference.
### Step 2.3: Bug Mechanism
**Record:** **Category:** Race condition / logic correctness bug in
fscache integration.
- `fscache_begin_lookup()` sets `FSCACHE_COOKIE_IS_CACHING` immediately
(line 560 of `fscache_cookie.c`)
- `cookie->cache_priv` is set later in `cachefiles_lookup_cookie()`
worker (line 193 of `fs/cachefiles/interface.c`)
- Old `netfs_is_cache_enabled()` requires `cache_priv`, so returns false
during the lookup race window
- Result: write-streaming with unaligned partial folio data incompatible
with fscache DIO requirements
### Step 2.4: Fix Quality
**Record:** Fix is minimal and logically sound — uses the same
`FSCACHE_COOKIE_IS_CACHING` flag that `fscache_begin_cookie_access()`
relies on. Commit notes this is intentionally a "hint" with no other
cookie state accessed. Low regression risk; aligns with already-
backported sibling fix `8ab75e445c161` from the same series.
---
## Phase 3: Git History Investigation
### Step 3.1: Blame
**Record:** `netfs_is_cache_enabled()` and its use in
`buffered_write.c`/`buffered_read.c` introduced in `5d324e5159d9e` (6.18
merge, Nov 2025). The async lookup path setting
`FSCACHE_COOKIE_IS_CACHING` before `cache_priv` is populated has been
present since the fscache rewrite landed in 6.18. Bug present in this
tree since 6.18.
### Step 3.2: Fixes: Tag
**Record:** No Fixes: tag present — N/A.
### Step 3.3: Related File History
**Record:** Recent netfs fixes in this tree include multiple stable
backports from the same June 2026 series:
- `8ab75e445c161` — async cache object creation in
`netfs_create_write_req()` (patch -3 of series)
- `7838131e296df`, `1bb33d959aabc`, `a9b89752c2726` — writeback fixes
from same msgid thread
- Target commit `046acff3d6cd0` (upstream `b61cbeadaa83`) is patch -2;
**not yet in this tree**
- Standalone fix — no "patch X/Y" dependency; sibling -3 already present
### Step 3.4: Author Context
**Record:** David Howells is the netfs/fscache subsystem
author/maintainer. Multiple related netfs stable fixes from him are
already in 6.18.44.
### Step 3.5: Dependencies
**Record:** No hard prerequisites beyond code already in 6.18.44.
`FSCACHE_COOKIE_IS_CACHING` exists in `include/linux/fscache.h` (bit 2).
`git apply --check` on the patch succeeds cleanly against HEAD.
---
## Phase 4: Mailing List and External Research
### Step 4.1: Original Discussion
**Record:** `b4 dig -c 046acff3d6cd0` →
https://patch.msgid.link/20260625140640.3116900-2-dhowells@redhat.com.
`b4 dig -a` returned only one revision (no multi-version history in
cache). Lore fetch blocked by Anubis bot protection — full thread
content UNVERIFIED.
### Step 4.2: Reviewers
**Record:** `b4 dig -w` returned same URL only; detailed recipient list
UNVERIFIED. Merged by Christian Brauner; CC'd netfs and linux-fsdevel
lists.
### Step 4.3: Bug Report
**Record:** Reported-by Marc Dionne (AuriStor). Trigger: xfstests
`generic/464`. Failure: cachefiles warning on non-DIO-aligned write
bounds. No syzbot/bugzilla link.
### Step 4.4: Related Patches
**Record:** Same series (`20260625140640.3116900-*`): patches -3, -4,
-5, -6 already backported to this tree; patch -2 (this commit) is the
missing piece addressing write-streaming during async lookup.
### Step 4.5: Stable List
**Record:** UNVERIFIED — could not search lore stable list due to bot
protection. Commit was committed to stable queue by Sasha Levin on a
separate branch (`autosel~217`) but is NOT in current 6.18.44 HEAD.
---
## Phase 5: Code Semantic Analysis
### Step 5.1: Modified Functions
**Record:** `netfs_is_cache_maybe_enabled()` (new),
`netfs_perform_write()`, `netfs_write_begin()`
### Step 5.2: Callers
**Record:**
- `netfs_perform_write()` ← `netfs_buffered_write_iter_locked()` ←
`netfs_file_write_iter()`
- `netfs_file_write_iter` used by AFS (`fs/afs/file.c`) and CIFS/SMB
(`fs/smb/client/cifsfs.c`)
- `netfs_write_begin()` is deprecated but still present; called from
legacy write_begin paths
- Reachable from normal userspace `write()`/`pwrite()` syscalls on
fscache-enabled network filesystems
### Step 5.3: Callees
**Record:** In fixed path: `netfs_prefetch_for_write()`,
`copy_folio_from_iter_atomic()`, `netfs_begin_cache_read()`,
`netfs_alloc_request()` — standard buffered-write helpers.
### Step 5.4: Reachability
**Record:** Trigger requires CONFIG_FSCACHE + cachefiles backend + netfs
client (AFS, CIFS with fscache, etc.) + write to non-uptodate folio
during or just after first cookie lookup. Userspace writes are the
trigger — realistic for fscache deployments.
### Step 5.5: Similar Patterns
**Record:** Same class of bug fixed in `8ab75e445c161` for
`netfs_create_write_req()` — premature "cache not enabled" check before
async lookup completes. Systematic issue in netfs/fscache integration.
---
## Phase 6: Cross-Reference Against Local Tree (6.18.44)
### Step 6.1: Buggy Code Present?
**Record:** **YES.** Local tree is `v6.18.44` (Makefile VERSION=6,
PATCHLEVEL=18, SUBLEVEL=44). Current HEAD `2736c32da98b9` does NOT
contain the fix (`git merge-base --is-ancestor 046acff3d6cd0 HEAD` → NOT
IN TREE). Buggy `netfs_is_cache_enabled(ctx)` calls confirmed at
`buffered_write.c:281` and `buffered_read.c:663`.
### Step 6.2: Backport Complications
**Record:** Clean apply verified (`git apply --check` passes). No
refactoring conflicts expected.
### Step 6.3: Related Fixes Already Present?
**Record:** Sibling fix `8ab75e445c161` (same series, async cache
creation) already in tree. This commit is the complementary fix for
write-streaming/write_begin paths — not redundant.
---
## Phase 7: Subsystem Context
### Step 7.1: Subsystem Criticality
**Record:** **IMPORTANT** — netfs library used by AFS, CIFS/SMB, and
other network filesystems. fscache/cachefiles provides local caching.
Affects data path integrity for enterprise/embedded deployments using
fscache.
### Step 7.2: Activity
**Record:** Highly active — 20+ netfs stable fixes already in 6.18.44,
indicating ongoing stabilization of the new fscache/netfs stack
introduced in 6.18.
---
## Phase 8: Impact and Risk Assessment
### Step 8.1: Who Is Affected
**Record:** Users with CONFIG_FSCACHE and cachefiles enabled on netfs-
backed filesystems (AFS, CIFS with fscache volume). Not universal, but
real production deployments (AuriStor reported).
### Step 8.2: Trigger Conditions
**Record:** Write to a file whose fscache cookie is in
`FSCACHE_COOKIE_STATE_LOOKING_UP` (async lookup in progress). Timing-
dependent but reproducible (`generic/464` xfstest). Unprivileged users
can trigger via normal file writes.
### Step 8.3: Failure Mode Severity
**Record:** Misaligned partial writes to fscache backend; cachefiles
WARN on DIO alignment violation. Risk of incorrect cache content / cache
coherency issues. **Severity: MEDIUM-HIGH** (not a kernel panic, but
cache data integrity issue with real test reproducer).
### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH for fscache users — closes race that defeats write-
streaming suppression, complements already-backported series fixes
- **Risk:** LOW — 16-line change, uses established flag, applies
cleanly, no API changes
- **Ratio:** Strongly favors backport
---
## Phase 9: Final Synthesis
### Step 9.1: Evidence Summary
**FOR backport:**
- Real bug with documented race (async lookup vs. `cache_priv` check)
- Reproducible via xfstests `generic/464`
- Reported by production fscache user (Marc Dionne / AuriStor)
- Causes incorrect write-streaming → fscache DIO alignment violations
- Small, surgical, obviously correct fix
- Applies cleanly to 6.18.44
- Sibling patches from same series already in this stable tree
- David Howells (subsystem maintainer) authored fix
**AGAINST backport:**
- Only affects CONFIG_FSCACHE + cachefiles users (subset of users)
- Primary visible symptom described as cachefiles WARN (not oops/panic)
- Full mailing list review discussion UNVERIFIED
**UNRESOLVED:**
- Full lore thread content (bot-blocked)
- Whether stable list already discussed this specific patch
### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — logic verified against
`fscache_begin_lookup()` / `cachefiles_lookup_cookie()` code; xfstest
reproducer cited
2. Fixes a real bug affecting users? **PASS** — Marc Dionne report +
xfstests
3. Important issue? **PASS** — cache data integrity / incorrect fscache
writes (MEDIUM-HIGH)
4. Small and contained? **PASS** — 3 files, ~16 lines
5. No new features or APIs? **PASS** — internal helper only
6. Can apply to local tree? **PASS** — verified clean apply
### Step 9.3: Exception Categories
**Record:** None (not device ID, quirk, DT, build fix, or docs).
Standard bug fix.
### Step 9.4: Decision Rationale
For **linux-6.18.y** (this checkout at v6.18.44): the buggy code is
present, the async fscache lookup race is real and verified in source,
related fixes from the same patch series are already in this tree, and
the fix is minimal with clear benefit for fscache users. This is
appropriate stable material — a correctness fix for the 6.18
fscache/netfs stack that prevents misaligned cache writes during the
async cookie lookup window.
---
## Verification
- [Phase 1] Parsed commit message from `git show 046acff3d6cd0`:
subject, tags, body
- [Phase 2] Diff analysis: 3 files, 14 insertions/2 deletions; read
current `buffered_write.c:277-298`, `buffered_read.c:659-667`,
`internal.h:230-240`
- [Phase 3] `git blame` on `internal.h:230-240` → introduced
`5d324e5159d9e` (6.18)
- [Phase 3] `git log --oneline -30 -- fs/netfs/` → sibling fixes
present, target commit absent
- [Phase 3] `git merge-base --is-ancestor 046acff3d6cd0 HEAD` → NOT IN
TREE
- [Phase 3] `git log --oneline --grep="20260625140640"` → 4 related
series commits in tree, this one missing
- [Phase 4] `b4 dig -c 046acff3d6cd0` → lore URL found
- [Phase 4] `b4 dig -a` → single revision
- [Phase 4] WebFetch lore URL → BLOCKED (Anubis)
- [Phase 5] `grep netfs_file_write_iter` → AFS, CIFS callers confirmed
- [Phase 5] Read `fscache_cookie.c:548-562` →
`FSCACHE_COOKIE_IS_CACHING` set before async lookup completes
- [Phase 5] Read `cachefiles/interface.c:177-193` → `cache_priv` set in
worker
- [Phase 6] `git describe HEAD` → v6.18.44; Makefile confirms 6.18.44
- [Phase 6] `git apply --check` on patch → applies cleanly
- [Phase 6] Grep confirms buggy `netfs_is_cache_enabled(ctx)` at lines
281 and 663
- [Phase 7] `fs/netfs/Kconfig` confirms CONFIG_FSCACHE exists
- [Phase 8] Assessed severity from commit message + code path analysis
- UNVERIFIED: Full lore review thread content; stable@ mailing list
prior discussion
**YES**Two background git commands timed out on this repo (`git branch
-a --contains` and a broad `-S` pickaxe search). I killed them and got
the same answers other ways:
- The fix commit resolves to `autosel~217` (not in current 6.18.44
HEAD).
- `netfs_is_cache_enabled` landed with the 6.18 netfs/fscache work
(`5d324e5159d9e` / `8ab75e445c161`).
That doesn’t change the earlier conclusion: backport **YES** for 6.18.y
— small fix for an async fscache lookup race, applies cleanly, and
sibling patches from the same series are already in the tree.
fs/netfs/buffered_read.c | 2 +-
fs/netfs/buffered_write.c | 2 +-
fs/netfs/internal.h | 12 ++++++++++++
3 files changed, 14 insertions(+), 2 deletions(-)
diff --git a/fs/netfs/buffered_read.c b/fs/netfs/buffered_read.c
index 221287f8925a9..cccc860893f0a 100644
--- a/fs/netfs/buffered_read.c
+++ b/fs/netfs/buffered_read.c
@@ -660,7 +660,7 @@ int netfs_write_begin(struct netfs_inode *ctx,
* within the cache granule containing the EOF, in which case we need
* to preload the granule.
*/
- if (!netfs_is_cache_enabled(ctx) &&
+ if (!netfs_is_cache_maybe_enabled(ctx) &&
netfs_skip_folio_read(folio, pos, len, false)) {
netfs_stat(&netfs_n_rh_write_zskip);
goto have_folio_no_wait;
diff --git a/fs/netfs/buffered_write.c b/fs/netfs/buffered_write.c
index dd0ce7b769ce0..0f61746bee2ac 100644
--- a/fs/netfs/buffered_write.c
+++ b/fs/netfs/buffered_write.c
@@ -278,7 +278,7 @@ ssize_t netfs_perform_write(struct kiocb *iocb, struct iov_iter *iter,
* caching service temporarily because the backing store got
* culled.
*/
- if (netfs_is_cache_enabled(ctx)) {
+ if (netfs_is_cache_maybe_enabled(ctx)) {
if (finfo) {
netfs_stat(&netfs_n_wh_wstream_conflict);
goto flush_content;
diff --git a/fs/netfs/internal.h b/fs/netfs/internal.h
index 645996ecfc803..d889caa401dc2 100644
--- a/fs/netfs/internal.h
+++ b/fs/netfs/internal.h
@@ -239,6 +239,18 @@ static inline bool netfs_is_cache_enabled(struct netfs_inode *ctx)
#endif
}
+static inline bool netfs_is_cache_maybe_enabled(struct netfs_inode *ctx)
+{
+#if IS_ENABLED(CONFIG_FSCACHE)
+ struct fscache_cookie *cookie = ctx->cache;
+
+ return fscache_cookie_valid(cookie) &&
+ test_bit(FSCACHE_COOKIE_IS_CACHING, &cookie->flags);
+#else
+ return false;
+#endif
+}
+
/*
* Get a ref on a netfs group attached to a dirty page (e.g. a ceph snap).
*/
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-6.1] cachefiles: Fix double fput
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (6 preceding siblings ...)
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18-6.12] netfs: Fix decision whether to disallow write-streaming due to fscache use Sasha Levin
@ 2026-08-31 13:27 ` Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18] iomap: prevent ioend merge when io_private differs Sasha Levin
` (3 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:27 UTC (permalink / raw)
To: patches, stable
Cc: David Howells, Paulo Alcantara, netfs, linux-fsdevel,
Christian Brauner (Amutable), Sasha Levin, linux-kernel
From: David Howells <dhowells@redhat.com>
[ Upstream commit af6830cc12dfe86c832dccc9c9878a93aaa22f83 ]
Fix a double fput() in error handling in cachefiles_create_tmpfile().
Link: https://sashiko.dev/#/patchset/20260608145432.681865-1-dhowells%40redhat.com
Signed-off-by: David Howells <dhowells@redhat.com>
Link: https://patch.msgid.link/20260625140640.3116900-4-dhowells@redhat.com
cc: Paulo Alcantara <pc@manguebit.org>
cc: netfs@lists.linux.dev
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
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:** `[cachefiles] [fix] Fix double fput` — explicit bug fix in
the cachefiles filesystem caching backend.
### Step 1.2: Tags
**Record:**
- **Link:** https://sashiko.dev/#/patchset/20260608145432.681865-1-
dhowells%40redhat.com (patchset reference)
- **Signed-off-by:** David Howells `<dhowells@redhat.com>` (author)
- **Link:**
https://patch.msgid.link/20260625140640.3116900-4-dhowells@redhat.com
(mailing list submission)
- **cc:** Paulo Alcantara, netfs@lists.linux.dev, linux-
fsdevel@vger.kernel.org
- **Signed-off-by:** Christian Brauner (Amutable) `<brauner@kernel.org>`
(merge sign-off)
- No Fixes:, Reported-by:, Tested-by:, Reviewed-by:, or Cc: stable tags
(expected for manual review)
- Part of **[PATCH v3 03/15]** series (standalone one-line fix within a
larger series)
### Step 1.3: Body analysis
**Record:**
- **Bug:** Double `fput()` on the error path in
`cachefiles_create_tmpfile()` when the backing cache filesystem lacks
`read_iter`/`write_iter`.
- **Symptom:** Reference count dropped twice on the same `struct file
*`; second `fput()` can trigger refcount underflow warnings,
`WARN_ON`, or use-after-free.
- **Root cause:** Extra `fput(file)` before `goto err_unuse`, but
`err_unuse` already calls `fput(file)`.
### Step 1.4: Hidden bug fix?
**Record:** No — this is an explicit, straightforward double-
free/refcount bug fix, not disguised cleanup.
---
## Phase 2: Diff Analysis
### Step 2.1: Inventory
**Record:**
- **File:** `fs/cachefiles/namei.c` — 1 line removed, 0 added
- **Function:** `cachefiles_create_tmpfile()`
- **Scope:** Single-file, surgical one-line fix
### Step 2.2: Code flow change
**Record:**
- **Before:** On `read_iter`/`write_iter` check failure → `fput(file)` →
`goto err_unuse` → `cachefiles_do_unmark_inode_in_use()` →
`fput(file)` again.
- **After:** On failure → `goto err_unuse` → single `fput(file)` via the
shared cleanup label.
- **Path affected:** Error path only, after successful tmpfile creation
but before capability validation.
### Step 2.3: Bug mechanism
**Record:** **Reference counting / double-free bug.** Category: extra
`fput()` on an error path that already releases the file reference.
Matches the correct pattern in sibling function `cachefiles_open_file()`
(lines 576–611), which uses `goto error_fput` with only one `fput()`.
### Step 2.4: Fix quality
**Record:** Obviously correct — removes redundant `fput()` and aligns
with existing convention in the same file. Minimal regression risk; no
locking or API changes.
---
## Phase 3: Git History Investigation
### Step 3.1: Blame
**Record:** `git blame` attributes all lines to merge commit
`5d324e5159d9e` (history in this tree is flattened). Tag comparison
shows the buggy pattern present since `cachefiles_create_tmpfile()` was
introduced:
- Present with bug in **v6.12.50** through **v6.12.99**
- Absent in **v6.18.0**; present with bug from **v6.18.1** through
**v6.18.44** (current HEAD)
### Step 3.2: Fixes: tag
**Record:** N/A — no Fixes: tag in commit message.
### Step 3.3: Related file history
**Record:** `git log --oneline -- fs/cachefiles/namei.c` shows only
merge commit in this tree’s shallow history. Tag comparison confirms the
bug has been present since the function’s introduction in this stable
series.
### Step 3.4: Author context
**Record:** David Howells is the primary fscache/cachefiles maintainer.
Patch was submitted to Christian Brauner and fsdevel/netfs lists.
### Step 3.5: Dependencies
**Record:** Standalone fix. Although labeled patch 03/15 of v3, this
one-line deletion has no structural dependency on other series patches.
Applies cleanly to the current tree.
---
## Phase 4: Mailing List and External Research
### Step 4.1: Original discussion
**Record:** `b4 dig -c HEAD` did not match (commit not in local
history). Found submission at https://lists.openwall.net/linux-
kernel/2026/06/25/1287 (Message-ID:
`<20260625140640.3116900-4-dhowells@redhat.com>`). Also appeared in v2
and v4 series. No NAKs or objections found in fetched content. No
explicit stable nomination in the patch email.
### Step 4.2: Reviewers
**Record:** CC’d: Christian Brauner, Christoph Hellwig, Paulo Alcantara,
netfs@lists.linux.dev, linux-fsdevel, plus netfs client lists (afs,
cifs, ceph). Appropriate maintainer coverage.
### Step 4.3: Bug report
**Record:** No external bug report or syzbot link. Bug identified by
code inspection during cachefiles development (sashiko patchset).
### Step 4.4: Series context
**Record:** Part of David Howells’ cachefiles patchset (v3 03/15). This
specific fix is self-contained and does not require other series
patches.
### Step 4.5: Stable list history
**Record:** Not searched exhaustively on lore stable@; no stable
discussion found in available sources.
---
## Phase 5: Code Semantic Analysis
### Step 5.1: Key functions
**Record:** `cachefiles_create_tmpfile()` modified.
### Step 5.2: Callers
**Record:**
- `cachefiles_create_file()` — `namei.c:531` (new cache object creation)
- `cachefiles_invalidate_cookie()` — `interface.c:407` (cookie
invalidation / tmpfile replacement)
Both are kernel fscache/cachefiles paths triggered during networked
filesystem cache operations.
### Step 5.3: Callees
**Record:** `kernel_tmpfile_open()`, `cachefiles_mark_inode_in_use()`,
`cachefiles_ondemand_init_object()`, `vfs_truncate()`, `fput()`,
`cachefiles_do_unmark_inode_in_use()`, `cachefiles_end_secure()`.
### Step 5.4: Reachability
**Record:** Reachable when `CONFIG_CACHEFILES` is enabled and a
user/admin configures cachefiles as a local backing store for fscache
(NFS, CIFS, AFS, Ceph, etc.). Trigger requires a backing filesystem
whose file operations lack `read_iter` or `write_iter` — marked
`unlikely()`, but ext4/xfs/btrfs normally provide these; exotic or
misconfigured backing FS could hit it. Not a direct syscall path, but
reachable from normal filesystem I/O for cache-enabled mounts.
### Step 5.5: Similar patterns
**Record:** `cachefiles_open_file()` at lines 576–611 implements the
same `read_iter`/`write_iter` check correctly with a single `fput()` via
`error_fput`. The tmpfile path was inconsistent — classic copy-paste
error.
---
## Phase 6: Cross-Reference Against Local Tree (6.18.44)
### Step 6.1: Buggy code exists?
**Record:** **YES.** Local tree is **6.18.44** (`git describe`:
`v6.18.44-1-g2736c32da98b9`). Buggy code confirmed at
`fs/cachefiles/namei.c:502–504`:
```499:515:fs/cachefiles/namei.c
ret = -EINVAL;
if (unlikely(!file->f_op->read_iter) ||
unlikely(!file->f_op->write_iter)) {
fput(file);
pr_notice("Cache does not support read_iter and
write_iter\n");
goto err_unuse;
}
// ...
err_unuse:
cachefiles_do_unmark_inode_in_use(object, file_inode(file));
fput(file);
```
Bug present since **v6.18.1** (function absent in v6.18.0).
### Step 6.2: Backport complications
**Record:** **Clean apply expected** — exact one-line deletion, no
conflicts anticipated. File structure matches the patch diff.
### Step 6.3: Related fixes already present?
**Record:** `git log --grep="double fput"` returns nothing. Fix not yet
applied in this tree.
---
## Phase 7: Subsystem Context
### Step 7.1: Subsystem criticality
**Record:** **fs/cachefiles** — IMPORTANT (filesystem caching for
network filesystems). Not universal core code, but affects production
NFS/CIFS/AFS caching deployments.
### Step 7.2: Subsystem activity
**Record:** Actively maintained by David Howells; recent tmpfile
infrastructure added in 6.18.y stable series.
---
## Phase 8: Impact and Risk Assessment
### Step 8.1: Who is affected
**Record:** Users with `CONFIG_CACHEFILES` enabled and cachefilesd (or
equivalent) configured. Subset of server/workstation deployments using
FS-Cache.
### Step 8.2: Trigger conditions
**Record:** Creating or invalidating a cache object tmpfile on a backing
filesystem missing `read_iter` or `write_iter`. Uncommon but plausible
with unusual FS choices. Triggered from kernel cache management, not
arbitrary userspace directly.
### Step 8.3: Failure mode severity
**Record:** **HIGH** — double `fput()` causes refcount underflow,
potential use-after-free, kernel `WARN`/`BUG`, or memory corruption. Not
merely cosmetic.
### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** Prevents refcount corruption and potential crash/UAF on a
real error path.
- **Risk:** Very low — one-line removal, matches established pattern in
same file.
- **Ratio:** Strong benefit, negligible risk.
---
## Phase 9: Final Synthesis
### Step 9.1: Evidence summary
**FOR backport:**
- Real double `fput()` bug on error path
- HIGH severity when triggered (refcount corruption / UAF)
- One-line, obviously correct fix
- Bug present in this 6.18.44 tree since v6.18.1
- Matches correct pattern already used in `cachefiles_open_file()`
- From subsystem maintainer (David Howells)
- Applies cleanly
**AGAINST backport:**
- Rare trigger path (`unlikely()` + specific FS requirements)
- CONFIG_CACHEFILES is optional
- No syzbot/user crash report
**Unresolved:** No runtime crash report; severity inferred from refcount
semantics and code analysis.
### Step 9.2: Stable rules checklist
1. Obviously correct and tested? **PASS** — logic is clear; no Tested-by
but fix is trivial
2. Fixes a real bug? **PASS** — confirmed double `fput()` in code
3. Important issue? **PASS** — refcount bug / potential UAF (HIGH)
4. Small and contained? **PASS** — 1 line, 1 file
5. No new features or APIs? **PASS**
6. Can apply to local tree? **PASS** — buggy code present, clean apply
### Step 9.3: Exception categories
**Record:** None (standard bug fix, not device ID/quirk/build fix).
### Step 9.4: Decision rationale
This is a textbook stable backport: a real reference-counting bug on an
error path, fixed by a single-line deletion that mirrors the correct
pattern already in the same file. The trigger is uncommon but the
consequence is serious. The bug exists in the locally checked-out
**6.18.44** tree and the patch applies without dependencies.
---
## Verification
- [Phase 1] Parsed commit message and tags from user-provided diff and
openwall mirror
- [Phase 2] Read current `fs/cachefiles/namei.c:442–515` — confirmed
double `fput()` path
- [Phase 2] Compared with `cachefiles_open_file()` at lines 576–611 —
correct single-fput pattern verified
- [Phase 3] `git describe HEAD` → v6.18.44-1-g2736c32da98b9; `make
kernelversion` → 6.18.44
- [Phase 3] `git blame -L 499,520 fs/cachefiles/namei.c` → lines
attributed to 5d324e5159d9e
- [Phase 3] Tag comparison: v6.18.0 (no function), v6.18.1–v6.18.44 (bug
present), v6.12.50–v6.12.99 (bug present)
- [Phase 3] `git show 1efe5d048a391:fs/cachefiles/namei.c` → buggy code
at 6.18.44 tag
- [Phase 3] `git log --grep="double fput"` → no existing fix
- [Phase 4] `b4 dig -c HEAD` → no match (commit not in tree)
- [Phase 4] WebFetch openwall 2026/06/25/1287 → confirmed patch content
and v3 03/15 series context
- [Phase 4] CC list verified: Brauner, Hellwig, fsdevel, netfs
maintainers
- [Phase 5] `grep cachefiles_create_tmpfile` → callers in `namei.c:531`,
`interface.c:407`
- [Phase 6] Current working tree has buggy code at line 502 (`fput`
before `goto err_unuse`)
- [Phase 6] `git log --grep="double fput"` → fix not yet applied
- [Phase 7] Read `fs/cachefiles/Kconfig` → CONFIG_CACHEFILES tristate,
depends on NETFS_SUPPORT && FSCACHE && BLOCK
- [Phase 8] Failure mode assessed from double-fput refcount semantics
(no runtime reproducer available)
**YES**
fs/cachefiles/namei.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/fs/cachefiles/namei.c b/fs/cachefiles/namei.c
index d1edb2ac38376..eae5045d50081 100644
--- a/fs/cachefiles/namei.c
+++ b/fs/cachefiles/namei.c
@@ -499,7 +499,6 @@ struct file *cachefiles_create_tmpfile(struct cachefiles_object *object)
ret = -EINVAL;
if (unlikely(!file->f_op->read_iter) ||
unlikely(!file->f_op->write_iter)) {
- fput(file);
pr_notice("Cache does not support read_iter and write_iter\n");
goto err_unuse;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18] iomap: prevent ioend merge when io_private differs
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (7 preceding siblings ...)
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.1] cachefiles: Fix double fput Sasha Levin
@ 2026-08-31 13:27 ` Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.12] iomap: don't make REQ_POLLED imply REQ_NOWAIT Sasha Levin
` (2 subsequent siblings)
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:27 UTC (permalink / raw)
To: patches, stable
Cc: Zhang Yi, Christoph Hellwig, Ojaswin Mujoo,
Christian Brauner (Amutable), Sasha Levin, linux-xfs,
linux-fsdevel, linux-kernel
From: Zhang Yi <yi.zhang@huawei.com>
[ Upstream commit c97cd6f447d8727af3d457bca3a9283a77dd70f8 ]
Different io_private values indicate distinct completion contexts that
must not be merged together, as this could leak or corrupt the private
data associated with each ioend.
Signed-off-by: Zhang Yi <yi.zhang@huawei.com>
Link: https://patch.msgid.link/20260713074206.1768006-1-yi.zhang@huaweicloud.com
Reviewed-by: Christoph Hellwig <hch@lst.de>
Reviewed-by: Ojaswin Mujoo <ojaswin@linux.ibm.com>
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Stable Backport Analysis: `iomap: prevent ioend merge when io_private
differs`
**Local tree:** `v6.18.44` (Makefile: 6.18.44)
---
## PHASE 1: COMMIT MESSAGE FORENSICS
### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[iomap] [prevent] prevent ioend merge when io_private
differs` — blocks incorrect merging of adjacent `iomap_ioend` structures
when their per-ioend private completion contexts differ.
### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
| Tag | Value |
|-----|-------|
| Signed-off-by | Zhang Yi `<yi.zhang@huawei.com>` (author) |
| Link | https://patch.msgid.link/20260713074206.1768006-1-
yi.zhang@huaweicloud.com |
| Reviewed-by | Christoph Hellwig `<hch@lst.de>` (iomap/XFS maintainer)
|
| Reviewed-by | Ojaswin Mujoo `<ojaswin@linux.ibm.com>` |
| Signed-off-by | Christian Brauner `<brauner@kernel.org>` (VFS
maintainer) |
**Notable patterns:** Two subsystem maintainers/reviewers (Hellwig,
Brauner). No `Reported-by:`, no syzbot, no `Fixes:` tag (expected for
manual review). No `Cc: stable` in the commit message.
### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** `iomap_ioend_can_merge()` allows merging adjacent ioends even
when `io_private` differs.
- **Symptom:** Leak or corruption of filesystem-private completion data.
- **Root cause (author):** Different `io_private` values mean distinct
completion contexts that must stay separate.
- **Version info:** None in the message.
- **Context (from lore):** Patch is part of ext4 iomap conversion work;
discussion linked to ext4 thread.
### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — this is an explicit correctness fix. The
"prevent" verb and corruption/leak language indicate a real bug, not
cleanup.
---
## PHASE 2: DIFF ANALYSIS
### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **Files:** `fs/iomap/ioend.c` (+2 lines)
- **Function:** `iomap_ioend_can_merge()`
- **Scope:** Single-file, surgical fix
### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
**Record:**
- **Before:** Adjacent ioends merge if status, flags, offsets, and
sectors match — `io_private` ignored.
- **After:** Merge rejected when `ioend->io_private !=
next->io_private`.
- **Path:** `iomap_ioend_try_merge()` → called from `xfs_end_io()`
during write completion processing.
### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:** **Logic / correctness fix** with **reference-counting** and
**data-corruption** consequences.
When ioends merge in `iomap_ioend_try_merge()`:
```335:348:fs/iomap/ioend.c
void iomap_ioend_try_merge(struct iomap_ioend *ioend,
struct list_head *more_ioends)
{
// ...
if (!iomap_ioend_can_merge(ioend, next))
break;
list_move_tail(&next->io_list, &ioend->io_list);
ioend->io_size += next->io_size;
```
Only `io_size` is accumulated on the parent; `io_private` from merged
children is not propagated. XFS completion then uses only the parent's
`io_private`:
```153:167:fs/xfs/xfs_aops.c
if (is_zoned)
error = xfs_zoned_end_io(ip, offset, size,
ioend->io_sector,
ioend->io_private, NULLFSBLOCK);
// ...
if (is_zoned)
xfs_ioend_put_open_zones(ioend);
```
If two adjacent ioends used different `xfs_open_zone` pointers
(`io_private`), merging causes:
1. **Data corruption:** `xfs_zoned_end_io()` maps the full merged byte
range using only the parent's zone, mis-mapping blocks written under
a different zone.
2. **Reference imbalance:** `xfs_ioend_put_open_zones()` walks the
merged chain and puts each child's `io_private` plus the parent's —
refcount behavior becomes inconsistent with how zones were acquired
in `xfs_submit_zoned_bio()`.
### Step 2.4: ASSESS THE FIX QUALITY
**Record:** Obviously correct — mirrors existing merge guards (status,
flags, offset, sector). Minimal (2 lines). Very low regression risk:
only prevents merges that should never have happened. No new APIs.
---
## PHASE 3: GIT HISTORY INVESTIGATION
### Step 3.1: BLAME THE CHANGED LINES
**Record:** `iomap_ioend_can_merge()` in this tree comes from commit
`5d324e5159d9e` (2025-11-28, v6.18 era). The missing `io_private` check
has been present since the function was introduced in this tree.
`io_private` exists in `include/linux/iomap.h` since at least tag
`v6.18`.
### 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/iomap/ioend.c` changes in this tree: split
bio_set, EOF trim guard, delalloc rejection. Standalone fix; not part of
a multi-patch series (b4 shows only v1).
### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** Zhang Yi is working on ext4 iomap conversion (per lore).
Hellwig and Mujoo reviewed. Author is an active contributor in this
area, not a drive-by.
### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No prerequisites. The `io_private` field and merge logic
already exist in v6.18.44. Fix is self-contained.
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:**
- **URL:** https://patch.msgid.link/20260713074206.1768006-1-
yi.zhang@huaweicloud.com
- **Series revisions:** v1 only (no v2/v3)
- **Reviewer feedback:** Hellwig: "Looks sensible and fine to queue up
now"; Mujoo: "Looks good Yi"
- **Stable nominations:** None found in thread
- **NAKs/concerns:** None
### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** CC'd: `linux-fsdevel`, `linux-xfs`, `linux-ext4`,
`brauner@kernel.org`, `djwong@kernel.org`, `hch@infradead.org`.
Appropriate maintainers included and reviewed.
### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** No external bug report or syzbot link. Bug identified during
ext4 iomap conversion development. Logical analysis of XFS zoned
completion path confirms real corruption risk.
### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** Related to ext4 iomap conversion (future in this tree). In
v6.18.44, only XFS sets `io_private` on ioends.
### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** Not searched exhaustively; no stable discussion found in the
patch thread.
---
## PHASE 5: CODE SEMANTIC ANALYSIS
### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `iomap_ioend_can_merge()` (modified),
`iomap_ioend_try_merge()` (caller).
### Step 5.2: TRACE CALLERS
**Record:** `iomap_ioend_try_merge()` called from `xfs_end_io()` in
`fs/xfs/xfs_aops.c` (line 204). Triggered during asynchronous write I/O
completion on XFS inodes — normal write path for buffered/direct I/O.
### Step 5.3: TRACE CALLEES
**Record:** Merge logic chains ioends via `list_move_tail`; completion
calls `xfs_end_ioend()` → `xfs_zoned_end_io()` /
`xfs_ioend_put_open_zones()`.
### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** `submit_bio` → `xfs_end_bio` → workqueue `xfs_end_io` →
`iomap_ioend_try_merge` → `xfs_end_ioend`. Reachable from normal file
writes on zoned XFS RT volumes. Zone fill in
`xfs_zone_alloc_and_submit()` can produce adjacent ioends with different
`io_private` when `select_zone` picks a new open zone.
### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** Other merge guards already check `bi_status`,
`IOMAP_IOEND_BOUNDARY`, `IOMAP_IOEND_NOMERGE_FLAGS`, offset continuity,
and sector continuity. The `io_private` check fills an obvious gap
consistent with those guards.
---
## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE
### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **Yes.** `io_private` field exists in
`include/linux/iomap.h` (line 413). XFS sets it in
`xfs_submit_zoned_bio()` (`fs/xfs/xfs_zone_alloc.c:833`).
`iomap_ioend_can_merge()` lacks the guard (lines 307–333). Fix commit
`c97cd6f447d8` is **not** an ancestor of HEAD (`merge-base --is-
ancestor` returned exit 1).
### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** Upstream patch does not apply verbatim (`git apply --check`
fails at line 385 — local tree has fewer lines in the function, no READ-
op guard). **Minor adjustment needed:** insert the 2 lines after the
`bi_status` check at line 310. Trivial backport.
### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** No duplicate fix found. `git log --grep="io_private"`
returns nothing in this tree's history.
---
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **Filesystem / iomap layer** (shared infrastructure) with
**XFS zoned RT** as the current consumer in this tree. Criticality:
**IMPORTANT** — affects filesystem data integrity for zoned XFS users.
### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** iomap and XFS zoned code actively developed in the 6.18
cycle. `io_private` and zoned allocation are relatively new, making this
bug relevant to current 6.18.y users.
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** Users of **XFS with zoned realtime volumes**
(`CONFIG_XFS_RT`, `xfs_has_zoned`). Not universal, but any such
deployment doing writes is affected. ext4 does not use `io_private` in
this tree yet.
### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:** Adjacent write ioends completing with different `io_private`
(e.g., zone boundary crossing during allocation). Plausible during
normal sequential or concurrent writes when zones fill. Privileged write
access required (not a direct syscall attack vector), but corruption
affects all data on the volume.
### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** **CRITICAL** — incorrect extent mapping via
`xfs_zoned_end_io()` on merged ranges causes **filesystem metadata/data
corruption**. Secondary refcount imbalance can cause leaks or premature
free of `xfs_open_zone` structures.
### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH for affected XFS zoned users — prevents silent
corruption
- **Risk:** VERY LOW — 2-line guard, no behavior change for correctly-
formed ioend chains
- **Ratio:** Strongly favors backport
---
## PHASE 9: FINAL SYNTHESIS
### Step 9.1: COMPILE THE EVIDENCE
**FOR backporting:**
- Fixes real data-corruption bug in XFS zoned write completion
- Small, surgical, reviewer-approved (Hellwig, Mujoo, Brauner)
- Buggy code and `io_private` usage both present in v6.18.44
- Fix mirrors existing merge guards — obviously correct
- Prevents refcount corruption on `xfs_open_zone`
**AGAINST backporting:**
- Affects niche config (`CONFIG_XFS_RT` zoned volumes only)
- No user bug report or syzbot reproduction
- Patch needs trivial line-offset adjustment for this tree (not a
blocker)
**Unresolved:** None that affect the decision.
### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — logic is clear; reviewed by
iomap/XFS maintainers
2. Fixes a real bug affecting users? **PASS** — XFS zoned write
completion corruption
3. Important issue? **PASS** — data corruption (CRITICAL)
4. Small and contained? **PASS** — 2 lines, 1 file
5. No new features or APIs? **PASS**
6. Can apply to the local tree? **PASS** — trivial insertion after
`bi_status` check (minor offset adjustment)
### Step 9.3: EXCEPTION CATEGORIES
**Record:** None (not a device ID, quirk, DT, build fix, or docs fix).
Qualifies on merit as a correctness/corruption fix.
### Step 9.4: DECISION RATIONALE
For **v6.18.44**, the buggy merge path exists, XFS actively uses
`io_private` for zoned completion contexts, and incorrect merging can
corrupt extent mappings. The fix is minimal, maintainer-reviewed, and
low-risk. The narrow config scope does not outweigh the severity of data
corruption.
---
## Verification
- **[Phase 1]** Parsed subject, tags, body; noted Hellwig/Mujoo reviews;
ext4 conversion context from lore
- **[Phase 2]** Read diff: +2 lines in `iomap_ioend_can_merge()`; traced
merge → XFS completion path
- **[Phase 3]** `git describe HEAD`: v6.18.44; `git blame` on lines
307–333: from `5d324e5159d9e`
- **[Phase 3]** `git log --grep="io_private"`: no prior fix in tree
- **[Phase 3]** `git merge-base --is-ancestor c97cd6f447d8 HEAD`: exit 1
(fix not in HEAD)
- **[Phase 4]** `b4 dig -c fff189121d344`: found lore URL; v1 only;
Hellwig/Mujoo reviewed; Brauner applied
- **[Phase 4]** `b4 dig -w`: CC list includes fsdevel, xfs, ext4,
maintainers
- **[Phase 4]** Read `/tmp/iomap_io_private.mbox`: no NAKs, no stable
nomination
- **[Phase 5]** `grep iomap_ioend_try_merge`: caller is `xfs_end_io()`
in `fs/xfs/xfs_aops.c`
- **[Phase 5]** Read `xfs_zone_alloc.c`: `io_private` set in
`xfs_submit_zoned_bio()`; zone refill via `select_zone`
- **[Phase 5]** Read `xfs_aops.c`: `xfs_zoned_end_io()` uses parent
`io_private` only; `xfs_ioend_put_open_zones()` walks merged chain
- **[Phase 6]** `grep io_private fs/ext4`: no matches (ext4 not affected
in this tree)
- **[Phase 6]** `git show v6.18:include/linux/iomap.h | grep
io_private`: field present since v6.18
- **[Phase 6]** `git apply --check` on upstream patch: fails at line 385
(offset mismatch; trivial manual apply)
- **[Phase 6]** Current `fs/iomap/ioend.c` lacks `io_private` merge
guard (verified by read)
- **[Phase 8]** Failure mode: extent mapping corruption on zoned XFS
writes — CRITICAL severity
**YES**The background check finished: `c97cd6f447d8` (the upstream fix)
is **not** in this tree’s HEAD (`v6.18.44`), so the bug is still present
here.
**Summary:** The patch adds a 2-line guard in `iomap_ioend_can_merge()`
so ioends with different `io_private` values are not merged. Without it,
XFS zoned RT write completion can mis-map extents and corrupt metadata.
The change is small, maintainer-reviewed, and should backport cleanly
with a minor line-offset tweak.
**YES**
fs/iomap/ioend.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/fs/iomap/ioend.c b/fs/iomap/ioend.c
index 2a86b05c88efc..da174d896abfe 100644
--- a/fs/iomap/ioend.c
+++ b/fs/iomap/ioend.c
@@ -309,6 +309,8 @@ static bool iomap_ioend_can_merge(struct iomap_ioend *ioend,
{
if (ioend->io_bio.bi_status != next->io_bio.bi_status)
return false;
+ if (ioend->io_private != next->io_private)
+ return false;
if (next->io_flags & IOMAP_IOEND_BOUNDARY)
return false;
if ((ioend->io_flags & IOMAP_IOEND_NOMERGE_FLAGS) !=
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-6.12] iomap: don't make REQ_POLLED imply REQ_NOWAIT
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (8 preceding siblings ...)
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18] iomap: prevent ioend merge when io_private differs Sasha Levin
@ 2026-08-31 13:27 ` Sasha Levin
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18] pidfs: preserve thread pidfds reopened by file handle Sasha Levin
2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: fix issue of direct writes beyond end-of-file Sasha Levin
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:27 UTC (permalink / raw)
To: patches, stable
Cc: Christoph Hellwig, Christian Brauner, Sasha Levin, axboe,
linux-xfs, linux-fsdevel, linux-kernel, linux-block
From: Christoph Hellwig <hch@lst.de>
[ Upstream commit 47f28b493daf9049307494689e515205f1c377af ]
As described in commit 2bc057692599 ("block: don't make REQ_POLLED imply
REQ_NOWAIT"), which fixed the same issue for the block device node, there
are valid cases to poll for I/O completion without REQ_NOWAIT.
Additionally, sing REQ_NOWAIT for file system writes is currently not
supported as file systems writes are not idempotent and would need a
retry of just the bio and not the entire operation to be fully supported.
Switch iomap to set REQ_POLLED and remove the now unused bio_set_polled
helper.
Signed-off-by: Christoph Hellwig <hch@lst.de>
Link: https://patch.msgid.link/20260518062917.506483-1-hch@lst.de
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Stable Backport Analysis: `iomap: don't make REQ_POLLED imply
REQ_NOWAIT`
**Local tree:** Linux **6.18.43** (`v6.18.43-1-gc7f0dac02d232`)
---
## PHASE 1: COMMIT MESSAGE FORENSICS
### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[iomap] [don't make / fix] [REQ_POLLED should not imply
REQ_NOWAIT on filesystem DIO bios]`
### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Fixes:** — none
- **Reported-by:** — none
- **Tested-by:** — none
- **Reviewed-by:** — none
- **Acked-by:** — none
- **Link:** https://patch.msgid.link/20260518062917.506483-1-hch@lst.de
- **Cc: stable:** — none (expected for manual review)
- **Signed-off-by:** Christoph Hellwig `<hch@lst.de>`, Christian Brauner
`<brauner@kernel.org>` (merge commit)
- **Notable:** References upstream commit `2bc057692599` (block-layer
companion fix). No syzbot, no user bug reports.
### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** `bio_set_polled()` propagates `REQ_NOWAIT` onto bios when
`IOCB_NOWAIT` is set. For iomap filesystem DIO this is incorrect —
filesystem writes are not idempotent at the bio level and cannot be
retried by re-submitting just the bio.
- **Symptom:** Polled filesystem DIO (e.g. io_uring
`IORING_SETUP_IOPOLL` on xfs/ext4 O_DIRECT) can hit spurious `-EAGAIN`
from the block layer, or fail to make progress — same class of bug
fixed for raw block devices in 2023.
- **Root cause:** iomap reused `bio_set_polled()` which couples
`REQ_POLLED` with conditional `REQ_NOWAIT`; block/fops.c was already
fixed to decouple them, but iomap was not.
- **Version info:** Commit dated 2026-05-18; not yet in this 6.18.43
tree.
### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — this is an explicit correctness fix, though
small. The removal of `bio_set_polled()` is cleanup after the last
caller is gone.
---
## PHASE 2: DIFF ANALYSIS
### Step 2.1: INVENTORY THE CHANGES
**Record:**
- `fs/iomap/direct-io.c`: 1 line changed (`bio_set_polled` →
`bio->bi_opf |= REQ_POLLED`)
- `include/linux/bio.h`: 14 lines removed (`bio_set_polled()` helper +
comment)
- **Functions modified:** `iomap_dio_submit_bio()`; `bio_set_polled()`
removed
- **Scope:** Single-subsystem, 2 files, ~16 lines total — surgical fix
### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
**Record:**
- **Hunk 1 (`iomap_dio_submit_bio`):** Before: for async HIPRI DIO, call
`bio_set_polled(bio, iocb)` which sets `REQ_POLLED` and also
`REQ_NOWAIT` when `IOCB_NOWAIT` is set. After: only `REQ_POLLED` is
set; `IOCB_NOWAIT` is handled separately at the iomap layer via
`IOMAP_NOWAIT` (line 654–655).
- **Hunk 2 (`bio.h`):** Remove now-dead `bio_set_polled()` helper (only
caller was iomap).
### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:**
- **Category:** Logic / correctness fix (incorrect flag propagation)
- **Mechanism:** `REQ_NOWAIT` on a bio causes the block layer to return
`-EAGAIN` instead of blocking on resource contention
(`__bio_queue_enter`, tag allocation in `blk-mq`). For filesystem DIO
through iomap, `IOCB_NOWAIT` is already translated to `IOMAP_NOWAIT`
for filesystem-level handling; passing `REQ_NOWAIT` to the block layer
is both unnecessary and harmful for writes.
### Step 2.4: ASSESS THE FIX QUALITY
**Record:**
- Obviously correct: mirrors the already-accepted block-layer fix
pattern in `block/fops.c`.
- Minimal: one-line functional change plus dead-code removal.
- **Regression risk:** Very low. Block device path already uses the same
pattern. `IOMAP_NOWAIT` continues to handle filesystem-level non-
blocking semantics.
---
## PHASE 3: GIT HISTORY INVESTIGATION
### Step 3.1: BLAME THE CHANGED LINES
**Record:** Shallow repository limits blame — all lines attribute to
`a112b91dd6349` (unrelated sunrpc backport). Verified current buggy code
exists at `fs/iomap/direct-io.c:77` and `include/linux/bio.h:688-693`.
Kernel.org history (via curl) shows iomap polled-IO support added in
`daa99c5a3319` (2023-08-01, Jens Axboe: "iomap: only set iocb->private
for polled bio"); block fix `2bc057692599` (2023-08-08) updated
`bio_set_polled()` but left iomap calling it.
### Step 3.2: FOLLOW THE FIXES: TAG
**Record:** No `Fixes:` tag. Referenced commit `2bc057692599` ("block:
don't make REQ_POLLED imply REQ_NOWAIT") exists as a git object in this
tree; `block/fops.c` already uses the decoupled pattern (`IOCB_NOWAIT`
and `REQ_POLLED` set independently). iomap was the remaining caller of
`bio_set_polled()`.
### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:** Shallow repo prevents meaningful `git log` on these files.
External kernel.org log confirms this is a standalone 1-patch fix (not
part of a series). Related prior fix: `2bc057692599` (block layer,
2023).
### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** Christoph Hellwig is the iomap maintainer. Christian Brauner
is VFS maintainer who applied the patch. Strong subsystem authority.
### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No prerequisites. Self-contained. Depends only on existing
`IOCB_HIPRI`/polled-IO infrastructure already present in 6.18.43. Commit
`47f28b493daf` is NOT in this tree (object not found via `git cat-
file`).
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:** `b4 dig -c` could not run — commit not in local repo.
Fetched via spinics.net:
- URL: https://www.spinics.net/lists/linux-fsdevel/msg338671.html
- Single patch, no series revisions found
- CC'd: `axboe`, `linux-block`, `linux-fsdevel`, `linux-xfs`, `djwong`,
`brauner`
### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** CC list includes block maintainer (Axboe), XFS, fsdevel,
block lists. Brauner applied to `vfs-7.2.iomap` branch. No explicit
Reviewed-by in commit; no NAKs found.
### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** No bug report, syzbot, or crash trace. Bug identified by
code analysis and parity with the 2023 block-layer fix. Failure mode
inferred from block commit message: "repeated -EAGAIN submissions and
not make any progress."
### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** Standalone 1/1 patch. Companion to `2bc057692599` (already
in stable block path).
### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** Not searched (no stable nomination found in thread). Absence
of `Cc: stable` is not a negative signal per review guidelines.
---
## PHASE 5: CODE SEMANTIC ANALYSIS
### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `iomap_dio_submit_bio()`, `bio_set_polled()` (removed)
### Step 5.2: TRACE CALLERS
**Record:** `iomap_dio_submit_bio()` called from iomap DIO write/read
paths in `fs/iomap/direct-io.c`. Reachable via `iomap_dio_rw()` →
filesystem `read_iter`/`write_iter` on xfs, ext4, f2fs, gfs2, zonefs,
btrfs (partial). io_uring sets `IOCB_HIPRI` for `IORING_SETUP_IOPOLL`
(`io_uring/rw.c:891-895`) and may set `IOCB_NOWAIT` for nonblock issue
(`io_uring/rw.c:950-954`).
### Step 5.3: TRACE CALLEES
**Record:** After fix: `bio->bi_opf |= REQ_POLLED`, then `submit_bio()`
(or filesystem `submit_io` hook). Block layer checks `REQ_NOWAIT` in
`__bio_queue_enter()` → `bio_wouldblock_error()` → `-EAGAIN`.
### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** Userspace io_uring IOPOLL → `IOCB_HIPRI` + possibly
`IOCB_NOWAIT` → `xfs_file_read_iter`/`ext4_file_write_iter` →
`iomap_dio_rw` → `iomap_dio_submit_bio` → block layer. **Reachable from
userspace** on common filesystems with `.iopoll = iocb_bio_iopoll` (xfs,
ext4, f2fs, gfs2, zonefs).
### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** `block/fops.c:383-388` already sets `REQ_NOWAIT` and
`REQ_POLLED` independently — the correct pattern this patch brings to
iomap.
---
## PHASE 6: CROSS-REFERENCING AGAINST THE LOCAL TREE
### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **YES.** Current code at `fs/iomap/direct-io.c:76-77` calls
`bio_set_polled(bio, iocb)`. `bio_set_polled()` at
`include/linux/bio.h:688-693` still sets `REQ_NOWAIT` when `IOCB_NOWAIT`
is set. Polled-IO infrastructure present since at least 6.18 branch
(xfs/ext4 `.iopoll` handlers exist).
### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** Expected **clean apply**. The one-line change in
`iomap_dio_submit_bio` is independent of surrounding `submit_bio` vs
`blk_crypto_submit_bio` differences. Removing unused `bio_set_polled()`
is safe — grep confirms only iomap used it.
### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** Block-layer fix (`2bc057692599`) is present in
`block/fops.c`. iomap-specific fix (`47f28b493daf`) is **NOT** present.
No alternate fix found.
---
## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT
### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **Filesystem I/O (iomap direct-I/O)** — **IMPORTANT/CORE-
adjacent**. Affects all iomap-based filesystem DIO, which includes xfs
and ext4 on most enterprise/desktop systems.
### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** iomap is mature and actively used. Polled I/O is a
performance-critical path for io_uring workloads (databases, NVMe-heavy
applications).
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** Users of **io_uring polled I/O** (`IORING_SETUP_IOPOLL`)
with **O_DIRECT** on **iomap filesystems** (xfs, ext4, f2fs, gfs2,
zonefs). Config-specific but affects a significant high-performance
workload segment.
### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:** `IOCB_HIPRI` set (IOPOLL) on async DIO through iomap. Worst
case when `IOCB_NOWAIT` is also set and block layer encounters queue
freeze or request-tag pressure. Trigger is realistic for io_uring
nonblock + IOPOLL combinations.
### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** Spurious `-EAGAIN` / I/O stalls / failure to make progress
on polled filesystem DIO. Not a kernel oops, but a **functional
correctness bug** that breaks a documented I/O path. Severity: **MEDIUM-
HIGH** (I/O failures on production workloads; same severity class as the
2023 block fix that was accepted for stable).
### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH for io_uring + filesystem DIO users; completes a fix
already applied to block devices
- **Risk:** VERY LOW — 1-line behavioral fix, dead-code removal, mirrors
proven block-layer pattern
- **Ratio:** Strong benefit, minimal risk
---
## PHASE 9: FINAL SYNTHESIS
### Step 9.1: COMPILE THE EVIDENCE
**FOR backport:**
- Buggy code confirmed present in 6.18.43
- Companion to block-layer fix already in this tree since 2023
- Affects major filesystems (xfs, ext4) via io_uring IOPOLL
- Small (16 lines), maintainer-authored, obviously correct
- Prevents incorrect `REQ_NOWAIT` on non-idempotent filesystem writes
- Same failure mode as documented in `2bc057692599`: repeated `-EAGAIN`,
no progress
**AGAINST backport:**
- No explicit crash report, syzbot, or user bugzilla reference
- Impact limited to polled + (optionally) nowait filesystem DIO path
- Not a security or data-corruption fix with demonstrated exploit
**UNRESOLVED:**
- Exact date iomap started using `bio_set_polled` (shallow repo);
kernel.org indicates ~Aug 2023
- Whether any distribution has filed user-visible bug reports
### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — mirrors accepted block fix;
maintainer-applied
2. Fixes a real bug affecting users? **PASS** — incorrect flag
propagation on reachable I/O path
3. Important issue? **PASS** — I/O failures / stalls on io_uring polled
filesystem DIO (MEDIUM-HIGH)
4. Small and contained? **PASS** — 16 lines, 2 files
5. No new features or APIs? **PASS** — flag-handling correction only
6. Can apply to local tree? **PASS** — buggy code present, clean apply
expected
### Step 9.3: EXCEPTION CATEGORIES
**Record:** None apply directly (not device ID, quirk, DT, build, or
docs). Standard bug fix.
### Step 9.4: DECISION RATIONALE
This patch completes a fix that was applied to the block-device
direct-I/O path in 2023 but was never carried to the iomap filesystem
DIO path. The buggy code is present in Linux 6.18.43. For io_uring users
running polled O_DIRECT I/O on xfs/ext4 and other iomap filesystems,
incorrectly setting `REQ_NOWAIT` on bios can cause spurious `-EAGAIN`
returns from the block layer — particularly harmful because filesystem
writes cannot safely retry at the bio level. The fix is tiny, low-risk,
and directly analogous to an already-stable fix.
---
## Verification
- **[Phase 1]** Parsed subject, tags, body from provided commit message;
fetched spinics.net thread
- **[Phase 1]** Confirmed no Reported-by/syzbot; found Link and Signed-
off-by tags
- **[Phase 2]** Diff analysis: 1 insertion, 15 deletions across 2 files
- **[Phase 2]** Read current `iomap_dio_submit_bio()` and
`bio_set_polled()` in tree
- **[Phase 3]** `git describe HEAD`: v6.18.43; `make kernelversion`
equivalent from Makefile: 6.18.43
- **[Phase 3]** `git show 2bc057692599`: confirmed block-layer companion
fix content
- **[Phase 3]** `git cat-file -e 47f28b493daf`: commit NOT in tree
- **[Phase 3]** `git blame` on changed lines: shallow-repo attribution
(limited)
- **[Phase 3]** curl kernel.org log for `fs/iomap/direct-io.c`: found
`daa99c5a3319` (2023-08-01 polled bio) and `47f28b493daf` (2026-05-18
fix)
- **[Phase 4]** spinics.net msg338671.html: original patch thread
fetched
- **[Phase 4]** spinics.net msg338717.html: Brauner applied confirmation
fetched
- **[Phase 4]** `b4 dig -c`: FAILED — commit not in local repo
- **[Phase 5]** `grep bio_set_polled`: only `fs/iomap/direct-io.c` +
`include/linux/bio.h`
- **[Phase 5]** `grep iopoll`: xfs, ext4, f2fs, gfs2, zonefs all use
`iocb_bio_iopoll`
- **[Phase 5]** Read `io_uring/rw.c:891-954`: IOPOLL sets `IOCB_HIPRI`;
nonblock sets `IOCB_NOWAIT`
- **[Phase 5]** Read `block/blk-core.c:333-342`: `REQ_NOWAIT` →
`-EAGAIN` on queue contention
- **[Phase 5]** Read `fs/iomap/direct-io.c:654-655`: `IOCB_NOWAIT` →
`IOMAP_NOWAIT` (separate fs-level handling)
- **[Phase 6]** Confirmed buggy `bio_set_polled()` call exists in
6.18.43 tree
- **[Phase 6]** `grep bio_set_polled`: only iomap caller — safe to
remove helper
- **[Phase 6]** Read `block/fops.c:383-388`: decoupled pattern already
in tree
- **[Phase 8]** Failure mode: spurious `-EAGAIN` / I/O stall on polled
fs DIO; severity MEDIUM-HIGH
- **UNVERIFIED:** Full `git log` ancestry for iomap polled-IO
introduction (shallow repo; used kernel.org instead)
- **UNVERIFIED:** Whether 6.18.y already has a different backport of
this fix (no evidence found)
**YES**The hung `git log HEAD` check didn’t finish — the shallow 6.18.43
checkout made full ancestry walks impractical. That didn’t change the
conclusion: I verified the block-layer companion fix another way (`git
show 2bc057692599`, reading `block/fops.c`), and the iomap bug is still
present here (`bio_set_polled()` at `fs/iomap/direct-io.c:77`).
**Verdict for Linux 6.18.43:** this commit should be backported. It’s a
small, low-risk fix that stops iomap filesystem DIO from incorrectly
setting `REQ_NOWAIT` on polled bios — the same class of bug already
fixed for block devices in 2023. It matters for io_uring IOPOLL +
O_DIRECT on xfs/ext4 and similar iomap filesystems, where spurious
`-EAGAIN` or stalled I/O can result.
**YES**
fs/iomap/direct-io.c | 2 +-
include/linux/bio.h | 14 --------------
2 files changed, 1 insertion(+), 15 deletions(-)
diff --git a/fs/iomap/direct-io.c b/fs/iomap/direct-io.c
index e73c71f39bd45..92f32e02f77f4 100644
--- a/fs/iomap/direct-io.c
+++ b/fs/iomap/direct-io.c
@@ -74,7 +74,7 @@ static void iomap_dio_submit_bio(const struct iomap_iter *iter,
/* Sync dio can't be polled reliably */
if ((iocb->ki_flags & IOCB_HIPRI) && !is_sync_kiocb(iocb)) {
- bio_set_polled(bio, iocb);
+ bio->bi_opf |= REQ_POLLED;
WRITE_ONCE(iocb->private, bio);
}
diff --git a/include/linux/bio.h b/include/linux/bio.h
index 16c1c85613b76..9a15f90359ade 100644
--- a/include/linux/bio.h
+++ b/include/linux/bio.h
@@ -678,20 +678,6 @@ static inline bool bioset_initialized(struct bio_set *bs)
return bs->bio_slab != NULL;
}
-/*
- * Mark a bio as polled. Note that for async polled IO, the caller must
- * expect -EWOULDBLOCK if we cannot allocate a request (or other resources).
- * We cannot block waiting for requests on polled IO, as those completions
- * must be found by the caller. This is different than IRQ driven IO, where
- * it's safe to wait for IO to complete.
- */
-static inline void bio_set_polled(struct bio *bio, struct kiocb *kiocb)
-{
- bio->bi_opf |= REQ_POLLED;
- if (kiocb->ki_flags & IOCB_NOWAIT)
- bio->bi_opf |= REQ_NOWAIT;
-}
-
static inline void bio_clear_polled(struct bio *bio)
{
bio->bi_opf &= ~REQ_POLLED;
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18] pidfs: preserve thread pidfds reopened by file handle
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (9 preceding siblings ...)
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18-6.12] iomap: don't make REQ_POLLED imply REQ_NOWAIT Sasha Levin
@ 2026-08-31 13:27 ` Sasha Levin
2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-5.10] hfsplus: fix issue of direct writes beyond end-of-file Sasha Levin
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:27 UTC (permalink / raw)
To: patches, stable
Cc: Christian Brauner, Li Chen, Jan Kara, Sasha Levin, viro,
linux-fsdevel, linux-kernel
From: Christian Brauner <brauner@kernel.org>
[ Upstream commit bb6bc13c53e211d9148ed2eab3e689c5cd5c75da ]
PIDFD_THREAD shares O_EXCL. do_dentry_open() clears O_EXCL after
pidfs_export_open() validates the flags, so open_by_handle_at()
silently turns a thread pidfd into a process pidfd.
Restore PIDFD_THREAD on the opened file, matching pidfs_alloc_file().
Signed-off-by: Li Chen <me@linux.beauty>
Reviewed-by: Jan Kara <jack@suse.cz>
Link: https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
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:** `[pidfs]` `[preserve]` — Preserve thread pidfd semantics
when a pidfd is reopened via file handle (`open_by_handle_at()`).
### Step 1.2: Tags
**Record:**
- **Reviewed-by:** Jan Kara `<jack@suse.cz>`
- **Link:**
https://patch.msgid.link/20260716052726.1032092-1-me@linux.beauty
- **Signed-off-by:** Li Chen `<me@linux.beauty>` (author)
- **Signed-off-by:** Christian Brauner `<brauner@kernel.org>` (pidfs
maintainer)
- No **Fixes:**, **Reported-by:**, **Tested-by:**, **Cc: stable**, or
syzbot tags
- Notable: maintainer review and ack from Brauner; no user/fuzzer
reports
### Step 1.3: Body Analysis
**Record:**
- **Bug:** `PIDFD_THREAD` is aliased to `O_EXCL`. `do_dentry_open()`
clears `O_EXCL` after `pidfs_export_open()` validates flags, so
`open_by_handle_at()` drops the thread-pidfd marker.
- **Symptom:** A reopened thread pidfd silently behaves as a process
pidfd.
- **Root cause:** `pidfs_alloc_file()` already re-applies `PIDFD_THREAD`
after `dentry_open()`; `pidfs_export_open()` did not.
- **Version info:** None in the message.
### Step 1.4: Hidden Bug Fix?
**Record:** Yes. Despite “preserve” wording, this is a functional
correctness bug fix, not cleanup. It restores API semantics on the
`open_by_handle_at()` path.
---
## Phase 2: Diff Analysis
### Step 2.1: Inventory
**Record:**
- **Files:** `fs/pidfs.c` only (+6 / -1 net)
- **Function:** `pidfs_export_open()`
- **Scope:** Single-file, surgical fix
### Step 2.2: Code Flow Change
**Record:**
- **Before:** `pidfs_export_open()` called `dentry_open()` and returned
immediately; `do_dentry_open()` stripped `O_EXCL` (`PIDFD_THREAD`).
- **After:** Save `file` from `dentry_open()`, then if successful
restore `file->f_flags |= oflags & PIDFD_THREAD`.
- **Path affected:** `open_by_handle_at()` → `do_handle_open()` →
`pidfs_export_open()` for pidfs-backed handles with `O_EXCL`.
### Step 2.3: Bug Mechanism
**Record:** **Logic / correctness fix.** `PIDFD_THREAD` is carried in
`f_flags`, not in the inode. Both thread and process pidfds share the
same `struct pid` in `inode->i_private`; semantics depend on `f_flags`.
Losing `PIDFD_THREAD` changes behavior of consumers that inspect
`f_flags`.
### Step 2.4: Fix Quality
**Record:** Obviously correct. Mirrors the existing pattern in
`pidfs_alloc_file()` in the same file. Minimal regression risk; no new
locks, APIs, or behavior changes beyond restoring intended semantics.
---
## Phase 3: Git History Investigation
### Step 3.1: Blame
**Record:** `pidfs_export_open()` introduced in `5d324e5159d9e`
(2025-11-28) without `PIDFD_THREAD` restoration. `pidfs_alloc_file()` in
the same commit already had the restoration at lines 1063–1065. Bug
present since pidfs export support landed in this tree.
### Step 3.2: Fixes: Tag
**Record:** Not applicable — no `Fixes:` tag.
### Step 3.3: Related File History
**Record:** Recent `fs/pidfs.c` commits in this tree:
- `7446125afb6d9` — pidfs: return -EREMOTE for cross-ns `PIDFD_GET_INFO`
- `ae7a542dbab5b` — pidfs: add missing `BUILD_BUG_ON()`
Standalone fix; not part of a multi-patch series.
### Step 3.4: Author Context
**Record:** Li Chen is not the primary pidfs maintainer; Christian
Brauner is. Patch reviewed by Jan Kara and merged with Brauner’s SOB.
### Step 3.5: Dependencies
**Record:** No prerequisites. Uses existing `PIDFD_THREAD`,
`dentry_open()`, and `pidfs_export_open()` infrastructure already
present in this tree.
---
## Phase 4: Mailing List and External Research
### Step 4.1: Original Discussion
**Record:** Could not retrieve lore/patch.msgid.link — Anubis bot
protection blocked WebFetch. `b4 dig -c <commit>` unavailable because
the fix commit is not in this checkout. Phase 4 partially blocked.
### Step 4.2: Reviewers
**Record:** Unverified via `b4 dig -w`. Commit message shows Reviewed-by
Jan Kara and SOB from Christian Brauner.
### Step 4.3: Bug Report
**Record:** No external bug report, syzbot link, or user Reported-by.
### Step 4.4: Related Patches
**Record:** No series indicated. Complements existing
`pidfs_alloc_file()` logic.
### Step 4.5: Stable List History
**Record:** Unverified — lore blocked. This tree already carries other
pidfs stable backports (`7446125afb6d9`, `ae7a542dbab5b`).
---
## Phase 5: Code Semantic Analysis
### Step 5.1: Key Functions
**Record:** `pidfs_export_open()`, `pidfs_alloc_file()`,
`do_dentry_open()`, `sys_open_by_handle_at()`, `sys_pidfd_send_signal()`
### Step 5.2: Callers
**Record:**
- `pidfs_export_open()` called from `do_handle_open()` in `fs/fhandle.c`
when `eops->open` is set
- Reachable from `open_by_handle_at()` syscall (userspace)
### Step 5.3: Callees
**Record:** `dentry_open()` → `do_dentry_open()`, which clears `O_EXCL`
at `fs/open.c:981`
### Step 5.4: Reachability
**Record:** Userspace can trigger via:
1. `pidfd_open(tid, PIDFD_THREAD)`
2. `name_to_handle_at(pidfd, ...)`
3. `open_by_handle_at(mountfd, fh, O_EXCL)`
4. `pidfd_send_signal()` or other operations reading `f_flags`
### Step 5.5: Similar Patterns
**Record:** Identical restoration already exists in
`pidfs_alloc_file()`:
```1062:1065:fs/pidfs.c
pidfd_file = dentry_open(&path, flags, current_cred());
/* Raise PIDFD_THREAD explicitly as do_dentry_open() strips it.
*/
if (!IS_ERR(pidfd_file))
pidfd_file->f_flags |= (flags & PIDFD_THREAD);
```
`pidfs_export_open()` currently lacks this:
```855:862:fs/pidfs.c
static struct file *pidfs_export_open(const struct path *path, unsigned
int oflags)
{
/*
- Clear O_LARGEFILE as open_by_handle_at() forces it and raise
- O_RDWR as pidfds always are.
*/
oflags &= ~O_LARGEFILE;
return dentry_open(path, oflags | O_RDWR, current_cred());
}
```
---
## Phase 6: Cross-Reference Against Local Tree
### Step 6.1: Buggy Code Exists?
**Record:** Yes. Local tree is **v6.18.44** (`git describe HEAD`, `make
kernelversion`). `fs/pidfs.c` exists with `pidfs_export_open()` missing
the fix. The fix commit is not present (scoped `-S 'do_dentry_open()
strips O_EXCL' -- fs/pidfs.c` returns nothing).
### Step 6.2: Backport Complications
**Record:** Clean apply expected — 6-line change in one function, no
structural conflicts visible.
### Step 6.3: Related Fixes Already Present?
**Record:** `pidfs_alloc_file()` already has the `PIDFD_THREAD`
restoration pattern. Other pidfs stable fixes (`7446125afb6d9`,
`ae7a542dbab5b`) are present. This specific export-path fix is not.
---
## Phase 7: Subsystem Context
### Step 7.1: Subsystem / Criticality
**Record:** `fs/pidfs.c` — VFS/pidfd subsystem. **IMPORTANT**: affects
process management APIs reachable from userspace; not universal like mm,
but core process-control infrastructure in modern kernels.
### Step 7.2: Activity
**Record:** Actively maintained — multiple pidfs commits in this 6.18.y
tree.
---
## Phase 8: Impact and Risk Assessment
### Step 8.1: Who Is Affected
**Record:** Users of thread pidfds (`PIDFD_THREAD`) combined with pidfs
file-handle APIs (`name_to_handle_at` / `open_by_handle_at`). Config-
independent when pidfs is present (always initialized from
`init/main.c`).
### Step 8.2: Trigger Conditions
**Record:** Reopen a thread-pidfd file handle with `O_EXCL` via
`open_by_handle_at()`. Uncommon but valid documented API usage
(`VALID_FILE_HANDLE_OPEN_FLAGS` explicitly allows `O_EXCL`).
Unprivileged users can trigger on their own pidfds.
### Step 8.3: Failure Mode Severity
**Record:** **MEDIUM–HIGH functional correctness bug.** Without
`PIDFD_THREAD`, `pidfd_send_signal()` uses `PIDTYPE_TGID` instead of
`PIDTYPE_PID`:
```4111:4115:kernel/signal.c
/* Infer scope from the type of pidfd. */
if (fd_file(f)->f_flags & PIDFD_THREAD)
type = PIDTYPE_PID;
else
type = PIDTYPE_TGID;
```
Signal may be delivered to the thread group instead of the specific
thread. Not a kernel oops, but wrong-target signal delivery is a
meaningful user-visible failure. `pidfd_get_pid()` also propagates
incorrect `f_flags` to callers.
### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** Restores correct thread-vs-process pidfd semantics on
file-handle reopen; prevents wrong signal scope.
- **Risk:** Very low — mirrors proven in-tree pattern.
- **Ratio:** Favorable for backport.
---
## Phase 9: Final Synthesis
### Step 9.1: Evidence Summary
**FOR backport:**
- Real, verifiable bug in this tree
- Wrong signal-delivery scope possible
- Tiny, obviously correct fix matching existing code
- Maintainer-reviewed (Brauner SOB, Kara Reviewed-by)
- pidfs fixes already being backported to 6.18.y
- Clean apply to local tree
**AGAINST backport:**
- Niche API path (file handles + thread pidfds)
- No syzbot/user reports
- Not a crash/corruption/deadlock
- Mailing-list discussion unverified
**Unresolved:** Full lore review thread unavailable.
### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — mirrors
`pidfs_alloc_file()`; no runtime test in commit, but pattern is
proven in-tree.
2. Fixes a real bug affecting users? **PASS** — silent semantic
corruption of thread pidfds.
3. Important issue? **PASS (moderate)** — wrong signal target; not
crash-level, but meaningful API correctness failure.
4. Small and contained? **PASS** — ~6 lines, one function.
5. No new features or APIs? **PASS**
6. Can apply to local tree? **PASS** — buggy code present, fix not yet
applied.
### Step 9.3: Exception Categories
**Record:** None (not device ID, quirk, DT, build, or docs).
### Step 9.4: Decision Rationale
This is an oversight in pidfs export support: the `pidfd_open()` path
was fixed at introduction, but the `open_by_handle_at()` path was not.
For 6.18.y users adopting pidfs file handles, a reopened thread pidfd
silently becomes a process pidfd, which can cause `pidfd_send_signal()`
to target the wrong scope. The fix is minimal, matches existing in-tree
code, and carries very low regression risk. Other pidfs fixes (including
less severe ones) are already in this 6.18.44 tree, establishing
precedent for pidfs corrections in stable.
---
## Verification
- [Phase 1] Parsed subject, tags, and body from provided commit message
- [Phase 1] Confirmed no syzbot/Fixes:/Reported-by tags
- [Phase 2] Read current `pidfs_export_open()` and `pidfs_alloc_file()`
in `fs/pidfs.c`
- [Phase 2] Verified `do_dentry_open()` strips `O_EXCL` at
`fs/open.c:981`
- [Phase 2] Confirmed `PIDFD_THREAD` is `O_EXCL` in
`include/uapi/linux/pidfd.h`
- [Phase 3] `git describe HEAD` → `v6.18.44-2-g1b9e1abadee04`; `make
kernelversion` → `6.18.44`
- [Phase 3] `git blame -L 855,863 fs/pidfs.c` → `pidfs_export_open()`
from `5d324e5159d9e`
- [Phase 3] `git blame -L 1060,1066 fs/pidfs.c` → `PIDFD_THREAD` restore
in `pidfs_alloc_file()` from same commit
- [Phase 3] `git log --oneline -S 'do_dentry_open() strips O_EXCL' --
fs/pidfs.c` → empty (fix not in tree)
- [Phase 3] `git show 7446125afb6d9`, `git show ae7a542dbab5b` → other
pidfs stable backports present
- [Phase 4] WebFetch lore/patch.msgid.link → blocked by Anubis
- [Phase 4] `b4 dig` → could not run without commit hash in tree
- [Phase 5] Traced `open_by_handle_at` → `do_handle_open()` →
`eops->open` in `fs/fhandle.c:436-437`
- [Phase 5] Verified `pidfd_send_signal()` checks `f_flags &
PIDFD_THREAD` in `kernel/signal.c:4112-4115`
- [Phase 5] Verified `pidfd_pid()` returns same `inode->i_private`
regardless of thread flag
- [Phase 6] Confirmed buggy `pidfs_export_open()` present in local
6.18.44 tree
- [Phase 6] Confirmed `pidfs_init()` called unconditionally from
`init/main.c`
- [Phase 8] Assessed failure mode as wrong signal scope, severity
MEDIUM-HIGH functional
**YES**The background git history checks finished and match what the
analysis already used:
- **`pidfs_export_open` and `pidfs_export_operations`** both date to
`5d324e5159d9e` in this 6.18.44 tree — the same commit that brought in
`fs/pidfs.c`, including the existing `PIDFD_THREAD` restore in
`pidfs_alloc_file()`.
- The broad `git log -S` search for the fix comment was killed after
hanging; a scoped search on `fs/pidfs.c` already showed the fix is
**not** in this tree.
**Verdict stands: YES** — small, correct fix for a real bug where
`open_by_handle_at()` drops thread-pidfd semantics on the export path.
fs/pidfs.c | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/fs/pidfs.c b/fs/pidfs.c
index 34987fcdd9a87..30450f7f5ec5b 100644
--- a/fs/pidfs.c
+++ b/fs/pidfs.c
@@ -854,12 +854,18 @@ static int pidfs_export_permission(struct handle_to_path_ctx *ctx,
static struct file *pidfs_export_open(const struct path *path, unsigned int oflags)
{
+ struct file *file;
+
/*
* Clear O_LARGEFILE as open_by_handle_at() forces it and raise
* O_RDWR as pidfds always are.
*/
oflags &= ~O_LARGEFILE;
- return dentry_open(path, oflags | O_RDWR, current_cred());
+ file = dentry_open(path, oflags | O_RDWR, current_cred());
+ /* do_dentry_open() strips O_EXCL, which encodes PIDFD_THREAD. */
+ if (!IS_ERR(file))
+ file->f_flags |= oflags & PIDFD_THREAD;
+ return file;
}
static const struct export_operations pidfs_export_operations = {
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread* [PATCH AUTOSEL 6.18-5.10] hfsplus: fix issue of direct writes beyond end-of-file
[not found] <20260831133314.4125787-1-sashal@kernel.org>
` (10 preceding siblings ...)
2026-08-31 13:27 ` [PATCH AUTOSEL 6.18] pidfs: preserve thread pidfds reopened by file handle Sasha Levin
@ 2026-08-31 13:30 ` Sasha Levin
11 siblings, 0 replies; 12+ messages in thread
From: Sasha Levin @ 2026-08-31 13:30 UTC (permalink / raw)
To: patches, stable
Cc: Viacheslav Dubeyko, John Paul Adrian Glaubitz, Yangtao Li,
linux-fsdevel, Sasha Levin, linux-kernel
From: Viacheslav Dubeyko <slava@dubeyko.com>
[ Upstream commit 5f63ac80aef2ee6bb58eab62e98c264774872da6 ]
The xfstests' test-case generic/729 fails with error:
sudo ./check generic/729
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.0.0-rc1+ #36 SMP PREEMPT_DYNAMIC Fri Apr 17 12:40:51 PDT 2026
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
generic/729 23s ... [failed, exit status 1]- output mismatch
mmap-rw-fault: /mnt/test/mmap-rw-fault.tmp: Input/output error
The hfsplus_get_block() only allows creating the next
sequential block. It returns -EIO for direct writes
beyond EOF. This patch waits for any in-flight DIO on the inode
to finish. Then, it extends the file by calling
generic_cont_expand_simple() with the goal to guarantee
that blockdev_direct_IO() finds all needed blocks
already reachable sequentially. And, finally, it flushes and
invalidates the DIO range again so the page cache is clean
before the direct write begins.
sudo ./check generic/729
FSTYP -- hfsplus
PLATFORM -- Linux/x86_64 hfsplus-testing-0001 7.0.0-rc1+ #40 SMP PREEMPT_DYNAMIC Thu Apr 16 15:41:03 PDT 2026
MKFS_OPTIONS -- /dev/loop51
MOUNT_OPTIONS -- /dev/loop51 /mnt/scratch
generic/729 23s ... 32s
Ran: generic/729
Passed all 1 tests
Closes: https://github.com/hfs-linux-kernel/hfs-linux-kernel/issues/210
cc: John Paul Adrian Glaubitz <glaubitz@physik.fu-berlin.de>
cc: Yangtao Li <frank.li@vivo.com>
cc: linux-fsdevel@vger.kernel.org
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Link: https://lore.kernel.org/r/20260417214940.2735557-2-slava@dubeyko.com
Signed-off-by: Viacheslav Dubeyko <slava@dubeyko.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
LLM Generated explanations, may be completely bogus:
# Stable Backport Analysis: `hfsplus: fix issue of direct writes beyond
end-of-file`
**Local tree:** Linux **6.18.43** (`git describe HEAD` → `v6.18.43`)
---
## PHASE 1: COMMIT MESSAGE FORENSICS
### Step 1.1: Subject Line
**Record:** `[hfsplus] [fix] [direct writes beyond end-of-file fail with
-EIO]`
### Step 1.2: Tags
**Record:**
- **Fixes:** — absent (expected for manual review)
- **Reported-by:** — absent
- **Tested-by:** — absent
- **Reviewed-by:** — absent
- **Acked-by:** — absent
- **Link:**
`https://lore.kernel.org/r/20260417214940.2735557-2-slava@dubeyko.com`
- **Closes:** `https://github.com/hfs-linux-kernel/hfs-linux-
kernel/issues/210`
- **cc:** John Paul Adrian Glaubitz, Yangtao Li, linux-
fsdevel@vger.kernel.org
- **Signed-off-by:** Viacheslav Dubeyko (author; ignore pipeline SOBs)
Notable: GitHub issue documents reproducible xfstests failure; no
syzbot/KASAN signals.
### Step 1.3: Body Analysis
**Record:**
- **Bug:** `hfsplus_get_block()` only allocates the *next sequential*
block (`iblock > hip->fs_blocks` → `-EIO`). Direct I/O writes starting
beyond EOF hit this path and fail.
- **Symptom:** xfstests `generic/729` fails with `mmap-rw-fault: ...
Input/output error` (userspace EIO).
- **Root cause:** DIO bypasses `cont_write_begin()` / page-cache
expansion that buffered writes use; `blockdev_direct_IO()` calls
`hfsplus_get_block()` with `create=1` on blocks beyond the current
allocation frontier.
- **Fix approach:** Before DIO write when `ki_pos > i_size`: wait for
in-flight DIO, expand via `generic_cont_expand_simple()`, flush and
invalidate the affected page-cache range, then proceed with
`blockdev_direct_IO()`.
- **Version info:** Issue filed against 6.15.0-rc4+; fix verified on
7.0.0-rc1+ per commit message and GitHub issue.
### Step 1.4: Hidden Bug Fix?
**Record:** No — this is an explicit functional bug fix, not disguised
cleanup. It corrects incorrect `-EIO` on a valid I/O path.
---
## PHASE 2: DIFF ANALYSIS
### Step 2.1: Inventory
**Record:**
- **Files:** `fs/hfsplus/inode.c` only (+34 / −2 lines)
- **Function modified:** `hfsplus_direct_IO()`
- **Scope:** Single-file, surgical fix in one function
### Step 2.2: Code Flow Change
**Record:**
| Hunk | Before | After |
|------|--------|-------|
| Pre-DIO path | Immediately calls `blockdev_direct_IO()` | For WRITE
with `ki_pos > i_size`: `inode_dio_wait()` →
`generic_cont_expand_simple()` → `filemap_write_and_wait_range()` →
`invalidate_inode_pages2_range()`, then DIO |
| Error cleanup | Declares local `isize`/`end` in error block | Reuses
`isize`/`end` hoisted to function scope |
Affected path: **O_DIRECT write beyond current EOF** (sparse extension /
hole before write).
### Step 2.3: Bug Mechanism
**Record:** **Logic / correctness fix** in filesystem block allocation.
In `hfsplus_get_block()`:
```239:243:fs/hfsplus/extents.c
if (iblock >= hip->fs_blocks) {
if (!create)
return 0;
if (iblock > hip->fs_blocks)
return -EIO;
```
Only `iblock == hip->fs_blocks` (next block) can be created. A DIO write
at offset 4096 on a zero-length file needs `iblock > fs_blocks` →
`-EIO`. Buffered writes avoid this via `cont_write_begin()` in
`hfsplus_write_begin()`.
### Step 2.4: Fix Quality
**Record:**
- **Obviously correct:** Mirrors the established pattern in
`hfsplus_setattr()` (same file, lines 278–284): `inode_dio_wait()` +
`generic_cont_expand_simple()`.
- **Minimal:** Only touches the DIO write-beyond-EOF case.
- **Regression risk:** Low — narrow trigger (`WRITE && ki_pos >
i_size`), uses standard VFS helpers already used elsewhere in hfsplus.
- **No new APIs or public interface changes.**
---
## PHASE 3: GIT HISTORY INVESTIGATION
### Step 3.1: Blame
**Record:** `hfsplus_direct_IO()` and the `iblock > hip->fs_blocks`
check both blame to `19eef1d98eeda` in this tree (a history-rewrite
artifact in the stable queue). The sequential-block constraint in
`hfsplus_get_block()` is longstanding hfsplus design; the DIO path has
lacked pre-expansion since `hfsplus_direct_IO` was wired into
`hfsplus_aops`.
### Step 3.2: Fixes: Tag
**Record:** N/A — no `Fixes:` tag present.
### Step 3.3: Related File History
**Record:** Recent `fs/hfsplus/inode.c` history in **this tree**
includes multiple backported hfsplus xfstests fixes from the same
author:
- `956b1d8051cfa` — generic/498 (volume corruption)
- `54694417d4384` — generic/480
- `66e2f3c1aefea` — generic/101
This fix is **standalone** (not part of a multi-patch series in the
commit message).
### Step 3.4: Author Context
**Record:** Viacheslav Dubeyko is an active hfsplus contributor;
multiple hfsplus fixes from this author are already in Linux 6.18.43.
### Step 3.5: Dependencies
**Record:** No prerequisite commits required. All APIs exist in this
tree:
- `generic_cont_expand_simple()` — `fs/buffer.c:2473`
- `inode_dio_wait()` — `fs/inode.c:2659`
- `filemap_write_and_wait_range()`, `invalidate_inode_pages2_range()` —
standard VFS
- Already used in `hfsplus_setattr()` at lines 278–284 of the same file
---
## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH
### Step 4.1: Original Discussion
**Record:** `b4 dig -c <sha>` could not be run — commit is not in this
checkout. Lore URL blocked by Anubis bot protection. GitHub issue #210
confirms the bug and fix (opened 2025-05-27, closed 2026-04-23 after
generic/729 passed).
### Step 4.2: Reviewers
**Record:** UNVERIFIED — could not fetch lore thread. Commit cc's
fsdevel and hfsplus maintainers.
### Step 4.3: Bug Report
**Record:** [GitHub issue #210](https://github.com/hfs-linux-kernel/hfs-
linux-kernel/issues/210):
- Failure: `mmap-rw-fault: ... Input/output error`
- Reproducible since at least 6.15.0-rc4
- Fixed on 7.0.0-rc1+ with this patch
- **Severity from reporter:** xfstests regression; user-visible EIO, not
corruption/crash
### Step 4.4: Related Patches
**Record:** `generic/729` (added 2023) tests mmap + DIO write — extends
generic/647. It exercises direct writes beyond EOF followed by mmap
fault I/O. Same test class has exposed real bugs in btrfs (deadlock) and
NFS (EFAULT).
### Step 4.5: Stable List History
**Record:** UNVERIFIED — lore stable list not searchable due to bot
protection. Precedent exists in-tree: other Dubeyko hfsplus xfstests
fixes already backported to 6.18.y.
---
## PHASE 5: CODE SEMANTIC ANALYSIS
### Step 5.1: Key Functions
**Record:** `hfsplus_direct_IO()` (modified); `hfsplus_get_block()`
(buggy callee, unchanged).
### Step 5.2: Callers
**Record:** `hfsplus_direct_IO` is registered in
`hfsplus_aops.direct_IO` (line 173). Invoked from VFS when `O_DIRECT` is
set on hfsplus files — reachable from `pwrite()`, `io_uring`, and
xfstests `mmap-rw-fault` helper.
### Step 5.3: Callees
**Record:** `inode_dio_wait`, `generic_cont_expand_simple` (→
`hfsplus_write_begin` → `cont_write_begin`),
`filemap_write_and_wait_range`, `invalidate_inode_pages2_range`,
`blockdev_direct_IO`.
### Step 5.4: Reachability
**Record:** **Userspace-reachable** on any hfsplus mount with O_DIRECT
writes extending past EOF. `generic/729` is the concrete, reproducible
trigger.
### Step 5.5: Similar Patterns
**Record:** `hfsplus_setattr()` already uses `inode_dio_wait()` +
`generic_cont_expand_simple()` for size extension. `hfs`
(`fs/hfs/inode.c`) has a similar bare `hfs_direct_IO()` — potentially
the same class of bug, but out of scope for this commit.
---
## PHASE 6: CROSS-REFERENCE AGAINST LOCAL TREE (6.18.43)
### Step 6.1: Buggy Code Present?
**Record:** **YES.** Current `hfsplus_direct_IO()` at lines 123–145
calls `blockdev_direct_IO()` directly with no pre-expansion.
`hfsplus_get_block()` sequential-only create logic at
`extents.c:239–243` is present.
### Step 6.2: Backport Complications
**Record:** **Clean apply** — `git apply --check` with the full upstream
diff succeeds on `fs/hfsplus/inode.c` in this tree.
### Step 6.3: Related Fixes Already Present?
**Record:** **NO** — `git log --grep="729"` and `git log --grep="beyond
end-of-file"` find no matching fix. This commit is not yet applied.
---
## PHASE 7: SUBSYSTEM CONTEXT
### Step 7.1: Subsystem / Criticality
**Record:** **fs/hfsplus** — IMPORTANT (filesystem I/O correctness), not
CORE but affects all hfsplus users doing DIO.
### Step 7.2: Activity
**Record:** Actively maintained in 6.18.y — multiple recent hfsplus
xfstests fixes from the same author already landed in this stable
series.
---
## PHASE 8: IMPACT AND RISK ASSESSMENT
### Step 8.1: Who Is Affected
**Record:** Users of hfsplus with `O_DIRECT` writes beyond EOF —
including Mac interoperability workloads, backup tools, and the standard
xfstests `generic/729` regression test.
### Step 8.2: Trigger Conditions
**Record:** `O_DIRECT` write where `ki_pos > i_size` (sparse extension).
Common in `generic/729` (truncate to 0, then write at offset 4096).
Unprivileged users can trigger on mounted hfsplus volumes they can write
to.
### Step 8.3: Failure Mode Severity
**Record:** Returns **-EIO** to userspace on valid I/O. No crash,
corruption, or deadlock documented for hfsplus. **Severity: MEDIUM** —
functional I/O failure / incorrect error, fits stable rules' "oh, that's
not good" category.
### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** MEDIUM — restores correct DIO semantics; passes standard
xfstests; fixes long-standing user-visible failure
- **Risk:** LOW — 34 lines, one function, mirrors existing
`hfsplus_setattr()` pattern, applies cleanly
- **Ratio:** Favorable for backport
---
## PHASE 9: FINAL SYNTHESIS
### Step 9.1: Evidence Summary
**FOR backport:**
- Real, reproducible bug (xfstests `generic/729`, GitHub #210)
- User-visible EIO on valid direct writes beyond EOF
- Small, surgical, obviously correct fix
- Uses APIs already present and used in same file
- Applies cleanly to Linux 6.18.43
- Same author/subsystem already has xfstests fixes in this stable series
- Tested before/after in commit message
**AGAINST backport:**
- Not crash/corruption/security — severity is functional I/O failure
- hfsplus is a niche filesystem
- Bug likely present since DIO support was added (long-standing)
- Lore review details unverified
**UNRESOLVED:**
- Full mailing-list review thread (lore blocked)
- Whether reviewers explicitly nominated for stable
### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — xfstests generic/729;
pattern proven in `hfsplus_setattr()`
2. Fixes a real bug affecting users? **PASS** — EIO on valid O_DIRECT
writes
3. Important issue? **PASS (MEDIUM)** — incorrect I/O failure on
standard xfstests path; "oh, that's not good" per stable-kernel-
rules.rst
4. Small and contained? **PASS** — 1 file, ~34 lines
5. No new features/APIs? **PASS** — uses existing VFS helpers
6. Can apply to local tree? **PASS** — clean `git apply --check`
### Step 9.3: Exception Category
**Record:** None (not device ID, quirk, DT, build fix, or docs).
Standard filesystem correctness fix.
### Step 9.4: Decision Rationale
For **Linux 6.18.43**, the buggy code is present, all dependencies
exist, the patch applies cleanly, and the fix addresses a reproducible
userspace I/O failure on a supported filesystem using established
patterns already in the same driver. While not as severe as the
corruption fixes already backported for hfsplus in this series, it meets
stable criteria: real bug, tested fix, low risk, user-visible impact.
The 6.18.y tree has already accepted similar hfsplus xfstests fixes from
this author.
---
## Verification
- **[Phase 1]** Parsed commit message, tags, and GitHub issue #210
(fetched successfully)
- **[Phase 2]** Read current `hfsplus_direct_IO()` at
`fs/hfsplus/inode.c:123–145` and `hfsplus_get_block()` at
`fs/hfsplus/extents.c:239–243`
- **[Phase 2]** Confirmed `hfsplus_setattr()` uses same expansion
pattern at `fs/hfsplus/inode.c:278–284`
- **[Phase 3]** `git describe HEAD` → v6.18.43; `make kernelversion`
equivalent via Makefile → 6.18.43
- **[Phase 3]** `git log --oneline -20 -- fs/hfsplus/inode.c` — found
related backported hfsplus fixes
- **[Phase 3]** `git blame` on changed lines — history rewrite artifact
noted
- **[Phase 3]** `git log --grep="729"` / `--grep="beyond end-of-file"` —
no existing fix in tree
- **[Phase 4]** GitHub issue #210 — confirmed failure mode and fix
verification
- **[Phase 4]** Web search — confirmed generic/729 tests mmap + DIO
write beyond EOF
- **[Phase 4]** UNVERIFIED: `b4 dig` (no commit SHA in tree),
lore.kernel.org (bot-blocked)
- **[Phase 5]** `grep hfsplus_direct_IO` — registered in `hfsplus_aops`
at line 173
- **[Phase 5]** `grep generic_cont_expand_simple` — present in
`fs/buffer.c:2473` and used in hfsplus
- **[Phase 6]** Confirmed buggy code present in current checkout
- **[Phase 6]** `git apply --check` with full upstream diff —
**succeeds**
- **[Phase 7]** Multiple Dubeyko hfsplus commits in `git log
--author="Dubeyko"` on this tree
- **[Phase 8]** Assessed severity as MEDIUM functional I/O failure, not
CRITICAL
**YES**The background git history search failed: it hit the 30s timeout
because the command fell through to `git log -S 'hfsplus_direct_IO'
--all`, which is exactly what we must avoid on this repo.
That didn’t block the backport analysis. I used narrower searches
instead (`git blame`, `git log -S ... -- fs/hfsplus/inode.c`), and the
conclusion for Linux **6.18.43** is unchanged: **YES** — the fix applies
cleanly and addresses a real O_DIRECT-beyond-EOF EIO bug in hfsplus.
fs/hfsplus/inode.c | 38 ++++++++++++++++++++++++++++++++++++--
1 file changed, 36 insertions(+), 2 deletions(-)
diff --git a/fs/hfsplus/inode.c b/fs/hfsplus/inode.c
index 6153e5cc6eb65..7e6cc4dde9f26 100644
--- a/fs/hfsplus/inode.c
+++ b/fs/hfsplus/inode.c
@@ -125,9 +125,44 @@ static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
struct file *file = iocb->ki_filp;
struct address_space *mapping = file->f_mapping;
struct inode *inode = mapping->host;
+ loff_t isize;
size_t count = iov_iter_count(iter);
+ loff_t end = iocb->ki_pos + count;
ssize_t ret;
+ /*
+ * The hfsplus_get_block() only allows creating the next sequential block.
+ * For direct writes beyond EOF, expand the file first.
+ */
+ if (iov_iter_rw(iter) == WRITE && iocb->ki_pos > i_size_read(inode)) {
+ loff_t start_off, end_off;
+ loff_t start_page, end_page;
+
+ isize = i_size_read(inode);
+
+ /*
+ * Wait for any in-flight DIO on this inode to finish before
+ * calling generic_cont_expand_simple().
+ */
+ inode_dio_wait(inode);
+
+ ret = generic_cont_expand_simple(inode, iocb->ki_pos);
+ if (ret)
+ return ret;
+
+ start_off = isize;
+ end_off = (end > 0) ? end - 1 : end;
+
+ ret = filemap_write_and_wait_range(mapping, start_off, end_off);
+ if (ret)
+ return ret;
+
+ start_page = start_off >> PAGE_SHIFT;
+ end_page = end_off >> PAGE_SHIFT;
+
+ invalidate_inode_pages2_range(mapping, start_page, end_page);
+ }
+
ret = blockdev_direct_IO(iocb, inode, iter, hfsplus_get_block);
/*
@@ -135,8 +170,7 @@ static ssize_t hfsplus_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
* blocks outside i_size. Trim these off again.
*/
if (unlikely(iov_iter_rw(iter) == WRITE && ret < 0)) {
- loff_t isize = i_size_read(inode);
- loff_t end = iocb->ki_pos + count;
+ isize = i_size_read(inode);
if (end > isize)
hfsplus_write_failed(mapping, end);
--
2.53.0
^ permalink raw reply related [flat|nested] 12+ messages in thread