Linux Btrfs filesystem development
 help / color / mirror / Atom feed
* [PATCH AUTOSEL 6.18-5.15] btrfs: protect sb_write_pointer() with invalidate lock
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
@ 2026-08-31 13:20 ` Sasha Levin
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: fix transaction abort logic in btrfs_fileattr_set() Sasha Levin
                   ` (14 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:20 UTC (permalink / raw)
  To: patches, stable
  Cc: KangNing Liao, Qu Wenruo, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: KangNing Liao <lkangn.kernel@gmail.com>

[ Upstream commit 123b9a545f4d0348e81f558a032bf2a93ee5722f ]

sb_write_pointer() reads the super block from the block device page cache
using read_cache_page_gfp(). This has the same race with BLKBSZSET as the
one fixed by commit 3f29d661e568 ("btrfs: sync read disk super and set
block size").

Take the mapping invalidate lock around read_cache_page_gfp() to
serialize the read against block size changes.

Signed-off-by: KangNing Liao <lkangn.kernel@gmail.com>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: protect sb_write_pointer() with
invalidate lock`

**Tree under review:** `v6.18.44` (`stable/linux-6.18.y`, detached HEAD)

**Commit analyzed:** `123b9a545f4d0` (not yet in this tree; applies
cleanly)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject line
**Record:** `[btrfs]` `[protect]` — Add invalidate-lock protection
around superblock page-cache reads in `sb_write_pointer()` for zoned
btrfs.

### Step 1.2: Tags
**Record:**
- **Signed-off-by:** KangNing Liao `<lkangn.kernel@gmail.com>` (author)
- **Reviewed-by:** Qu Wenruo `<wqu@suse.com>` (btrfs maintainer)
- **Signed-off-by:** David Sterba `<dsterba@suse.com>` (btrfs
  maintainer)
- No `Fixes:`, `Reported-by:`, `Link:`, `Cc: stable`, or `Tested-by:`
  tags
- Notable: References upstream commit `3f29d661e568` as the prior fix
  for the same race class

### Step 1.3: Body analysis
**Record:**
- **Bug:** `sb_write_pointer()` calls `read_cache_page_gfp()` without
  synchronizing against `BLKBSZSET` block-size changes on the block
  device mapping.
- **Symptom:** Same race as the syzbot-reported crash fixed in
  `3f29d661e568` / stable `ccb3c75d57039`: folio order vs.
  `mapping_min_folio_order()` mismatch → `VM_BUG_ON_FOLIO` or NULL
  pointer dereference in `create_empty_buffers()`.
- **Root cause:** Block-size change via `BLKBSZSET` alters
  `mapping->flags` while a folio is being allocated/read.
- **Fix:** Wrap `read_cache_page_gfp()` with `filemap_invalidate_lock()`
  / `filemap_invalidate_unlock()`.

### Step 1.4: Hidden bug fix?
**Record:** Yes — despite “protect” wording rather than “fix”, this is a
real concurrency/crash bug fix, not cleanup. It completes the same
protection pattern already applied to `btrfs_read_disk_super()` in this
tree.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
**Record:**
- **Files:** `fs/btrfs/zoned.c` only (+2 lines)
- **Functions:** `sb_write_pointer()` only
- **Scope:** Single-file, surgical fix (2 insertions)

### Step 2.2: Code flow per hunk
**Record:**
- **Before:** In the `full[0] && full[1]` branch (both superblock log
  zones full), loop calls `read_cache_page_gfp()` unlocked to compare
  superblock generations.
- **After:** Same path, but `read_cache_page_gfp()` is serialized
  against block-size invalidation via `filemap_invalidate_lock/unlock`.
- **Affected path:** Error and success paths unchanged; only the page-
  cache read is synchronized.

### Step 2.3: Bug mechanism
**Record:**
- **Category:** Race condition / memory safety (folio order mismatch)
- **Mechanism:** Concurrent `BLKBSZSET` changes
  `mapping_min_folio_order()` after folio allocation begins but before
  `filemap_add_folio()` completes, producing kernel BUG or NULL deref —
  identical to the already-backported `btrfs_read_disk_super()` bug.

### Step 2.4: Fix quality
**Record:**
- **Quality:** Obviously correct — mirrors the exact pattern already in
  `btrfs_read_disk_super()` at `fs/btrfs/volumes.c:1368-1370`.
- **Regression risk:** Very low; `filemap_invalidate_lock` is the
  established synchronization primitive for this race.
- **No new APIs, no behavior change beyond preventing the race.**

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:**
- `read_cache_page_gfp()` in `sb_write_pointer()` introduced in
  `12659251ca5df` (Nov 2020, “implement log-structured superblock for
  ZONED mode”).
- Loop structure updated in `02ca9e6fb5f66a` / `d2715d1db455e`
  (2023–2024).
- Buggy unlocked read has been present since zoned superblock logging
  was added.

### Step 3.2: Fixes: tag
**Record:** N/A — no `Fixes:` tag. Referenced commit `3f29d661e568`
exists in repo; equivalent backport `ccb3c75d57039` **is** in this tree
(committed by Greg K-H, Feb 2026).

### Step 3.3: Related file history
**Record:**
- `ccb3c75d57039` backported the `btrfs_read_disk_super()` fix to
  6.18.y.
- `123b9a545f4d0` is on `master` but not yet on `stable/linux-6.18.y`.
- Standalone single-patch series (v1 only per `b4 dig -a`).

### Step 3.4: Author context
**Record:** KangNing Liao has prior btrfs zoned contributions. Patch
reviewed by Qu Wenruo (active btrfs maintainer).

### Step 3.5: Dependencies
**Record:**
- References `3f29d661e568` conceptually; stable tree has
  `ccb3c75d57039` (same fix, different hash).
- No structural dependencies — patch applies cleanly (`git apply
  --check` succeeded).
- Standalone; does not require other commits from the series.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original discussion
**Record:**
- **URL:** https://patch.msgid.link/20260521122945.524890-1-
  lkangn.kernel@gmail.com
- **Series:** v1 only (2026-05-21)
- **Reviewer feedback:** Qu Wenruo replied with `Reviewed-by:` and
  “Thanks” — no NAKs or concerns
- **Stable nomination:** None found in thread

### Step 4.2: Reviewers
**Record:** `b4 dig -w` shows CC to `linux-btrfs@vger.kernel.org`, David
Sterba, Edward Davis (author of the original BLKBSZSET fix), Filipe
Manana’s address not listed but David Sterba committed.

### Step 4.3: Bug report
**Record:** No direct syzbot report for this path. Indirect evidence
from `ccb3c75d57039` syzbot report (`b4a2af3000eaa84d95d5`) documenting
identical failure mode in `btrfs_read_disk_super()`.

### Step 4.4: Related patches
**Record:** Companion to `ccb3c75d57039` — same race, different code
path in zoned superblock handling.

### Step 4.5: Stable list
**Record:** Lore fetch blocked by bot protection for full thread; mbox
download via `b4 dig -m` succeeded. No stable-list discussion found in
mbox content.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key functions
**Record:** `sb_write_pointer()` (modified)

### Step 5.2: Callers
**Record:**
- `sb_log_location()` → `sb_write_pointer()`
- `btrfs_sb_log_location_bdev()` → `sb_log_location()` — called from
  `btrfs_read_disk_super()` (`fs/btrfs/volumes.c:1346`)
- `btrfs_sb_log_location()` → `sb_log_location()` — called from `disk-
  io.c` (super write/read), `scrub.c`, and zoned device validation
  (`zoned.c:585`)

### Step 5.3: Callees
**Record:** `filemap_invalidate_lock()`, `read_cache_page_gfp()`,
`filemap_invalidate_unlock()`, `btrfs_release_disk_super()`

### Step 5.4: Reachability
**Record:**
- Triggered on zoned block devices (`bdev_is_zoned()`) when both
  superblock log zones are full.
- Reachable during **mount** (`btrfs_read_disk_super` →
  `btrfs_sb_log_location_bdev`), **superblock writes**, **scrub**, and
  **device validation**.
- `BLKBSZSET` requires privileged access to the block device; syzbot
  demonstrated the race is reachable from userspace with appropriate
  privileges.

### Step 5.5: Similar patterns
**Record:** Identical lock pattern already present in
`btrfs_read_disk_super()` in this tree (`volumes.c:1368-1370`). This
path was simply missed when that fix was backported.

---

## PHASE 6: CROSS-REFERENCE AGAINST LOCAL TREE (6.18.y)

### Step 6.1: Buggy code present?
**Record:** **Yes.** `fs/btrfs/zoned.c:133-134` calls
`read_cache_page_gfp()` without invalidate lock. Bug present since zoned
superblock logging (2020).

### Step 6.2: Backport complications
**Record:** **Clean apply** — `git apply --check` passed with zero
conflicts.

### Step 6.3: Related fixes already present?
**Record:** **Partial.** `ccb3c75d57039` fixed `btrfs_read_disk_super()`
in this tree but left `sb_write_pointer()` unprotected. This commit
closes that gap.

---

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: Subsystem
**Record:** `fs/btrfs` — filesystem, zoned-mode superblock handling.
**Criticality: IMPORTANT** (filesystem mount/write path; not universal
like VFS core, but crash on mount/write for zoned btrfs users).

### Step 7.2: Activity
**Record:** Actively maintained; recent zoned fixes in 6.18.y
(`deddd28fd83c2`, `4d4ef6627304a`, etc.).

---

## PHASE 8: IMPACT AND RISK

### Step 8.1: Who is affected
**Record:** Users of **zoned btrfs** on host-managed zoned block devices
(SMR/ZNS SSDs). Config: btrfs with zoned devices enabled at format/mount
time.

### Step 8.2: Trigger conditions
**Record:**
- Zoned btrfs with both superblock log zones full (normal steady-state
  after superblock updates)
- Concurrent `BLKBSZSET` on the same block device
- Uncommon in production but proven reachable (syzbot for sibling path);
  mount-time scenario explicitly described in `ccb3c75d57039`

### Step 8.3: Failure mode
**Record:** Kernel `VM_BUG_ON_FOLIO` or KASAN NULL pointer dereference
in buffer-head setup → **CRITICAL** (oops/panic during mount or
superblock I/O).

### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** HIGH for zoned btrfs users — prevents kernel crash;
  completes already-backported fix family
- **Risk:** VERY LOW — 2 lines, identical to proven pattern already in
  tree
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence summary

**FOR backport:**
- Real race bug with documented crash mechanism (syzbot for same pattern
  in `btrfs_read_disk_super`)
- Partial fix already in 6.18.y (`ccb3c75d57039`); this closes the
  remaining hole
- Kernel crash severity (CRITICAL)
- Tiny, surgical, obviously correct
- Reviewed by btrfs maintainer (Qu Wenruo)
- Applies cleanly to 6.18.y

**AGAINST backport:**
- Affects only zoned btrfs (niche subset of btrfs users)
- No direct syzbot report for this exact function (indirect evidence
  only)
- Requires privileged `BLKBSZSET` concurrent with superblock read

**Unresolved:** None material to the decision.

### Step 9.2: Stable rules checklist
1. Obviously correct and tested? **PASS** — mirrors in-tree fix;
   maintainer reviewed
2. Fixes a real bug? **PASS** — race with `BLKBSZSET` on page-cache read
3. Important issue? **PASS** — kernel crash (BUG/null deref)
4. Small and contained? **PASS** — 2 lines, 1 file, 1 function
5. No new features/APIs? **PASS**
6. Can apply to local tree? **PASS** — clean apply verified;
   prerequisite fix already present

### Step 9.3: Exception category
**Record:** N/A (standard bug fix, not device-ID/quirk/DT/docs
exception)

### Step 9.4: Decision rationale

This tree (`6.18.44`) already backported the `BLKBSZSET` vs.
`read_cache_page_gfp()` race fix for `btrfs_read_disk_super()`, but
`sb_write_pointer()` — called from the same mount and superblock I/O
paths on zoned devices — still performs the identical unlocked read when
both superblock log zones are full. Leaving this unprotected means the
backport is incomplete: zoned btrfs users can still hit the same crash
class the stable tree already decided was worth fixing. The patch is
minimal, follows the established pattern already in `volumes.c`, and
applies without modification.

---

## Verification

- **[Phase 1]** `git show 123b9a545f4d0 --format=fuller`: parsed
  subject, body, tags
- **[Phase 1]** No syzbot/Fixes tags on this commit; references
  `3f29d661e568`
- **[Phase 2]** Diff: +2 lines in `sb_write_pointer()`,
  `filemap_invalidate_lock/unlock` around `read_cache_page_gfp()`
- **[Phase 2]** Read `fs/btrfs/zoned.c:88-159`: confirmed buggy unlocked
  read in `full[0] && full[1]` branch
- **[Phase 2]** Read `fs/btrfs/volumes.c:1368-1370`: confirmed identical
  fix pattern already in tree
- **[Phase 3]** `git describe HEAD`: `v6.18.44`
- **[Phase 3]** `git blame -L 128,140 fs/btrfs/zoned.c`:
  `read_cache_page_gfp` since `12659251ca5df` (2020)
- **[Phase 3]** `git merge-base --is-ancestor ccb3c75d57039 HEAD`:
  prerequisite fix **in tree**
- **[Phase 3]** `git merge-base --is-ancestor 123b9a545f4d0 HEAD`: fix
  commit **not in tree**
- **[Phase 3]** `git show ccb3c75d57039`: stable backport of BLKBSZSET
  race fix with syzbot stack trace
- **[Phase 3]** `git apply --check` on patch: **applies cleanly**
- **[Phase 4]** `b4 dig -c 123b9a545f4d0`: lore URL found
- **[Phase 4]** `b4 dig -a`: v1 only, no revisions
- **[Phase 4]** `b4 dig -w`: CC to linux-btrfs, dsterba, eadavis
- **[Phase 4]** `b4 dig -m /tmp/sb_write_pointer.mbox`: Qu Wenruo
  `Reviewed-by`, no NAKs, no stable CC
- **[Phase 5]** `grep btrfs_sb_log_location`: callers in `volumes.c`,
  `disk-io.c`, `scrub.c`, `zoned.c`
- **[Phase 5]** Traced mount path: `btrfs_read_disk_super` →
  `btrfs_sb_log_location_bdev` → `sb_write_pointer`
- **[Phase 6]** Confirmed `v6.18.44` tree has unlocked
  `read_cache_page_gfp` at `zoned.c:133-134`
- **[Phase 6]** Confirmed `ccb3c75d57039` already protects
  `btrfs_read_disk_super()` read
- **[Phase 8]** Failure mode from `ccb3c75d57039` message: KASAN null-
  ptr-deref / VM_BUG_ON — CRITICAL

**YES**

 fs/btrfs/zoned.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 0dfbb28b7445c..6ca5816a67381 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -130,8 +130,10 @@ static int sb_write_pointer(struct block_device *bdev, struct blk_zone *zones,
 			u64 bytenr = ALIGN_DOWN(zone_end, BTRFS_SUPER_INFO_SIZE) -
 						BTRFS_SUPER_INFO_SIZE;
 
+			filemap_invalidate_lock(mapping);
 			page[i] = read_cache_page_gfp(mapping,
 					bytenr >> PAGE_SHIFT, GFP_NOFS);
+			filemap_invalidate_unlock(mapping);
 			if (IS_ERR(page[i])) {
 				if (i == 1)
 					btrfs_release_disk_super(super[0]);
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: fix transaction abort logic in btrfs_fileattr_set()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
  2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.15] btrfs: protect sb_write_pointer() with invalidate lock Sasha Levin
@ 2026-08-31 13:22 ` Sasha Levin
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: tree-checker: validate INODE_REF's namelen Sasha Levin
                   ` (13 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:22 UTC (permalink / raw)
  To: patches, stable
  Cc: Filipe Manana, Qu Wenruo, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: Filipe Manana <fdmanana@suse.com>

[ Upstream commit 9d78a98796f215d9973e1e53871b2d63420f3608 ]

There's no need to abort the transaction if we failed to set or delete a
property, as we haven't done any change. However we need to abort if we
set a property or delete a property and then fail to update the inode
item, as that would leave the inode's state in subvolume tree
inconsistent.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: fix transaction abort logic in
btrfs_fileattr_set()`

## Phase 1: Commit Message Forensics

### Step 1.1: Subject Line
**Record:** `[btrfs]` `[fix]` — Correct transaction abort handling in
`btrfs_fileattr_set()` when setting/deleting compression properties and
updating the inode item.

### Step 1.2: Tags
**Record:**
- **Reviewed-by:** Qu Wenruo `<wqu@suse.com>` — btrfs developer
- **Signed-off-by:** Filipe Manana `<fdmanana@suse.com>` — author
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` — btrfs maintainer
- **Signed-off-by:** David Sterba `<dsterba@suse.com>`
- No Fixes:, Reported-by:, Link:, Cc: stable, or Tested-by: tags
- Notable: dual maintainer review (Sterba, Qu Wenruo); no syzbot or user
  bug report

### Step 1.3: Body Analysis
**Record:**
- **Bug:** Transaction abort is triggered at the wrong points in
  `btrfs_fileattr_set()`.
- **Symptom (false positive):** Aborting when `btrfs_set_prop()` fails
  even though no metadata was changed — unnecessarily puts the
  filesystem into error/RO state.
- **Symptom (false negative):** Not aborting when `btrfs_set_prop()`
  succeeds but `btrfs_update_inode()` fails — leaves on-disk inode state
  inconsistent between the property item and the inode item.
- **Root cause:** Abort logic tied to property-set failure instead of
  tracking whether a property was actually modified, and missing abort
  after a successful property change followed by inode-update failure.

### Step 1.4: Hidden Bug Fix?
**Record:** No — this is an explicit bug fix, not disguised cleanup. It
corrects two concrete metadata-consistency / over-abort bugs.

---

## Phase 2: Diff Analysis

### Step 2.1: Inventory
**Record:**
- **Files:** `fs/btrfs/ioctl.c` only (~+10/−5 net, ~20 lines touched)
- **Function:** `btrfs_fileattr_set()`
- **Scope:** Single-file surgical fix

### Step 2.2: Code Flow Change
**Record:**

| Hunk | Before | After |
|------|--------|-------|
| Property set (`comp` non-NULL) | `btrfs_set_prop()` failure →
`btrfs_abort_transaction()` | Failure → `goto out_end_trans` (no abort);
success → `prop_set = true` |
| Property delete (`comp` NULL) | Non-`-ENODATA` failure → abort | Same,
but track `prop_set = (ret == 0)`; `-ENODATA` proceeds without abort |
| `btrfs_update_inode()` | No abort on failure | If `ret && prop_set` →
`btrfs_abort_transaction()` |

### Step 2.3: Bug Mechanism
**Record:**
- **Category:** Logic/correctness fix — incorrect transaction abort
  policy
- **False positive:** `btrfs_abort_transaction()` on `btrfs_set_prop()`
  failure when `btrfs_set_prop()` made no durable change (see `props.c`:
  returns early on `btrfs_setxattr()` failure; rolls back on `apply()`
  failure)
- **False negative:** Missing abort after partial transaction success —
  property written via `btrfs_setxattr()` in `btrfs_set_prop()`, but
  inode item update via `btrfs_update_inode()` fails; without abort the
  transaction can commit with inconsistent metadata

### Step 2.4: Fix Quality
**Record:** Obviously correct. `prop_set` accurately tracks whether a
property mutation occurred. Minimal scope. Low regression risk — aligns
with btrfs patterns elsewhere (e.g. `d11aefe654a04` for received-subvol
ioctl abort logic). Removing abort on clean `set_prop` failure is
strictly less aggressive; adding abort after successful `set_prop` +
failed `update_inode` is the standard btrfs consistency response.

---

## Phase 3: Git History Investigation

### Step 3.1: Blame
**Record:** Buggy abort-on-`set_prop`-failure pattern present since
`97fc297754878` ("btrfs: convert to fileattr", 2021-04-07), inherited
from pre-fileattr `btrfs_ioctl_setflags()` (`ff9fef559babe`,
2019-04-20). `unlikely()` wrappers added in `a929904cf73b6` (2025-09).
Bug has been in this code path for years.

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

### Step 3.3: Related Changes
**Record:**
- `d11aefe654a04` — same author (Filipe Manana), same file, fixes
  incorrect transaction abort in another ioctl path; was nominated `Cc:
  stable@vger.kernel.org`
- `014a021075c58` — adds missing abort on inode/root update failure in
  received-subvol ioctl
- `a929904cf73b6` — only added `unlikely()` around existing abort
  branches
- Standalone fix; not part of a series

### Step 3.4: Author Context
**Record:** Filipe Manana is an active btrfs developer with multiple
stable-worthy fixes in this tree. David Sterba is btrfs maintainer and
co-signer.

### Step 3.5: Dependencies
**Record:** None. `btrfs_fileattr_set()`, `btrfs_set_prop()`, and
`btrfs_update_inode()` all exist in this tree. Applies standalone.

---

## Phase 4: Mailing List and External Research

### Step 4.1–4.5
**Record:** Commit hash not present in this checkout (candidate under
evaluation). `b4 dig -c` could not be run without hash. `b4 dig -q`
failed (wrong syntax). lore.kernel.org blocked by bot protection.
**UNVERIFIED:** mailing list thread, stable nominations in review,
series revisions. Reviewed-by tags from btrfs maintainers are present in
the commit message itself.

---

## Phase 5: Code Semantic Analysis

### Step 5.1: Key Functions
**Record:** `btrfs_fileattr_set()` (modified)

### Step 5.2: Callers
**Record:**
- `fs/btrfs/inode.c` — `.fileattr_set = btrfs_fileattr_set` on btrfs
  inode ops
- `ioctl_setflags()` → `vfs_fileattr_set()` → `btrfs_fileattr_set()`
  (`fs/file_attr.c`)
- `ioctl_fssetxattr()`, `file_setattr` syscall also reach
  `vfs_fileattr_set()`

### Step 5.3: Callees
**Record:** `btrfs_start_transaction()`, `btrfs_set_prop()` →
`btrfs_setxattr()`, `btrfs_update_inode()` →
`btrfs_delayed_update_inode()`, `btrfs_abort_transaction()` →
`__btrfs_handle_fs_error()`, `btrfs_end_transaction()`

### Step 5.4: Reachability
**Record:** Userspace-reachable via `FS_IOC_SETFLAGS` /
`FS_IOC_FSSETXATTR` / `file_setattr` on files the caller owns
(`inode_owner_or_capable` in `vfs_fileattr_set`). Compression flag
changes (`FS_COMPR_FL` / `FS_NOCOMP_FL`) trigger the
`btrfs_set_prop("btrfs.compression", ...)` path. Unprivileged file
owners can trigger this for their own files.

### Step 5.5: Similar Patterns
**Record:** Same file has related abort-logic fixes (`d11aefe654a04`,
`014a021075c58`). Pattern throughout btrfs: abort only after metadata
has been modified, not on pre-change failures.

---

## 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`, Makefile VERSION=6 PATCHLEVEL=18
SUBLEVEL=44). Current `fs/btrfs/ioctl.c` lines 376–401 show the buggy
pattern: abort on `btrfs_set_prop()` failure, no abort on
`btrfs_update_inode()` failure. No `prop_set` variable present (fix not
yet applied).

### Step 6.2: Backport Complications
**Record:** Clean apply expected — minimal diff against current
`btrfs_fileattr_set()`. No conflicting recent churn in this function.

### Step 6.3: Related Fixes Already Present?
**Record:** Related ioctl abort fixes (`d11aefe654a04`, `014a021075c58`)
are in tree, but this specific `btrfs_fileattr_set()` bug is **not**
fixed. `git log -S 'prop_set' -- fs/btrfs/ioctl.c` returns empty.

---

## Phase 7: Subsystem Context

### Step 7.1: Subsystem
**Record:** `fs/btrfs` — btrfs filesystem. **Criticality: IMPORTANT**
(metadata integrity for all btrfs users).

### Step 7.2: Activity
**Record:** Actively maintained; recent commits in `ioctl.c` include
transaction-abort fixes, indicating ongoing attention to this class of
bug.

---

## Phase 8: Impact and Risk Assessment

### Step 8.1: Who Is Affected
**Record:** All btrfs users who change file flags (especially
compression flags) via `chattr`, `FS_IOC_SETFLAGS`, or related
interfaces.

### Step 8.2: Trigger Conditions
**Record:**
- **False positive (current bug):** Any `btrfs_set_prop()` failure
  during flag change (e.g. `-ENOSPC`, `-ENOMEM`) → full transaction
  abort → filesystem error/RO via `__btrfs_handle_fs_error()`.
  Relatively uncommon but serious when hit.
- **False negative (current bug):** `btrfs_set_prop()` succeeds, then
  `btrfs_update_inode()` fails → transaction ends without abort → risk
  of committed inconsistent metadata (property vs. inode flags). Rare
  but severe.

### Step 8.3: Failure Mode Severity
**Record:**
- False positive: **CRITICAL** — entire filesystem forced into error
  state for a recoverable per-file operation failure
- False negative: **CRITICAL** — on-disk metadata inconsistency (data
  integrity)

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents filesystem-wide abort on benign errors;
  prevents metadata inconsistency on partial failure
- **Risk:** LOW — ~15 lines, single function, reviewed by maintainers,
  follows established btrfs abort patterns
- **Ratio:** Strongly favors backport

---

## Phase 9: Final Synthesis

### Step 9.1: Evidence Summary

**FOR backport:**
- Fixes two real bugs with severe consequences (filesystem abort,
  metadata inconsistency)
- Small, surgical, obviously correct
- Reviewed by btrfs maintainers (Sterba, Qu Wenruo)
- Buggy code present in v6.18.44 since 2021
- Userspace-reachable on file flag changes
- Same author/file has prior stable-nominated abort-logic fixes
- No dependencies

**AGAINST backport:**
- No user/syzbot report in commit message (weak signal only)
- Mailing list discussion unverified

**UNRESOLVED:**
- Lore review thread and explicit stable nomination in discussion
  (UNVERIFIED)

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — logic is clear; maintainer-
   reviewed (no runtime Tested-by)
2. Fixes a real bug affecting users? **PASS**
3. Important issue? **PASS** — filesystem abort + metadata inconsistency
   (CRITICAL)
4. Small and contained? **PASS** — single function, ~20 lines
5. No new features or APIs? **PASS**
6. Can apply to local tree? **PASS** — buggy code confirmed present;
   clean apply expected

### Step 9.3: Exception Categories
**Record:** N/A — standard bug fix, not a quirk/ID/DT/build fix.

### Step 9.4: Decision Rationale
This commit corrects inverted transaction-abort logic in a userspace-
reachable metadata path. The current code unnecessarily aborts the
entire filesystem when property setting fails without making changes,
and fails to abort when a property change succeeds but the inode update
fails — leaving persistent metadata inconsistency. The fix is minimal,
maintainer-reviewed, self-contained, and the buggy code is confirmed
present in this v6.18.44 tree.

---

## Verification

- [Phase 1] Parsed subject, tags, body from provided commit message
- [Phase 2] Diff analysis: `prop_set` tracking, abort moved from
  `set_prop` failure to `update_inode` failure after successful prop
  change
- [Phase 3] `git describe HEAD`: v6.18.44-1-g2736c32da98b9
- [Phase 3] `git show 97fc297754878`: abort-on-set_prop-failure present
  since fileattr conversion (2021-04-07)
- [Phase 3] `git show ff9fef559babe`: pattern inherited from
  `btrfs_ioctl_setflags()` (2019)
- [Phase 3] `git show d11aefe654a04`: related stable-nominated abort fix
  by same author in same file
- [Phase 3] `git log -S 'prop_set' -- fs/btrfs/ioctl.c`: empty — fix not
  in tree
- [Phase 4] `b4 dig`: failed (no commit hash / syntax error)
- [Phase 4] lore.kernel.org fetch: blocked by bot protection —
  **UNVERIFIED**
- [Phase 5] `grep btrfs_fileattr_set`: registered in `inode.c`, called
  via `vfs_fileattr_set` from `ioctl_setflags`
- [Phase 5] Read `fs/btrfs/props.c` `btrfs_set_prop()`: failure paths
  make no durable change
- [Phase 5] Read `fs/btrfs/transaction.h` `btrfs_abort_transaction`:
  calls `__btrfs_handle_fs_error()`
- [Phase 5] Read `fs/btrfs/transaction.c` `__btrfs_end_transaction()`:
  `TRANS_ABORTED` path returns `trans->aborted`
- [Phase 6] Read `fs/btrfs/ioctl.c` lines 376–401: buggy code confirmed
  in v6.18.44
- [Phase 6] Makefile VERSION/PATCHLEVEL/SUBLEVEL: 6.18.44
- [Phase 8] `vfs_fileattr_set()` requires `inode_owner_or_capable` —
  file owners can trigger

**YES**The background git search finished. It didn’t find the fix commit
in this tree (expected — it’s still a candidate), but it did turn up one
related earlier change: `9b8a233bc294d` (“btrfs: handle transaction
start error in btrfs_fileattr_set”), which only covers transaction
*start* failures, not the abort-on-`set_prop` / missing-abort-
on-`update_inode` logic this patch addresses.

That doesn’t change the earlier conclusion: the buggy code is present in
v6.18.44, and this fix should be backported.

**YES**

 fs/btrfs/ioctl.c | 18 ++++++++++++------
 1 file changed, 12 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 3197f61d612b4..8716241bcbd52 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -264,6 +264,7 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
 	int ret;
 	const char *comp = NULL;
 	u32 inode_flags;
+	bool prop_set = false;
 
 	if (btrfs_root_readonly(root))
 		return -EROFS;
@@ -376,16 +377,15 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
 	if (comp) {
 		ret = btrfs_set_prop(trans, inode, "btrfs.compression",
 				     comp, strlen(comp), 0);
-		if (unlikely(ret)) {
-			btrfs_abort_transaction(trans, ret);
+		if (ret)
 			goto out_end_trans;
-		}
+		prop_set = true;
 	} else {
 		ret = btrfs_set_prop(trans, inode, "btrfs.compression", NULL, 0, 0);
-		if (unlikely(ret && ret != -ENODATA)) {
-			btrfs_abort_transaction(trans, ret);
+		prop_set = (ret == 0);
+		/* If ret == -ENODATA ignore and proceed to update inode item. */
+		if (ret && ret != -ENODATA)
 			goto out_end_trans;
-		}
 	}
 
 update_flags:
@@ -395,6 +395,12 @@ int btrfs_fileattr_set(struct mnt_idmap *idmap,
 	inode_inc_iversion(&inode->vfs_inode);
 	inode_set_ctime_current(&inode->vfs_inode);
 	ret = btrfs_update_inode(trans, inode);
+	/*
+	 * If we set a property or deleted one, we must abort if we fail to
+	 * update the inode, to avoid persisting an inconsistent state.
+	 */
+	if (unlikely(ret && prop_set))
+		btrfs_abort_transaction(trans, ret);
 
  out_end_trans:
 	btrfs_end_transaction(trans);
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-5.15] btrfs: tree-checker: validate INODE_REF's namelen
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
  2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.15] btrfs: protect sb_write_pointer() with invalidate lock Sasha Levin
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: fix transaction abort logic in btrfs_fileattr_set() Sasha Levin
@ 2026-08-31 13:22 ` Sasha Levin
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: validate data reloc tree file extent item members Sasha Levin
                   ` (12 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:22 UTC (permalink / raw)
  To: patches, stable
  Cc: Weiming Shi, Xiang Mei, Qu Wenruo, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: Weiming Shi <bestswngs@gmail.com>

[ Upstream commit 3dc22abc21f5892406c09202fa2627196cc96967 ]

[BUG]
A crafted btrfs image can trigger the following crash:

  BUG: unable to handle page fault for address: ffffd1dc42884000
  #PF: supervisor write access in kernel mode
  #PF: error_code(0x0002) - not-present page
  CPU: 9 UID: 0 PID: 1034 Comm: poc Not tainted 7.1.0-rc4-custom+ #383 PREEMPT(full)  46af0a92938a63be7132e0dfd71e62327c51d5c2
  Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS unknown 02/02/2022
  RIP: 0010:memcpy+0xc/0x10
  Call Trace:
   <TASK>
   read_extent_buffer+0xe4/0x100 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f]
   btrfs_get_name+0x15e/0x1e0 [btrfs 3cf0785dd58fec8c5ff84633b772f17ce1f92a8f]
   reconnect_path+0x165/0x390
   exportfs_decode_fh_raw+0x337/0x400
   ? drop_caches_sysctl_handler+0xb0/0xb0
   </TASK>
  ---[ end trace 0000000000000000 ]---
  RIP: 0010:memcpy+0xc/0x10
  Kernel panic - not syncing: Fatal exception

[CAUSE]
TThe crafted image has the following corrupted INODE_REF item:

         item 9 key (258 INODE_REF 257) itemoff 11544 itemsize 4106
         	index 2 namelen 4096 name: d\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000

The itemsize matches the namelen, but the namelen is 4096, way larger
than normal name length limit (BTRFS_NAME_LEN, 255).

Meanwhile the memory of the @name is only 255 byte sized, this will cause
out-of-boundary access, and cause the above crash.

[FIX]
Add extra namelen verification for INODE_REF, just like what we have
done in ROOT_REF checks.

Now the crafted image can be rejected gracefully:

 BTRFS critical (device dm-2): corrupt leaf: root=5 block=30572544 slot=14 ino=259, invalid inode ref name length, has 4096 expect [1, 255]
 BTRFS error (device dm-2): read time tree block corruption detected on logical 30572544 mirror 2

Reported-by: Xiang Mei <xmei5@asu.edu>
Link: https://lore.kernel.org/linux-btrfs/aik0hEV6ehKx6Ldv@Air.local/
Acked-by: Weiming Shi <bestswngs@gmail.com>
Signed-off-by: Weiming Shi <bestswngs@gmail.com>
[ Rebase, add a Link: tag, add an simple cause analyze ]
Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.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:** `[btrfs: tree-checker] [validate] validate INODE_REF's
namelen — add missing name-length bounds check in btrfs tree-checker for
INODE_REF items`

### Step 1.2: Commit Message Tags
**Record:**
- **Reported-by:** Xiang Mei `<xmei5@asu.edu>` — real reporter with PoC
- **Link:** https://lore.kernel.org/linux-
  btrfs/aik0hEV6ehKx6Ldv@Air.local/
- **Acked-by:** Weiming Shi `<bestswngs@gmail.com>`
- **Signed-off-by:** Weiming Shi, Qu Wenruo, David Sterba
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` (btrfs maintainer)
- No Fixes:, Cc: stable, or syzbot tags
- Notable: maintainer review; concrete crash reproducer in message body

### Step 1.3: Commit Body Analysis
**Record:**
- **Bug:** Crafted btrfs image with `INODE_REF` item where
  `namelen=4096` but item fits in leaf (`itemsize=4106`). Tree-checker
  passes the within-item bounds check, but downstream code copies the
  name into a ~255-byte buffer.
- **Symptom:** Kernel page fault in `memcpy` via `read_extent_buffer` →
  `btrfs_get_name` → `reconnect_path` → `exportfs_decode_fh_raw`; fatal
  exception / panic.
- **Root cause:** `check_inode_ref()` validates `ptr + sizeof(*iref) +
  namelen <= end` but does not enforce `namelen <= BTRFS_NAME_LEN`
  (255). `btrfs_get_name()` uses a `NAME_MAX+1` (~256 byte) stack
  buffer.
- **Fix result:** Corrupt image rejected at read time with `-EUCLEAN`
  and clear error message.

### Step 1.4: Hidden Bug Fix Detection
**Record:** Not disguised — this is an explicit bug fix. It closes a
validation gap analogous to existing `check_dir_item()` name-length
checks (lines 602–606 in `tree-checker.c`).

---

## Phase 2: Diff Analysis

### Step 2.1: Change Inventory
**Record:**
- **Files:** `fs/btrfs/tree-checker.c` only (+6 lines)
- **Function:** `check_inode_ref()`
- **Scope:** Single-file, surgical fix

### Step 2.2: Code Flow Change
**Record:**
- **Before:** After reading `namelen`, only checked that `sizeof(*iref)
  + namelen` fits within the item boundary.
- **After:** Rejects `namelen == 0` or `namelen > BTRFS_NAME_LEN` before
  the boundary check.
- **Path affected:** Read-time leaf validation for every
  `BTRFS_INODE_REF_KEY` item (`disk-io.c` → `btrfs_check_leaf()`).

### Step 2.3: Bug Mechanism
**Record:**
- **Category:** Buffer overflow / out-of-bounds write (memory safety)
- **Mechanism:** `struct btrfs_inode_ref` is 10 bytes packed (`index` +
  `name_len`). With `namelen=4096` and `itemsize=4106`, `10 + 4096 =
  4106` passes the item-boundary check. Later, `btrfs_get_name()` in
  `export.c` calls `read_extent_buffer(leaf, name, name_ptr, name_len)`
  into a `NAME_MAX+1` buffer (`expfs.c:445`), causing OOB access and
  kernel panic.

### Step 2.4: Fix Quality
**Record:** Obviously correct; mirrors the existing `check_dir_item()`
pattern. Minimal, no API changes. Very low regression risk — only
rejects already-invalid metadata.

---

## Phase 3: Git History Investigation

### Step 3.1: Blame
**Record:** `check_inode_ref()` introduced in `71bf92a9b8777` (Aug 2019,
Qu Wenruo). The namelen boundary check has been missing since
introduction. Overflow check refined in `c7c01a4a2524b3` (David Sterba,
Nov 2020). Bug present in this tree since at least v4.x-era checker
addition.

### Step 3.2: Fixes: Tag
**Record:** N/A — no Fixes: tag. Underlying gap dates to original
`check_inode_ref()` commit.

### Step 3.3: Related File History
**Record:** Recent `tree-checker.c` commits include similar validation
fixes (`e92c2941204de` bounds check in `check_inode_extref`,
`96fa515e70f3e` inode ref size typo). Standalone fix; not part of a
multi-patch series in the message.

### Step 3.4: Author Context
**Record:** Qu Wenruo is a regular btrfs contributor; David Sterba is
btrfs maintainer. Weiming Shi authored the fix with maintainer
ack/review.

### Step 3.5: Dependencies
**Record:** No prerequisites. `BTRFS_NAME_LEN`, `check_inode_ref()`, and
`inode_ref_err()` all exist in this tree. Fix applies cleanly after line
1784 in local `tree-checker.c`.

---

## Phase 4: Mailing List and External Research

### Step 4.1: Original Discussion
**Record:** Lore URL from commit message blocked (403/Anubis). `b4
shazam 'validate INODE_REF namelen'` found no match. Full technical
details available from commit message (stack trace, corrupt item dump,
before/after behavior).

### Step 4.2: Reviewers
**Record:** David Sterba Reviewed-by + Signed-off-by confirms maintainer
review. UNVERIFIED: full CC list from `b4 dig -w` (could not run
successfully for this commit hash).

### Step 4.3: Bug Report
**Record:** Reported-by Xiang Mei with reproducible PoC. Crash:
supervisor write page fault in `memcpy` during NFS exportfs reconnect
path. Severity: kernel panic.

### Step 4.4: Related Patches
**Record:** Commit references ROOT_REF checks as precedent; no
`check_root_ref` or ROOT_BACKREF name-length validation found in this
tree's `tree-checker.c`. The analogous existing pattern is
`check_dir_item()` at lines 602–606. `check_inode_extref()` has the same
gap (no `BTRFS_NAME_LEN` check) but is out of scope for this commit.

### Step 4.5: Stable List History
**Record:** UNVERIFIED — could not search lore stable list due to access
restrictions.

---

## Phase 5: Code Semantic Analysis

### Step 5.1: Key Functions
**Record:** `check_inode_ref()` (modified); downstream vulnerable
consumer `btrfs_get_name()` in `export.c`.

### Step 5.2: Callers
**Record:**
- `check_inode_ref()` called from `check_leaf_item()` for
  `BTRFS_INODE_REF_KEY` → `__btrfs_check_leaf()` → `btrfs_check_leaf()`
- `btrfs_check_leaf()` called on **read** in `disk-io.c:457` (“read time
  tree block corruption detected”)
- `btrfs_get_name()` registered as `export_operations.get_name` in
  `btrfs_export_ops`; invoked from `exportfs_decode_fh_raw()` →
  `reconnect_path()` with `char nbuf[NAME_MAX+1]`

### Step 5.3: Callees
**Record:** `btrfs_inode_ref_name_len()`, `inode_ref_err()`, standard
extent_buffer helpers.

### Step 5.4: Reachability
**Record:** Trigger requires mounting/accessing a btrfs image with
corrupt `INODE_REF` metadata and hitting the NFS exportfs reconnect
path. Mounting crafted images typically needs `CAP_SYS_ADMIN`, but the
panic is still a real robustness/security issue for NFS servers
exporting btrfs and for any admin mounting untrusted images. Tree-
checker fix protects all consumers at block-read time.

### Step 5.5: Similar Patterns
**Record:** `check_dir_item()` validates `name_len > BTRFS_NAME_LEN`
(lines 602–606). `check_inode_ref()` and `check_inode_extref()` lack
equivalent checks — this commit closes the INODE_REF gap.

---

## Phase 6: Cross-Reference Against Local Tree (6.18.44)

### Step 6.1: Buggy Code Present?
**Record:** **YES.** Local tree is `v6.18.44` (`VERSION=6,
PATCHLEVEL=18, SUBLEVEL=44`). `check_inode_ref()` at lines 1783–1790
reads `namelen` and only checks item-boundary fit — no `BTRFS_NAME_LEN`
validation. Fix string `"invalid inode ref name length"` not present
(grep confirms fix not yet applied).

### Step 6.2: Backport Complications
**Record:** Clean apply expected — 6 lines inserted in one function. No
structural conflicts observed.

### Step 6.3: Related Fixes Already Present?
**Record:** No equivalent fix found. Related precedent: `e92c2941204de`
(inode extref bounds check fix, different bug).

---

## Phase 7: Subsystem Context

### Step 7.1: Subsystem Criticality
**Record:** **btrfs filesystem** — IMPORTANT. Affects metadata integrity
validation and NFS export path.

### Step 7.2: Subsystem Activity
**Record:** `tree-checker.c` actively maintained; multiple checker fixes
in recent history on this branch.

---

## Phase 8: Impact and Risk Assessment

### Step 8.1: Who Is Affected
**Record:** btrfs users, especially those with NFS exports
(`CONFIG_NFS_SERVER`). Also any path reading corrupt `INODE_REF` items
that assumed checker enforced name-length limits.

### Step 8.2: Trigger Conditions
**Record:** Corrupt/malicious btrfs image with `namelen > 255` but
within item bounds; block read succeeds checker; exportfs reconnect
calls `btrfs_get_name()`. Uncommon in practice but trivially craftable
(PoC provided).

### Step 8.3: Failure Mode Severity
**Record:** Kernel page fault → panic. **Severity: CRITICAL** (system
crash). Potential denial-of-service via crafted filesystem image.

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents kernel panic; rejects corruption
  gracefully at read time
- **Risk:** VERY LOW — 6-line validation matching existing dir_item
  pattern
- **Ratio:** Strongly favors backport

---

## Phase 9: Final Synthesis

### Step 9.1: Evidence Summary

**FOR backport:**
- Real, reproducible kernel panic on crafted btrfs image
- Missing validation gap since 2019
- Small, surgical, maintainer-reviewed fix
- Matches established tree-checker validation patterns
- Buggy code confirmed present in local 6.18.44 tree
- Fixes corruption handling at read time (defense in depth)

**AGAINST backport:**
- Crash path specifically involves NFS exportfs reconnect (narrower than
  generic mount)
- Requires ability to mount/access corrupt btrfs (usually root)
- `check_inode_extref()` has similar gap (not addressed here — separate
  issue)

**UNRESOLVED:**
- Full lore thread / stable nomination discussion (access blocked)
- Whether ROOT_REF checker mentioned in commit exists elsewhere in
  mainline (not found in this tree)

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — PoC before/after; maintainer
   reviewed
2. Fixes a real bug? **PASS** — OOB write → kernel panic
3. Important issue? **PASS** — CRITICAL (kernel panic)
4. Small and contained? **PASS** — 6 lines, one function
5. No new features/APIs? **PASS**
6. Can apply to local tree? **PASS** — code exists, clean apply expected

### Step 9.3: Exception Categories
**Record:** N/A — standard bug fix, not a quirk/ID/DT exception.

### Step 9.4: Decision Rationale

This commit closes a real validation hole in btrfs's tree-checker. A
crafted `INODE_REF` with `namelen=4096` passes the existing within-item
bounds check (`sizeof(*iref)` is 10 bytes, so `10 + 4096 = 4106` fits a
4106-byte item) but violates `BTRFS_NAME_LEN` (255). Downstream,
`btrfs_get_name()` copies into a `NAME_MAX+1` buffer, causing a kernel
panic. The fix is minimal, follows the existing `check_dir_item()`
pattern, is reviewed by the btrfs maintainer, and the vulnerable code is
present in this 6.18.44 tree without the fix.

---

## Verification

- **[Phase 1]** Parsed subject, tags, body; found Reported-by, Link,
  Reviewed-by David Sterba
- **[Phase 2]** Diff: +6 lines in `check_inode_ref()` adding `namelen ==
  0 || namelen > BTRFS_NAME_LEN` check
- **[Phase 3]** `git describe HEAD`: `v6.18.44`; blame shows
  `check_inode_ref` from `71bf92a9b8777` (2019)
- **[Phase 3]** `git log --oneline -20 -- fs/btrfs/tree-checker.c`:
  related checker fixes confirmed
- **[Phase 4]** Lore fetch blocked (403/Anubis); `b4 shazam` no match —
  UNVERIFIED for thread discussion
- **[Phase 5]** `grep btrfs_inode_ref_name_len`: multiple consumers;
  `export.c:287-290` vulnerable path confirmed
- **[Phase 5]** `expfs.c:445`: `char nbuf[NAME_MAX+1]` confirms small
  destination buffer
- **[Phase 5]** `include/uapi/linux/btrfs_tree.h:22`: `BTRFS_NAME_LEN
  255`
- **[Phase 5]** `include/uapi/linux/btrfs_tree.h:852-856`: `struct
  btrfs_inode_ref` is 10 bytes packed
- **[Phase 5]** `disk-io.c:457-465`: tree-checker runs at read time
- **[Phase 5]** `tree-checker.c:602-606`: `check_dir_item()` already
  validates name length — precedent confirmed
- **[Phase 6]** `grep 'invalid inode ref name length'`: no matches — fix
  not in tree
- **[Phase 6]** Read `check_inode_ref()` at lines 1752–1799: buggy code
  confirmed present
- **[Phase 8]** Failure mode: OOB write → page fault → panic, severity
  CRITICAL

**YES****Backport analysis: `btrfs: tree-checker: validate INODE_REF's
namelen`**

For the local **6.18.44** tree, this should be backported.

`check_inode_ref()` only verified that the name fits inside the item,
not that `namelen <= BTRFS_NAME_LEN` (255). A crafted image with
`namelen=4096` in a 4106-byte item passes the checker but later triggers
an out-of-bounds write in `btrfs_get_name()` during NFS exportfs
reconnect, causing a kernel panic.

The fix is small (6 lines), matches the existing `check_dir_item()`
pattern, is reviewed by btrfs maintainer David Sterba, and the
vulnerable code is present in this tree without the fix.

**YES**

 fs/btrfs/tree-checker.c | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index c7e0e5fb6402d..13b711992a1c8 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1840,6 +1840,12 @@ static int check_inode_ref(struct extent_buffer *leaf,
 
 		iref = (struct btrfs_inode_ref *)ptr;
 		namelen = btrfs_inode_ref_name_len(leaf, iref);
+		if (unlikely(namelen == 0 || namelen > BTRFS_NAME_LEN)) {
+			inode_ref_err(leaf, slot,
+				"invalid inode ref name length, has %u expect [1, %u]",
+				namelen, BTRFS_NAME_LEN);
+			return -EUCLEAN;
+		}
 		if (unlikely(ptr + sizeof(*iref) + namelen > end)) {
 			inode_ref_err(leaf, slot,
 				"inode ref overflow, ptr %lu end %lu namelen %u",
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: validate data reloc tree file extent item members
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (2 preceding siblings ...)
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: tree-checker: validate INODE_REF's namelen Sasha Levin
@ 2026-08-31 13:22 ` Sasha Levin
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr() Sasha Levin
                   ` (11 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:22 UTC (permalink / raw)
  To: patches, stable
  Cc: Teng Liu, Qu Wenruo, David Sterba, syzbot+3e20d8f3d41bac5dc9a2,
	Sasha Levin, clm, linux-btrfs, linux-kernel

From: Teng Liu <27rabbitlt@gmail.com>

[ Upstream commit a6908f88c9da9778957a07ac568aa643124278a8 ]

get_new_location() uses BUG_ON() to crash the kernel if the file extent
item it looks up has any of offset, compression, encryption, or
other_encoding set non-zero. The data reloc inode is only written by
relocation's own paths and the four fields are always 0 in what the
kernel writes:

  - insert_prealloc_file_extent() memsets the stack item to zero and
    only fills in type, disk_bytenr, disk_num_bytes and num_bytes, so
    offset/compression/encryption/other_encoding stay 0.
  - insert_ordered_extent_file_extent() copies oe->compress_type into
    the file extent's compression field, but the data reloc inode is
    created with BTRFS_INODE_NOCOMPRESS so compress_type is always 0;
    encryption and other_encoding are reserved-and-zero in btrfs.

A non-zero value here means the leaf decoded from disk does not match
what the kernel wrote, i.e. on-disk corruption. A malformed image
reaches this code via balance and panics the kernel.

A previous attempt to enforce all four constraints in tree-checker's
check_extent_data_item() was merged as commit 7d0ee95979e9 ("btrfs:
validate data reloc tree file extent item members in tree-checker")
and then reverted by commit 1c034697fcaa after btrfs/061 produced
false positives on arm64 with 64K pages. The reason: relocation
writeback legitimately produces REG file_extent_items with offset != 0
in the data reloc tree. When an ordered extent covers only the back
portion of an underlying PREALLOC (num_bytes < ram_bytes on the input
file_extent), insert_ordered_extent_file_extent() inserts a REG with

  offset    = oe->offset
  num_bytes = oe->num_bytes
  ram_bytes preserved from the original PREALLOC,

and this item can reach disk if a transaction commit fires while it
is present in the leaf.

The four fields belong in different layers:

  - compression, encryption and other_encoding are universal
    invariants for every item in the data reloc tree, regardless of
    cluster geometry. Enforce them in tree-checker's
    check_extent_data_item() so a corrupt leaf is rejected at read
    time.

  - offset is only an invariant at the cluster-boundary keys that
    get_new_location() searches (the key is computed as
    src_disk_bytenr - reloc_block_group_start). Partial-PREALLOC
    writebacks legitimately place REG items at non-boundary keys with
    offset != 0; tree-checker cannot reject these. The cluster-
    boundary item is always written by either
    insert_prealloc_file_extent() (offset=0 by memset) or by the
    front portion of a partial writeback (offset=0 by construction),
    so a non-zero offset there is corruption.

Enforce the universal invariants in check_extent_data_item() with a
file_extent_err() rejection. Convert the BUG_ON() in
get_new_location() to a -EUCLEAN return paired with btrfs_print_leaf()
and btrfs_err() so the offending leaf is logged. The caller in
replace_file_extents() already handles non-zero returns from
get_new_location() by breaking out of the loop without aborting the
transaction.

Suggested-by: Qu Wenruo <wqu@suse.com>
Suggested-by: David Sterba <dsterba@suse.com>
Reported-by: syzbot+3e20d8f3d41bac5dc9a2@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=3e20d8f3d41bac5dc9a2
Signed-off-by: Teng Liu <27rabbitlt@gmail.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

The background git searches finished with no matches — expected, since
this tree is **v6.18.44** and commit `a6908f88c9da` isn’t on it yet.
Subject grep and related-commit searches also came up empty, which fits:
the earlier mainline-only fix/revert pair (`7d0ee95979e9` /
`1c034697fcaa`) never landed in 6.18.y.

That doesn’t change the earlier conclusion: this is a syzbot-reported
kernel panic fix (`BUG_ON()` → `-EUCLEAN`), it applies cleanly, and it
should be backported to this tree.

**YES**

 fs/btrfs/relocation.c   | 22 ++++++++++++++++++----
 fs/btrfs/tree-checker.c | 27 +++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 4 deletions(-)

diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index 80e5de6736e82..f1104fa6efb3f 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -822,6 +822,7 @@ static int get_new_location(struct inode *reloc_inode, u64 *new_bytenr,
 			    u64 bytenr, u64 num_bytes)
 {
 	struct btrfs_root *root = BTRFS_I(reloc_inode)->root;
+	struct btrfs_fs_info *fs_info = root->fs_info;
 	BTRFS_PATH_AUTO_FREE(path);
 	struct btrfs_file_extent_item *fi;
 	struct extent_buffer *leaf;
@@ -843,10 +844,23 @@ static int get_new_location(struct inode *reloc_inode, u64 *new_bytenr,
 	fi = btrfs_item_ptr(leaf, path->slots[0],
 			    struct btrfs_file_extent_item);
 
-	BUG_ON(btrfs_file_extent_offset(leaf, fi) ||
-	       btrfs_file_extent_compression(leaf, fi) ||
-	       btrfs_file_extent_encryption(leaf, fi) ||
-	       btrfs_file_extent_other_encoding(leaf, fi));
+	/*
+	 * The cluster-boundary key searched above is always written by
+	 * relocation with offset 0: either by insert_prealloc_file_extent()
+	 * (memsets the stack item to 0) or by the front portion of a partial
+	 * writeback (offset=0 by construction). A non-zero value here means
+	 * the on-disk leaf does not match what relocation wrote, i.e.
+	 * corruption. The other encoding fields are caught earlier by
+	 * tree-checker's check_extent_data_item().
+	 */
+	if (unlikely(btrfs_file_extent_offset(leaf, fi))) {
+		btrfs_print_leaf(leaf);
+		btrfs_err(fs_info,
+"unexpected non-zero offset in file extent item for data reloc inode %llu key offset %llu offset %llu",
+			  btrfs_ino(BTRFS_I(reloc_inode)), bytenr,
+			  btrfs_file_extent_offset(leaf, fi));
+		return -EUCLEAN;
+	}
 
 	if (num_bytes != btrfs_file_extent_disk_num_bytes(leaf, fi))
 		return -EINVAL;
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index 97be0c1ed22f7..c7e0e5fb6402d 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -296,6 +296,33 @@ static int check_extent_data_item(struct extent_buffer *leaf,
 		return 0;
 	}
 
+	/*
+	 * For the data reloc tree, file extent items are written by
+	 * relocation's own paths. The data reloc inode is created with
+	 * BTRFS_INODE_NOCOMPRESS, so insert_ordered_extent_file_extent()
+	 * always leaves the compression field at 0. Encryption and
+	 * other_encoding are reserved-and-zero in btrfs. A non-zero value
+	 * for any of these means the leaf decoded from disk does not match
+	 * what the kernel wrote, i.e. on-disk corruption.
+	 *
+	 * The file_extent_item's offset field is NOT a universal invariant
+	 * here: partial-PREALLOC writebacks legitimately produce REG items
+	 * with non-zero offset at non-boundary keys. The offset check is
+	 * performed at the call site in get_new_location(), which only
+	 * inspects cluster-boundary keys where offset is always 0.
+	 */
+	if (unlikely(btrfs_header_owner(leaf) == BTRFS_DATA_RELOC_TREE_OBJECTID &&
+		     (btrfs_file_extent_compression(leaf, fi) ||
+		      btrfs_file_extent_encryption(leaf, fi) ||
+		      btrfs_file_extent_other_encoding(leaf, fi)))) {
+		file_extent_err(leaf, slot,
+"invalid encoding fields for data reloc tree, compression=%u encryption=%u other_encoding=%u",
+				btrfs_file_extent_compression(leaf, fi),
+				btrfs_file_extent_encryption(leaf, fi),
+				btrfs_file_extent_other_encoding(leaf, fi));
+		return -EUCLEAN;
+	}
+
 	/* Regular or preallocated extent has fixed item size */
 	if (unlikely(item_size != sizeof(*fi))) {
 		file_extent_err(leaf, slot,
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-5.15] btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (3 preceding siblings ...)
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: validate data reloc tree file extent item members Sasha Levin
@ 2026-08-31 13:22 ` Sasha Levin
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.12] btrfs: derive f_fsid from on-disk fsid and dev_t Sasha Levin
                   ` (10 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:22 UTC (permalink / raw)
  To: patches, stable
  Cc: Dave Chen, Filipe Manana, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: Dave Chen <davechen@synology.com>

[ Upstream commit 9411aafdf352b8d72668732af5a37dcb27383e25 ]

btrfs_getattr() unconditionally reads BTRFS_I(inode)->new_delalloc_bytes
and adds it (sector-aligned) to stat->blocks for every inode type.
However, new_delalloc_bytes lives in a union with last_dir_index_offset:

    union {
        u64 new_delalloc_bytes;     /* files only */
        u64 last_dir_index_offset;  /* directories only */
    };

For a directory inode this memory holds last_dir_index_offset, which is
set during directory logging (e.g. flush_dir_items_batch()) to the
offset of the last logged BTRFS_DIR_INDEX_KEY.  That offset grows with
the number of entries ever created in the directory (dir indexes are
monotonic and never reused), so it can be arbitrarily large.

As a result, after a directory has been logged (e.g. via an fsync that
triggers directory logging), btrfs_getattr() reports inflated st_blocks
for that directory.  The inflation is purely in-core and disappears
after the inode is evicted and reloaded (btrfs_alloc_inode() zeroes the
union), e.g. after a remount.

Reproducer (on a btrfs filesystem):

    D=/mnt/btrfs/d
    mkdir -p $D
    for i in $(seq 1 20000); do touch $D/f$i; done
    sync                      # commit, push dir index high
    touch $D/trigger          # dirty the dir in a new transaction
    xfs_io -c fsync $D        # log the directory -> sets last_dir_index_offset
    stat -c '%b' $D           # st_blocks is now inflated (e.g. 40)
    # umount + mount -> st_blocks drops back to the correct value

The evict path already knows this union is type-dependent and guards the
corresponding WARN_ON with !S_ISDIR() in btrfs_destroy_inode(); only
btrfs_getattr() was missing the equivalent check.

Only read new_delalloc_bytes for regular files, which are the only
inodes that ever set it.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Dave Chen <davechen@synology.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: btrfs: only account delalloc bytes for
regular file inodes in btrfs_getattr()

**Local tree:** Linux **6.18.44** (`v6.18.44-1-g2736c32da98b9`)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject line
**Record:** `[btrfs]` `[fix implicit]` — only account delalloc bytes for
regular file inodes in `btrfs_getattr()`.

### Step 1.2: Tags
**Record:**
- **Reviewed-by:** Filipe Manana `<fdmanana@suse.com>` (btrfs
  maintainer)
- **Signed-off-by:** Dave Chen `<davechen@synology.com>` (author)
- **Signed-off-by:** Filipe Manana `<fdmanana@suse.com>`
- **Signed-off-by:** David Sterba `<dsterba@suse.com>` (btrfs
  maintainer)
- No Fixes:, Reported-by:, Tested-by:, Link:, or Cc: stable tags
- Notable: dual btrfs maintainer sign-off; no syzbot/fuzzer report

### Step 1.3: Body analysis
**Record:**
- **Bug:** `btrfs_getattr()` always reads
  `BTRFS_I(inode)->new_delalloc_bytes`, but that field shares a union
  with `last_dir_index_offset` (directories only).
- **Symptom:** After directory logging (e.g. `fsync` on a dirty
  directory), `stat()` reports inflated `st_blocks` for that directory.
  Value scales with number of directory entries ever created.
- **Failure mode:** Incorrect userspace-visible block count; purely in-
  core; resets after inode eviction/remount (`btrfs_alloc_inode()`
  zeroes the union).
- **Root cause:** Union member read without inode-type check;
  `btrfs_destroy_inode()` already guards the equivalent `WARN_ON` with
  `!S_ISDIR()`.
- **Reproducer:** Provided in commit message (20,000 files in a
  directory, sync, fsync, `stat -c '%b'`).

### Step 1.4: Hidden bug fix?
**Record:** No — this is an explicit correctness fix for wrong
`st_blocks` reporting, not disguised cleanup.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
**Record:**
- **Files:** `fs/btrfs/inode.c` (+2/-1 lines)
- **Function:** `btrfs_getattr()`
- **Scope:** Single-file, surgical fix

### Step 2.2: Code flow change
**Record:**
- **Before:** `delalloc_bytes = BTRFS_I(inode)->new_delalloc_bytes` for
  every inode type.
- **After:** `delalloc_bytes = S_ISREG(inode->i_mode) ?
  BTRFS_I(inode)->new_delalloc_bytes : 0`
- **Path affected:** Normal `stat`/`statx` path for all btrfs inodes;
  bug manifests on directories after logging.

### Step 2.3: Bug mechanism
**Record:** **Logic / union misuse correctness bug.**
`new_delalloc_bytes` and `last_dir_index_offset` occupy the same union
memory. Directory logging (`flush_dir_items_batch()` in `tree-log.c`)
writes `last_dir_index_offset`; `btrfs_getattr()` misinterprets it as
pending delalloc bytes and inflates `stat->blocks`.

### Step 2.4: Fix quality
**Record:** Obviously correct — mirrors the existing `!S_ISDIR()` guard
in `btrfs_destroy_inode()`. Minimal change. No new locks or API changes.
Regression risk: very low (directories/symlinks/special files never
legitimately set `new_delalloc_bytes`).

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:** Lines 8173–8178 blame to `5d324e5159d9e` (2025-11-28 merge).
Shallow clone limits deeper history; cannot pinpoint the exact
introducing commit beyond confirming the buggy pattern is present in
this tree.

### Step 3.2: Fixes: tag
**Record:** Not applicable — no Fixes: tag in commit message.

### Step 3.3: File history
**Record:** Recent `fs/btrfs/inode.c` changes are unrelated (bool types,
IO failure fix, folio removal, delalloc bit handling). No prior fix for
this issue found in this tree. Fix commit itself is **not** present
locally.

### Step 3.4: Author context
**Record:** Dave Chen has at least one other btrfs commit in this tree
(`39f196f64bd38` — metadata accounting type fix). btrfs maintainers
reviewed and signed off.

### Step 3.5: Dependencies
**Record:** Standalone — requires only `S_ISREG()` and existing union
layout. No series dependencies. Union and `btrfs_getattr()` delalloc
accounting both exist in v6.18.44.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original discussion
**Record:** `b4 dig` on related commit `0912b98151eea` succeeded (found
unrelated patch thread). Direct `b4 dig -c` on the fix commit hash was
unavailable (commit not in local tree). Lore.kernel.org fetch blocked by
bot protection. **Could not retrieve the fix patch's original lore
thread.**

### Step 4.2: Reviewers
**Record:** Filipe Manana (Reviewed-by + SOB) and David Sterba (SOB) —
both btrfs subsystem maintainers.

### Step 4.3: Bug report
**Record:** No external bug report links. Reproducer is self-contained
in the commit message.

### Step 4.4: Related patches
**Record:** Standalone one-commit fix; not part of a series.

### Step 4.5: Stable list history
**Record:** Not searched successfully (lore blocked). No Cc: stable in
commit message.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key functions
**Record:** `btrfs_getattr()` modified.

### Step 5.2: Callers
**Record:** `btrfs_getattr` is registered as `.getattr` in:
- `btrfs_dir_inode_operations` (line 10597)
- `btrfs_file_inode_operations` (line 10658)
- `btrfs_special_inode_operations` (line 10670)
- `btrfs_symlink_inode_operations` (line 10680)

All inode types go through this function on `stat`/`statx`/`fstatat`.

### Step 5.3: Callees
**Record:** `generic_fillattr()`, `inode_get_bytes()`, spin lock on
`BTRFS_I(inode)->lock`, block alignment math for `stat->blocks`.

### Step 5.4: Reachability
**Record:** **Userspace-reachable** via `stat()`, `fstat()`, `statx()`,
`ls -l`, `du`, and any tool reading `st_blocks`. Trigger requires btrfs
+ directory with logged entries + `fsync` — realistic on production
btrfs systems with large directories.

### Step 5.5: Similar patterns
**Record:** `btrfs_destroy_inode()` at lines 8045–8048 already uses `if
(!S_ISDIR(...))` before checking `new_delalloc_bytes`.
`btrfs_alloc_inode()` at lines 7967–7968 documents the union and zeroes
it. `btrfs_inode.h` lines 241–254 document per-type union usage. Only
`btrfs_getattr()` was missing the type guard.

---

## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE (v6.18.44)

### Step 6.1: Buggy code present?
**Record:** **YES.** Current code at line 8174:

```8173:8178:fs/btrfs/inode.c
        spin_lock(&BTRFS_I(inode)->lock);
        delalloc_bytes = BTRFS_I(inode)->new_delalloc_bytes;
        inode_bytes = inode_get_bytes(inode);
        spin_unlock(&BTRFS_I(inode)->lock);
        stat->blocks = (ALIGN(inode_bytes, blocksize) +
                        ALIGN(delalloc_bytes, blocksize)) >>
SECTOR_SHIFT;
```

Union definition confirmed in `btrfs_inode.h` lines 241–254.
`last_dir_index_offset` is set in `tree-log.c` line 4090 during
`flush_dir_items_batch()`.

### Step 6.2: Backport complications
**Record:** **Clean apply expected** — context matches the provided diff
exactly. No conflicting recent changes in this hunk.

### Step 6.3: Related fixes already present?
**Record:** **No** — `git grep` finds no `S_ISREG` guard around
`new_delalloc_bytes` in `btrfs_getattr()`. Fix commit not in tree.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: Subsystem criticality
**Record:** **fs/btrfs** — IMPORTANT. btrfs is widely deployed (servers,
NAS appliances, desktops). `stat` correctness affects monitoring, quota
tools, and backup software.

### Step 7.2: Subsystem activity
**Record:** Actively maintained — multiple recent fixes in `inode.c` in
this tree.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who is affected
**Record:** btrfs users who `stat`/`du` directories that have undergone
directory logging (common after `fsync` on directories with many
entries). Config-specific: `CONFIG_BTRFS_FS=y/m`.

### Step 8.2: Trigger conditions
**Record:** Directory with many entries → transaction commit → dirty
directory → `fsync` triggers directory logging → `last_dir_index_offset`
set → subsequent `stat` inflates `st_blocks`. Unprivileged users with
directory read access can trigger `stat`; `fsync` requires write access.
Not a race — deterministic logic bug.

### Step 8.3: Failure mode severity
**Record:** **Incorrect `st_blocks` reporting** — **MEDIUM**. No kernel
crash, corruption, deadlock, or security impact. User-visible wrong
disk-usage data. Self-corrects on remount/inode eviction. Can mislead
`du`, monitoring, and capacity planning tools.

### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** MEDIUM — corrects real, reproducible stat data on a
  widely used filesystem
- **Risk:** VERY LOW — 2-line type guard matching existing in-tree
  pattern
- **Ratio:** Favorable

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence summary

**FOR backport:**
- Real, reproducible bug with clear mechanism (union misuse)
- User-visible incorrect `st_blocks` on normal btrfs operations
- Obviously correct fix, reviewed by btrfs maintainers
- Tiny, self-contained, no dependencies
- Buggy code confirmed present in v6.18.44; fix not yet applied
- Consistent with existing `!S_ISDIR()` guard in `btrfs_destroy_inode()`

**AGAINST backport:**
- No crash, corruption, security, or deadlock
- In-core only; self-heals on remount
- Does not affect on-disk state or actual space allocation
- `st_blocks` for directories is often less critical than for files

**Unresolved:**
- Exact commit that introduced the bug (shallow clone)
- Lore discussion thread (site blocked)
- No user/distro bug reports found

### Step 9.2: Stable rules checklist
1. Obviously correct and tested? **PASS** — logic is clear; maintainers
   reviewed; reproducer provided
2. Fixes a real bug affecting users? **PASS** — wrong `st_blocks` after
   `fsync` on large directories
3. Important issue? **PASS (borderline)** — not crash/corruption, but a
   real user-visible "oh, that's not good" stat bug per `stable-kernel-
   rules.rst`
4. Small and contained? **PASS** — 3 lines in one function
5. No new features or APIs? **PASS**
6. Can apply to local tree? **PASS** — buggy code present, clean apply
   expected

### Step 9.3: Exception categories
**Record:** Not applicable (not device ID, quirk, DT, build, or docs
fix).

### Step 9.4: Decision rationale

For **Linux 6.18.44**, the bug exists: `btrfs_getattr()` reads a file-
only union member for directory inodes, producing inflated `st_blocks`
after directory logging. This is a genuine btrfs correctness bug
reachable through normal `fsync` + `stat` operations. While not a crash
or corruption issue, it produces wrong userspace-visible data on a major
filesystem — exactly the kind of contained logic fix stable trees accept
when maintainers have reviewed it. The fix is trivial, matches an
existing in-tree pattern, and carries negligible regression risk.

---

## Verification

- **[Phase 1]** Parsed commit message: subsystem=btrfs, tags=Reviewed-by
  Filipe Manana, SOBs from author + maintainers
- **[Phase 2]** Diff: 1 file, `btrfs_getattr()`, +2/-1, union misuse fix
- **[Phase 3]** `git describe HEAD`: v6.18.44; shallow repo (11944
  commits)
- **[Phase 3]** `git blame -L 8173,8174 fs/btrfs/inode.c`: buggy line
  present, dates to 2025-11-28 merge
- **[Phase 3]** `git log --grep="only account delalloc"`: no match — fix
  not in tree
- **[Phase 4]** `b4 dig -c 0912b98151eea`: found unrelated thread; fix
  commit lore thread **UNVERIFIED**
- **[Phase 4]** WebFetch lore.kernel.org: blocked by bot protection
- **[Phase 5]** `grep btrfs_getattr`: registered for dir, file, special,
  symlink inode ops
- **[Phase 5]** Read `btrfs_destroy_inode()` lines 8045–8048:
  `!S_ISDIR()` guard confirmed
- **[Phase 5]** Read `btrfs_alloc_inode()` lines 7967–7968: union zeroed
  with comment
- **[Phase 5]** Read `btrfs_inode.h` lines 241–254: union documented per
  inode type
- **[Phase 5]** Read `tree-log.c` line 4090: `last_dir_index_offset` set
  during directory logging
- **[Phase 6]** Read `inode.c` lines 8173–8178: unconditional
  `new_delalloc_bytes` read confirmed
- **[Phase 6]** `git grep S_ISREG.*new_delalloc`: no existing fix in
  tree
- **[Phase 7]** `git log --oneline -20 -- fs/btrfs/inode.c`: active
  btrfs development confirmed
- **[Phase 8]** Failure mode: inflated `st_blocks`, severity MEDIUM, no
  crash/corruption

**YES**The background `b4 dig` check didn’t finish cleanly — it only
confirmed `b4` is installed at `/home/sasha/.local/bin/b4`, then stalled
on `b4 dig -c 5d324e5159d9e` (a merge commit, not the fix itself) and
exited with an error.

That doesn’t change the backport analysis: the bug is present in this
**6.18.44** tree, the fix is a small type guard in `btrfs_getattr()`,
and the recommendation remains **YES**.

 fs/btrfs/inode.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
index 36f75c6a8344d..323d56e1fc183 100644
--- a/fs/btrfs/inode.c
+++ b/fs/btrfs/inode.c
@@ -8171,7 +8171,8 @@ static int btrfs_getattr(struct mnt_idmap *idmap,
 	stat->result_mask |= STATX_SUBVOL;
 
 	spin_lock(&BTRFS_I(inode)->lock);
-	delalloc_bytes = BTRFS_I(inode)->new_delalloc_bytes;
+	delalloc_bytes = S_ISREG(inode->i_mode) ?
+			 BTRFS_I(inode)->new_delalloc_bytes : 0;
 	inode_bytes = inode_get_bytes(inode);
 	spin_unlock(&BTRFS_I(inode)->lock);
 	stat->blocks = (ALIGN(inode_bytes, blocksize) +
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] btrfs: derive f_fsid from on-disk fsid and dev_t
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (4 preceding siblings ...)
  2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr() Sasha Levin
@ 2026-08-31 13:23 ` Sasha Levin
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18] btrfs: validate properties before setting them Sasha Levin
                   ` (9 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:23 UTC (permalink / raw)
  To: patches, stable
  Cc: Anand Jain, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: Anand Jain <asj@kernel.org>

[ Upstream commit c2a74ed0494c2736486b49c52767b2f50b83425f ]

The f_fsid was originally derived from fs_devices->fsid and the
subvolume root ID. However, when temp_fsid is active, fs_devices->fsid
is randomized, making the standard derivation inconsistent.

Since metadata_uuid is optional, it is not a reliable alternative.  This
patch instead retrieves the on-disk UUID from fs_info->super_copy->fsid.

To prevent f_fsid collisions between original and cloned filesystems,
this implementation hashes the dev_t for single-device btrfs filesystems
to ensure uniqueness. This is limited to single-device filesystems as
cloned mounts are currently only supported for that configuration. Note
that f_fsid will change if the device is replaced.

Additionally, since the kernel cannot distinguish between the original
and the cloned filesystem, this new f_fsid derivation is applied to
both.

Link: https://lore.kernel.org/linux-btrfs/cover.1772095546.git.asj@kernel.org/
Link: https://lore.kernel.org/linux-btrfs/cover.1774092915.git.asj@kernel.org/
Signed-off-by: Anand Jain <asj@kernel.org>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: derive f_fsid from on-disk fsid and
dev_t`

**Local tree:** `v6.18.44` (6.18.44)
**Upstream commit:** `c2a74ed0494c2736486b49c52767b2f50b83425f`
(2026-06-09)
**Status in this tree:** Fix **not** present; buggy code **is** present
in `fs/btrfs/super.c`

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject line
**Record:** `[btrfs] [derive] — Change `f_fsid` derivation to use on-
disk FSID and `dev_t` for stability/uniqueness with `temp_fsid` cloned
mounts.`

### Step 1.2: Tags
**Record:**
- **Link:** `https://lore.kernel.org/linux-
  btrfs/cover.1772095546.git.asj@kernel.org/`
- **Link:** `https://lore.kernel.org/linux-
  btrfs/cover.1774092915.git.asj@kernel.org/`
- **Signed-off-by:** Anand Jain `<asj@kernel.org>`
- **Signed-off-by:** David Sterba `<dsterba@suse.com>` (btrfs
  maintainer)
- No `Fixes:`, `Reported-by:`, `Cc: stable`, `Tested-by:`, or `Reviewed-
  by:` tags
- No syzbot/sanitizer indicators

### Step 1.3: Body analysis
**Record:**
- **Bug:** `f_fsid` was derived from `fs_devices->fsid`, which is
  randomized when `temp_fsid` is active (cloned-device mount support).
- **Symptom:** `f_fsid` is inconsistent across mount cycles for cloned
  btrfs filesystems; original and cloned mounts can also collide on
  `f_fsid`.
- **Root cause:** `temp_fsid` assigns a random in-memory UUID to
  `fs_devices->fsid`; `metadata_uuid` is optional and unreliable.
- **Fix approach:** Use on-disk `super_copy->fsid` when `temp_fsid` is
  active; XOR in `dev_t` (via `huge_encode_dev`) for all single-device
  btrfs to ensure uniqueness between original and clone.
- **Version info:** None explicit; `temp_fsid` landed in this tree since
  v6.10.

### Step 1.4: Hidden bug fix?
**Record:** Yes — described as derivation change, but it fixes (1) non-
persistent `f_fsid` across remounts with `temp_fsid`, and (2) `f_fsid`
collisions between original and cloned single-device btrfs.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
**Record:**
- **File:** `fs/btrfs/super.c` (+33 / -8 lines)
- **Function:** `btrfs_statfs()`
- **Scope:** Single-file surgical fix

### Step 2.2: Code flow changes
**Record:**
- **Hunk 1:** Defer `fsid` pointer assignment; add local `f_fsid`
  accumulator.
- **Hunk 2 (before → after):**
  - Before: Always use `fs_devices->fsid`; write directly to
    `buf->f_fsid`.
  - After: If `temp_fsid`, use `super_copy->fsid`; else
    `fs_devices->fsid`. Compute into local `f_fsid`, XOR root ID,
    optionally XOR `dev_t` hash for single-device FS, then `memcpy` to
    `buf->f_fsid`.

### Step 2.3: Bug mechanism
**Record:**
- **Category:** Logic/correctness fix (filesystem identification)
- **Mechanism:** Randomized `fs_devices->fsid` under `temp_fsid` made
  `statfs()` `f_fsid` non-deterministic; identical on-disk FSID + root
  ID between original and clone caused collisions. Fix uses stable on-
  disk UUID and mixes in `dev_t` for disambiguation.

### Step 2.4: Fix quality
**Record:**
- Fix is minimal, readable, and matches existing patterns
  (`u64_to_fsid`, `huge_encode_dev` used elsewhere e.g. xfs).
- **Regression risk:** Low for crashes; **medium** for userspace-visible
  semantics — `f_fsid` changes for all single-device btrfs (not only
  `temp_fsid` mounts), by design.
- `latest_dev->bdev` is valid when `total_devices == 1` and mount
  succeeded (verified: `latest_dev` set during device open in
  `volumes.c`).

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:**
- `f_fsid` base computation dates to 2008 (`9d03632e26e1a`).
- Root ID masking added 2024 (`e094f48040cda6`).
- Buggy `fs_devices->fsid` usage at line 1738 is pre-`temp_fsid`; bug
  activated when `temp_fsid` was introduced in `a5b8a5f9f8355`
  (2023-10-12, first in v6.10).

### Step 3.2: Fixes: tag
**Record:** N/A — no `Fixes:` tag. Introducing commit for the underlying
feature: `a5b8a5f9f8355` ("btrfs: support cloned-device mount
capability"), confirmed present in this tree.

### Step 3.3: Related file history
**Record:**
- Companion patch in same series: `df84f6c773771` ("btrfs: use on-disk
  uuid for s_uuid in temp_fsid mounts") — **not** in this tree.
- This `f_fsid` fix is standalone (only touches `super.c`); does not
  depend on the `s_uuid` patch.
- No "patch X/Y" marker; two-commit series addressing related
  `temp_fsid` identification issues.

### Step 3.4: Author context
**Record:** Anand Jain is an active btrfs contributor; David Sterba
(maintainer) signed off. Author has multiple `temp_fsid`-related commits
in this tree.

### Step 3.5: Dependencies
**Record:** No prerequisites. `u64_to_fsid` exists in
`include/linux/statfs.h`; `temp_fsid`, `total_devices`, `latest_dev`,
`super_copy` all exist in this tree. Applies cleanly against current
`super.c`.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original discussion
**Record:** `b4 dig -c c2a74ed0494c2` returned no match (commit likely
too recent for b4 cache). Lore URLs blocked by Anubis bot protection —
could not read thread. No matching `.mbx` files in workspace.

### Step 4.2: Reviewers
**Record:** `b4 dig -w` also failed. Maintainer sign-off from David
Sterba verified via commit metadata.

### Step 4.3: Bug reports
**Record:** No external bug report links beyond series cover letters
(unreadable). No syzbot/fuzzer reports.

### Step 4.4: Related patches
**Record:** Two-patch series: (1) `s_uuid` fix in `disk-io.c`, (2) this
`f_fsid` fix. Only this patch is needed for the `statfs`/`f_fsid` bug;
`s_uuid` fix addresses a separate overlayfs identification issue.

### Step 4.5: Stable list
**Record:** Could not search lore stable list (blocked). No evidence
found of prior stable nomination or rejection.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key functions
**Record:** `btrfs_statfs()` (modified)

### Step 5.2: Callers
**Record:** `btrfs_statfs` registered as `sb->s_op->statfs` at line
2441. Reachable via:
- `vfs_statfs()` / `statfs()` syscall
- `vfs_get_fsid()` in `fs/statfs.c` (used by fanotify)

### Step 5.3: Callees
**Record:** `be32_to_cpu`, `btrfs_root_id`, `u64_to_fsid`,
`huge_encode_dev`, `memcpy` — all standard, available in-tree.

### Step 5.4: Reachability
**Record:** Any userspace `statfs()` on btrfs, and fanotify mark setup
(`fanotify_test_fsid()` in `fs/notify/fanotify/fanotify_user.c` calls
`vfs_get_fsid()`). Reachable from unprivileged userspace via syscalls.
`temp_fsid` triggers only when mounting a cloned single-device btrfs
while the original is already mounted.

### Step 5.5: Similar patterns
**Record:** Same `u64_to_fsid(huge_encode_dev(...))` pattern used in
`fs/xfs/xfs_super.c`. VFS fanotify work (v6.7) added `f_fsid`
requirements across filesystems (`freevxfs`, `gfs2`, simple
filesystems).

---

## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE

### Step 6.1: Buggy code present?
**Record:** **Yes.** Current code at lines 1738 and 1828–1832 uses
`fs_devices->fsid` unconditionally. `temp_fsid` support confirmed
present (`a5b8a5f9f8355` is ancestor of HEAD). Bug has existed since
v6.10 in this series.

### Step 6.2: Backport complications
**Record:** Clean apply expected — target code matches upstream diff
base. No conflicting recent changes to `f_fsid` block in `super.c`.

### Step 6.3: Related fixes already present?
**Record:** No — `git merge-base --is-ancestor c2a74ed0494c2 HEAD`
returns false. Companion `s_uuid` fix (`df84f6c773771`) also absent.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: Subsystem criticality
**Record:** btrfs filesystem (`fs/btrfs/`) — **IMPORTANT** (widely
deployed filesystem; core VFS statfs path).

### Step 7.2: Subsystem activity
**Record:** Actively maintained; recent commits in `super.c` include
leak fixes and statfs improvements.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who is affected
**Record:** btrfs users, specifically those using cloned-device
(`temp_fsid`) mounts. Also fanotify users on btrfs. All single-device
btrfs get changed `f_fsid` values (broader but intentional).

### Step 8.2: Trigger conditions
**Record:**
- Primary bug: mount cloned btrfs image while original is mounted
  (`temp_fsid` active) → randomized `f_fsid` each mount.
- Collision bug: original + clone mounted simultaneously without `dev_t`
  disambiguation.
- Trigger is config/use-case specific (not every boot), but reproducible
  when cloning workflow is used.

### Step 8.3: Failure mode severity
**Record:**
- **Failure mode:** Incorrect/non-persistent `f_fsid`; possible ID
  collision between distinct mounts.
- **Impact:** Breaks filesystem identification for `statfs()` consumers
  and fanotify (`vfs_get_fsid`). No crash, corruption, deadlock, or
  security vulnerability.
- **Severity: MEDIUM** (functional correctness, fanotify compatibility)

### Step 8.4: Risk-benefit
**Record:**
- **Benefit:** Restores stable, unique `f_fsid` for btrfs clones; aligns
  with VFS fanotify `f_fsid` requirements.
- **Risk:** Low implementation risk (small, maintainer-reviewed);
  moderate semantic risk (`f_fsid` value changes for all single-device
  btrfs).
- **Ratio:** Favorable for users of `temp_fsid`/fanotify; acceptable
  risk given small diff and maintainer authorship.

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence summary

**FOR backport:**
- Real bug in shipped `temp_fsid` feature (present since v6.10 in this
  tree)
- Non-persistent `f_fsid` across remounts breaks `statfs()` and fanotify
  identification
- `f_fsid` collision between original and clone without `dev_t` mixing
- Small (41 lines), single-file, maintainer-signed fix
- Applies cleanly; no dependencies
- Consistent with broader VFS `f_fsid`/fanotify work already in tree

**AGAINST backport:**
- Not a crash, corruption, deadlock, or security issue
- `temp_fsid`/cloned mounts are a niche workflow
- Changes `f_fsid` semantics for all single-device btrfs (userspace-
  visible)
- Companion `s_uuid` patch not included (separate concern for overlayfs)
- No fuzzer report or explicit stable nomination found
- Lore discussion unverified

**Unresolved:** Full mailing list review thread; whether reviewers
nominated for stable.

### Step 9.2: Stable rules checklist
1. Obviously correct and tested? **PASS** — logic is clear; maintainer
   SOB; no Tested-by but straightforward.
2. Fixes a real bug affecting users? **PASS** — inconsistent `f_fsid`
   with `temp_fsid`; clone/original collision.
3. Important issue? **PASS (borderline)** — MEDIUM severity
   functional/fanotify correctness, not crash-class.
4. Small and contained? **PASS** — 1 file, ~33 lines added.
5. No new features or APIs? **PASS** — fixes existing `statfs` behavior.
6. Can apply to local tree? **PASS** — buggy code present, helpers
   available, clean apply.

### Step 9.3: Exception categories
**Record:** None (not device ID, quirk, DT, build, or docs fix).

### Step 9.4: Decision rationale

This fix addresses a real correctness bug in the `temp_fsid` cloned-
mount feature that has been in this 6.18.y tree since v6.10. Without it,
`statfs()` returns a different `f_fsid` on every remount of a cloned
btrfs, and original/clone mounts can collide — both break
`vfs_get_fsid()` users including fanotify. The patch is small, self-
contained, and authored/signed by the btrfs maintainer. The severity is
functional rather than crash-class, but stable trees routinely take such
fixes for filesystem identification correctness, especially where
fanotify integration depends on stable `f_fsid`. The intentional
`f_fsid` value change for all single-device btrfs is a minor stable risk
but necessary to prevent clone/original collisions.

---

## Verification

- **[Phase 1]** Parsed commit message from `git show c2a74ed0494c2`:
  subject, body, tags, links, SOBs
- **[Phase 2]** Full diff reviewed via `git show c2a74ed0494c2 -p`; 1
  file, `btrfs_statfs()` modified
- **[Phase 3]** `git describe HEAD` → v6.18.44; `make kernelversion` →
  6.18.44
- **[Phase 3]** `git blame -L 1825,1832 fs/btrfs/super.c` → f_fsid code
  from 2008/2024
- **[Phase 3]** `git merge-base --is-ancestor a5b8a5f9f8355 HEAD` →
  temp_fsid present
- **[Phase 3]** `git merge-base --is-ancestor c2a74ed0494c2 HEAD` → fix
  NOT present
- **[Phase 3]** `git show a5b8a5f9f8355` → temp_fsid introduced
  2023-10-12; first tag v6.10
- **[Phase 3]** `git log --oneline df84f6c773771^..c2a74ed0494c2` →
  2-patch series identified
- **[Phase 3]** `git merge-base --is-ancestor df84f6c773771 HEAD` →
  s_uuid companion NOT in tree
- **[Phase 4]** `b4 dig -c c2a74ed0494c2` → no match (exit 1)
- **[Phase 4]** WebFetch lore cover URL → blocked by Anubis
- **[Phase 4]** Grep `*.mbx` for patch subject → no matches
- **[Phase 5]** Read `fs/statfs.c` `vfs_get_fsid()` → calls
  `statfs_by_dentry` → `btrfs_statfs`
- **[Phase 5]** Read `fanotify_user.c` `fanotify_test_fsid()` → uses
  `vfs_get_fsid`
- **[Phase 5]** Grep `u64_to_fsid` in `include/linux/statfs.h` → helper
  exists
- **[Phase 5]** Grep `latest_dev` in `fs/btrfs/volumes.c` → set during
  device open
- **[Phase 6]** Read current `fs/btrfs/super.c:1738,1828-1832` → buggy
  code confirmed
- **[Phase 6]** Grep `temp_fsid` in `fs/btrfs/` → feature fully present
- **[Phase 6]** Read `fs/btrfs/disk-io.c:3428` → `s_uuid` still uses
  `fs_devices->fsid` (companion fix absent)
- **[Phase 7]** David Sterba SOB on commit verified
- **UNVERIFIED:** Mailing list review feedback and stable nominations
  (lore inaccessible, b4 failed)

**YES**

 fs/btrfs/super.c | 41 +++++++++++++++++++++++++++++++++--------
 1 file changed, 33 insertions(+), 8 deletions(-)

diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 157d551344707..9dc399e5dc091 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1735,12 +1735,13 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	u64 total_free_data = 0;
 	u64 total_free_meta = 0;
 	u32 bits = fs_info->sectorsize_bits;
-	__be32 *fsid = (__be32 *)fs_info->fs_devices->fsid;
+	__be32 *fsid;
 	unsigned factor = 1;
 	struct btrfs_block_rsv *block_rsv = &fs_info->global_block_rsv;
 	int ret;
 	u64 thresh = 0;
 	int mixed = 0;
+	__kernel_fsid_t f_fsid;
 
 	list_for_each_entry(found, &fs_info->space_info, list) {
 		if (found->flags & BTRFS_BLOCK_GROUP_DATA &&
@@ -1822,14 +1823,38 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
 	buf->f_bsize = fs_info->sectorsize;
 	buf->f_namelen = BTRFS_NAME_LEN;
 
-	/* We treat it as constant endianness (it doesn't matter _which_)
-	   because we want the fsid to come out the same whether mounted
-	   on a big-endian or little-endian host */
-	buf->f_fsid.val[0] = be32_to_cpu(fsid[0]) ^ be32_to_cpu(fsid[2]);
-	buf->f_fsid.val[1] = be32_to_cpu(fsid[1]) ^ be32_to_cpu(fsid[3]);
+	/*
+	 * fs_devices->fsid is dynamically generated when temp_fsid is active
+	 * to support cloned filesystems. Use the original on-disk fsid instead,
+	 * as it remains consistent across mount cycles.
+	 */
+	if (fs_info->fs_devices->temp_fsid)
+		fsid = (__be32 *)fs_info->super_copy->fsid;
+	else
+		fsid = (__be32 *)fs_info->fs_devices->fsid;
+
+	/*
+	 * We treat it as constant endianness (it doesn't matter _which_)
+	 * because we want the fsid to come out the same whether mounted
+	 * on a big-endian or little-endian host.
+	 */
+	f_fsid.val[0] = be32_to_cpu(fsid[0]) ^ be32_to_cpu(fsid[2]);
+	f_fsid.val[1] = be32_to_cpu(fsid[1]) ^ be32_to_cpu(fsid[3]);
+
 	/* Mask in the root object ID too, to disambiguate subvols */
-	buf->f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32;
-	buf->f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root);
+	f_fsid.val[0] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root) >> 32;
+	f_fsid.val[1] ^= btrfs_root_id(BTRFS_I(d_inode(dentry))->root);
+
+	/* Hash dev_t to avoid f_fsid collision with cloned filesystems. */
+	if (fs_info->fs_devices->total_devices == 1) {
+		__kernel_fsid_t dev_fsid =
+			u64_to_fsid(huge_encode_dev(fs_info->fs_devices->latest_dev->bdev->bd_dev));
+
+		f_fsid.val[0] ^= dev_fsid.val[1];
+		f_fsid.val[1] ^= dev_fsid.val[0];
+	}
+
+	memcpy(&buf->f_fsid, &f_fsid, sizeof(f_fsid));
 
 	return 0;
 }
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: validate properties before setting them
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (5 preceding siblings ...)
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.12] btrfs: derive f_fsid from on-disk fsid and dev_t Sasha Levin
@ 2026-08-31 13:23 ` Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: balance: fix potential bg lookup failure in btrfs_may_alloc_data_chunk() Sasha Levin
                   ` (8 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:23 UTC (permalink / raw)
  To: patches, stable
  Cc: Filipe Manana, Qu Wenruo, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: Filipe Manana <fdmanana@suse.com>

[ Upstream commit cf1afec09e9f004a62c54c471863209ed249fca7 ]

We set the xattr and then attempt to apply the property. If the apply
fails we then attempt to delete the xattr to avoid an inconsistency.
However we don't verify if the deletion succeed, so if it fails we
leave an inconsistency between the state in the btree and the in-memory
inode.

Address this by validating first if we can apply the property, then set
the xattr, then apply the property, and this last step should not fail
since the validation succeeded before - assert that it does not fail but
leave code to attempt to delete the xattr if it happens, and then abort
the transaction only if the xattr delete failed.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: validate properties before setting
them`

**Local tree:** `v6.18.44` (`6.18.44`) — checked-out stable tree, not
mainline.

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[btrfs] [validate] validate properties before setting them`
— btrfs filesystem property handling; action is validation/reordering of
set path to prevent inconsistency.

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Reviewed-by:** Qu Wenruo `<wqu@suse.com>`
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` (btrfs maintainer)
- **Signed-off-by:** Filipe Manana `<fdmanana@suse.com>` (author)
- **Signed-off-by:** David Sterba `<dsterba@suse.com>`
- **No** `Fixes:` tag
- **No** `Reported-by:` tag
- **No** `Cc: stable@vger.kernel.org`
- **No** syzbot / sanitizer links

Notable: dual maintainer review (Qu Wenruo + David Sterba); no
user/fuzzer report.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** `btrfs_set_prop()` writes the xattr to the btree first, then
  calls `handler->apply()`. On apply failure it attempts to delete the
  xattr, but ignores whether deletion succeeded.
- **Symptom:** If rollback deletion fails, the on-disk btree has the
  xattr while the in-memory inode state was not updated by `apply()` —
  metadata inconsistency.
- **Fix approach:** Validate first (`handler->validate()`), then set
  xattr, then apply (should not fail after validation). On unexpected
  apply failure, try xattr delete; if delete also fails, call
  `btrfs_abort_transaction()`.
- **Version info:** None stated in message.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — this is an explicit correctness/consistency
bug fix in error handling, though it also restores validate-before-
setxattr ordering that existed in the original 2014 property code.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **Files:** `fs/btrfs/props.c` only (+13 / −3 lines)
- **Function modified:** `btrfs_set_prop()`
- **Scope:** Single-file, surgical fix in one function's non-zero-value
  path.

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE

**Hunk 1 (validate before xattr):**
- **Before:** Set xattr → apply → on failure, attempt xattr delete
  (ignore result).
- **After:** Validate → set xattr → apply → on failure, attempt delete
  and abort transaction if delete fails.

**Record:** Normal property-set path for `value_len > 0`; error path
improved. The `value_len == 0` (property removal) path is unchanged.

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:**
- **Category:** Logic/correctness fix + error-path resource/state
  consistency fix.
- **Mechanism:** Incomplete rollback on apply failure leaves btree xattr
  present while in-memory inode property state is stale. Fix validates
  early (reducing apply failures), and escalates to
  `btrfs_abort_transaction()` when rollback cannot complete.

### Step 2.4: ASSESS THE FIX QUALITY
**Record:**
- Fix is minimal and obviously correct.
- Restores validate-before-setxattr ordering that existed in the
  original 2014 `__btrfs_set_prop()` before validation was moved
  external in 2019 (`f22125e5d8ae1`).
- `ASSERT(ret == 0)` matches existing pattern in the `value_len == 0`
  branch.
- `btrfs_abort_transaction()` on failed cleanup is consistent with
  `xattr.c` and `ioctl.c` error handling.
- **Regression risk:** Very low. Duplicate validation on the xattr path
  is harmless. Abort-on-failed-rollback is conservative but appropriate
  for metadata inconsistency.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:**
- Core `btrfs_set_prop()` logic dates to **2014** (`63541927c8d11d` —
  Filipe Manana, "Btrfs: add support for inode properties").
- Original 2014 code **did** call `handler->validate()` before
  `setxattr`.
- The rollback-without-checking-delete pattern has existed since 2014.
- Validation was **removed** from inside `btrfs_set_prop()` in **2019**
  (`f22125e5d8ae1` — "refactor btrfs_set_props to validate externally").
- Recent `props.c` changes (2022–2025) are struct/type refactors, not
  related to this bug.

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

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:**
- Related historical fix: `3763771cf6023` (2019) — different issue
  (fsync/log persistence of compression xattr deletion).
- Patch is **1/3** of series "[PATCH 0/3] btrfs: fixes and cleanups
  setting/clearing properties".
- Patches 2/3 and 3/3 touch `ioctl.c` (`btrfs_fileattr_set()`), not
  `props.c` — **this patch is standalone**.
- Commit is **not yet present** in this tree (`git log --grep` found
  nothing).

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** Filipe Manana is an active btrfs developer with multiple
btrfs fixes in history. David Sterba is btrfs maintainer and signed off.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:**
- `handler->validate` callback exists in `prop_handler` struct in this
  tree.
- `prop_compression_validate()` exists and is wired up.
- `btrfs_abort_transaction()` is available via existing includes.
- **No prerequisites** — patch applies cleanly (`git apply --check`
  succeeded).
- Patches 2/3 and 3/3 are independent ioctl cleanups.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:**
- **URL:** https://www.spinics.net/lists/linux-btrfs/msg166052.html
- **Series cover:** https://www.spinics.net/lists/linux-
  btrfs/msg166051.html
- **Date:** Mon, 8 Jun 2026
- **Series:** v1, 3 patches; patch 1 is this commit.
- `b4 dig` could not be used (commit not in local tree).
- No explicit stable nomination found in fetched thread snippets.

### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** Author Filipe Manana; Reviewed-by Qu Wenruo and David Sterba
(maintainer). Appropriate reviewers for btrfs properties code.

### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** No `Reported-by:` or `Link:` tags. Bug identified by code
analysis, not a user crash report or syzbot hit.

### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:**
- Patch 2/3: `btrfs: don't over reserve metadata space for property in
  btrfs_fileattr_set()` — ioctl.c only.
- Patch 3/3: `btrfs: fix transaction abort logic in
  btrfs_fileattr_set()` — ioctl.c only.
- This patch is self-contained for the `btrfs_set_prop()` inconsistency.

### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** lore.kernel.org blocked by bot protection; no stable-list
discussion verified. Not a negative signal per instructions.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `btrfs_set_prop()` — only function modified.

### Step 5.2: TRACE CALLERS
**Record:**
1. **`btrfs_xattr_handler_set_prop()`** (`xattr.c:451`) — userspace
   `setfattr` / `setxattr` on `btrfs.compression`. Already calls
   `btrfs_validate_prop()` first (line 440). Reachable from userspace.
2. **`btrfs_fileattr_set()`** (`ioctl.c:377,384`) — `FS_IOC_SETFLAGS` /
   file attributes ioctl path. Calls `btrfs_set_prop()` **without**
   prior `btrfs_validate_prop()`, but passes known-good strings from
   `btrfs_compress_type2str()`. Reachable from userspace.

### Step 5.3: TRACE CALLEES
**Record:** `handler->validate()`, `btrfs_setxattr()`,
`handler->apply()`, `btrfs_abort_transaction()`,
`set_bit(BTRFS_INODE_HAS_PROPS)`.

### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** Userspace sets btrfs inode properties via xattr or ioctl →
transaction started → `btrfs_set_prop()` → btree xattr + in-memory inode
flags. Buggy path is reachable from unprivileged userspace (with write
access to the file/inode).

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** `btrfs_inode_inherit_props()` (`props.c:440–446`) has the
same unchecked-rollback pattern (`apply` fails → `btrfs_setxattr` delete
without checking result). **Not fixed by this commit.** Separate issue;
does not block this fix.

---

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

### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **Yes.** Current `fs/btrfs/props.c` lines 130–138 show
setxattr → apply → unchecked rollback delete. Bug present since property
support was added; validate-before-setxattr was removed in 2019 refactor
still present in 6.18.44.

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** **Clean apply** — `git apply --check` passed with no
conflicts. No structural divergence from patch context.

### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** No equivalent fix found (`git log --grep` for subject
returned empty). Bug remains unfixed in v6.18.44.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **Filesystem — btrfs** (`fs/btrfs/`). **Criticality:
IMPORTANT** — btrfs metadata consistency affects data integrity for all
btrfs users.

### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** `props.c` actively maintained (refactors in 2022–2025).
Property code is mature but still receiving correctness fixes.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** Users setting btrfs inode properties (compression xattr via
`setfattr`/`setxattr`, or compression flags via ioctl). **CONFIG_BTRFS**
users.

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:**
1. `handler->apply()` fails after xattr was written (more likely on
   ioctl path without internal validate; rare on xattr path where
   external validate already ran).
2. Rollback `btrfs_setxattr(..., NULL, 0)` also fails (e.g., metadata
   ENOSPC, transaction error).
- **Likelihood:** Low but realistic on error paths (space pressure, I/O
  errors).
- **Userspace triggerable:** Yes, with write permission on the inode.

### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** **Metadata inconsistency** — on-disk compression xattr
present but in-memory inode compression state not updated (or vice versa
after partial failure). Can cause incorrect compression behavior and
inconsistent state across remounts/replays. **Severity: HIGH** (metadata
integrity; corruption-class issue, not a simple WARN).

### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** Prevents silent btree/in-memory desync; escalates to
  transaction abort when cleanup impossible. Restores internal
  validation defense-in-depth.
- **Risk:** Very low — 16-line change, one function, reviewed by btrfs
  maintainers, applies cleanly.
- **Ratio:** Strong benefit, minimal risk.

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backport:**
- Fixes real metadata inconsistency bug in `btrfs_set_prop()`.
- Affects userspace-reachable property-set paths.
- Small, surgical, maintainer-reviewed fix.
- Applies cleanly to v6.18.44.
- Buggy code confirmed present in this tree.
- `btrfs_abort_transaction()` on failed rollback prevents silent
  corruption.
- Restores validate-before-setxattr ordering (original 2014 design).

**AGAINST backport:**
- No user crash report or syzbot reproduction.
- Trigger requires apply failure **and** rollback delete failure (narrow
  window).
- xattr path already validates externally (main value is rollback abort
  + ioctl-path internal validate).
- Related pattern in `btrfs_inode_inherit_props()` remains unfixed
  (separate issue).

**Unresolved:**
- Whether commit has landed in mainline yet (not in this v6.18.44
  checkout; expected for candidate evaluation).
- No stable-list nomination verified (lore blocked).

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — logic is clear; reviewed by
   Qu Wenruo and David Sterba.
2. Fixes a real bug affecting users? **PASS** — metadata inconsistency
   on property set error path.
3. Important issue? **PASS** — metadata integrity / corruption-class
   inconsistency.
4. Small and contained? **PASS** — 16 lines, one file.
5. No new features or APIs? **PASS** — error-handling reorder only.
6. Can apply to local tree? **PASS** — clean apply verified.

### Step 9.3: EXCEPTION CATEGORIES
**Record:** None (not a device ID, quirk, DT, build, or docs fix).
Qualifies on metadata integrity grounds.

### Step 9.4: DECISION RATIONALE

For **v6.18.44**, the buggy code is present, the fix is small and self-
contained, btrfs maintainers reviewed it, and it addresses a real
metadata consistency failure mode on a userspace-reachable path. The
failure mode — silent desync between btree xattrs and in-memory inode
state — is exactly the class of issue stable btrfs trees should fix.
Patches 2/3 and 3/3 are not prerequisites.

---

## Verification

- **[Phase 1]** Parsed subject, tags, body from provided commit message.
- **[Phase 1]** No Fixes:/Reported-by:/Cc: stable tags found.
- **[Phase 2]** Read current `fs/btrfs/props.c` lines 107–144; confirmed
  pre-fix code matches described bug.
- **[Phase 2]** Diff is +13/−3 in `btrfs_set_prop()` only.
- **[Phase 3]** `git describe HEAD` → `v6.18.44`; `make kernelversion` →
  `6.18.44`.
- **[Phase 3]** `git blame -L 107,145 fs/btrfs/props.c` → core logic
  from 2014 (`63541927c8d11d`).
- **[Phase 3]** `git show 63541927c8d11d` → original code had validate-
  before-setxattr.
- **[Phase 3]** `git show f22125e5d8ae1` → 2019 refactor moved
  validation external.
- **[Phase 3]** `git log --grep="validate properties"` → empty (commit
  not in tree).
- **[Phase 3]** `git apply --check` on provided diff → clean apply.
- **[Phase 4]** curl spinics msg166051 (cover), msg166052 (patch 1/3) →
  series context, standalone patch 1.
- **[Phase 4]** curl spinics msg166053, msg166054 → patches 2/3 and 3/3
  are ioctl.c only.
- **[Phase 4]** `b4 dig` not usable — commit hash not in local tree.
- **[Phase 4]** lore.kernel.org fetch blocked by Anubis — stable-list
  search unverified.
- **[Phase 5]** `grep btrfs_set_prop` → callers in `xattr.c:451`,
  `ioctl.c:377,384`.
- **[Phase 5]** Read `xattr.c:429–462` → external
  `btrfs_validate_prop()` before `btrfs_set_prop()`.
- **[Phase 5]** Read `ioctl.c:256–401` → `btrfs_fileattr_set()` calls
  `btrfs_set_prop()` without validate.
- **[Phase 5]** Read `prop_compression_validate()` /
  `prop_compression_apply()` → validate is stricter (checks
  `btrfs_inode_can_compress`).
- **[Phase 5]** Found similar unchecked rollback in
  `btrfs_inode_inherit_props()` lines 440–446 (not fixed here).
- **[Phase 6]** Buggy code confirmed at `props.c:130–138` in v6.18.44.
- **[Phase 6]** `git apply --check` → applies cleanly.
- **[Phase 8]** Failure mode: btree/in-memory metadata inconsistency;
  severity HIGH.

**YES**

 fs/btrfs/props.c | 16 +++++++++++++---
 1 file changed, 13 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/props.c b/fs/btrfs/props.c
index adc956432d2f1..bb77d46376d4b 100644
--- a/fs/btrfs/props.c
+++ b/fs/btrfs/props.c
@@ -127,14 +127,24 @@ int btrfs_set_prop(struct btrfs_trans_handle *trans, struct btrfs_inode *inode,
 		return ret;
 	}
 
+	ret = handler->validate(inode, value, value_len);
+	if (ret)
+		return ret;
 	ret = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, value,
 			     value_len, flags);
 	if (ret)
 		return ret;
 	ret = handler->apply(inode, value, value_len);
-	if (ret) {
-		btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name, NULL,
-			       0, flags);
+	/* We validated before, so it should not fail here. */
+	ASSERT(ret == 0);
+	if (unlikely(ret)) {
+		int ret2;
+
+		/* Try to delete xattr, if not possible abort transaction. */
+		ret2 = btrfs_setxattr(trans, &inode->vfs_inode, handler->xattr_name,
+				      NULL, 0, flags);
+		if (unlikely(ret2))
+			btrfs_abort_transaction(trans, ret2);
 		return ret;
 	}
 
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-5.10] btrfs: balance: fix potential bg lookup failure in btrfs_may_alloc_data_chunk()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (6 preceding siblings ...)
  2026-08-31 13:23 ` [PATCH AUTOSEL 6.18] btrfs: validate properties before setting them Sasha Levin
@ 2026-08-31 13:24 ` Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use lockless read in nr_cached_objects shrinker callback Sasha Levin
                   ` (7 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:24 UTC (permalink / raw)
  To: patches, stable
  Cc: ZhengYuan Huang, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: ZhengYuan Huang <gality369@gmail.com>

[ Upstream commit 18d32b0013efba19f7ad3e5b08d7aee813d604a6 ]

[BUG]
Running btrfs balance can trigger a null-ptr-deref before relocating a
data chunk when metadata corruption leaves a chunk in the chunk tree
without a corresponding block group in the in-memory cache:

  KASAN: null-ptr-deref in range [0x0000000000000088-0x000000000000008f]
  RIP: 0010:btrfs_may_alloc_data_chunk+0x40/0x1c0 fs/btrfs/volumes.c:3601
  Call Trace:
    __btrfs_balance fs/btrfs/volumes.c:4217 [inline]
    btrfs_balance+0x2516/0x42b0 fs/btrfs/volumes.c:4604
    btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline]
    btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313
    ...

[CAUSE]
__btrfs_balance() iterates the on-disk chunk tree and passes the chunk
logical bytenr to btrfs_may_alloc_data_chunk() before relocating a data
chunk. That helper then queries the in-memory block group cache:

  cache = btrfs_lookup_block_group(fs_info, chunk_offset);
  chunk_type = cache->flags;   /* cache may be NULL */

A corrupt image can contain a chunk item whose matching block group
item is missing, so no block group is ever inserted into the cache. In
that case btrfs_lookup_block_group() returns NULL.

The code only guards this with ASSERT(cache), which becomes a no-op when
CONFIG_BTRFS_ASSERT is disabled. The subsequent dereference of
cache->flags therefore crashes the kernel.

[FIX]
Add a NULL check after btrfs_lookup_block_group() in
btrfs_may_alloc_data_chunk() and print and error message for clarity.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## Phase 1: Commit Message Forensics

### Step 1.1: Parse the Subject Line
**Record:** `[btrfs: balance]` `[fix]` — Fix potential block-group
lookup failure in `btrfs_may_alloc_data_chunk()` during balance
operations.

### Step 1.2: Parse All Commit Message Tags
**Record:**
- **Fixes:** none in the provided message (v1/v3 on lore have `Fixes:
  a6f93c71d412`)
- **Reported-by:** none
- **Tested-by:** none
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` (btrfs maintainer)
- **Acked-by:** none
- **Link:** none in provided message
- **Cc: stable@vger.kernel.org:** absent in provided message; present in
  v1 lore submission
- **Signed-off-by:** ZhengYuan Huang; David Sterba (ignore any pipeline-
  added SOBs)

Notable: maintainer review; v1 explicitly nominated for stable on lore.

### Step 1.3: Analyze Commit Body
**Record:**
- **Bug:** NULL pointer dereference in `btrfs_may_alloc_data_chunk()`
  when running `btrfs balance` on a filesystem where metadata corruption
  leaves a chunk in the chunk tree without a matching in-memory block
  group.
- **Symptom:** KASAN null-ptr-deref at `cache->flags` (offset 0x88),
  stack through `__btrfs_balance` → `btrfs_balance` →
  `btrfs_ioctl_balance`.
- **Root cause:** `btrfs_lookup_block_group()` can return NULL; only
  `ASSERT(cache)` guards it, and `ASSERT` is a no-op when
  `CONFIG_BTRFS_ASSERT` is disabled (the default).
- **Fix:** NULL check, `btrfs_err()` message, return `-EUCLEAN`.
- **Version info:** Bug tied to function introduced in `a6f93c71d412ba`
  (2017/2018).

### Step 1.4: Detect Hidden Bug Fixes
**Record:** Not disguised — explicitly a NULL-deref crash fix. The
`unlikely()` wrapper in the provided diff matches existing EUCLEAN-path
style in this tree (e.g. commit `9264d004a6c97`).

---

## Phase 2: Diff Analysis

### Step 2.1: Inventory the Changes
**Record:**
- **Files:** `fs/btrfs/volumes.c` (+5/-1 net in v1; +6/-1 with
  `unlikely` in provided diff)
- **Function:** `btrfs_may_alloc_data_chunk()`
- **Scope:** Single-file, surgical fix

### Step 2.2: Code Flow Change
**Record:**
- **Before:** `cache = btrfs_lookup_block_group(...); ASSERT(cache);
  chunk_type = cache->flags;` — ASSERT no-op in production → NULL deref.
- **After:** If `!cache`, log error and return `-EUCLEAN`; otherwise
  proceed as before.
- **Path affected:** Balance relocation path in `__btrfs_balance()`
  before `btrfs_relocate_chunk()`.

### Step 2.3: Bug Mechanism
**Record:** **Category:** NULL pointer dereference (memory safety).
**Mechanism:** Missing NULL check after lookup; assertion disabled in
production kernels. Fix converts kernel oops into controlled `-EUCLEAN`
error propagation.

### Step 2.4: Fix Quality
**Record:** Obviously correct and minimal. Matches existing patterns in
the same file (e.g. lines 3587–3589, 8326–8328). `-EUCLEAN` is the
established btrfs corruption error code (used at lines 4272, 2041,
etc.). **Regression risk:** Very low — only affects the already-broken
corruption case.

---

## Phase 3: Git History Investigation

### Step 3.1: Blame Changed Lines
**Record:** `btrfs_may_alloc_data_chunk()` introduced in
`a6f93c71d412ba` (Liu Bo, 2017-11-15 / committed 2018-01-22).
`ASSERT(cache)` present since introduction. Bug has existed ~8 years in
this code path.

### Step 3.2: Follow Fixes Tag
**Record:** N/A in provided message. Lore v3 has `Fixes: a6f93c71d412` —
that commit is in this tree and introduced the vulnerable function.

### Step 3.3: Related File History
**Record:** Related recent fix `c19830db30a09` replaced `BUG()` with
`-EUCLEAN` in `__btrfs_balance()` — same corruption-handling philosophy.
Fix commit not found in this tree (`git log --grep='null-ptr-deref in
btrfs_may_alloc_data_chunk'` returned empty). Buggy code confirmed
present at lines 3723–3725.

### Step 3.4: Author's Other Commits
**Record:** ZhengYuan Huang has other btrfs fixes in this tree (e.g.
root drop_level validation). Part of a 4-patch series on lore fixing
similar balance NULL derefs.

### Step 3.5: Dependencies
**Record:** **Standalone.** Patch 3/4 in the series; fixes only
`btrfs_may_alloc_data_chunk()`. Other series patches fix
`chunk_usage_filter()` and `chunk_usage_range_filter()` separately. No
structural prerequisites — applies cleanly to v6.18.44.

---

## Phase 4: Mailing List and External Research

### Step 4.1: Original Patch Discussion
**Record:**
- `b4 dig` / `b4 am` did not find the commit (not yet merged here; no
  commit hash provided).
- Lore v1: https://lkml.iu.edu/2603.2/00971.html (Mar 16, 2026)
- Lore v3 patch 3/4: https://lkml.iu.edu/2603.3/02434.html (Mar 24,
  2026)
- Series cover v3: https://lkml.iu.edu/2603.3/02432.html
- v1 included `Cc: stable@vger.kernel.org`
- v3 adds `btrfs_may_alloc_data_chunk` fix per maintainer feedback;
  reviewed by David Sterba

### Step 4.2: Reviewers
**Record:** David Sterba (btrfs maintainer) reviewed and signed off.
Series CC'd `linux-btrfs@`.

### Step 4.3: Bug Report
**Record:** KASAN null-ptr-deref with full stack trace in commit
message. Reproducible on corrupted images. No syzbot report. Trigger:
`btrfs balance` on corrupted metadata.

### Step 4.4: Related Patches
**Record:** 4-patch series; patches 1–2 fix analogous NULL derefs in
balance filters; patch 4 fixes mount-time verification. This commit
(patch 3) is independently valuable even without the others.

### Step 4.5: Stable Mailing List
**Record:** v1 explicitly requested stable backport via `Cc:
stable@vger.kernel.org`. No stable-list rejection found.

---

## Phase 5: Code Semantic Analysis

### Step 5.1: Key Functions
**Record:** `btrfs_may_alloc_data_chunk()` (modified); callers:
`__btrfs_balance()`, device-shrink path (~line 5119), zoned repair path
(~line 8333).

### Step 5.2: Callers
**Record:**
- `__btrfs_balance()` at line 4347 — primary path, checks `ret < 0` →
  `goto error`
- Device shrink loop at line 5119 — same error handling
- Zoned repair at line 8333 — `ret < 0` → `goto out`
All three callers properly propagate negative returns.

### Step 5.3: Callees
**Record:** `btrfs_lookup_block_group()` →
`block_group_cache_tree_search()` — can return NULL when no matching
block group exists in cache.

### Step 5.4: Call Chain / Reachability
**Record:**
```
userspace btrfs balance (CAP_SYS_ADMIN)
  → btrfs_ioctl_balance() [ioctl.c:3555]
    → btrfs_balance() [volumes.c:4733]
      → __btrfs_balance() [volumes.c:4347]
        → btrfs_may_alloc_data_chunk() [volumes.c:3723]
```
Reachable from userspace via `BTRFS_IOC_BALANCE_V2` ioctl by root/admin.

### Step 5.5: Similar Patterns
**Record:** Same file already NULL-checks `btrfs_lookup_block_group()`
at lines 3587–3589 and 8326–8328. `chunk_usage_filter()` and
`chunk_usage_range_filter()` at lines 3968 and 3997 still dereference
without NULL checks (fixed by sibling patches, not this one).

---

## Phase 6: Cross-Referencing Against Local Tree

### Step 6.1: Does Buggy Code Exist?
**Record:** **YES.** Local tree is **v6.18.44** (`git describe HEAD`).
At `fs/btrfs/volumes.c:3723–3725`:

```3723:3726:fs/btrfs/volumes.c
        cache = btrfs_lookup_block_group(fs_info, chunk_offset);
        ASSERT(cache);
        chunk_type = cache->flags;
        btrfs_put_block_group(cache);
```

Fix error string not present (`grep` found no matches). Bug introduced
with function in 2018.

### Step 6.2: Backport Complications
**Record:** **Clean apply expected.** Function and call sites unchanged
in structure. Only line numbers differ from lore (3601 vs 3723) due to
tree evolution.

### Step 6.3: Related Fixes Already Present?
**Record:** **No.** `c19830db30a09` fixed a different
`__btrfs_balance()` BUG() path. Sibling NULL-deref fixes for balance
filters not present.

---

## Phase 7: Subsystem and Maintainer Context

### Step 7.1: Subsystem Criticality
**Record:** **btrfs filesystem** — IMPORTANT (widely deployed, data
integrity critical).

### Step 7.2: Subsystem Activity
**Record:** Actively maintained; recent balance-related hardening
(`c19830db30a09`, EUCLEAN annotations `9264d004a6c97`).

---

## Phase 8: Impact and Risk Assessment

### Step 8.1: Who Is Affected
**Record:** btrfs users running balance on filesystems with chunk/block-
group metadata inconsistency. All btrfs-enabled configs.

### Step 8.2: Trigger Conditions
**Record:** Metadata corruption (chunk without block group) + balance
operation. Uncommon but realistic — corruption can survive mount due to
incomplete `check_chunk_block_group_mappings()` (per series cover).
Requires `CAP_SYS_ADMIN` for balance ioctl.

### Step 8.3: Failure Mode Severity
**Record:** **CRITICAL** — kernel NULL pointer dereference / oops. Fix
downgrades to `-EUCLEAN` returned to userspace with error log.

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents kernel crash during balance on corrupted
  FS; balance is a common recovery/maintenance tool
- **Risk:** VERY LOW — 5-line defensive check on error-only path
- **Ratio:** Strongly favors backport

---

## Phase 9: Final Synthesis

### Step 9.1: Evidence Summary

**FOR backport:**
- Real NULL deref with KASAN stack trace
- Kernel crash → controlled error
- Small, surgical, maintainer-reviewed fix
- Bug present since 2018 in this tree
- Consistent with existing EUCLEAN handling
- v1 nominated for stable
- Applies cleanly to v6.18.44

**AGAINST backport:**
- Requires metadata corruption (rare)
- Requires admin privileges to trigger via balance
- Sibling patches fix related NULL derefs in other balance filters (this
  commit doesn't fix those)

**Unresolved:** Exact upstream commit hash not in local tree (candidate
evaluation). Does not affect decision.

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — maintainer reviewed; logic
   matches existing NULL-check patterns
2. Fixes real bug? **PASS** — KASAN-confirmed NULL deref
3. Important issue? **PASS** — kernel oops (CRITICAL severity)
4. Small and contained? **PASS** — ~5 lines, one function
5. No new features/APIs? **PASS** — defensive error handling only
6. Can apply to local tree? **PASS** — buggy code confirmed present,
   clean apply expected

### Step 9.3: Exception Categories
**Record:** None (standard bug fix, not device ID/quirk/build fix).

### Step 9.4: Decision Rationale

For **v6.18.44**, this commit fixes a long-standing NULL dereference in
the btrfs balance path that crashes the kernel when metadata corruption
leaves a chunk without a corresponding block group. The fix is minimal,
follows established btrfs error-handling conventions (`-EUCLEAN`), is
reviewed by the btrfs maintainer, and converts a kernel oops into a
clean error return. The bug exists in this tree and the fix is not yet
applied.

---

## Verification

- **[Phase 1]** Parsed subject, tags (Reviewed-by: David Sterba), body
  (KASAN trace, root cause, fix)
- **[Phase 2]** Diff: replaces `ASSERT(cache)` with NULL check +
  `-EUCLEAN` in `btrfs_may_alloc_data_chunk()`
- **[Phase 3]** `git blame`: function from `a6f93c71d412ba` (2018);
  `ASSERT(cache)` since introduction
- **[Phase 3]** `git log --grep='null-ptr-deref in
  btrfs_may_alloc_data_chunk'`: empty — fix not in tree
- **[Phase 3]** Related commit `c19830db30a09`: BUG→EUCLEAN in
  `__btrfs_balance()`
- **[Phase 4]** `b4 am`: no match (commit not merged)
- **[Phase 4]** WebFetch lkml v1 (00971): `Cc: stable@vger.kernel.org`
  confirmed
- **[Phase 4]** WebFetch lkml v3 (02434): patch 3/4, Reviewed-by David
  Sterba
- **[Phase 4]** WebFetch series cover (02432): standalone per-function
  fixes, mount-time gap described
- **[Phase 5]** Grep callers: lines 4347, 5119, 8333 — all check `ret <
  0`
- **[Phase 5]** `btrfs_lookup_block_group()` in `block-group.c:279-283`
  — returns search result, can be NULL
- **[Phase 6]** `git describe HEAD`: v6.18.44
- **[Phase 6]** Read `volumes.c:3723-3725`: buggy code present
- **[Phase 6]** Grep for fix message string: not found — fix absent
- **[Phase 6]** Existing NULL checks at lines 3587-3589, 8326-8328
  confirmed
- **[Phase 6]** `messages.h:170-171`: `ASSERT` → `(void)(cond)` when
  `CONFIG_BTRFS_ASSERT` disabled
- **[Phase 6]** `Kconfig:76-85`: BTRFS_ASSERT defaults to N ("If unsure,
  say N")
- **[Phase 8]** Call chain traced: ioctl → balance → `__btrfs_balance` →
  `btrfs_may_alloc_data_chunk`
- **[Phase 8]** Error propagation: `ret < 0` → `goto error` → returned
  through `btrfs_balance` → `btrfs_ioctl_balance`

**YES**The background searches finished and match the earlier analysis:

- The fix commit is **not in v6.18.44** — `git log --grep` for the null-
  ptr-deref / bg lookup fix returned nothing.
- The only related hit was an older indentation cleanup
  (`5ae2169290695`), not this fix.
- The buggy `ASSERT(cache)` path is still present in
  `fs/btrfs/volumes.c`.

Verdict stands: **YES** for backport to this tree.

 fs/btrfs/volumes.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index ff67f5ecacb2e..a17fcf8bd8a99 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3721,7 +3721,11 @@ static int btrfs_may_alloc_data_chunk(struct btrfs_fs_info *fs_info,
 	u64 chunk_type;
 
 	cache = btrfs_lookup_block_group(fs_info, chunk_offset);
-	ASSERT(cache);
+	if (unlikely(!cache)) {
+		btrfs_err(fs_info, "balance: chunk at bytenr %llu has no corresponding block group",
+			  chunk_offset);
+		return -EUCLEAN;
+	}
 	chunk_type = cache->flags;
 	btrfs_put_block_group(cache);
 
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] btrfs: use lockless read in nr_cached_objects shrinker callback
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (7 preceding siblings ...)
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: balance: fix potential bg lookup failure in btrfs_may_alloc_data_chunk() Sasha Levin
@ 2026-08-31 13:24 ` Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol() Sasha Levin
                   ` (6 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:24 UTC (permalink / raw)
  To: patches, stable
  Cc: Ben Maurer, Boris Burkov, Qu Wenruo, Shakeel Butt, David Sterba,
	Sasha Levin, clm, linux-btrfs, linux-kernel

From: Ben Maurer <bmaurer@meta.com>

[ Upstream commit 1ba72d847c7aa3c0887f749115af5232fd61b598 ]

Under heavy memcg-driven slab reclaim with many memcgs and CPUs,
shrink_slab_memcg() invokes the per-superblock count callback once per
(memcg, NUMA node) tuple. For btrfs that callback reaches
percpu_counter_sum_positive() on fs_info->evictable_extent_maps, which
takes the percpu_counter's raw spinlock with IRQs disabled and walks
every online CPU. With hundreds of memcgs driving reclaim on a host with
dozens of CPUs, this counter lock becomes a global serialization point:
profiles show CPU pinned in the spin_lock_irqsave acquire under
__percpu_counter_sum, with cross-CPU IPIs hitting csd_lock_wait_toolong
while waiting for spinning vCPUs.

The shrinker count is advisory -- super_cache_count() already notes
"counts can change between super_cache_count and super_cache_scan, so we
really don't need locks here." Use percpu_counter_read_positive(), which
is lockless. Worst-case skew is bounded by batch * num_online_cpus (a
few thousand), negligible compared to the millions of extent maps a busy
filesystem accumulates and well within the noise that the shrinker
already tolerates.

Tested-by: Boris Burkov <boris@bur.io>
Reviewed-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: Shakeel Butt <shakeel.butt@linux.dev>
Signed-off-by: Ben Maurer <bmaurer@meta.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[btrfs]` `[use]` — Replace the locked
`percpu_counter_sum_positive()` call in the extent-map shrinker count
callback with the lockless `percpu_counter_read_positive()`.

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Tested-by:** Boris Burkov `<boris@bur.io>` — fix verified on real
  workload
- **Reviewed-by:** Qu Wenruo `<wqu@suse.com>` — btrfs maintainer
- **Reviewed-by:** Shakeel Butt `<shakeel.butt@linux.dev>` — memcg/mm
  maintainer
- **Signed-off-by:** Ben Maurer `<bmaurer@meta.com>` — author
- **Signed-off-by:** David Sterba `<dsterba@suse.com>` — btrfs
  maintainer
- No `Fixes:`, `Reported-by:`, `Link:`, or `Cc: stable@vger.kernel.org`
  tags (expected for manual review)
- Notable: dual maintainer review (btrfs + memcg), production-scale
  author (Meta)

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** Under heavy memcg-driven slab reclaim with many memcgs and
  CPUs, `shrink_slab_memcg()` invokes the per-superblock count callback
  once per (memcg, NUMA node) tuple. For btrfs this reaches
  `percpu_counter_sum_positive()` on `fs_info->evictable_extent_maps`,
  which takes a raw spinlock with IRQs disabled and walks every online
  CPU.
- **Symptom:** Global serialization — CPUs pinned in `spin_lock_irqsave`
  under `__percpu_counter_sum`, cross-CPU IPIs hitting
  `csd_lock_wait_toolong` while waiting for spinning vCPUs.
- **Root cause:** Using the expensive accurate-sum API in an advisory
  shrinker count path that explicitly does not require locks or
  precision.
- **Fix rationale:** `super_cache_count()` already documents that counts
  are advisory and locks are unnecessary; use lockless
  `percpu_counter_read_positive()` instead.
- **Accuracy bound:** Worst-case skew ≤ `batch * num_online_cpus` (a few
  thousand), negligible vs. millions of extent maps.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Yes — described as a performance optimization, but it fixes
a scalability defect in the memory-reclaim hot path. The VFS shrinker
framework deliberately avoids locking in `super_cache_count()`; btrfs's
locked sum undermines that design and can stall reclaim under memory
pressure. This is a correctness-of-API-usage fix with stability impact,
not mere throughput tuning.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **Files:** `fs/btrfs/super.c` only (+0/-0 net, 1 line changed)
- **Functions:** `btrfs_nr_cached_objects()`
- **Scope:** Single-file, single-line surgical fix

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE
**Record:**
- **Hunk (line 2413):** Before:
  `percpu_counter_sum_positive(&fs_info->evictable_extent_maps)` —
  acquires `fbc->lock`, iterates all online/dying CPUs, sums per-CPU
  values. After:
  `percpu_counter_read_positive(&fs_info->evictable_extent_maps)` —
  single `READ_ONCE(fbc->count)`, no lock, no cross-CPU walk.
- **Execution path:** Called from `super_cache_count()` →
  `sb->s_op->nr_cached_objects()` during `shrink_slab_memcg()` reclaim,
  potentially once per (memcg, node) per shrinker invocation.

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:**
- **Category:** Scalability / lock-contention bug in hot reclaim path
  (synchronization misuse)
- **Mechanism:** `__percpu_counter_sum()` in `lib/percpu_counter.c`
  takes a global raw spinlock and walks every CPU. Invoked repeatedly
  from memcg-aware superblock shrinker counting. Creates a global
  serialization point exactly when the system is under memory pressure
  and needs fast reclaim.

### Step 2.4: ASSESS THE FIX QUALITY
**Record:**
- **Quality:** Obviously correct — direct API substitution; matches VFS
  shrinker contract and btrfs precedent in `space-info.c` (commit
  `2cdb3909c9e95`).
- **Regression risk:** Very low. Under-counting bounded by
  `percpu_counter_batch` (32) × num_cpus; shrinker counts are advisory
  per `fs/super.c:247-249`. xfs uses the same estimate-vs-sum pattern
  (`xfs_estimate_freecounter()`).
- **No new APIs, no behavior change beyond count approximation in an
  already-tolerant path.**

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:**
- `btrfs_nr_cached_objects()` introduced in `956a17d9d0507` ("btrfs: add
  a shrinker for extent maps", 2024-05-07) by Filipe Manana
- `percpu_counter_sum_positive()` line from `0d89a15e1a0dcc`
  (tracepoints commit, 2024-04-09)
- Bug present since extent-map shrinker landed (~kernel 6.9); confirmed
  ancestor of current HEAD (6.18.44)

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

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:**
- `956a17d9d0507` — added extent map shrinker and
  `btrfs_nr_cached_objects`
- `f1d97e7691528` — added `evictable_extent_maps` percpu counter
- `2cdb3909c9e95` — btrfs already switched `need_preemptive_reclaim()`
  from `sum_positive` to `read_positive` for same reason (perf/lock
  avoidance)
- `15b3b3254d145` — extent map shrinker iput fix
- Standalone 1-line fix, not part of a series

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** No prior commits from Ben Maurer in this tree's btrfs
history. David Sterba (committer) is btrfs maintainer.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No dependencies. Requires only `evictable_extent_maps`
counter and `btrfs_nr_cached_objects()` — both present in 6.18.44.
Applies cleanly as a single-line change.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:** UNVERIFIED — commit not yet in this tree (no SHA for `b4 dig
-c`). `b4 dig` subject search not supported. lore.kernel.org returned
403 (bot protection). Review tags in commit message are the available
review evidence.

### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** From commit message: Qu Wenruo (btrfs), Shakeel Butt
(memcg/mm), David Sterba (btrfs maintainer/committer). Appropriate
reviewers for this change.

### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** N/A — no `Reported-by:` or `Link:` tags. Issue identified
via production profiling at Meta (per commit body).

### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** Standalone fix. Direct precedent: `2cdb3909c9e95` (same
sum→read change in btrfs `space-info.c`).

### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** UNVERIFIED — lore.kernel.org inaccessible.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `btrfs_nr_cached_objects()` (modified); callers:
`super_cache_count()` in `fs/super.c`

### Step 5.2: TRACE CALLERS
**Record:**
- `super_cache_count()` → `shrinker->count_objects` for superblock
  shrinker (`s->s_shrink`, `SHRINKER_MEMCG_AWARE | SHRINKER_NUMA_AWARE`)
- Invoked from `do_shrink_slab()` → `shrink_slab_memcg()` →
  `shrink_slab()` during memory reclaim
- Hot path under memory pressure; frequency scales with num_memcgs ×
  num_nodes × num_shrinkers

### Step 5.3: TRACE CALLEES
**Record:**
- Before: `percpu_counter_sum_positive()` → `__percpu_counter_sum()` →
  `raw_spin_lock_irqsave` + per-CPU iteration
- After: `percpu_counter_read_positive()` → `READ_ONCE(fbc->count)`
  (from `include/linux/percpu_counter.h:118-126`)

### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** Memory reclaim (kernel-initiated under pressure, triggered
by allocation failures or memcg limits) → `shrink_slab` → superblock
shrinker count → btrfs extent map count. Reachable whenever btrfs is
mounted and memory reclaim runs. Container hosts with many memcgs are
the high-impact scenario.

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:**
- btrfs `space-info.c:1031-1032` — already uses `read_positive` for
  heuristic decisions
- xfs `xfs_mount.h:733-736` — `xfs_estimate_freecounter()` uses
  `read_positive` with comment "just provides an estimate"
- `backing-dev.h`, `mm.h` — same read-vs-sum pattern for hot paths vs.
  accurate counts

---

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

### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** YES. Local tree is **6.18.44** (`git describe HEAD` =
v6.18.44). `fs/btrfs/super.c:2413` still uses
`percpu_counter_sum_positive()`. Extent map shrinker present since
`956a17d9d0507` (May 2024, in 6.18.y ancestry).

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** Clean apply expected — single-line substitution, no
structural changes needed. No recent churn around
`btrfs_nr_cached_objects()`.

### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** The `space-info.c` precedent fix (`2cdb3909c9e95`) is
already in tree. This specific shrinker callback fix is NOT yet applied.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **btrfs filesystem** / memory reclaim interaction.
**Criticality: IMPORTANT** — affects memory reclaim behavior for all
btrfs mounts under memory pressure; severity scales with memcg count.

### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** btrfs actively maintained in 6.18.y with regular merges from
for-6.17/6.18 tags. Extent map shrinker is relatively new (2024) but
stable in tree.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** btrfs users under memory pressure, especially:
- Systems with `CONFIG_MEMCG` and many cgroups (containers/K8s)
- Multi-socket / many-CPU hosts
- btrfs root or btrfs data volumes on memory-constrained systems

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:** Heavy memcg-driven slab reclaim + btrfs mounted + many
(memcg, node) tuples. Common on container hosts; not every boot, but
realistic in production. Unprivileged users can trigger via memory
allocation within their cgroup.

### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** Global spinlock contention during reclaim → CPU spinning,
cross-CPU IPI stalls (`csd_lock_wait_toolong`), severely degraded
reclaim throughput, potential soft-lockup warnings and system
unresponsiveness under memory pressure. **Severity: HIGH** (stability
under memory pressure, not data corruption or security).

### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH for affected deployments — removes global lock from
  hot reclaim path; aligns btrfs with VFS shrinker design
- **Risk:** VERY LOW — 1-line change, bounded count imprecision already
  tolerated by shrinker framework
- **Ratio:** Strong benefit, minimal risk

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backporting:**
- Real production issue (Meta, profiled lock contention)
- Reviewed by btrfs and memcg maintainers; Tested-by present
- 1-line, obviously correct fix
- VFS explicitly documents shrinker counts need no locks
  (`fs/super.c:247-249`)
- Direct btrfs precedent (`2cdb3909c9e95`) and xfs pattern
  (`xfs_estimate_freecounter`)
- Bug present in 6.18.44 since extent map shrinker (6.9+)
- Can cause severe reclaim stalls under memory pressure — stability-
  relevant per stable rules for perf fixes that prevent lockups

**AGAINST backporting:**
- Primarily a scalability/performance issue, not
  crash/corruption/security
- Only affects btrfs + memcg + high-scale configurations
- Approximate count could theoretically under-report objects (bounded,
  maintainer-accepted)

**UNRESOLVED:**
- Mailing list thread and stable-list discussion (lore inaccessible,
  commit not in tree for b4 dig)

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — maintainer-reviewed, Tested-
   by, matches established API-usage pattern
2. Fixes a real bug affecting users? **PASS** — lock contention in
   reclaim path on production workloads
3. Important issue? **PASS** — HIGH severity stability under memory
   pressure (reclaim stalls, potential lockup warnings)
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 confirmed present,
   clean apply

### Step 9.3: EXCEPTION CATEGORIES
**Record:** N/A — not a device ID, quirk, DT, build, or docs fix.
Standard bug fix.

### Step 9.4: DECISION RATIONALE

This commit fixes btrfs using the wrong percpu_counter API in a shrinker
count callback. The VFS shrinker framework deliberately avoids locking
because counts are advisory; btrfs's use of
`percpu_counter_sum_positive()` introduces a global spinlock into a path
invoked repeatedly per (memcg, node) during reclaim. On large container
hosts this creates a serialization bottleneck during exactly the
operation (memory reclaim) that must remain fast.

The fix is minimal, maintainer-reviewed, has direct precedent in the
same btrfs codebase, and carries negligible regression risk. While not a
traditional crash or corruption bug, it causes serious stability
degradation under memory pressure — a scenario stable trees must handle
well.

---

## Verification

- [Phase 1] Parsed commit message tags: Tested-by, 2× Reviewed-by, 2×
  Signed-off-by; no Fixes/Reported-by/Link
- [Phase 2] Diff analysis: 1-line change in `btrfs_nr_cached_objects()`,
  `sum_positive` → `read_positive`
- [Phase 2] Read `include/linux/percpu_counter.h:97-126`: confirmed
  `sum_positive` locks and iterates CPUs; `read_positive` is lockless
  `READ_ONCE`
- [Phase 2] Read `lib/percpu_counter.c:159-185`: `__percpu_counter_sum`
  documented as "more accurate but much slower"
- [Phase 3] `git describe HEAD`: v6.18.44 / 6.18.44
- [Phase 3] `git blame fs/btrfs/super.c:2410-2418`: function from
  `956a17d9d0507` (2024-05-07)
- [Phase 3] `git merge-base --is-ancestor 956a17d9d0507 HEAD`: shrinker
  commit in 6.18.y
- [Phase 3] `git show 2cdb3909c9e95`: btrfs precedent for same API
  change
- [Phase 4] UNVERIFIED: `b4 dig` failed (no commit SHA in tree);
  lore.kernel.org returned 403
- [Phase 5] Read `fs/super.c:235-262`: `super_cache_count()` explicitly
  avoids locks, notes counts are advisory
- [Phase 5] Read `fs/super.c:377-385`: superblock shrinker is
  `SHRINKER_MEMCG_AWARE | SHRINKER_NUMA_AWARE`
- [Phase 5] Read `mm/shrinker.c:478-594`: `shrink_slab_memcg()` iterates
  shrinkers per memcg/node
- [Phase 5] Read `fs/xfs/xfs_mount.h:729-736`: xfs uses `read_positive`
  for estimates
- [Phase 6] `grep fs/btrfs/super.c`: buggy `percpu_counter_sum_positive`
  confirmed at line 2413
- [Phase 6] `git log -S evictable_extent_maps`: counter and shrinker
  both in tree since 2024
- [Phase 8] `percpu_counter_batch` default = 32
  (`lib/percpu_counter.c:255`)

**YES**

 fs/btrfs/super.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 9dc399e5dc091..d3661be700810 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -2435,7 +2435,7 @@ static int btrfs_show_devname(struct seq_file *m, struct dentry *root)
 static long btrfs_nr_cached_objects(struct super_block *sb, struct shrink_control *sc)
 {
 	struct btrfs_fs_info *fs_info = btrfs_sb(sb);
-	const s64 nr = percpu_counter_sum_positive(&fs_info->evictable_extent_maps);
+	const s64 nr = percpu_counter_read_positive(&fs_info->evictable_extent_maps);
 
 	trace_btrfs_extent_map_shrinker_count(fs_info, nr);
 
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-5.10] btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (8 preceding siblings ...)
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use lockless read in nr_cached_objects shrinker callback Sasha Levin
@ 2026-08-31 13:24 ` Sasha Levin
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.1] btrfs: tree-checker: validate names in ROOT_REF and ROOT_BACKREF Sasha Levin
                   ` (5 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:24 UTC (permalink / raw)
  To: patches, stable
  Cc: Filipe Manana, syzbot+b3d472d13f9d7bf20669, Qu Wenruo,
	David Sterba, Sasha Levin, clm, linux-btrfs, linux-kernel

From: Filipe Manana <fdmanana@suse.com>

[ Upstream commit 83201804efa4a5168be754e1dfc9b2faee760cac ]

If during relocation we fail in insert_dirty_subvol() because
btrfs_update_reloc_root() returned an error, we will leave a root's
reloc_root field pointing to a reloc root that was freed instead of NULL,
resulting later in a use-after-free, or double free attempt during
unmount.

The sequence of steps is this:

1) During relocation the call to btrfs_update_reloc_root() in
   insert_dirty_subvol() fails, so insert_dirty_subvol() returns the
   error to merge_reloc_root() without adding the root to the list
   rc->dirty_subvol_roots;

2) Then merge_reloc_root() aborts the current transaction because
   insert_dirty_subvol() returned an error;

3) Up the call chain, merge_reloc_roots() gets the error, adds the
   reloc root for root X to the local reloc_roots list and jumps to the
   'out' label, where it calls free_reloc_roots() to free all the reloc
   roots in the local reloc_roots list. This frees the reloc root for
   root X;

4) We go up the call chain to relocate_block_group() which calls
   clean_dirty_subvols() to go over dirty roots and set their
   ->reloc_root field to NULL, but root X is not in the dirty_subvol_roots
   list, so its ->reloc_root still points to a reloc root;

5) Relocation finishes, with an error and a transaction abort, but the
   ->reloc_root field for root X still points to the reloc root that was
   freed in step 3;

6) When unmounting the fs we end up calling:

     btrfs_free_fs_roots()
        btrfs_drop_and_free_fs_root()
           --> calls btrfs_put_root() against root X's ->reloc_root
               which is not NULL and points to the already freed
               reloc root in step 4 above

  Resulting in a use-after-free to a double free attempt.

Syzbot reported this with the following dmesg/syslog:

   [  106.004389][ T5339] BTRFS error (device loop0 state A): Transaction aborted (error -5)
   [  106.014266][ T5339] BTRFS: error (device loop0 state A) in merge_reloc_root:1655: errno=-5 IO failure
   [  106.021891][ T1061] BTRFS error (device loop0 state A): error while writing out transaction: -5
   [  106.026964][ T1061] BTRFS warning (device loop0 state A): Skipping commit of aborted transaction.
   [  106.033807][ T5340] BTRFS error (device loop0 state A): bdev /dev/loop0 errs: wr 3, rd 0, flush 0, corrupt 0, gen 0
   [  106.039265][ T1061] BTRFS: error (device loop0 state A) in cleanup_transaction:2067: errno=-5 IO failure
   [  106.044382][ T5339] BTRFS info (device loop0 state EA): forced readonly
   [  106.074329][ T5339] BTRFS: error (device loop0 state EA) in merge_reloc_roots:1887: errno=-5 IO failure
   [  106.081004][ T5356] BTRFS info (device loop0 state EA): scrub: started on devid 1
   [  106.085611][ T5339] BTRFS info (device loop0 state EA): balance: ended with status: -30
   [  106.089517][ T5356] BTRFS info (device loop0 state EA): scrub: not finished on devid 1 with status: -30
   [  106.662365][ T5338] BTRFS info (device loop0 state EA): last unmount of filesystem 3a375e4e-b156-4d76-a2ad-16e198ce1409
   [  106.682946][ T5338] ==================================================================
   [  106.686574][ T5338] BUG: KASAN: slab-use-after-free in btrfs_put_root+0x2f/0x250
   [  106.690090][ T5338] Write of size 4 at addr ffff88803f978630 by task syz.0.0/5338
   [  106.693173][ T5338]
   [  106.694279][ T5338] CPU: 0 UID: 0 PID: 5338 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full)
   [  106.694293][ T5338] Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
   [  106.694300][ T5338] Call Trace:
   [  106.694308][ T5338]  <TASK>
   [  106.694314][ T5338]  dump_stack_lvl+0xe8/0x150
   [  106.694331][ T5338]  print_address_description+0x55/0x1e0
   [  106.694343][ T5338]  ? btrfs_put_root+0x2f/0x250
   [  106.694358][ T5338]  print_report+0x58/0x70
   [  106.694368][ T5338]  kasan_report+0x117/0x150
   [  106.694384][ T5338]  ? btrfs_put_root+0x2f/0x250
   [  106.694399][ T5338]  kasan_check_range+0x264/0x2c0
   [  106.694416][ T5338]  btrfs_put_root+0x2f/0x250
   [  106.694430][ T5338]  btrfs_drop_and_free_fs_root+0x160/0x210
   [  106.694447][ T5338]  btrfs_free_fs_roots+0x2f9/0x3c0
   [  106.694464][ T5338]  ? __pfx_btrfs_free_fs_roots+0x10/0x10
   [  106.694479][ T5338]  ? free_root_pointers+0x5bf/0x5f0
   [  106.694494][ T5338]  close_ctree+0x798/0x12d0
   [  106.694511][ T5338]  ? __pfx_close_ctree+0x10/0x10
   [  106.694526][ T5338]  ? _raw_spin_unlock_irqrestore+0x74/0x80
   [  106.694599][ T5338]  ? rcu_preempt_deferred_qs_irqrestore+0x906/0xbc0
   [  106.694620][ T5338]  ? __rcu_read_unlock+0x83/0xe0
   [  106.694636][ T5338]  ? btrfs_put_super+0x48/0x1c0
   [  106.694652][ T5338]  ? __pfx_btrfs_put_super+0x10/0x10
   [  106.694667][ T5338]  generic_shutdown_super+0x13d/0x2d0
   [  106.694682][ T5338]  kill_anon_super+0x3b/0x70
   [  106.694695][ T5338]  btrfs_kill_super+0x41/0x50
   [  106.694710][ T5338]  deactivate_locked_super+0xbc/0x130
   [  106.694722][ T5338]  cleanup_mnt+0x437/0x4d0
   [  106.694736][ T5338]  ? _raw_spin_unlock_irq+0x23/0x50
   [  106.694752][ T5338]  task_work_run+0x1d9/0x270
   [  106.694769][ T5338]  ? __pfx_task_work_run+0x10/0x10
   [  106.694784][ T5338]  ? do_raw_spin_unlock+0x4d/0x210
   [  106.694802][ T5338]  do_exit+0x70f/0x22c0
   [  106.694817][ T5338]  ? trace_irq_disable+0x3b/0x140
   [  106.694835][ T5338]  ? __pfx_do_exit+0x10/0x10
   [  106.694848][ T5338]  ? preempt_schedule_thunk+0x16/0x30
   [  106.694863][ T5338]  ? preempt_schedule_common+0x82/0xd0
   [  106.694878][ T5338]  ? preempt_schedule_thunk+0x16/0x30
   [  106.694892][ T5338]  do_group_exit+0x21b/0x2d0
   [  106.694906][ T5338]  ? entry_SYSCALL_64_after_hwframe+0x77/0x7f
   [  106.694918][ T5338]  __x64_sys_exit_group+0x3f/0x40
   [  106.694932][ T5338]  x64_sys_call+0x221a/0x2240
   [  106.694944][ T5338]  do_syscall_64+0x174/0x580
   [  106.694954][ T5338]  ? clear_bhb_loop+0x40/0x90
   [  106.694967][ T5338]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
   [  106.694978][ T5338] RIP: 0033:0x7f958ef9ce59
   [  106.694988][ T5338] Code: Unable to access opcode bytes at 0x7f958ef9ce2f.
   [  106.694994][ T5338] RSP: 002b:00007fffd4058318 EFLAGS: 00000246 ORIG_RAX: 00000000000000e7
   [  106.695008][ T5338] RAX: ffffffffffffffda RBX: 0000000000000000 RCX: 00007f958ef9ce59
   [  106.695015][ T5338] RDX: 00007f958c3f8000 RSI: 0000000000000000 RDI: 0000000000000000
   [  106.695022][ T5338] RBP: 0000000000000003 R08: 0000000000000000 R09: 00007f958f1e73e0
   [  106.695028][ T5338] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
   [  106.695034][ T5338] R13: 00007f958f1e73e0 R14: 0000000000000003 R15: 00007fffd40583d0
   [  106.695046][ T5338]  </TASK>
   [  106.695050][ T5338]
   [  106.821635][ T5338] Allocated by task 1061:
   [  106.823446][ T5338]  kasan_save_track+0x3e/0x80
   [  106.825498][ T5338]  __kasan_kmalloc+0x93/0xb0
   [  106.827381][ T5338]  __kmalloc_cache_noprof+0x31c/0x660
   [  106.829525][ T5338]  btrfs_alloc_root+0x75/0x930
   [  106.831458][ T5338]  read_tree_root_path+0x127/0xb00
   [  106.833556][ T5338]  btrfs_read_tree_root+0x34/0x60
   [  106.835553][ T5338]  create_reloc_root+0x6b3/0xcb0
   [  106.837556][ T5338]  btrfs_init_reloc_root+0x2ec/0x4b0
   [  106.839557][ T5338]  record_root_in_trans+0x2ab/0x350
   [  106.841685][ T5338]  btrfs_record_root_in_trans+0x15c/0x180
   [  106.844237][ T5338]  start_transaction+0x39c/0x1820
   [  106.846638][ T5338]  btrfs_finish_one_ordered+0x88e/0x2680
   [  106.849436][ T5338]  btrfs_work_helper+0x37b/0xc20
   [  106.851549][ T5338]  process_scheduled_works+0xb5d/0x1860
   [  106.853807][ T5338]  worker_thread+0xa53/0xfc0
   [  106.855773][ T5338]  kthread+0x389/0x470
   [  106.857548][ T5338]  ret_from_fork+0x514/0xb70
   [  106.859493][ T5338]  ret_from_fork_asm+0x1a/0x30
   [  106.861504][ T5338]
   [  106.862527][ T5338] Freed by task 5339:
   [  106.864224][ T5338]  kasan_save_track+0x3e/0x80
   [  106.866180][ T5338]  kasan_save_free_info+0x46/0x50
   [  106.868371][ T5338]  __kasan_slab_free+0x5c/0x80
   [  106.870462][ T5338]  kfree+0x1c5/0x640
   [  106.872180][ T5338]  __del_reloc_root+0x341/0x3b0
   [  106.874290][ T5338]  free_reloc_roots+0x5f/0x90
   [  106.876282][ T5338]  merge_reloc_roots+0x73f/0x8a0
   [  106.878489][ T5338]  relocate_block_group+0xbcc/0xe70
   [  106.880742][ T5338]  do_nonremap_reloc+0xa8/0x5b0
   [  106.882885][ T5338]  btrfs_relocate_block_group+0x7e6/0xc40
   [  106.885336][ T5338]  btrfs_relocate_chunk+0x115/0x820
   [  106.887502][ T5338]  __btrfs_balance+0x1db0/0x2ae0
   [  106.889543][ T5338]  btrfs_balance+0xaf3/0x11b0
   [  106.891456][ T5338]  btrfs_ioctl_balance+0x3d3/0x610
   [  106.893672][ T5338]  __se_sys_ioctl+0xfc/0x170
   [  106.895530][ T5338]  do_syscall_64+0x174/0x580
   [  106.897518][ T5338]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
   [  106.900101][ T5338]
   [  106.901123][ T5338] The buggy address belongs to the object at ffff88803f978000
   [  106.901123][ T5338]  which belongs to the cache kmalloc-4k of size 4096
   [  106.906907][ T5338] The buggy address is located 1584 bytes inside of
   [  106.906907][ T5338]  freed 4096-byte region [ffff88803f978000, ffff88803f979000)
   [  106.912980][ T5338]
   [  106.914022][ T5338] The buggy address belongs to the physical page:
   [  106.916716][ T5338] page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x3f978
   [  106.920390][ T5338] head: order:3 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
   [  106.923834][ T5338] flags: 0x4fff00000000040(head|node=1|zone=1|lastcpupid=0x7ff)
   [  106.927104][ T5338] page_type: f5(slab)
   [  106.928898][ T5338] raw: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
   [  106.932507][ T5338] raw: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
   [  106.936193][ T5338] head: 04fff00000000040 ffff88801ac42140 dead000000000122 0000000000000000
   [  106.939856][ T5338] head: 0000000000000000 0000000800040004 00000000f5000000 0000000000000000
   [  106.943601][ T5338] head: 04fff00000000003 fffffffffffffe01 00000000ffffffff 00000000ffffffff
   [  106.947268][ T5338] head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000008
   [  106.950988][ T5338] page dumped because: kasan: bad access detected
   [  106.953710][ T5338] page_owner tracks the page as allocated
   [  106.956198][ T5338] page last allocated via order 3, migratetype Unmovable, gfp_mask 0xd2820(GFP_ATOMIC|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 24, tgid 24 (kworker/u4:2), ts 105728970387, free_ts 29540875453
   [  106.964984][ T5338]  post_alloc_hook+0x22d/0x280
   [  106.966956][ T5338]  get_page_from_freelist+0x2593/0x2610
   [  106.969307][ T5338]  __alloc_frozen_pages_noprof+0x18d/0x380
   [  106.971839][ T5338]  allocate_slab+0x77/0x660
   [  106.973709][ T5338]  refill_objects+0x339/0x3d0
   [  106.975696][ T5338]  __pcs_replace_empty_main+0x321/0x720
   [  106.978136][ T5338]  __kmalloc_node_track_caller_noprof+0x572/0x7b0
   [  106.981009][ T5338]  __alloc_skb+0x2c1/0x7d0
   [  106.982983][ T5338]  nsim_dev_trap_report_work+0x29a/0xb90
   [  106.985356][ T5338]  process_scheduled_works+0xb5d/0x1860
   [  106.987710][ T5338]  worker_thread+0xa53/0xfc0
   [  106.989847][ T5338]  kthread+0x389/0x470
   [  106.991727][ T5338]  ret_from_fork+0x514/0xb70
   [  106.993722][ T5338]  ret_from_fork_asm+0x1a/0x30
   [  106.995900][ T5338] page last free pid 77 tgid 77 stack trace:
   [  106.998479][ T5338]  __free_frozen_pages+0xc1c/0xd30
   [  107.000819][ T5338]  vfree+0x1d1/0x2f0
   [  107.002631][ T5338]  delayed_vfree_work+0x55/0x80
   [  107.004848][ T5338]  process_scheduled_works+0xb5d/0x1860
   [  107.007366][ T5338]  worker_thread+0xa53/0xfc0
   [  107.009388][ T5338]  kthread+0x389/0x470
   [  107.011177][ T5338]  ret_from_fork+0x514/0xb70
   [  107.013313][ T5338]  ret_from_fork_asm+0x1a/0x30
   [  107.015454][ T5338]
   [  107.016460][ T5338] Memory state around the buggy address:
   [  107.019052][ T5338]  ffff88803f978500: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
   [  107.022691][ T5338]  ffff88803f978580: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
   [  107.026264][ T5338] >ffff88803f978600: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
   [  107.029721][ T5338]                                      ^
   [  107.032062][ T5338]  ffff88803f978680: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
   [  107.035547][ T5338]  ffff88803f978700: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
   [  107.038865][ T5338] ==================================================================

Fix this by resetting a root's ->reloc_root if we get an error while
trying to merge a reloc root.

Reported-by: syzbot+b3d472d13f9d7bf20669@syzkaller.appspotmail.com
Link: https://lore.kernel.org/linux-btrfs/6a1ebde9.c1435f33.112120.0176.GAE@google.com/
Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: fix use-after-free on reloc root
after error in insert_dirty_subvol()`

**Local tree:** Linux **6.18.44** (`v6.18.44-1-g2736c32da98b9`,
`VERSION=6 PATCHLEVEL=18 SUBLEVEL=44`)
**Fix commit:** `83201804efa4a` (not present in this checkout)
**Verdict target:** Should this fix be backported to **this** 6.18.y
tree?

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[btrfs] [fix] use-after-free on reloc root after error in
insert_dirty_subvol()`

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Reported-by:** syzbot+b3d472d13f9d7bf20669@syzkaller.appspotmail.com
  (fuzzer-found, reproducible)
- **Link:** https://lore.kernel.org/linux-
  btrfs/6a1ebde9.c1435f33.112120.0176.GAE@google.com/ (syzbot report)
- **Reviewed-by:** Qu Wenruo \<wqu@suse.com\> (btrfs maintainer)
- **Signed-off-by:** Filipe Manana, David Sterba
- No `Fixes:` tag in the committed version (v1 had `Fixes:
  7934133fae5e`)
- No `Cc: stable@vger.kernel.org` (expected for manual review)
- No `Tested-by:`

**Notable patterns:** syzbot report + KASAN slab-use-after-free stack
trace = strong YES signal.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** On relocation error in `insert_dirty_subvol()` (when
  `btrfs_update_reloc_root()` fails), the subvolume root's
  `->reloc_root` is left pointing at a reloc root that gets freed in
  `merge_reloc_roots()` error cleanup, but the root is never added to
  `dirty_subvol_roots`, so `clean_dirty_subvols()` does not NULL it out.
- **Symptom:** KASAN slab-use-after-free (or double-free attempt) in
  `btrfs_put_root()` during unmount via `btrfs_free_fs_roots()` →
  `btrfs_drop_and_free_fs_root()`.
- **Trigger:** Balance/relocation with I/O failure during merge
  (`errno=-5` in syzbot log).
- **Root cause:** Missing cleanup of `root->reloc_root` on the
  `merge_reloc_root()` error path before `free_reloc_roots()`.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — explicitly labeled as UAF fix. The
`clear_reloc_root()` helper extraction is refactoring of existing
cleanup logic, not a feature.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **Files:** `fs/btrfs/relocation.c` only (~55 lines changed)
- **Functions modified:** new `clear_reloc_root()`,
  `clean_dirty_subvols()`, `merge_reloc_roots()`
- **Scope:** Single-file, surgical error-path fix

### Step 2.2: CODE FLOW CHANGE (per hunk)

**Hunk 1 — new `clear_reloc_root()`:**
- **Before:** Inline `root->reloc_root = NULL; smp_wmb();
  clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE)` in `clean_dirty_subvols()`
- **After:** Shared helper with same semantics
- **Path:** Cleanup of merged subvolume reloc roots

**Hunk 2 — `clean_dirty_subvols()`:**
- **Before:** Inline NULL/barrier/clear_bit
- **After:** Calls `clear_reloc_root(root)` — behavior unchanged

**Hunk 3 — `merge_reloc_roots()` error path:**
- **Before:** On `merge_reloc_root()` failure: re-queue reloc_root to
  local list, `goto out` → `free_reloc_roots()` frees it, but
  `root->reloc_root` still points to freed object
- **After:** On failure: `clear_reloc_root(root)` first; properly
  balance refs with `btrfs_grab_root(reloc_root)` when re-queuing;
  `btrfs_put_root(reloc_root)` to drop `root->reloc_root` ref; move
  `btrfs_put_root(root)` after success path only

### Step 2.3: BUG MECHANISM
**Record:** **Category:** Use-after-free / reference-counting bug
**Mechanism:** Reloc root freed via `free_reloc_roots()` →
`__del_reloc_root()` while `root->reloc_root` still holds a dangling
pointer. On unmount with `BTRFS_FS_ERROR` set,
`btrfs_drop_and_free_fs_root()` calls `btrfs_put_root(root->reloc_root)`
on the freed object.

### Step 2.4: FIX QUALITY
**Record:** Fix is obviously correct and minimal. Extracting
`clear_reloc_root()` preserves the existing `smp_wmb()` pairing with
`have_reloc_root()`. The added `btrfs_grab_root()` on re-queue fixes a
secondary refcount imbalance. Low regression risk — only affects error
paths during relocation merge.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:** Current buggy error path at lines 1864–1870 last touched by
merge commit `5d324e5159d9e` (Nov 2025); underlying logic predates that.
The early-return-on-error pattern in `insert_dirty_subvol()` is present
at lines 1448–1450 in this tree.

### Step 3.2: FOLLOW THE FIXES: TAG
**Record:** No `Fixes:` tag in committed version. v1 referenced `Fixes:
7934133fae5e` ("btrfs: handle btrfs_update_reloc_root failure in
insert_dirty_subvol", Mar 2021). That commit object exists in the repo
but `git merge-base --is-ancestor` reports it is **not** reachable from
HEAD (likely limited/disconnected history in this autosel checkout).
Regardless, the early-return pattern **is present** in the current tree.

### Step 3.3: FILE HISTORY FOR RELATED CHANGES
**Record:** Related recent fix already in tree: `60a23d4ea169e` "fix
root leak if its reloc root is unexpected in merge_reloc_roots()" —
different bug, same function. No duplicate fix for this UAF found.

### Step 3.4: AUTHOR'S OTHER COMMITS
**Record:** Filipe Manana is an active btrfs contributor. David Sterba
is btrfs maintainer. Qu Wenruo reviewed.

### Step 3.5: DEPENDENT/PREREQUISITE COMMITS
**Record:** Standalone single patch (v1–v5 were iterations of the same
fix). `git apply --check` on `83201804efa4a` succeeds cleanly against
HEAD. No series dependencies.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: ORIGINAL PATCH DISCUSSION
**Record:** `b4 dig -c 83201804efa4a` → [PATCH v5](https://patch.msgid.l
ink/cf84f1a217c719e25b6b69e4298dd7afd36c9427.1781194426.git.fdmanana@sus
e.com). Series: v1 (Jun 9) → v5 (Jun 11, 2026). Committed version
matches v5.

### Step 4.2: REVIEWERS
**Record:** `b4 dig -w` — sent to `fdmanana@kernel.org`, `linux-
btrfs@vger.kernel.org`. Reviewed-by Qu Wenruo in commit and on list.

### Step 4.3: BUG REPORT
**Record:** syzbot report with full KASAN trace. Trigger:
`btrfs_ioctl_balance` → relocation → I/O error during merge → UAF on
unmount. Crash type: `KASAN: slab-use-after-free in btrfs_put_root`.

### Step 4.4: RELATED PATCHES
**Record:** v1 proposed fixing `insert_dirty_subvol()` to always add to
dirty list even on error; v4/v5 moved fix to `merge_reloc_roots()` error
path (cleaner). Final committed approach is v5.

### Step 4.5: STABLE MAILING LIST
**Record:** No explicit stable-list nomination found in available thread
excerpts. Not a negative signal.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: KEY FUNCTIONS
**Record:** `insert_dirty_subvol()`, `merge_reloc_root()`,
`merge_reloc_roots()`, `free_reloc_roots()`, `clean_dirty_subvols()`,
`clear_reloc_root()` (new), `btrfs_drop_and_free_fs_root()`

### Step 5.2: CALLERS
**Record:**
- `insert_dirty_subvol()` ← `merge_reloc_root()` (line 1661)
- `merge_reloc_root()` ← `merge_reloc_roots()` (line 1864)
- `merge_reloc_roots()` ← `relocate_block_group()` (line 3653), remap
  path (line 4198)
- `clean_dirty_subvols()` ← `relocate_block_group()` (line 3669)
- `btrfs_free_fs_roots()` ← `close_ctree()` during unmount

### Step 5.3: CALLEES
**Record:** `btrfs_update_reloc_root()`, `btrfs_grab_root()`,
`btrfs_put_root()`, `free_reloc_roots()` → `__del_reloc_root()` →
`kfree()`, `btrfs_abort_transaction()`

### Step 5.4: CALL CHAIN / REACHABILITY
**Record:** Userspace `ioctl(BTRFS_IOC_BALANCE)` →
`btrfs_ioctl_balance()` → `__btrfs_balance()` → `btrfs_relocate_chunk()`
→ relocation merge path. **Reachable from userspace** via
balance/relocation ioctl.

### Step 5.5: SIMILAR PATTERNS
**Record:** `clean_dirty_subvols()` already does the correct `reloc_root
= NULL` + barrier + `clear_bit` for roots on the dirty list. The bug is
the missing equivalent cleanup for roots that fail before being added to
that list.

---

## PHASE 6: CROSS-REFERENCE AGAINST LOCAL TREE

### Step 6.1: DOES THE BUGGY CODE EXIST?
**Record:** **YES.** Current tree at lines 1448–1450
(`insert_dirty_subvol` early return on error) and 1864–1870
(`merge_reloc_roots` error path without clearing `root->reloc_root`). No
`clear_reloc_root()` helper exists.

### Step 6.2: BACKPORT COMPLICATIONS
**Record:** **Clean apply expected.** `git show 83201804efa4a | git
apply --check` passes with no conflicts.

### Step 6.3: RELATED FIXES ALREADY PRESENT?
**Record:** `60a23d4ea169e` fixes a different leak in
`merge_reloc_roots()`. This UAF fix (`83201804efa4a`) is **not**
present.

---

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: SUBSYSTEM AND CRITICALITY
**Record:** **Filesystem (btrfs)** — **IMPORTANT/CORE** for btrfs users.
Balance/relocation is a standard admin operation.

### Step 7.2: SUBSYSTEM ACTIVITY
**Record:** Actively maintained; recent reloc-related fixes in this tree
(`797dc567146c7`, `60a23d4ea169e`).

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: WHO IS AFFECTED
**Record:** All btrfs users who run balance/relocation (or hit
relocation during chunk management) and encounter I/O errors during
merge. Not config-gated beyond `CONFIG_BTRFS_FS`.

### Step 8.2: TRIGGER CONDITIONS
**Record:** Relocation merge fails (I/O error `-EIO` demonstrated by
syzbot). Requires balance/relocation + subsequent unmount. Unprivileged
users can trigger via `BTRFS_IOC_BALANCE` if they have access to the
mount.

### Step 8.3: FAILURE MODE SEVERITY
**Record:** **CRITICAL** — KASAN-confirmed slab use-after-free /
potential double-free in `btrfs_put_root()` during filesystem teardown.
Memory corruption class bug with security implications.

### Step 8.4: RISK-BENEFIT
**Record:**
- **Benefit:** HIGH — prevents UAF on a standard error-recovery path
- **Risk:** LOW — ~55 lines, error-path only, reviewed by maintainer,
  applies cleanly
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: EVIDENCE COMPILED

**FOR backport:**
- syzbot-reported, KASAN-confirmed UAF
- Reachable from userspace via balance ioctl
- Crash on unmount after relocation I/O error
- Small, surgical, single-file fix
- Reviewed by btrfs maintainer (Qu Wenruo)
- Applies cleanly to this 6.18.44 tree
- Buggy code confirmed present; fix not yet applied

**AGAINST backport:**
- Trigger requires relocation I/O failure (not everyday, but realistic)
- No concerns significant enough to block

**Unresolved:** `7934133fae5e` not reachable from HEAD in this repo's
history, but irrelevant since the buggy pattern is visibly present in
current code.

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — syzbot reproducer,
   maintainer review
2. Fixes a real bug affecting users? **PASS** — confirmed UAF
3. Important issue? **PASS** — CRITICAL (UAF/double-free, security-
   relevant)
4. Small and contained? **PASS** — one file, ~55 lines, error path only
5. No new features or APIs? **PASS** — bug fix only
6. Can apply to local tree? **PASS** — `git apply --check` succeeds

### Step 9.3: EXCEPTION CATEGORIES
**Record:** N/A (standard bug fix, not device-ID/quirk/build fix).

### Step 9.4: DECISION RATIONALE

This fix addresses a real, syzbot-confirmed use-after-free in btrfs
relocation error handling. The buggy code is present in Linux 6.18.44;
the fix is absent. When `btrfs_update_reloc_root()` fails during
relocation merge, the reloc root gets freed but `root->reloc_root`
remains dangling; on unmount with the filesystem in error state,
`btrfs_drop_and_free_fs_root()` dereferences the freed reloc root. The
patch correctly clears the pointer (with proper memory barrier),
balances reference counts, and applies cleanly. This is exactly the kind
of fix stable trees need.

---

## Verification

- [Phase 1] Parsed subject, tags: syzbot Reported-by, Link, Reviewed-by
  Qu Wenruo
- [Phase 1] Body describes 6-step failure sequence ending in KASAN UAF
  on unmount
- [Phase 2] Diff: 1 file, `clear_reloc_root()` helper +
  `merge_reloc_roots()` error-path fix
- [Phase 2] Verified UAF mechanism: `free_reloc_roots()` frees reloc
  root while `root->reloc_root` still set
- [Phase 3] `git describe HEAD`: v6.18.44-1-g2736c32da98b9
- [Phase 3] `git merge-base --is-ancestor 83201804efa4a HEAD`: fix
  **NOT** in tree (exit 1)
- [Phase 3] `git show 83201804efa4a | git apply --check`: **passes
  cleanly**
- [Phase 3] Buggy code at `relocation.c:1448-1450` and `1864-1870`
  confirmed by read
- [Phase 4] `b4 dig -c 83201804efa4a`: v1–v5 series, v5 is final
- [Phase 4] `b4 dig -w`: linux-btrfs@vger.kernel.org CC'd
- [Phase 4] spinics v5 thread: Reviewed-by Qu Wenruo present
- [Phase 5] Call chain: `btrfs_ioctl_balance` → relocation →
  `merge_reloc_roots` → unmount
- [Phase 5] `btrfs_drop_and_free_fs_root()` at `disk-io.c:4119-4124`
  puts `reloc_root` when `BTRFS_FS_ERROR` — matches syzbot stack
- [Phase 6] `grep clear_reloc_root`: not found in tree (fix absent)
- [Phase 6] `insert_dirty_subvol` early return on
  `btrfs_update_reloc_root` error: present
- [Phase 8] Failure mode: KASAN slab-use-after-free, severity CRITICAL
- [Phase 3] UNVERIFIED: `7934133fae5e` ancestry from HEAD (merge-base
  exit 1); does not affect verdict since buggy pattern is confirmed
  present in current source

**YES**

 fs/btrfs/relocation.c | 42 +++++++++++++++++++++++++++++++++---------
 1 file changed, 33 insertions(+), 9 deletions(-)

diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index f1104fa6efb3f..851cef16d4017 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -1471,6 +1471,17 @@ static int insert_dirty_subvol(struct btrfs_trans_handle *trans,
 	return 0;
 }
 
+static void clear_reloc_root(struct btrfs_root *root)
+{
+	root->reloc_root = NULL;
+	/*
+	 * Need barrier to ensure clear_bit() only happens after
+	 * root->reloc_root = NULL. Pairs with have_reloc_root().
+	 */
+	smp_wmb();
+	clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
+}
+
 static int clean_dirty_subvols(struct reloc_control *rc)
 {
 	struct btrfs_root *root;
@@ -1485,13 +1496,7 @@ static int clean_dirty_subvols(struct reloc_control *rc)
 			struct btrfs_root *reloc_root = root->reloc_root;
 
 			list_del_init(&root->reloc_dirty_list);
-			root->reloc_root = NULL;
-			/*
-			 * Need barrier to ensure clear_bit() only happens after
-			 * root->reloc_root = NULL. Pairs with have_reloc_root.
-			 */
-			smp_wmb();
-			clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
+			clear_reloc_root(root);
 			if (reloc_root) {
 				/*
 				 * btrfs_drop_snapshot drops our ref we hold for
@@ -1876,13 +1881,32 @@ void merge_reloc_roots(struct reloc_control *rc)
 				goto out;
 			}
 			ret = merge_reloc_root(rc, root);
-			btrfs_put_root(root);
 			if (ret) {
-				if (list_empty(&reloc_root->root_list))
+				/*
+				 * Clear the reloc root since below we will call
+				 * free_reloc_roots(), otherwise we leave
+				 * root->reloc_root pointing to a freed reloc
+				 * root and trigger a use-after-free during
+				 * unmount or elsewhere.
+				 */
+				clear_reloc_root(root);
+				btrfs_put_root(root);
+				/*
+				 * We are adding the reloc_root to the local
+				 * reloc_roots list, so we add a ref for this
+				 * list which will be dropped below by the call
+				 * to free_reloc_roots().
+				 */
+				if (list_empty(&reloc_root->root_list)) {
 					list_add_tail(&reloc_root->root_list,
 						      &reloc_roots);
+					btrfs_grab_root(reloc_root);
+				}
+				/* Now drop the ref for root->reloc_root. */
+				btrfs_put_root(reloc_root);
 				goto out;
 			}
+			btrfs_put_root(root);
 		} else {
 			if (!IS_ERR(root)) {
 				if (root->reloc_root == reloc_root) {
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.1] btrfs: tree-checker: validate names in ROOT_REF and ROOT_BACKREF
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (9 preceding siblings ...)
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol() Sasha Levin
@ 2026-08-31 13:24 ` Sasha Levin
  2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix reloc root cleanup in merge_reloc_roots() Sasha Levin
                   ` (4 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:24 UTC (permalink / raw)
  To: patches, stable
  Cc: Zhang Cen, Qu Wenruo, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: Zhang Cen <rollkingzzc@gmail.com>

[ Upstream commit 0af37c217edf15fa21dac1c40822086df356c6bb ]

ROOT_REF and ROOT_BACKREF items contain a struct btrfs_root_ref followed
by the subvolume name. Several readers assume that this layout is already
valid and then use the on-disk name length directly. A corrupted item can
therefore make those readers address bytes outside the item, and
BTRFS_IOC_GET_SUBVOL_INFO can copy too many bytes into its fixed-size UAPI
name buffer.

Validate ROOT_REF and ROOT_BACKREF items in tree-checker before any reader
uses them. Reject records that do not contain a non-empty name, whose
name_len does not exactly describe the remaining item payload, or whose
name exceeds BTRFS_NAME_LEN.

For BTRFS_IOC_GET_SUBVOL_INFO, copy only the validated on-disk name_len
instead of deriving the copy length from the item size. The ioctl result is
zeroed when allocated. That leaves the existing trailing zero byte
untouched.

Reviewed-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: Zhang Cen <rollkingzzc@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: btrfs: tree-checker: validate names in
ROOT_REF and ROOT_BACKREF

**Local tree:** `v6.18.44` (Linux 6.18.44)
**Commit under review:** `0af37c217edf1` (on `master`, **not** in this
tree)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[btrfs: tree-checker]` `[validate]` — Add validation of
subvolume names embedded in ROOT_REF and ROOT_BACKREF tree items.

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Fixes:** — absent (expected for manual review)
- **Reported-by:** — absent
- **Tested-by:** — absent
- **Reviewed-by:** Qu Wenruo `<wqu@suse.com>`, David Sterba
  `<dsterba@suse.com>`
- **Acked-by:** — absent
- **Link:** — absent
- **Cc: stable:** — absent (expected)
- **Signed-off-by:** Zhang Cen `<rollkingzzc@gmail.com>`, David Sterba
  `<dsterba@suse.com>` (ignore pipeline-added SOBs)

Notable: reviewed by two btrfs maintainers; no syzbot report, but the
commit message describes a concrete memory-safety failure mode.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** ROOT_REF/ROOT_BACKREF items store `struct btrfs_root_ref`
  followed by a variable-length name. Readers trust on-disk `name_len`
  and item layout without validation.
- **Symptom:** Corrupted items cause readers to access bytes outside the
  item; `BTRFS_IOC_GET_SUBVOL_INFO` can copy more than 256 bytes into
  its fixed-size UAPI name buffer.
- **Root cause:** Tree-checker validates INODE_REF and ROOT_ITEM but not
  ROOT_REF/ROOT_BACKREF; ioctl derives copy length from total item size
  instead of validated `name_len`.
- **Version info:** None in commit message.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — this is an explicit memory-safety /
corruption-handling fix, not cleanup. The ioctl change is defense-in-
depth on top of tree-checker validation.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- `fs/btrfs/tree-checker.c`: +35 lines — new `check_root_ref()`, two new
  switch cases
- `fs/btrfs/ioctl.c`: +6/−6 lines — `btrfs_ioctl_get_subvol_info()`
- **Functions modified:** `check_root_ref()` (new), `check_leaf_item()`,
  `btrfs_ioctl_get_subvol_info()`
- **Scope:** Single-subsystem, surgical, 2 files, ~40 lines net

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE

**Hunk 1 — `tree-checker.c`:**
- **Before:** ROOT_REF/ROOT_BACKREF items fell through
  `check_leaf_item()` with no item-specific validation.
- **After:** `check_root_ref()` rejects items where:
  - `item_size <= sizeof(*rref)` (no non-empty name)
  - `name_len > BTRFS_NAME_LEN` (255)
  - `item_size != sizeof(*rref) + name_len` (layout mismatch)
- **Path affected:** Every leaf block read from disk via
  `btrfs_check_leaf()`.

**Hunk 2 — `ioctl.c`:**
- **Before:** `item_len = btrfs_item_size(...) - sizeof(struct
  btrfs_root_ref)`; copy `item_len` bytes into `subvol_info->name[256]`.
- **After:** Copy `btrfs_root_ref_name_len(leaf, rref)` bytes instead.
- **Path affected:** `BTRFS_IOC_GET_SUBVOL_INFO` ioctl on non-top-level
  subvolumes.

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:**
- **Category:** Buffer overflow / out-of-bounds read (memory safety)
- **Mechanism:** On-disk `name_len` is `__le16` (up to 65535).
  `check_inode_ref()` validates inode refs but ROOT_REF/ROOT_BACKREF had
  no equivalent. In ioctl, `item_len` derived from item size can exceed
  `BTRFS_VOL_NAME_MAX + 1` (256). `read_extent_buffer()` bounds-checks
  the *source* extent-buffer range, not the *destination* buffer size —
  so a 300-byte copy into a 256-byte `name[]` overflows kernel memory.
  Other readers (`send.c`, `export.c`, `super.c`) use
  `btrfs_root_ref_name_len()` directly and can similarly misbehave on
  corrupt metadata.

### Step 2.4: ASSESS THE FIX QUALITY
**Record:**
- Fix mirrors the existing `check_inode_ref()` pattern — obviously
  correct.
- Minimal, no API changes, no refactoring.
- Tree-checker fix protects all consumers at block-read time; ioctl fix
  adds per-call-site safety.
- **Regression risk:** Very low. Valid filesystems always have
  consistent ROOT_REF layout; only corrupt/malicious metadata is
  rejected (returns `-EUCLEAN`/`-EIO` at read time).

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:**
- Vulnerable ioctl code introduced in `b64ec075bded2` (2018-05-21):
  "btrfs: Add unprivileged ioctl which returns subvolume information"
- `item_len` derivation changed in `3212fa14e77291` (2021-10-21)
- Bug present since 2018 in this tree; ROOT_REF validation gap existed
  since tree-checker was introduced (`check_inode_ref` added 2019 in
  `71bf92a9b8777`, but never extended to ROOT_REF)

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

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:**
- Related on master (not in this tree): `3dc22abc21f58` — "btrfs: tree-
  checker: validate INODE_REF's namelen" (adds `namelen >
  BTRFS_NAME_LEN` to `check_inode_ref`)
- This commit is **standalone** — does not depend on `3dc22abc21f58`
- Part of a review series (v1–v4 on linux-btrfs); committed version is
  the final v4 form

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** Zhang Cen is a btrfs contributor; David Sterba (committer)
is btrfs maintainer. Patch went through maintainer review cycle.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No dependencies. `git apply --check` on `0af37c217edf1`
succeeds cleanly against this tree's `ioctl.c` and `tree-checker.c`.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:**
- `b4 dig -c 0af37c217edf1` returned empty (likely too recent for b4
  cache)
- Found via spinics: [PATCH v4] at https://www.spinics.net/lists/linux-
  btrfs/msg165221.html
- Series revisions: v1–v4 exist; committed version matches v4
- Reviewed-by tags from Qu Wenruo and David Sterba in final patch

### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** CC'd to `linux-btrfs@xxxxxxxxxxxxxxx`; reviewed by Qu Wenruo
and David Sterba (subsystem maintainers). `b4 dig -w` returned empty.

### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** No external bug report or syzbot link. Bug identified
through code analysis of metadata validation gaps (consistent with other
btrfs tree-checker hardening patches).

### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** Related but separate: INODE_REF namelen cap
(`3dc22abc21f58`) addresses the same class of bug for a different item
type. Not a prerequisite for this patch.

### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** No stable-list discussion found. Not a negative signal per
review instructions.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `check_root_ref()`, `check_leaf_item()`,
`btrfs_ioctl_get_subvol_info()`

### Step 5.2: TRACE CALLERS
**Record:**
- `check_leaf_item()` → `__btrfs_check_leaf()` → `btrfs_check_leaf()` →
  called from `read_extent_buffer_pages()` in `disk-io.c:457` on every
  metadata leaf read
- `btrfs_ioctl_get_subvol_info()` → `btrfs_ioctl()` case
  `BTRFS_IOC_GET_SUBVOL_INFO` (`ioctl.c:5361`)

### Step 5.3: TRACE CALLEES
**Record:** `btrfs_root_ref_name_len()`, `btrfs_item_size()`,
`read_extent_buffer()`, `generic_err()`, `copy_to_user()`

### Step 5.4: FOLLOW THE CALL CHAIN
**Record:**
1. Mount/access btrfs filesystem with corrupt ROOT_BACKREF metadata
2. Block read triggers `btrfs_check_leaf()` — currently passes corrupt
   ROOT_REF items
3. User opens inode on subvolume, calls `BTRFS_IOC_GET_SUBVOL_INFO`
4. Kernel copies `item_len` bytes into 256-byte `name[]` → **kernel
   buffer overflow**
5. **Userspace reachable:** yes, via ioctl on accessible inode (ioctl
   introduced as "unprivileged")

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** Same vulnerability class as `check_inode_ref()` (validates
item size vs embedded name length). `send.c:2493`, `export.c:282`,
`super.c:847` all read `btrfs_root_ref_name_len()` without local bounds
checks — tree-checker fix protects all of them centrally.

---

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

### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **YES.** Vulnerable ioctl code at `ioctl.c:2129–2134`:

```2129:2134:fs/btrfs/ioctl.c
                        item_off = btrfs_item_ptr_offset(leaf, slot)
                                        + sizeof(struct btrfs_root_ref);
                        item_len = btrfs_item_size(leaf, slot)
     - sizeof(struct btrfs_root_ref);
                        read_extent_buffer(leaf, subvol_info->name,
                                           item_off, item_len);
```

`check_root_ref` does not exist; `check_leaf_item()` has no cases for
`BTRFS_ROOT_REF_KEY` / `BTRFS_ROOT_BACKREF_KEY`. Commit `0af37c217edf1`
is **not** an ancestor of HEAD.

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** `git apply --check` passes cleanly. ioctl.c uses
`kzalloc`/`kfree` here (not mainline's `AUTO_KFREE`/`kzalloc_obj`), but
the patch hunks align with this tree's code.

### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** `3dc22abc21f58` (INODE_REF namelen cap) is **not** in this
tree. No duplicate ROOT_REF validation fix present.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **btrfs filesystem** — **IMPORTANT** (widely deployed;
metadata corruption handling and ioctl safety affect data integrity and
kernel memory safety).

### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** Actively maintained; tree-checker receives regular hardening
patches in this tree (e.g., root drop_level validation, error-message
fixes in recent history).

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** Users of btrfs with `CONFIG_BTRFS_FS=y/m`. Any system
mounting a btrfs volume (including corrupted or attacker-crafted images)
where ROOT_REF/ROOT_BACKREF items are read.

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:**
- Corrupt/malicious ROOT_REF or ROOT_BACKREF metadata (item size ≠
  header + name_len, or name_len > 255)
- Filesystem mounted and metadata block read into cache
- ioctl or other reader consumes the item
- **Likelihood:** Low for organic bitrot with checksums, but realistic
  for crafted images; ioctl path is directly triggerable
- **Unprivileged trigger:** Partially — mounting requires
  `CAP_SYS_ADMIN`, but `BTRFS_IOC_GET_SUBVOL_INFO` is available to users
  with access to inodes on the mount

### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:**
- Kernel buffer overflow in `btrfs_ioctl_get_subvol_info()` (256-byte
  destination, unbounded source length)
- Out-of-bounds reads in other ROOT_REF consumers on corrupt metadata
- **Severity: HIGH** (kernel memory corruption; potential crash or worse
  depending on layout)

### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH — prevents kernel memory corruption on corrupt
  metadata; hardens a gap left open since tree-checker was introduced
- **Risk:** VERY LOW — ~40 lines, follows established `check_inode_ref`
  pattern, reviewed by maintainers, applies cleanly
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backport:**
- Real memory-safety bug with kernel buffer overflow in ioctl path
- Affects long-standing code (since 2018)
- Small, surgical, maintainer-reviewed
- Applies cleanly to v6.18.44
- Tree-checker fix protects all ROOT_REF readers, not just ioctl
- Consistent with stable btrfs tree-checker hardening pattern

**AGAINST backport:**
- No syzbot/user crash report (theoretical on well-checksummed
  filesystems)
- Related INODE_REF namelen cap (`3dc22abc21f58`) is a separate follow-
  up, not included here

**UNRESOLVED:**
- No explicit stable nomination found in mailing list (not required)

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — mirrors `check_inode_ref`;
   reviewed by Qu Wenruo and David Sterba
2. Fixes a real bug? **PASS** — kernel buffer overflow on corrupt
   ROOT_REF metadata
3. Important issue? **PASS** — memory corruption / potential crash (HIGH
   severity)
4. Small and contained? **PASS** — 2 files, ~40 lines
5. No new features or APIs? **PASS** — validation only, no UAPI changes
6. Can apply to local tree? **PASS** — `git apply --check` succeeds

### Step 9.3: CHECK FOR EXCEPTION CATEGORIES
**Record:** Not a device-ID/quirk/DT/build/doc exception — standard bug-
fix category.

### Step 9.4: DECISION RATIONALE
This tree (6.18.44) contains the vulnerable ioctl code and lacks
ROOT_REF/ROOT_BACKREF validation in tree-checker. The fix is small,
maintainer-reviewed, prevents kernel memory corruption on corrupt btrfs
metadata, and applies cleanly. It meets all stable kernel criteria.

---

## Verification

- **[Phase 1]** Parsed subject, tags, body; no syzbot/Fixes tags;
  Reviewed-by from Qu Wenruo and David Sterba
- **[Phase 2]** Analyzed diff: 2 files, ~40 lines; buffer overflow
  mechanism confirmed in ioctl path
- **[Phase 3]** `git describe HEAD`: v6.18.44; `git blame
  ioctl.c:2129-2134`: introduced 2018; `git merge-base --is-ancestor
  0af37c217edf1 HEAD`: fix NOT present; `git apply --check`: PASS
- **[Phase 4]** `b4 dig -c 0af37c217edf1`: empty; WebFetch spinics
  msg165221: found v4 patch thread; no stable nomination found
- **[Phase 5]** Traced callers: `btrfs_check_leaf` at `disk-io.c:457`;
  ioctl at `ioctl.c:5361`; `read_extent_buffer` only bounds-checks
  source (`extent_io.c:3938-3947`)
- **[Phase 6]** Buggy code confirmed at `ioctl.c:2129-2134`; no
  `check_root_ref` in tree; patch applies cleanly
- **[Phase 7]** Subsystem: btrfs, IMPORTANT criticality
- **[Phase 8]** Failure mode: kernel buffer overflow, HIGH severity;
  benefit HIGH, risk VERY LOW
- **[Phase 9]** All 6 stable rules PASS

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

The commit fixes a real memory-safety bug: corrupt
`ROOT_REF`/`ROOT_BACKREF` metadata isn’t validated by tree-checker, and
`BTRFS_IOC_GET_SUBVOL_INFO` can copy more than 256 bytes into its fixed
`name[]` buffer (kernel overflow). The fix adds `check_root_ref()`
(mirroring existing `check_inode_ref()` logic) and tightens the ioctl
copy length. It’s small (~40 lines), maintainer-reviewed, applies
cleanly here, and the vulnerable code is present in this tree.

 fs/btrfs/ioctl.c        | 11 +++++------
 fs/btrfs/tree-checker.c | 35 +++++++++++++++++++++++++++++++++++
 2 files changed, 40 insertions(+), 6 deletions(-)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 2f1c5f5e2e725..3197f61d612b4 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -2046,7 +2046,6 @@ static int btrfs_ioctl_get_subvol_info(struct inode *inode, void __user *argp)
 	struct btrfs_root_ref *rref;
 	struct extent_buffer *leaf;
 	unsigned long item_off;
-	unsigned long item_len;
 	int slot;
 	int ret = 0;
 
@@ -2121,17 +2120,17 @@ static int btrfs_ioctl_get_subvol_info(struct inode *inode, void __user *argp)
 		btrfs_item_key_to_cpu(leaf, &key, slot);
 		if (key.objectid == subvol_info->treeid &&
 		    key.type == BTRFS_ROOT_BACKREF_KEY) {
+			u16 name_len;
+
 			subvol_info->parent_id = key.offset;
 
 			rref = btrfs_item_ptr(leaf, slot, struct btrfs_root_ref);
+			name_len = btrfs_root_ref_name_len(leaf, rref);
 			subvol_info->dirid = btrfs_root_ref_dirid(leaf, rref);
 
-			item_off = btrfs_item_ptr_offset(leaf, slot)
-					+ sizeof(struct btrfs_root_ref);
-			item_len = btrfs_item_size(leaf, slot)
-					- sizeof(struct btrfs_root_ref);
+			item_off = btrfs_item_ptr_offset(leaf, slot) + sizeof(*rref);
 			read_extent_buffer(leaf, subvol_info->name,
-					   item_off, item_len);
+					   item_off, name_len);
 		} else {
 			ret = -ENOENT;
 			goto out;
diff --git a/fs/btrfs/tree-checker.c b/fs/btrfs/tree-checker.c
index db7402836340a..97be0c1ed22f7 100644
--- a/fs/btrfs/tree-checker.c
+++ b/fs/btrfs/tree-checker.c
@@ -1288,6 +1288,37 @@ static int check_root_item(struct extent_buffer *leaf, struct btrfs_key *key,
 	return 0;
 }
 
+static int check_root_ref(struct extent_buffer *leaf, struct btrfs_key *key, int slot)
+{
+	struct btrfs_root_ref *rref;
+	u32 item_size = btrfs_item_size(leaf, slot);
+	u32 name_len;
+
+	if (unlikely(item_size <= sizeof(*rref))) {
+		generic_err(leaf, slot,
+			    "invalid root ref item size for key type %u, have %u expect > %zu",
+			    key->type, item_size, sizeof(*rref));
+		return -EUCLEAN;
+	}
+
+	rref = btrfs_item_ptr(leaf, slot, struct btrfs_root_ref);
+	name_len = btrfs_root_ref_name_len(leaf, rref);
+	if (unlikely(name_len > BTRFS_NAME_LEN)) {
+		generic_err(leaf, slot,
+			    "root ref name too long for key type %u, have %u max %u",
+			    key->type, name_len, BTRFS_NAME_LEN);
+		return -EUCLEAN;
+	}
+	if (unlikely(item_size != sizeof(*rref) + name_len)) {
+		generic_err(leaf, slot,
+			    "invalid root ref item size for key type %u, have %u expect %zu",
+			    key->type, item_size, sizeof(*rref) + name_len);
+		return -EUCLEAN;
+	}
+
+	return 0;
+}
+
 __printf(3,4)
 __cold
 static void extent_err(const struct extent_buffer *eb, int slot,
@@ -1965,6 +1996,10 @@ static enum btrfs_tree_block_status check_leaf_item(struct extent_buffer *leaf,
 	case BTRFS_ROOT_ITEM_KEY:
 		ret = check_root_item(leaf, key, slot);
 		break;
+	case BTRFS_ROOT_REF_KEY:
+	case BTRFS_ROOT_BACKREF_KEY:
+		ret = check_root_ref(leaf, key, slot);
+		break;
 	case BTRFS_EXTENT_ITEM_KEY:
 	case BTRFS_METADATA_ITEM_KEY:
 		ret = check_extent_item(leaf, key, slot, prev_key);
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-5.10] btrfs: fix reloc root cleanup in merge_reloc_roots()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (10 preceding siblings ...)
  2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.1] btrfs: tree-checker: validate names in ROOT_REF and ROOT_BACKREF Sasha Levin
@ 2026-08-31 13:25 ` Sasha Levin
  2026-08-31 13:26 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_filter() Sasha Levin
                   ` (3 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:25 UTC (permalink / raw)
  To: patches, stable
  Cc: Filipe Manana, Boris Burkov, David Sterba, Sasha Levin, clm,
	linux-btrfs, linux-kernel

From: Filipe Manana <fdmanana@suse.com>

[ Upstream commit b78fe9563e2d5ae47805f1e5dc722c91fd30e1f8 ]

If the root we got has zero root refs in its root item, we are resetting
the root's ->reloc_root without using barriers like we do everywhere else.
Sashiko complained about this while reviewing another patch, and it's
correct (see the Link tag below).

Also, we should not clear BTRFS_ROOT_DEAD_RELOC_TREE from the root unless
the root points to the reloc root we have.

Fix this by using clear_reloc_root(), which issues the memory barrier
after setting the root's ->reloc_root to NULL and before clearing the bit
BTRFS_ROOT_DEAD_RELOC_TREE from the root.

Link: https://sashiko.dev/#/patchset/cf84f1a217c719e25b6b69e4298dd7afd36c9427.1781194426.git.fdmanana%40suse.com
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: fix reloc root cleanup in
merge_reloc_roots()`

**Local tree:** Linux **6.18.44** (`v6.18.44-1-g2736c32da98b9`)

**Commit analyzed:** `b78fe9563e2d5ae47805f1e5dc722c91fd30e1f8` (not yet
merged into this checkout)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject Line
**Record:** `[btrfs]` `[fix]` — correct reloc-root cleanup in
`merge_reloc_roots()` when a reloc root has zero root refs.

### Step 1.2: Tags
**Record:**
- **Link:** Sashiko automated review (https://sashiko.dev/...)
- **Reviewed-by:** Boris Burkov `<boris@bur.io>`
- **Signed-off-by:** Filipe Manana, David Sterba
- No Fixes:, Reported-by:, Tested-by:, Cc: stable@vger.kernel.org
- Notable: found during code review (Sashiko), not a syzbot/user crash
  report for this specific path

### Step 1.3: Body Analysis
**Record:**
- **Bug:** In the zero-ref reloc-root branch of `merge_reloc_roots()`,
  `root->reloc_root` is cleared without the memory barrier used
  elsewhere; `BTRFS_ROOT_DEAD_RELOC_TREE` is cleared unconditionally
  even when `root->reloc_root != reloc_root`.
- **Symptom:** Incorrect synchronization with `have_reloc_root()` /
  `reloc_root_is_dead()`; can observe stale `reloc_root` pointers or
  wrong dead-tree state during relocation/balance.
- **Root cause:** Inconsistent barrier usage and misplaced `clear_bit()`
  outside the matching-reloc-root guard.
- **Fix approach:** Use `clear_reloc_root()` helper (sets NULL →
  `smp_wmb()` → `clear_bit()`), only when `root->reloc_root ==
  reloc_root`.

### Step 1.4: Hidden Bug Fix?
**Record:** No — explicitly described as a bug fix (barrier + logic
error).

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Inventory
**Record:**
- **Files:** `fs/btrfs/relocation.c` (+2 / -3)
- **Function:** `merge_reloc_roots()`
- **Scope:** Single-file, surgical fix in one error/cleanup branch

### Step 2.2: Code Flow Change
**Record:**

| Hunk | Before | After |
|------|--------|-------|
| Zero-ref cleanup branch | `root->reloc_root = NULL;
btrfs_put_root(reloc_root);` then unconditional
`clear_bit(DEAD_RELOC_TREE)` | `clear_reloc_root(root);
btrfs_put_root(reloc_root);` only inside `if (root->reloc_root ==
reloc_root)` |

**Affected path:** Relocation merge when
`btrfs_root_refs(&reloc_root->root_item) == 0` (dead/orphan reloc tree
cleanup during balance).

### Step 2.3: Bug Mechanism
**Record:**
- **Category:** Synchronization / logic correctness
- **Mechanism 1 (missing `smp_wmb()`):** Writers in
  `clean_dirty_subvols()` (lines 1474–1480) and
  `btrfs_update_reloc_root()` (lines 796–801) use `smp_wmb()` between
  NULL-ing `reloc_root` and clearing `BTRFS_ROOT_DEAD_RELOC_TREE`.
  `merge_reloc_roots()` did not, breaking pairing with
  `reloc_root_is_dead()`'s `smp_rmb()`.
- **Mechanism 2 (wrong `clear_bit` scope):** `clear_bit()` ran even when
  `root->reloc_root != reloc_root`, corrupting state for a root still
  associated with a different reloc root.

### Step 2.4: Fix Quality
**Record:** Fix is minimal and matches the established pattern in the
same file. Low regression risk. **Caveat:** depends on
`clear_reloc_root()` helper, which is **not present** in this tree (see
Phase 6).

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:** Buggy lines (1873–1879) blamed to `5d324e5159d9e` (6.18-rc8
era merge, Nov 2025). Barrier infrastructure (`reloc_root_is_dead`,
`BTRFS_ROOT_DEAD_RELOC_TREE`) introduced in same timeframe — relatively
new in 6.18.

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

### Step 3.3: Related Changes
**Record:**
- `60a23d4ea169e` — related fix in same function (root leak on
  unexpected reloc_root); already in this tree.
- Part of 2-patch series `[PATCH 0/2] btrfs: fix incorrect barrier usage
  in relocation`:
  - **1/2:** this commit
  - **2/2:** `btrfs: fix memory barrier order in reloc_root_is_dead()`
- `clear_reloc_root()` introduced in separate UAF-fix series (`[PATCH
  v2] btrfs: fix use-after-free on reloc root after error in
  insert_dirty_subvol()`); **not in this tree**.

### Step 3.4: Author Context
**Record:** Filipe Manana — active btrfs maintainer; multiple recent
`merge_reloc_roots()` fixes in this tree.

### Step 3.5: Dependencies
**Record:** Commit calls `clear_reloc_root()`, which does not exist in
6.18.44. **Not standalone as-is**, but trivially adaptable using the
inline pattern already in `clean_dirty_subvols()`:

```c
root->reloc_root = NULL;
smp_wmb();
clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
```

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original Discussion
**Record:**
- **b4 dig:** https://patch.msgid.link/50682caa6bbf69740c629a26ff6f19a72
  ce55e03.1781263239.git.fdmanana@suse.com
- **Series:** v1 only (2026-06-12)
- **Reviewer feedback:** Boris Burkov Reviewed-by on cover letter; David
  Sterba replied on patch 2/2; kernel test robot build-tested patch 2/2
- **Stable nomination:** None found in thread

### Step 4.2: Reviewers
**Record:** `linux-btrfs@vger.kernel.org`; Boris Burkov reviewed; David
Sterba (btrfs maintainer) engaged on patch 2/2.

### Step 4.3: Bug Report
**Record:** No syzbot/user crash report for this specific bug.
Identified by Sashiko during review of a related patch. Related UAF in
relocation (syzbot-reported) motivated the `clear_reloc_root()` helper
in a separate series.

### Step 4.4: Related Patches
**Record:** Patch 2/2 fixes read-side barrier ordering in
`reloc_root_is_dead()`. Ideally backported together for complete barrier
correctness, but patch 1/2 independently fixes a real write-side bug.

### Step 4.5: Stable List History
**Record:** No stable-list discussion found.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key Functions
**Record:** `merge_reloc_roots()`, `reloc_root_is_dead()`,
`have_reloc_root()`, `clear_reloc_root()` (upstream only)

### Step 5.2: Callers
**Record:** `merge_reloc_roots()` called from:
- `relocate_block_group()` (line 3653) — balance/relocation path
- Another relocation path (line 4198)

Both are btrfs balance/relocation operations, reachable via
`BTRFS_IOC_BALANCE` ioctl (privileged).

### Step 5.3: Callees
**Record:** `btrfs_get_fs_root()`, `btrfs_put_root()`, `clear_bit()`,
barrier primitives; interacts with refcounted `btrfs_root` objects.

### Step 5.4: Reachability
**Record:** Triggered during btrfs balance/relocation (admin/root
operation). Not every boot, but real production use (rebalancing, device
replacement). Unprivileged users cannot directly trigger, but corruption
from a privileged balance affects the whole filesystem.

### Step 5.5: Similar Patterns
**Record:** Correct barrier pattern exists in `clean_dirty_subvols()` at
lines 1474–1480; `merge_reloc_roots()` is the inconsistent outlier.

---

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

### Step 6.1: Buggy Code Present?
**Record:** **YES** — confirmed at lines 1873–1879:

```1873:1880:fs/btrfs/relocation.c
                        if (!IS_ERR(root)) {
                                if (root->reloc_root == reloc_root) {
                                        root->reloc_root = NULL;
                                        btrfs_put_root(reloc_root);
                                }
                                clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE,
                                          &root->state);
                                btrfs_put_root(root);
```

Barrier infrastructure (`BTRFS_ROOT_DEAD_RELOC_TREE`,
`reloc_root_is_dead`) also present since 6.18.

### Step 6.2: Backport Complications
**Record:** **Minor adaptation needed.** `clear_reloc_root()` does not
exist in this tree. Equivalent inline fix (matching
`clean_dirty_subvols()`) is straightforward. No conflicting refactors in
this area.

### Step 6.3: Related Fixes Already Present?
**Record:** `60a23d4ea169e` (root leak fix) is present. This
barrier/logic fix is **not** present. No duplicate fix found.

---

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: Subsystem
**Record:** **fs/btrfs** — IMPORTANT (filesystem, data integrity)

### Step 7.2: Activity
**Record:** Active — multiple recent `merge_reloc_roots()` fixes in
6.18.y.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who Is Affected
**Record:** Users running btrfs balance/relocation on 6.18.y kernels
with the `BTRFS_ROOT_DEAD_RELOC_TREE` barrier mechanism.

### Step 8.2: Trigger Conditions
**Record:** Balance/relocation reaching `merge_reloc_roots()` with a
reloc root whose root item has zero refs. Uncommon relative to normal
I/O, but standard admin workflow. Privileged trigger only.

### Step 8.3: Failure Mode Severity
**Record:**
- Stale `reloc_root` pointer observed after bit cleared → potential
  **UAF** or double-free (same class as syzbot-reported relocation UAF)
- Wrong `clear_bit` when `reloc_root` doesn't match → incorrect
  `have_reloc_root()` behavior
- **Severity: HIGH** (filesystem corruption / crash potential)

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents barrier/logic bug in relocation cleanup
- **Risk:** LOW — 3-line effective change, matches existing in-file
  pattern
- **Ratio:** Favorable

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence Summary

**FOR backport:**
- Real, verifiable bug in this tree (missing barrier + unconditional
  `clear_bit`)
- Same file already documents correct pattern (`clean_dirty_subvols`)
- Small, surgical fix reviewed by btrfs reviewers
- Same bug class as syzbot-reported relocation UAF (barrier
  inconsistency in reloc-root lifecycle)
- Barrier infrastructure is present in 6.18.44 — bug is live

**AGAINST backport:**
- No direct crash report for this exact path (review-found)
- Depends on `clear_reloc_root()` not in tree (needs minor backport
  adaptation)
- Patch 2/2 ideally accompanies for complete read-side fix
- Only affects balance/relocation (not hot path)

**Unresolved:** No runtime reproduction confirmed for this exact path;
impact inferred from code analysis and related UAF class.

### Step 9.2: Stable Rules Checklist
| Criterion | Result |
|-----------|--------|
| 1. Obviously correct and tested? | **PASS** — matches established in-
file pattern; reviewed by Boris Burkov; build-tested in series |
| 2. Fixes real bug affecting users? | **PASS** — barrier + logic error
in relocation cleanup |
| 3. Important issue? | **PASS** — potential UAF/corruption during
balance |
| 4. Small and contained? | **PASS** — 5 lines net in one function |
| 5. No new features/APIs? | **PASS** — correctness fix only |
| 6. Can apply to local tree? | **PASS** (with adaptation) — inline
`smp_wmb()` pattern substitutes for missing `clear_reloc_root()` |

### Step 9.3: Exception Category
**Record:** N/A — standard bug fix.

### Step 9.4: Decision Rationale
The buggy code exists in Linux 6.18.44. The fix corrects a memory-
ordering inconsistency and a logic error (`clear_bit` outside the
matching-reloc-root guard) in btrfs relocation cleanup — the same
synchronization design used elsewhere in `relocation.c`. While the patch
calls `clear_reloc_root()` which is not yet in this tree, the equivalent
inline fix is trivial and already demonstrated in
`clean_dirty_subvols()`. The fix is small, low-risk, and addresses a
HIGH-severity failure mode in filesystem code.

---

## Verification

- **[Phase 1]** Parsed subject, tags, body from provided commit message
- **[Phase 1]** Confirmed no syzbot Reported-by; Sashiko Link tag
  present
- **[Phase 2]** Read diff: +2/-3 in `merge_reloc_roots()` zero-ref
  branch
- **[Phase 2]** Read current buggy code at lines 1873–1879 in
  `fs/btrfs/relocation.c`
- **[Phase 2]** Read correct barrier pattern at lines 1474–1480 and
  796–801
- **[Phase 3]** `git describe HEAD`: v6.18.44
- **[Phase 3]** `git blame -L 1873,1880`: lines from 5d324e5159d9e (Nov
  2025)
- **[Phase 3]** `git log --grep="clear_reloc_root"`: no results in tree
- **[Phase 3]** `grep clear_reloc_root fs/btrfs/`: no matches — helper
  absent
- **[Phase 3]** `60a23d4ea169e` confirmed related prior fix already in
  tree
- **[Phase 3]** `git merge-base --is-ancestor b78fe95 HEAD`: exit 1 —
  commit not merged
- **[Phase 4]** `b4 dig -c b78fe95`: found lore URL, patch 1/2 of
  2-patch series
- **[Phase 4]** `b4 dig -a`: v1 only
- **[Phase 4]** `b4 dig -w`: linux-btrfs@vger.kernel.org CC'd
- **[Phase 4]** Read `/tmp/b78fe95.mbox`: cover letter, patch 2/2
  content, Boris Reviewed-by
- **[Phase 4]** spinics.net msg166109: confirmed `clear_reloc_root()`
  introduced in separate UAF series
- **[Phase 5]** `grep merge_reloc_roots(`: 2 call sites (lines 3653,
  4198)
- **[Phase 5]** Read `reloc_root_is_dead()` / `have_reloc_root()` at
  lines 246–274
- **[Phase 6]** Confirmed buggy code present in 6.18.44 checkout
- **[Phase 6]** Confirmed `clear_reloc_root()` absent; inline adaptation
  viable
- **[Phase 8]** Assessed severity from barrier pairing analysis and
  related UAF class (not directly reproduced)

**YES**The background search finished: **`clear_reloc_root` does not
appear anywhere in this 6.18.44 tree** — no commits introduce it under
`fs/btrfs/` or elsewhere.

That matches the earlier analysis: commit `b78fe95` calls a helper that
isn’t here yet (it lands in a separate UAF-fix series). A backport would
need a small adaptation — use the same inline pattern already in
`clean_dirty_subvols()` (`reloc_root = NULL` → `smp_wmb()` →
`clear_bit`) instead of calling `clear_reloc_root()`.

The verdict for this tree remains **YES**, with that minor backport
tweak.

 fs/btrfs/relocation.c | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index 851cef16d4017..6b0bf7eff9d3e 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -1910,11 +1910,10 @@ void merge_reloc_roots(struct reloc_control *rc)
 		} else {
 			if (!IS_ERR(root)) {
 				if (root->reloc_root == reloc_root) {
-					root->reloc_root = NULL;
+					clear_reloc_root(root);
+					/* Drop the ref for root->reloc_root. */
 					btrfs_put_root(reloc_root);
 				}
-				clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE,
-					  &root->state);
 				btrfs_put_root(root);
 			}
 
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_filter()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (11 preceding siblings ...)
  2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix reloc root cleanup in merge_reloc_roots() Sasha Levin
@ 2026-08-31 13:26 ` Sasha Levin
  2026-08-31 13:28 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_range_filter() Sasha Levin
                   ` (2 subsequent siblings)
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:26 UTC (permalink / raw)
  To: patches, stable
  Cc: ZhengYuan Huang, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: ZhengYuan Huang <gality369@gmail.com>

[ Upstream commit 6dde5221f608e0b548fcf43c68034496f1e58542 ]

[BUG]
Running btrfs balance with a usage filter (-dusage=N) can trigger a
null-ptr-deref when metadata corruption causes a chunk to have no
corresponding block group in the in-memory cache:

  KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077]
  RIP: 0010:chunk_usage_filter fs/btrfs/volumes.c:3874 [inline]
  RIP: 0010:should_balance_chunk fs/btrfs/volumes.c:4018 [inline]
  RIP: 0010:__btrfs_balance fs/btrfs/volumes.c:4172 [inline]
  RIP: 0010:btrfs_balance+0x2024/0x42b0 fs/btrfs/volumes.c:4604
  ...
  Call Trace:
    btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline]
    btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313
    vfs_ioctl fs/ioctl.c:51 [inline]
    ...

The bug is reproducible on current development branch.

[CAUSE]
Two separate data structures are involved:

1. The on-disk chunk tree, which records every chunk (logical address
   space region) and is iterated by __btrfs_balance().

2. The in-memory block group cache (fs_info->block_group_cache_tree),
   which is built at mount time by btrfs_read_block_groups() and holds
   a struct btrfs_block_group for each chunk. This cache is what the
   usage filter queries.

On a well-formed filesystem, these two are kept in 1:1 correspondence.
However, btrfs_read_block_groups() builds the cache from block group
items in the extent tree, not directly from the chunk tree. A corrupted
image can therefore contain a chunk item in the chunk tree whose
corresponding block group item is absent from the extent tree; that
chunk's block group is then never inserted into the in-memory cache.

When balance iterates the chunk tree and reaches such an orphaned chunk,
should_balance_chunk() calls chunk_usage_filter(), which queries the block
group cache:

  cache = btrfs_lookup_block_group(fs_info, chunk_offset);
  chunk_used = cache->used;   /* cache may be NULL */

btrfs_lookup_block_group() returns NULL silently when no cached entry
covers chunk_offset. chunk_usage_filter() does not check the return value,
so the immediately following dereference of cache->used triggers the crash.

[FIX]
Add a NULL check after btrfs_lookup_block_group() in chunk_usage_filter().
When the lookup fails, emit a btrfs_err() message identifying the
affected bytenr and return -EUCLEAN to indicate filesystem corruption.

Since chunk_usage_filter() now has an error path, change its return type
from bool to error pointer and 0 if the chunk passes the usage filter,
and 1 if it should be skipped.

Update should_balance_chunk() accordingly to propagate negative errors
from the usage filter.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: btrfs balance NULL deref in
`chunk_usage_filter()`

**Local tree:** `v6.18.44` (6.18.y stable)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: PARSE THE SUBJECT LINE
**Record:** `[btrfs: balance]` `[fix]` — Fix potential block-group
lookup failure in `chunk_usage_filter()` during btrfs balance with a
usage filter.

### Step 1.2: PARSE ALL COMMIT MESSAGE TAGS
**Record:**
- **Fixes:** — not present (expected for manual review)
- **Reported-by:** — none
- **Tested-by:** — none
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` (btrfs maintainer)
- **Acked-by:** — none
- **Link:** — none in committed message
- **Cc: stable@vger.kernel.org:** — absent from final commit message;
  present in v2 mailing-list submission (per web search)
- **Signed-off-by:** ZhengYuan Huang (author); David Sterba (maintainer)

Notable: maintainer reviewed and committed; author nominated stable in
patch series v2.

### Step 1.3: ANALYZE THE COMMIT BODY TEXT
**Record:**
- **Bug:** NULL pointer dereference in `chunk_usage_filter()` when
  running `btrfs balance` with `-dusage=N` on a filesystem where
  metadata corruption left a chunk in the chunk tree without a matching
  in-memory block group.
- **Symptom:** KASAN null-ptr-deref at `cache->used` (offset ~0x70),
  call chain through `should_balance_chunk()` → `__btrfs_balance()` →
  `btrfs_ioctl_balance()`.
- **Root cause:** `btrfs_lookup_block_group()` returns NULL when no
  cached block group covers the chunk offset; `chunk_usage_filter()`
  dereferences without checking.
- **Fix:** NULL check, `btrfs_err()` log, return `-EUCLEAN`; change
  `chunk_usage_filter()` and `should_balance_chunk()` to propagate
  errors; handle negative return in `__btrfs_balance()`.
- **Version info:** Bug reproducible on current development branch;
  underlying usage-filter code dates to 2012.

### Step 1.4: DETECT HIDDEN BUG FIXES
**Record:** Not disguised — explicitly a NULL pointer dereference fix.
The return-type refactor (`bool` → `int`) is required to propagate
`-EUCLEAN`, not cosmetic cleanup.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: INVENTORY THE CHANGES
**Record:**
- **File:** `fs/btrfs/volumes.c` only (~40 lines changed)
- **Functions modified:** `chunk_usage_filter()`,
  `should_balance_chunk()`, `__btrfs_balance()`
- **Scope:** Single-file surgical fix in btrfs balance filtering path

### Step 2.2: UNDERSTAND THE CODE FLOW CHANGE

**Hunk 1 — `chunk_usage_filter()`:**
- **Before:** `btrfs_lookup_block_group()` → immediate `cache->used`
  dereference; returns `bool`.
- **After:** NULL check with `unlikely(!cache)` → log + `-EUCLEAN`;
  returns `int` (negative=error, 0=pass filter, 1=skip chunk).

**Hunk 2 — `should_balance_chunk()`:**
- **Before:** `if (usage flag && chunk_usage_filter()) return false;`
- **After:** Calls filter, propagates `ret2 < 0`, treats `ret2` truthy
  as skip; return type `bool` → `int`.

**Hunk 3 — `__btrfs_balance()`:**
- **Before:** `ret = should_balance_chunk(...)` then `if (!ret) goto
  loop` with no error handling.
- **After:** `if (ret < 0) { unlock; goto error; }` before the skip
  check.

### Step 2.3: IDENTIFY THE BUG MECHANISM
**Record:** **Category:** NULL pointer dereference (memory safety).
**Mechanism:** On corrupted metadata, chunk tree iteration reaches an
orphaned chunk; block group cache lookup returns NULL; unchecked
dereference of `cache->used` crashes the kernel during balance ioctl.

### Step 2.4: ASSESS THE FIX QUALITY
**Record:**
- Fix is obviously correct — mirrors existing btrfs patterns (e.g.
  `scrub.c` checks `if (!cache) goto skip`).
- Minimal, focused change; no unrelated edits.
- Low regression risk: only affects the usage-filter error path on
  corrupted FS; normal filesystems unchanged.
- Minor note: `chunk_usage_range_filter()` has the same unchecked
  dereference but is a separate code path (usage-range filter, not
  `-dusage`); not addressed by this commit.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: BLAME THE CHANGED LINES
**Record:** `chunk_usage_filter()` introduced in `5ce5b3c0916ba`
("Btrfs: usage filter", Ilya Dryomov, 2012-01-16). The unchecked
`cache->used` dereference (`bf38be65f3703d`, David Sterba, 2019) has
been present for years. Bug is long-standing, not recently introduced.

### Step 3.2: FOLLOW THE FIXES: TAG
**Record:** No `Fixes:` tag in commit message. N/A.

### Step 3.3: CHECK FILE HISTORY FOR RELATED CHANGES
**Record:** Recent `volumes.c` changes include `c19830db30a09` ("replace
BUG() with error handling in __btrfs_balance()") — complementary error-
path hardening, not a prerequisite. No duplicate fix for this NULL deref
found in this tree. Patch is part of a larger series (v2/v3: also fixes
`chunk_usage_range_filter` and mount-time
`check_chunk_block_group_mappings()`), but this commit is self-contained
for the `-dusage` path.

### Step 3.4: CHECK THE AUTHOR'S OTHER COMMITS
**Record:** ZhengYuan Huang has btrfs contributions in this tree (e.g.
`850de3d87f472` tree-checker fix). David Sterba is btrfs maintainer and
committed this patch.

### Step 3.5: CHECK FOR DEPENDENT/PREREQUISITE COMMITS
**Record:** No prerequisites. All modified functions and
`btrfs_lookup_block_group()` exist in 6.18.44. The `error:` path in
`__btrfs_balance()` already exists and returns errors to userspace.
Applies standalone.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: FIND THE ORIGINAL PATCH DISCUSSION
**Record:** `b4 dig -c HEAD` failed (commit not in local tree). Web
search found:
- [PATCH v2 1/3] on spinics/lore — subject matches, includes `Cc:
  stable@vger.kernel.org`
- [PATCH v3 1/4] on linux-btrfs list — evolved version with `unlikely()`
  annotation
- Series cover (v2 0/3): describes two balance NULL derefs plus mount-
  time verification fix

Reviewer feedback (v2): David Sterba noted `bool ret = true`
inconsistent with changed return type — addressed in committed version
(`int ret = 1`).

### Step 4.2: CHECK WHO REVIEWED THE PATCH
**Record:** CC'd to `linux-btrfs@`, `linux-kernel@`, David Sterba.
**Reviewed-by** and **Signed-off-by** David Sterba (maintainer).

### Step 4.3: SEARCH FOR THE BUG REPORT
**Record:** No external bug report or syzbot link. Reproducibility
claimed by author with KASAN stack trace in commit message. Self-
contained reproduction: corrupted btrfs image + `btrfs balance` with
usage filter.

### Step 4.4: CHECK FOR RELATED PATCHES AND SERIES
**Record:** Part of 3–4 patch series fixing:
1. `chunk_usage_filter()` NULL deref (this commit)
2. `chunk_usage_range_filter()` NULL deref (separate patch)
3. `check_chunk_block_group_mappings()` iteration bug (separate patch)

This commit stands alone for the `-dusage` crash.

### Step 4.5: CHECK STABLE MAILING LIST HISTORY
**Record:** Author explicitly nominated `Cc: stable@vger.kernel.org` in
v2 submission. No evidence of rejection from stable maintainers found.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: IDENTIFY KEY FUNCTIONS IN THE DIFF
**Record:** `chunk_usage_filter()`, `should_balance_chunk()`,
`__btrfs_balance()`

### Step 5.2: TRACE CALLERS
**Record:**
- `chunk_usage_filter()` ← `should_balance_chunk()` (when
  `BTRFS_BALANCE_ARGS_USAGE` set)
- `should_balance_chunk()` ← `__btrfs_balance()` (chunk tree iteration
  loop)
- `__btrfs_balance()` ← `btrfs_balance()` ← `btrfs_ioctl_balance()` ←
  `btrfs_ioctl()` ← `vfs_ioctl()`

Balance is triggered via `BTRFS_IOC_BALANCE` ioctl, requiring
`CAP_SYS_ADMIN`.

### Step 5.3: TRACE CALLEES
**Record:** `btrfs_lookup_block_group()` →
`block_group_cache_tree_search()` (returns NULL when no matching entry);
`btrfs_put_block_group()`, `btrfs_err()`, `mult_perc()`.

### Step 5.4: FOLLOW THE CALL CHAIN
**Record:** Userspace admin runs `btrfs balance start -dusage=N` → ioctl
→ balance iterates chunk tree → hits orphaned chunk → NULL deref.
**Reachable from userspace** (with admin capability) on corrupted
filesystems.

### Step 5.5: SEARCH FOR SIMILAR PATTERNS
**Record:** `scrub.c:2690-2695` already handles NULL from
`btrfs_lookup_block_group()` with `if (!cache) goto skip`.
`check_chunk_block_group_mappings()` in `block-group.c:2339-2346`
returns `-EUCLEAN` on missing block group. This fix aligns balance with
established btrfs corruption-handling patterns.

---

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

### Step 6.1: DOES THE BUGGY CODE EXIST IN THIS TREE?
**Record:** **YES.** In 6.18.44 at `fs/btrfs/volumes.c:3997-3998`:

```3997:3998:fs/btrfs/volumes.c
        cache = btrfs_lookup_block_group(fs_info, chunk_offset);
        chunk_used = cache->used;
```

No NULL check. Bug present since 2012 in this code path.

### Step 6.2: CHECK FOR BACKPORT COMPLICATIONS
**Record:** Expected **clean apply**. Code structure matches the diff
base. `__btrfs_balance()` already has `error:` label at line 4384.
Recent `volumes.c` churn is unrelated to these functions.

### Step 6.3: CHECK IF RELATED FIXES ARE ALREADY HERE
**Record:** Fix not present (no "has no corresponding block group" error
string in tree). `check_chunk_block_group_mappings()` exists but has a
known iteration limitation (separate series patch); does not prevent
this balance crash.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

### Step 7.1: IDENTIFY THE SUBSYSTEM AND ITS CRITICALITY
**Record:** **Subsystem:** btrfs filesystem (`fs/btrfs/`).
**Criticality:** IMPORTANT — filesystem code; balance is an
administrative maintenance operation; crash affects system stability.

### Step 7.2: ASSESS SUBSYSTEM ACTIVITY
**Record:** btrfs is actively maintained in 6.18.y with regular fixes
(error handling, corruption detection). Long-standing balance filter
code with a latent NULL deref.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: DETERMINE WHO IS AFFECTED
**Record:** btrfs users running balance with usage filter (`-dusage`,
`-musage`, `-susage`) on filesystems with chunk/block-group metadata
inconsistency. Config-specific (btrfs + balance + usage filter) but
affects real admin workflows on damaged images.

### Step 8.2: DETERMINE THE TRIGGER CONDITIONS
**Record:**
- **Trigger:** Metadata corruption (chunk in chunk tree, no block group
  in cache) + balance with usage filter
- **Likelihood:** Low in healthy systems; realistic on
  corrupted/recovered images
- **Privilege:** Requires `CAP_SYS_ADMIN` — not unprivileged attack, but
  admin shouldn't kernel-panic the system

### Step 8.3: DETERMINE THE FAILURE MODE SEVERITY
**Record:** **NULL pointer dereference → kernel oops/panic.**
**Severity: CRITICAL** (system crash during filesystem maintenance).

### Step 8.4: CALCULATE RISK-BENEFIT RATIO
**Record:**
- **Benefit:** HIGH — prevents kernel crash; returns `-EUCLEAN` so admin
  gets actionable corruption error
- **Risk:** LOW — ~40 lines, error-path only, maintainer-reviewed,
  matches existing patterns
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: COMPILE THE EVIDENCE

**FOR backport:**
- Real, reproducible NULL pointer dereference
- Kernel crash (CRITICAL severity)
- Buggy code confirmed present in 6.18.44
- Long-standing bug (since 2012)
- Small, single-file fix
- Reviewed and committed by btrfs maintainer (David Sterba)
- Author nominated for stable
- Aligns with btrfs corruption-handling conventions (`-EUCLEAN`)
- No prerequisites; applies standalone
- Prevents crash that mount-time checks may not catch

**AGAINST backport:**
- Requires filesystem corruption to trigger (mitigated: btrfs routinely
  hardens corruption paths)
- Only affects usage-filter balance path, not all balance operations
- `chunk_usage_range_filter()` has same latent bug (separate fix;
  doesn't invalidate this one)
- Requires admin capability (not a general security CVE, but still a
  stability bug)

**Unresolved:** No syzbot or user bugzilla report; reproduction relies
on author's KASAN trace.

### Step 9.2: STABLE RULES CHECKLIST
1. Obviously correct and tested? **PASS** — NULL check + error
   propagation; maintainer reviewed
2. Fixes a real bug affecting users? **PASS** — NULL deref on balance
   with corrupted FS
3. Important issue? **PASS** — kernel oops/panic (CRITICAL)
4. Small and contained? **PASS** — one file, ~40 lines
5. No new features or APIs? **PASS** — defensive error handling only
6. Can apply to local tree? **PASS** — code exists, clean apply expected

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

### Step 9.4: DECISION RATIONALE

For **6.18.44**, this commit fixes a confirmed NULL pointer dereference
in a reachable admin code path on corrupted btrfs filesystems. The fix
is minimal, maintainer-reviewed, and converts a kernel crash into a
proper `-EUCLEAN` error — consistent with how btrfs handles metadata
inconsistency elsewhere. The bug has existed since 2012 and is present
in this tree today.

---

## Verification

- **[Phase 1]** Parsed subject, tags, body; found Reviewed-by/Signed-
  off-by David Sterba
- **[Phase 1]** No Fixes:/Reported-by:/Link: tags in commit message
- **[Phase 2]** Diff analysis: NULL check in `chunk_usage_filter()`,
  error propagation through `should_balance_chunk()` and
  `__btrfs_balance()`
- **[Phase 2]** Confirmed `chunk_usage_range_filter()` at lines
  3968-3969 has same unchecked pattern (not fixed by this commit)
- **[Phase 3]** `git describe HEAD` → `v6.18.44`; `make kernelversion` →
  `6.18.44`
- **[Phase 3]** `git blame` lines 3990-4012: function from 2012
  (`5ce5b3c0916ba`), unchecked deref from 2019
- **[Phase 3]** `git show 5ce5b3c0916ba` — introduced usage filter
- **[Phase 3]** `git log --oneline -20 -- fs/btrfs/volumes.c` — no
  duplicate fix found
- **[Phase 3]** No prerequisite commits identified
- **[Phase 4]** `b4 dig -c HEAD` — failed (commit not in tree)
- **[Phase 4]** Web search found v2/v3 lore submissions with `Cc:
  stable@vger.kernel.org`
- **[Phase 4]** Reviewer feedback on bool/int mismatch documented in v2
  thread
- **[Phase 5]** Call chain verified: ioctl → balance →
  `should_balance_chunk()` → `chunk_usage_filter()`
- **[Phase 5]** `btrfs_ioctl_balance()` requires `CAP_SYS_ADMIN`
  (ioctl.c:3490)
- **[Phase 5]** `btrfs_lookup_block_group()` returns NULL when no match
  (block-group.c:230-264)
- **[Phase 5]** `scrub.c:2694` — precedent for NULL check after lookup
- **[Phase 6]** Buggy code confirmed at volumes.c:3997-3998 in 6.18.44
- **[Phase 6]** `grep "has no corresponding block group"` — fix not yet
  applied
- **[Phase 6]** `check_chunk_block_group_mappings()` exists at block-
  group.c:2321 but has separate iteration limitation
- **[Phase 8]** Failure mode: NULL deref → kernel oops; severity
  CRITICAL

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

- **Related commits search** — `git log --grep` turned up older btrfs
  balance work (e.g. balance vs. unused block group deletion), but
  nothing that already fixes this NULL deref in 6.18.44.
- **b4 dig** — Failed because the commit isn’t in this checkout; the
  lore context came from web search instead.
- **Author search** — ZhengYuan Huang has other btrfs fixes in-tree
  (e.g. tree-checker), which fits a focused corruption-handling fix
  reviewed by David Sterba.

**Verdict for 6.18.44: YES** — small, maintainer-reviewed fix for a real
NULL deref on corrupted btrfs during `balance` with a usage filter
(`-dusage=N`).

 fs/btrfs/volumes.c | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 863f69b9cf14b..ff67f5ecacb2e 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3987,14 +3987,19 @@ static bool chunk_usage_range_filter(struct btrfs_fs_info *fs_info, u64 chunk_of
 	return ret;
 }
 
-static bool chunk_usage_filter(struct btrfs_fs_info *fs_info, u64 chunk_offset,
-			       struct btrfs_balance_args *bargs)
+static int chunk_usage_filter(struct btrfs_fs_info *fs_info, u64 chunk_offset,
+			      struct btrfs_balance_args *bargs)
 {
 	struct btrfs_block_group *cache;
 	u64 chunk_used, user_thresh;
-	bool ret = true;
+	int ret = 1;
 
 	cache = btrfs_lookup_block_group(fs_info, chunk_offset);
+	if (unlikely(!cache)) {
+		btrfs_err(fs_info, "balance: chunk at bytenr %llu has no corresponding block group",
+			  chunk_offset);
+		return -EUCLEAN;
+	}
 	chunk_used = cache->used;
 
 	if (bargs->usage_min == 0)
@@ -4005,7 +4010,7 @@ static bool chunk_usage_filter(struct btrfs_fs_info *fs_info, u64 chunk_offset,
 		user_thresh = mult_perc(cache->length, bargs->usage);
 
 	if (chunk_used < user_thresh)
-		ret = false;
+		ret = 0;
 
 	btrfs_put_block_group(cache);
 	return ret;
@@ -4110,8 +4115,8 @@ static bool chunk_soft_convert_filter(u64 chunk_type, struct btrfs_balance_args
 	return false;
 }
 
-static bool should_balance_chunk(struct extent_buffer *leaf, struct btrfs_chunk *chunk,
-				 u64 chunk_offset)
+static int should_balance_chunk(struct extent_buffer *leaf, struct btrfs_chunk *chunk,
+				u64 chunk_offset)
 {
 	struct btrfs_fs_info *fs_info = leaf->fs_info;
 	struct btrfs_balance_control *bctl = fs_info->balance_ctl;
@@ -4138,9 +4143,14 @@ static bool should_balance_chunk(struct extent_buffer *leaf, struct btrfs_chunk
 	}
 
 	/* usage filter */
-	if ((bargs->flags & BTRFS_BALANCE_ARGS_USAGE) &&
-	    chunk_usage_filter(fs_info, chunk_offset, bargs)) {
-		return false;
+	if (bargs->flags & BTRFS_BALANCE_ARGS_USAGE) {
+		int ret2;
+
+		ret2 = chunk_usage_filter(fs_info, chunk_offset, bargs);
+		if (ret2 < 0)
+			return ret2;
+		if (ret2)
+			return false;
 	} else if ((bargs->flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
 	    chunk_usage_range_filter(fs_info, chunk_offset, bargs)) {
 		return false;
@@ -4302,6 +4312,10 @@ static int __btrfs_balance(struct btrfs_fs_info *fs_info)
 		ret = should_balance_chunk(leaf, chunk, found_key.offset);
 
 		btrfs_release_path(path);
+		if (ret < 0) {
+			mutex_unlock(&fs_info->reclaim_bgs_lock);
+			goto error;
+		}
 		if (!ret) {
 			mutex_unlock(&fs_info->reclaim_bgs_lock);
 			goto loop;
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_range_filter()
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (12 preceding siblings ...)
  2026-08-31 13:26 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_filter() Sasha Levin
@ 2026-08-31 13:28 ` Sasha Levin
  2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use on-disk uuid for s_uuid in temp_fsid mounts Sasha Levin
  2026-08-31 13:31 ` [PATCH AUTOSEL 6.18] btrfs: zoned: always set data_relocation_bg Sasha Levin
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:28 UTC (permalink / raw)
  To: patches, stable
  Cc: ZhengYuan Huang, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: ZhengYuan Huang <gality369@gmail.com>

[ Upstream commit 7a308f6d29cc689ceaf313b9ebdf68099f50e452 ]

[BUG]
Running btrfs balance with a usage range filter (-dusage=min..max) can
trigger a null-ptr-deref when metadata corruption causes a chunk to have
no corresponding block group in the in-memory cache:

  KASAN: null-ptr-deref in range [0x0000000000000070-0x0000000000000077]
  RIP: 0010:chunk_usage_range_filter fs/btrfs/volumes.c:3845 [inline]
  RIP: 0010:should_balance_chunk fs/btrfs/volumes.c:4031 [inline]
  RIP: 0010:__btrfs_balance fs/btrfs/volumes.c:4182 [inline]
  RIP: 0010:btrfs_balance+0x249e/0x4320 fs/btrfs/volumes.c:4618
  ...
  Call Trace:
    btrfs_ioctl_balance fs/btrfs/ioctl.c:3577 [inline]
    btrfs_ioctl+0x25cf/0x5b90 fs/btrfs/ioctl.c:5313
    vfs_ioctl fs/ioctl.c:51 [inline]
    ...

The bug is reproducible on recent development branch.

[CAUSE]
Two separate data structures are involved:

1. The on-disk chunk tree, which records every chunk (logical address
   space region) and is iterated by __btrfs_balance().

2. The in-memory block group cache (fs_info->block_group_cache_tree),
   which is built at mount time by btrfs_read_block_groups() and holds
   a struct btrfs_block_group for each chunk. This cache is what the
   usage range filter queries.

On a well-formed filesystem, these two are kept in 1:1 correspondence.
However, btrfs_read_block_groups() builds the cache from block group
items in the extent tree, not directly from the chunk tree. A corrupted
image can therefore contain a chunk item in the chunk tree whose
corresponding block group item is absent from the extent tree; that
chunk's block group is then never inserted into the in-memory cache.

When balance iterates the chunk tree and reaches such an orphaned chunk,
should_balance_chunk() calls chunk_usage_range_filter(), which queries
the block group cache:

  cache = btrfs_lookup_block_group(fs_info, chunk_offset);
  chunk_used = cache->used;   /* cache may be NULL */

btrfs_lookup_block_group() returns NULL silently when no cached entry
covers chunk_offset. chunk_usage_range_filter() does not check the return
value, so the immediately following dereference of cache->used triggers
the crash.

[FIX]
Add a NULL check after btrfs_lookup_block_group() in
chunk_usage_range_filter(). When the lookup fails, emit a btrfs_err()
message identifying the affected bytenr and return -EUCLEAN to indicate
filesystem corruption.

Since chunk_usage_range_filter() now has an error path, change its
return type from bool to error pointer, return 0 if the chunk matches
the usage range, and 1 if it should be filtered out.

Signed-off-by: ZhengYuan Huang <gality369@gmail.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: balance: fix potential bg lookup
failure in chunk_usage_range_filter()`

**Local tree:** `linux-6.18.y` at `v6.18.44` (kernel 6.18.44)

---

## PHASE 1: COMMIT MESSAGE FORENSICS

### Step 1.1: Subject Line
**Record:** `[btrfs/balance]` **`fix`** — NULL block-group lookup in
`chunk_usage_range_filter()` during balance with usage-range filter
(`-dusage=min..max`).

### Step 1.2: Commit Message Tags
**Record:**
- **Fixes:** — not present (expected for manual review)
- **Reported-by:** — none
- **Tested-by:** — none
- **Reviewed-by:** David Sterba `<dsterba@suse.com>` (btrfs maintainer)
- **Acked-by:** — none
- **Link:** — none
- **Cc: stable:** — none (expected)
- **Signed-off-by:** ZhengYuan Huang `<gality369@gmail.com>`; David
  Sterba (committer)
- **Notable:** Reviewed and committed by btrfs maintainer; KASAN stack
  trace in body

### Step 1.3: Body Analysis
**Record:**
- **Bug:** NULL pointer dereference in `chunk_usage_range_filter()` when
  running `btrfs balance` with `BTRFS_BALANCE_ARGS_USAGE_RANGE` on a
  corrupted filesystem where a chunk exists in the chunk tree but has no
  matching block group in the in-memory cache.
- **Symptom:** KASAN null-ptr-deref at `cache->used` (offset ~0x70 into
  `struct btrfs_block_group`), reachable via `btrfs_ioctl_balance` →
  `btrfs_balance` → `__btrfs_balance` → `should_balance_chunk`.
- **Root cause:** `btrfs_lookup_block_group()` can return NULL; caller
  dereferences without checking.
- **Fix:** NULL check, `btrfs_err()` log, return `-EUCLEAN`; change
  return type from `bool` to `int` for error propagation.
- **Version info:** Reproducible on recent development branch; no
  specific kernel version cited.

### Step 1.4: Hidden Bug Fix Detection
**Record:** Not disguised — explicitly labeled `[BUG]` with KASAN trace.
Clear NULL-dereference fix.

---

## PHASE 2: DIFF ANALYSIS

### Step 2.1: Change Inventory
**Record:**
- **Files:** `fs/btrfs/volumes.c` only (+17 / -7 lines)
- **Functions modified:** `chunk_usage_range_filter()`,
  `should_balance_chunk()` (usage-range branch only)
- **Scope:** Single-file surgical fix

### Step 2.2: Code Flow Changes
**Record:**
- **Hunk 1 (`chunk_usage_range_filter`):** Before: lookup block group,
  unconditionally dereference `cache->used`. After: check
  `unlikely(!cache)`, log error, return `-EUCLEAN`; otherwise same logic
  with `int` return (0 = match filter, 1 = filter out).
- **Hunk 2 (`should_balance_chunk`):** Before: inline bool call, filter
  out if true. After: call filter, propagate negative errors (`return
  ret2`), filter out if positive return.

### Step 2.3: Bug Mechanism
**Record:** **Category:** NULL pointer dereference (memory safety).
**Mechanism:** Missing NULL check after `btrfs_lookup_block_group()` on
a corruption path where chunk-tree and block-group cache are
inconsistent.

### Step 2.4: Fix Quality
**Record:** Fix is obviously correct and minimal. Matches the pattern
already applied to `chunk_usage_filter()` in prerequisite commit
`6dde5221f608e`. Low regression risk — only affects error path on
corrupted metadata. `btrfs_put_block_group()` still called on success
path only.

---

## PHASE 3: GIT HISTORY INVESTIGATION

### Step 3.1: Blame
**Record:** Buggy NULL-deref line (`chunk_used = cache->used` without
check) introduced in `bc3094673f22d` (David Sterba, Oct 2015) — "btrfs:
extend balance filter usage to take minimum and maximum". Present in
this tree since 2015.

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

### Step 3.3: Related File History
**Record:** Part of a 3-commit series by ZhengYuan Huang (Mar 25, 2026):
1. `6dde5221f608e` — fix `chunk_usage_filter()` + change
   `should_balance_chunk()` to `int` + add `ret < 0` handling in
   `__btrfs_balance`
2. `7a308f6d29cc6` — **this commit** — fix `chunk_usage_range_filter()`
3. `18d32b0013efb` — fix `btrfs_may_alloc_data_chunk()`

None of these three are in `linux-6.18.y` yet.

### Step 3.4: Author Context
**Record:** ZhengYuan Huang is a btrfs contributor (other fixes in tree-
checker/root-item validation). David Sterba (maintainer) reviewed and
committed all three.

### Step 3.5: Dependencies
**Record:** **Prerequisite:** `6dde5221f608e` is required:
- Changes `should_balance_chunk()` from `bool` to `int` and adds `if
  (ret < 0) goto error` in `__btrfs_balance`
- Without it, `-EUCLEAN` propagation from this commit is broken
- Verified: `6dde5221` applies cleanly to `v6.18.44`; `7a308f6` fails
  alone but applies cleanly after `6dde5221`

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

### Step 4.1: Original Discussion
**Record:** `b4 dig -c 7a308f6d29cc6` — **no match found** on
lore.kernel.org. Lore web search blocked by bot protection. Cannot
verify mailing-list discussion or stable nominations.

### Step 4.2: Reviewers
**Record:** David Sterba (btrfs maintainer) — Reviewed-by and Signed-
off-by. Sufficient subsystem review.

### Step 4.3: Bug Report
**Record:** KASAN trace in commit message only. No syzbot, bugzilla, or
user reports. Author states reproducible on development branch.

### Step 4.4: Related Patches
**Record:** Sibling commits `6dde5221` and `18d32b0013efb` fix the same
class of bug in adjacent balance code paths. Ideally backported as a
series; this commit is not standalone for clean apply.

### Step 4.5: Stable List History
**Record:** Not searched successfully (lore inaccessible). No evidence
found of prior stable discussion.

---

## PHASE 5: CODE SEMANTIC ANALYSIS

### Step 5.1: Key Functions
**Record:** `chunk_usage_range_filter()`, `should_balance_chunk()`,
callers: `__btrfs_balance()`, `btrfs_balance()`,
`btrfs_ioctl_balance()`.

### Step 5.2: Callers
**Record:** `should_balance_chunk()` called from `__btrfs_balance()`
chunk-tree iteration loop (every balance operation per chunk).
`btrfs_ioctl_balance()` requires `CAP_SYS_ADMIN`.

### Step 5.3: Callees
**Record:** `btrfs_lookup_block_group()` →
`block_group_cache_tree_search()` — returns NULL when no cached block
group covers the bytenr. `btrfs_put_block_group()`, `mult_perc()`,
`btrfs_err()`.

### Step 5.4: Reachability
**Record:** Trigger: admin runs `btrfs balance` with usage-range filter
(`BTRFS_BALANCE_ARGS_USAGE_RANGE`) on filesystem with chunk/block-group
metadata inconsistency. Reachable from `ioctl()` syscall path. Requires
corruption + specific filter flag; not everyday path but real and
reproducible.

### Step 5.5: Similar Patterns
**Record:** Same missing-NULL-check pattern exists in:
- `chunk_usage_filter()` (fixed by `6dde5221`)
- `btrfs_may_alloc_data_chunk()` with `ASSERT(cache)` only (fixed by
  `18d32b0013efb`)

---

## PHASE 6: CROSS-REFERENCE WITH LOCAL TREE (6.18.44)

### Step 6.1: Buggy Code Present?
**Record:** **YES.** At lines 3968–3969 in `fs/btrfs/volumes.c`:

```3968:3969:fs/btrfs/volumes.c
        cache = btrfs_lookup_block_group(fs_info, chunk_offset);
        chunk_used = cache->used;
```

No NULL check. `BTRFS_BALANCE_ARGS_USAGE_RANGE` support present since
2015 (`bc3094673f22d` is ancestor).

### Step 6.2: Backport Complications
**Record:** Does not apply cleanly alone (`git apply --check` fails at
line 4158). Applies cleanly after prerequisite `6dde5221`. Minor
adaptation needed only if backported without prerequisite (not
recommended).

### Step 6.3: Related Fixes Already Present?
**Record:** **NO.** String `"has no corresponding block group"` not in
tree. `6dde5221` and `18d32b0013efb` also absent.

---

## PHASE 7: SUBSYSTEM CONTEXT

### Step 7.1: Subsystem Criticality
**Record:** **fs/btrfs** — IMPORTANT. Btrfs is widely deployed; balance
is an admin maintenance operation on live filesystems.

### Step 7.2: Subsystem Activity
**Record:** Actively maintained. Recent balance-related work includes
`f963e0128b180` (bool conversion, Apr 2025) and `c19830db30a09` (BUG() →
error handling in `__btrfs_balance`).

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

### Step 8.1: Who Is Affected
**Record:** Btrfs users running balance with usage-range filter on
corrupted or inconsistently-metadata filesystems. Admin-only trigger
(`CAP_SYS_ADMIN`).

### Step 8.2: Trigger Conditions
**Record:** Corrupted chunk tree / extent tree inconsistency + balance
with `-dusage=min..max` range syntax. Uncommon but plausible during
recovery operations on damaged filesystems — exactly when robust error
handling matters most.

### Step 8.3: Failure Mode Severity
**Record:** **CRITICAL** — kernel NULL pointer dereference / oops.
System crash during admin maintenance on a filesystem that may already
be in distress.

### Step 8.4: Risk-Benefit
**Record:**
- **Benefit:** HIGH — prevents kernel crash; returns `-EUCLEAN` for
  corruption (correct btrfs behavior)
- **Risk:** LOW — ~17 lines, well-reviewed, mirrors already-accepted
  pattern from sibling commit
- **Ratio:** Strongly favorable, provided prerequisite `6dde5221` is
  included

---

## PHASE 9: FINAL SYNTHESIS

### Step 9.1: Evidence Summary

**FOR backport:**
- Real, reproducible NULL pointer dereference with KASAN trace
- Bug present in 6.18.44 since 2015
- Small, surgical, maintainer-reviewed fix
- Prevents kernel oops during balance on corrupted filesystems
- Matches established btrfs corruption-handling pattern (`-EUCLEAN`)

**AGAINST backport:**
- Requires prerequisite commit `6dde5221` for clean apply and correct
  error propagation
- Trigger needs metadata corruption + usage-range filter (less common
  than simple `-dusage=N`)
- No syzbot or user bug reports
- Admin-only path (not unprivileged attack surface)

**Unresolved:**
- No lore discussion found via b4
- Third sibling commit `18d32b0013efb` addresses another balance NULL-
  deref path; ideally included in same backport series

### Step 9.2: Stable Rules Checklist
1. Obviously correct and tested? **PASS** — NULL check after documented-
   NULL-returning function; maintainer reviewed; KASAN-reproduced
2. Fixes real bug affecting users? **PASS** — kernel crash on balance
   ioctl
3. Important issue? **PASS** — CRITICAL (kernel oops)
4. Small and contained? **PASS** — 17 lines, one file
5. No new features/APIs? **PASS** — bug fix only
6. Can apply to local tree? **PASS** (with prerequisite `6dde5221`;
   verified via `git apply`)

### Step 9.3: Exception Categories
**Record:** N/A — standard bug fix, not device ID/quirk/docs/build fix.

### Step 9.4: Decision Rationale

This commit fixes a long-standing NULL pointer dereference in btrfs
balance code that exists in the 6.18.44 tree. The fix is minimal,
obviously correct, and reviewed by the btrfs maintainer. While the
trigger requires filesystem corruption and the usage-range filter,
crashing the kernel during recovery/maintenance on a damaged btrfs
filesystem is exactly the kind of issue stable trees should fix.

The prerequisite commit `6dde5221f608e` must accompany this backport (it
applies cleanly to 6.18.44 and is required for proper `-EUCLEAN`
propagation). Commit `18d32b0013efb` fixes the same bug class in
`btrfs_may_alloc_data_chunk()` and should also be considered for a
complete fix.

---

## Verification

- **[Phase 1]** Parsed commit message: BUG/CAUSE/FIX sections, Reviewed-
  by David Sterba, no Fixes:/Reported-by:/Link: tags
- **[Phase 2]** Diff analysis: 17 lines in `fs/btrfs/volumes.c`; NULL
  check + int return type + error propagation in `should_balance_chunk`
- **[Phase 3]** `git blame`: buggy code from `bc3094673f22d` (Oct 2015)
- **[Phase 3]** `git log -S`: found series `6dde5221`, `7a308f6`,
  `18d32b0013efb` on master; none in 6.18.44
- **[Phase 3]** `git merge-base --is-ancestor`: `bc3094673f22d` present;
  `6dde5221` NOT present
- **[Phase 3]** `git apply --check 6dde5221`: applies cleanly to
  v6.18.44
- **[Phase 3]** `git apply --check 7a308f6` alone: **FAILS** at line
  4158
- **[Phase 3]** `git apply 6dde5221` then `git apply --check 7a308f6`:
  **SUCCEEDS**
- **[Phase 4]** `b4 dig -c 7a308f6d29cc6`: no lore match found
- **[Phase 4]** lore.kernel.org fetch: blocked by bot protection
- **[Phase 5]** Call chain verified: `btrfs_ioctl_balance`
  (CAP_SYS_ADMIN) → `btrfs_balance` → `__btrfs_balance` →
  `should_balance_chunk` → `chunk_usage_range_filter`
- **[Phase 5]** `btrfs_lookup_block_group()` in `block-group.c:279-283`
  returns NULL via `block_group_cache_tree_search`
- **[Phase 6]** `git describe HEAD`: v6.18.44 on `stable/linux-6.18.y`
- **[Phase 6]** Grep confirmed buggy code at lines 3968-3969; no `"has
  no corresponding block group"` string in tree
- **[Phase 6]** `BTRFS_BALANCE_ARGS_USAGE_RANGE` present in
  `include/uapi/linux/btrfs.h`
- **[Phase 8]** Failure mode: KASAN null-ptr-deref → kernel oops,
  severity CRITICAL

---

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

- **`b4 dig`** on HEAD found no lore.kernel.org thread for this commit.
- **`git log --grep="fix potential bg lookup failure"`** returned
  nothing — the fix isn’t in the local `linux-6.18.y` tree yet.

That supports the earlier **YES** verdict: the bug is still present in
6.18.44, and this commit should be backported together with prerequisite
`6dde5221f608e` (`chunk_usage_filter()` fix).The `git log -S "has no
corresponding block group"` search finished with no matches in the
current `linux-6.18.y` tree — that error string only appears in the
unfixed commits on `master` (`6dde5221`, `7a308f6`, `18d32b0013efb`),
which confirms the fix series hasn’t landed in 6.18.44 yet.

 fs/btrfs/volumes.c | 24 +++++++++++++++++-------
 1 file changed, 17 insertions(+), 7 deletions(-)

diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index a17fcf8bd8a99..298e85a69e61c 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3960,16 +3960,21 @@ static bool chunk_profiles_filter(u64 chunk_type, struct btrfs_balance_args *bar
 	return true;
 }
 
-static bool chunk_usage_range_filter(struct btrfs_fs_info *fs_info, u64 chunk_offset,
-				     struct btrfs_balance_args *bargs)
+static int chunk_usage_range_filter(struct btrfs_fs_info *fs_info, u64 chunk_offset,
+				    struct btrfs_balance_args *bargs)
 {
 	struct btrfs_block_group *cache;
 	u64 chunk_used;
 	u64 user_thresh_min;
 	u64 user_thresh_max;
-	bool ret = true;
+	int ret = 1;
 
 	cache = btrfs_lookup_block_group(fs_info, chunk_offset);
+	if (unlikely(!cache)) {
+		btrfs_err(fs_info, "balance: chunk at bytenr %llu has no corresponding block group",
+			  chunk_offset);
+		return -EUCLEAN;
+	}
 	chunk_used = cache->used;
 
 	if (bargs->usage_min == 0)
@@ -3985,7 +3990,7 @@ static bool chunk_usage_range_filter(struct btrfs_fs_info *fs_info, u64 chunk_of
 		user_thresh_max = mult_perc(cache->length, bargs->usage_max);
 
 	if (user_thresh_min <= chunk_used && chunk_used < user_thresh_max)
-		ret = false;
+		ret = 0;
 
 	btrfs_put_block_group(cache);
 	return ret;
@@ -4155,9 +4160,14 @@ static int should_balance_chunk(struct extent_buffer *leaf, struct btrfs_chunk *
 			return ret2;
 		if (ret2)
 			return false;
-	} else if ((bargs->flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
-	    chunk_usage_range_filter(fs_info, chunk_offset, bargs)) {
-		return false;
+	} else if (bargs->flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) {
+		int ret2;
+
+		ret2 = chunk_usage_range_filter(fs_info, chunk_offset, bargs);
+		if (ret2 < 0)
+			return ret2;
+		if (ret2)
+			return false;
 	}
 
 	/* devid filter */
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18-6.12] btrfs: use on-disk uuid for s_uuid in temp_fsid mounts
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (13 preceding siblings ...)
  2026-08-31 13:28 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_range_filter() Sasha Levin
@ 2026-08-31 13:30 ` Sasha Levin
  2026-08-31 13:31 ` [PATCH AUTOSEL 6.18] btrfs: zoned: always set data_relocation_bg Sasha Levin
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:30 UTC (permalink / raw)
  To: patches, stable
  Cc: Anand Jain, David Sterba, Sasha Levin, clm, linux-btrfs,
	linux-kernel

From: Anand Jain <asj@kernel.org>

[ Upstream commit df84f6c773771fa7b78fe06931709df1aca5907f ]

When mounting a cloned filesystem with a temporary fsuuid (temp_fsid),
layered modules like overlayfs require a persistent identifier.

While internal in-memory fs_devices->fsid must remain unique to
the kernel module, let s_uuid carry the original on-disk UUID.

Signed-off-by: Anand Jain <asj@kernel.org>
Signed-off-by: David Sterba <dsterba@suse.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: `[btrfs] [use] use on-disk uuid for s_uuid in temp_fsid
  mounts` — fixes incorrect `sb->s_uuid` assignment when btrfs’s in-
  memory `temp_fsid` feature is active.

**Step 1.2 — Tags**
- Record: `Signed-off-by: Anand Jain <asj@kernel.org>`, `Signed-off-by:
  David Sterba <dsterba@suse.com>`
- No `Fixes:`, `Reported-by:`, `Cc: stable`, `Tested-by:`, `Reviewed-
  by:`, or `Link:` tags (expected for manual review candidates).
- Notable: David Sterba (btrfs maintainer) signed off.

**Step 1.3 — Body analysis**
- Record: **Bug**: When mounting a cloned btrfs image with `temp_fsid`,
  `sb->s_uuid` was set from the random in-memory `fs_devices->fsid`, not
  the on-disk UUID. **Symptom**: Layered filesystems (overlayfs) that
  rely on a persistent `s_uuid` break — remounting the same image fails
  origin verification. **Root cause**: `temp_fsid` intentionally
  randomizes `fs_devices->fsid` for kernel uniqueness, but that value
  was incorrectly propagated to `sb->s_uuid`. **Fix**: For `temp_fsid`
  mounts, copy the on-disk UUID from `super_copy->fsid` into
  `sb->s_uuid`.

**Step 1.4 — Hidden bug fix?**
- Record: Yes. Despite not using “fix” in the subject, this is a
  functional correctness bug in how btrfs exposes filesystem identity to
  the VFS and overlayfs.

---

## Phase 2: Diff Analysis

**Step 2.1 — Inventory**
- Record: `fs/btrfs/disk-io.c`: +10 / −1 lines. Function:
  `open_ctree()`. Scope: single-file surgical fix.

**Step 2.2 — Code flow change**
- Record:
  - **Before**: `memcpy(&sb->s_uuid, fs_info->fs_devices->fsid, ...)`
    always — for `temp_fsid`, this is a per-mount random UUID.
  - **After**: If `temp_fsid`, use `fs_info->super_copy->fsid` (on-
    disk); otherwise unchanged behavior.
  - **Path**: Normal mount path in `open_ctree()`, after `super_copy` is
    populated (line 3344) and before chunk root read.

**Step 2.3 — Bug mechanism**
- Record: **Logic/correctness fix**. `sb->s_uuid` must reflect
  persistent filesystem identity; `fs_devices->fsid` is intentionally
  volatile under `temp_fsid`. Wrong identifier exposed to VFS consumers.

**Step 2.4 — Fix quality**
- Record: Obviously correct — `super_copy` is already populated and
  validated at this point. Minimal change, no API changes. Low
  regression risk; non-`temp_fsid` path unchanged.

---

## Phase 3: Git History Investigation

**Step 3.1 — Blame**
- Record: `memcpy(&sb->s_uuid, ...)` introduced by Nikolay Borisov
  (2018-10-30, commit `de37aa513105f8`). `temp_fsid` introduced by Anand
  Jain in `a5b8a5f9f8355` (“btrfs: support cloned-device mount
  capability”, merged Oct 2023, first in **v6.7**). Bug present since
  v6.7 whenever both features coexist.

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

**Step 3.3 — Related history**
- Record: Part of v3 series `[PATCH v3 0/2] fix s_uuid and f_fsid
  consistency for cloned filesystems`. Companion patch 2/2
  (`c2a74ed0494c2`) fixes `f_fsid` in `btrfs_statfs()` — separate
  concern (statfs/fanotify/ima). This commit (patch 1/2) is standalone
  for the `s_uuid`/overlayfs issue.

**Step 3.4 — Author context**
- Record: Anand Jain is an active btrfs contributor; David Sterba
  (maintainer) reviewed and signed off.

**Step 3.5 — Dependencies**
- Record: Requires `temp_fsid` support (present since v6.7). Requires
  `fs_info->super_copy` (long-standing). No other commits needed for
  this hunk. `git apply --check` on the patch against 6.18.44 succeeds.

---

## Phase 4: Mailing List and External Research

**Step 4.1 — Original discussion**
- Record: b4 dig found `[PATCH v3 1/2]` at https://patch.msgid.link/b4b5
  637ca4137d71eba368e37c67abcf60df0cab.1777281686.git.asj@kernel.org
- Series: v1 → v2 → v3 (latest applied version).

**Step 4.2 — Reviewers**
- Record: CC’d to `linux-btrfs@vger.kernel.org`, `dsterba@suse.com`.
  David Sterba replied on patch 2/2 with changelog corrections (May
  2026).

**Step 4.3 — Bug report**
- Record: Cover letter references André Almeida’s overlayfs report:
  https://lore.kernel.org/linux-
  btrfs/20251014015707.129013-1-andrealmeid@igalia.com
- **Reproduction** (verified from mbox): `mkfs.btrfs`, clone image,
  mount twice, use overlayfs with `index=on` — second mount of same
  image fails because btrfs assigns a new random `temp_fsid` UUID each
  mount while overlayfs stores/compares `s_uuid` in `overlay.origin`.
- **dmesg**: `"failed to verify upper root origin"`
- Christoph Hellwig: “Please fix btrfs to not change uuids, as that
  completely defeats the point of uuids.”

**Step 4.4 — Series context**
- Record: Patch 2/2 (`c2a74ed0494c2`) addresses `f_fsid` via statfs for
  fanotify/ima — not required for this commit’s overlayfs `s_uuid` fix
  but addresses related instability.

**Step 4.5 — Stable discussion**
- Record: No explicit `Cc: stable` found in thread. Not a negative
  signal.

---

## Phase 5: Code Semantic Analysis

**Step 5.1 — Modified functions**
- Record: `open_ctree()` in `fs/btrfs/disk-io.c`.

**Step 5.2 — Callers**
- Record: `open_ctree()` is called during btrfs mount
  (`btrfs_fill_super` / `btrfs_get_tree`). Every btrfs mount goes
  through this path.

**Step 5.3 — Key callees at change site**
- Record: Uses already-populated `fs_info->super_copy` and
  `fs_info->fs_devices->temp_fsid`. No new allocations or locks.

**Step 5.4 — Reachability**
- Record: Triggered by any user mounting a cloned btrfs device while
  another instance with the same on-disk UUID is already registered —
  exactly the `temp_fsid` use case (since v6.7). Unprivileged users can
  trigger via mount namespaces / loop devices.

**Step 5.5 — Similar patterns**
- Record: Patch 2/2 applies the same `super_copy->fsid` principle to
  `btrfs_statfs()` `f_fsid`. The `temp_fsid` design in `volumes.h`
  documents that in-memory `fsid` is random while `metadata_uuid ==
  sb->fsid`.

---

## Phase 6: Cross-Reference Against Local Tree (6.18.44)

**Step 6.1 — Buggy code present?**
- Record: **Yes.** Tree is `v6.18.44` (`stable/linux-6.18.y`). Line 3428
  in `disk-io.c` still has the buggy unconditional `memcpy`. `temp_fsid`
  feature confirmed present (`git merge-base --is-ancestor a5b8a5f9f8355
  HEAD` → yes, since v6.7).

**Step 6.2 — Backport complications**
- Record: **Clean apply.** `git show df84f6c773771 -- fs/btrfs/disk-io.c
  | git apply --check` succeeds on current HEAD. No conflicting recent
  churn at this location.

**Step 6.3 — Related fixes already present?**
- Record: **No.** Neither `df84f6c773771` (this commit) nor
  `c2a74ed0494c2` (companion f_fsid fix) are ancestors of HEAD.

---

## Phase 7: Subsystem and Maintainer Context

**Step 7.1 — Subsystem**
- Record: `fs/btrfs` — IMPORTANT (widely deployed filesystem, container
  rootfs stacks).

**Step 7.2 — Activity**
- Record: btrfs actively maintained in 6.18.y; `temp_fsid` is a shipped
  feature since 6.7.

---

## Phase 8: Impact and Risk Assessment

**Step 8.1 — Who is affected**
- Record: Users combining btrfs cloned-device mounts (`temp_fsid`) with
  overlayfs `index=on` (common in container/OCI immutable-root
  workflows).

**Step 8.2 — Trigger conditions**
- Record: Mount same btrfs clone image twice; use overlayfs with
  `index=on` on second mount. Reproducible, documented. Not timing-
  dependent.

**Step 8.3 — Failure mode severity**
- Record: **Mount failure** — overlayfs refuses to mount with `"failed
  to verify upper root origin"`. Breaks remount of unchanged images.
  Severity: **MEDIUM** (functional breakage, not
  crash/corruption/security, but breaks a real documented workflow).

**Step 8.4 — Risk-benefit**
- Record: **Benefit**: HIGH for affected btrfs+overlayfs users (restores
  expected remount behavior). **Risk**: VERY LOW (10 lines, conditional
  on `temp_fsid`, non-temp path unchanged). **Ratio**: Favorable.

---

## Phase 9: Final Synthesis

**Step 9.1 — Evidence summary**

FOR backport:
- Real, documented bug (André Almeida RFC, Oct 2025) with reproduction
  script
- Maintainer-signed fix (David Sterba)
- Small, surgical, applies cleanly to 6.18.44
- Bug exists in this tree since `temp_fsid` landed (v6.7)
- Directly fixes overlayfs `s_uuid` comparison in `ovl_decode_real_fh()`
  / origin verification
- btrfs maintainer community agreed btrfs should expose stable UUIDs

AGAINST backport:
- Not a crash, data corruption, or security issue — functional mount
  failure only
- Part of 2-patch series (patch 2/2 for `f_fsid`/statfs is separate;
  ideally backported too but not a prerequisite for this fix)
- Affects a specific feature combination (btrfs clone + overlayfs index)

**Step 9.2 — Stable rules checklist**
1. Obviously correct and tested? **PASS** — logic is clear; maintainer
   SOB; applies cleanly.
2. Fixes a real bug affecting users? **PASS** — documented overlayfs
   remount failure.
3. Important issue? **PASS (MEDIUM)** — mount failure breaking overlayfs
   `index=on` with btrfs clones; not crash/corruption but real user
   impact.
4. Small and contained? **PASS** — 10 lines, one file, one function.
5. No new features or APIs? **PASS** — corrects existing `s_uuid`
   semantics.
6. Can apply to local tree? **PASS** — verified clean apply.

**Step 9.3 — Exception categories**
- Record: None (not device ID, quirk, DT, build, or docs). Standard bug
  fix.

**Step 9.4 — Decision rationale**

This commit fixes a real functional regression introduced when btrfs’s
`temp_fsid` feature (present in 6.18.y since v6.7) started exposing a
per-mount random UUID via `sb->s_uuid`. Overlayfs with `index=on` stores
and later verifies that UUID; remounting the same btrfs clone image
fails with `"failed to verify upper root origin"`. The fix is minimal,
maintainer-approved, and applies cleanly to the 6.18.44 tree. While not
a crash or corruption issue, it restores correct behavior for a
supported btrfs+overlayfs combination that btrfs maintainers explicitly
addressed.

Note: The companion commit `c2a74ed0494c2` (f_fsid/statfs stability)
addresses a related but separate symptom and should be evaluated
independently.

---

## Verification

- [Phase 1] `git show df84f6c773771`: parsed commit message and tags
- [Phase 2] Diff: 10 insertions, 1 deletion in `open_ctree()`;
  conditional on `temp_fsid`
- [Phase 3] `git describe HEAD`: v6.18.44; `git blame` line 3428:
  `de37aa513105f8` (2018); `temp_fsid` from `a5b8a5f9f8355` (v6.7)
- [Phase 3] `git merge-base --is-ancestor a5b8a5f9f8355 HEAD`: temp_fsid
  IS in 6.18.y
- [Phase 3] `git merge-base --is-ancestor df84f6c773771 HEAD`: patch NOT
  in tree
- [Phase 3] `git show df84f6c773771 -- fs/btrfs/disk-io.c | git apply
  --check`: clean apply
- [Phase 4] `b4 dig -c df84f6c773771`: found v3 series at
  lore.kernel.org
- [Phase 4] `b4 dig -c df84f6c773771 -a`: v1/v2/v3 revisions; patch 1/2
  of 2-patch series
- [Phase 4] `b4 dig -c df84f6c773771 -w`: CC linux-btrfs,
  dsterba@suse.com
- [Phase 4] curl lore mbox for overlayfs RFC: confirmed reproduction and
  `"failed to verify upper root origin"` failure mode
- [Phase 4] Cover letter: references Christoph Hellwig “fix btrfs to not
  change uuids”
- [Phase 5] `grep s_uuid fs/overlayfs/`: overlayfs uses
  `mnt->mnt_sb->s_uuid` in `ovl_decode_real_fh()` (namei.c:176) and
  copy_up.c:424
- [Phase 5] `open_ctree()` called on every btrfs mount
- [Phase 6] Current `disk-io.c:3428`: buggy code confirmed present
- [Phase 6] `super_copy` populated at line 3344, before s_uuid
  assignment at 3428
- [Phase 8] Failure mode: overlayfs mount refusal on remount, severity
  MEDIUM

**YES**The background searches finished and matched what the full
analysis used:

- **Commit identified:** `df84f6c773771` — *btrfs: use on-disk uuid for
  s_uuid in temp_fsid mounts*
- **On master, not in 6.18.44:** neither this commit nor its series mate
  are in the current stable tree
- **Companion patch:** `c2a74ed0494c2` — *btrfs: derive f_fsid from on-
  disk fsid and dev_t* (patch 2/2; separate `statfs`/`f_fsid` fix)

**Verdict for 6.18.y: YES** — small, maintainer-signed fix for overlayfs
`index=on` remount failures with btrfs cloned mounts; applies cleanly.
Consider evaluating patch 2/2 separately for `statfs`/`f_fsid`
stability.

 fs/btrfs/disk-io.c | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index 655eed981078b..1664b22961ee0 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3425,7 +3425,16 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
 	/* Update the values for the current filesystem. */
 	sb->s_blocksize = sectorsize;
 	sb->s_blocksize_bits = blksize_bits(sectorsize);
-	memcpy(&sb->s_uuid, fs_info->fs_devices->fsid, BTRFS_FSID_SIZE);
+	/*
+	 * When temp_fsid is active, fs_devices->fsid is assigned a random UUID
+	 * at mount. This inconsistent UUID causes issues for layered filesystems
+	 * like OverlayFS. Since metadata_uuid may or may not be set, provide the
+	 * on-disk UUID directly from the super_copy.
+	 */
+	if (fs_info->fs_devices->temp_fsid)
+		memcpy(&sb->s_uuid, fs_info->super_copy->fsid, BTRFS_FSID_SIZE);
+	else
+		memcpy(&sb->s_uuid, fs_info->fs_devices->fsid, BTRFS_FSID_SIZE);
 
 	mutex_lock(&fs_info->chunk_mutex);
 	ret = btrfs_read_sys_array(fs_info);
-- 
2.53.0


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

* [PATCH AUTOSEL 6.18] btrfs: zoned: always set data_relocation_bg
       [not found] <20260831133314.4125787-1-sashal@kernel.org>
                   ` (14 preceding siblings ...)
  2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use on-disk uuid for s_uuid in temp_fsid mounts Sasha Levin
@ 2026-08-31 13:31 ` Sasha Levin
  15 siblings, 0 replies; 16+ messages in thread
From: Sasha Levin @ 2026-08-31 13:31 UTC (permalink / raw)
  To: patches, stable
  Cc: Johannes Thumshirn, Boris Burkov, Naohiro Aota, David Sterba,
	Sasha Levin, clm, linux-btrfs, linux-kernel

From: Johannes Thumshirn <johannes.thumshirn@wdc.com>

[ Upstream commit 82fd26090ebdbe6fa4b6039d059a7ea0a974cc36 ]

When searching for a data relocation block-group on mount,
btrfs_zoned_reserve_data_reloc_bg() is looking for the first empty DATA
block-group. But it first checks if the block-group is empty and if yes
continues the search, and then checks if it is the first DATA block-group.

There is actually no point in looking for the second empty DATA block
group as new DATA allocations will just allocate a new chunk for it. Pick
the first DATA block-group without any allocations done and set it as
relocation block-group.

At first, the commit 694ce5e143d6 ("btrfs: zoned: reserve data_reloc
block group on mount") introduced the functionality. At that time, we
took second unused (used == 0) block group, as the first one might be a
block group used for normal data.  Later, commit daa0fde32235 ("btrfs:
zoned: fix data relocation block group reservation") switched to look
for an empty block group (alloc_offset == 0). At this point, there is no
reason taking the second one anymore. So, this commit is fixing an issue
in commit daa0fde32235.

Reviewed-by: Boris Burkov <boris@bur.io>
Reviewed-by: Naohiro Aota <naohiro.aota@wdc.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

# Stable Backport Analysis: `btrfs: zoned: always set
data_relocation_bg`

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

---

## PHASE 1: COMMIT MESSAGE FORENSICS

**Step 1.1 – Subject line**
Record: `[btrfs: zoned]` `[always set]` — ensures `data_relocation_bg`
is set on mount by picking the first empty DATA block group instead of
skipping it.

**Step 1.2 – Tags**
Record:
- `Reviewed-by: Boris Burkov <boris@bur.io>`
- `Reviewed-by: Naohiro Aota <naohiro.aota@wdc.com>`
- `Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>`
- `Signed-off-by: David Sterba <dsterba@suse.com>`
- No `Fixes:`, `Reported-by:`, `Link:`, `Cc: stable`, or `Tested-by:`
  tags
- Notable: two btrfs zoned subsystem reviewers signed off

**Step 1.3 – Body analysis**
Record:
- **Bug:** After `daa0fde32235` switched selection to `alloc_offset ==
  0`, the code still skipped the first empty DATA block group (leftover
  from when `used == 0` was the criterion).
- **Symptom:** When only one empty DATA block group exists and the
  device cannot allocate a new chunk (zone-limited), `data_reloc_bg` is
  never set on mount.
- **Root cause:** Obsolete “take the second empty block group” logic
  from `694ce5e143d6` was not removed when the selection criterion
  changed in `daa0fde32235`.
- **Version context:** Fix targets a regression in `daa0fde32235`;
  original feature in `694ce5e143d6`.

**Step 1.4 – Hidden bug fix?**
Record: **Yes.** Despite the neutral subject, this is a logic-correction
bug fix. Mailing-list discussion (Boris Burkov) documents a real remount
scenario where no relocation block group gets reserved.

---

## PHASE 2: DIFF ANALYSIS

**Step 2.1 – Inventory**
Record:
- **Files:** `fs/btrfs/zoned.c` only (+1 / −10 lines)
- **Function:** `btrfs_zoned_reserve_data_reloc_bg()`
- **Scope:** Single-file surgical fix

**Step 2.2 – Code flow change**
Record per hunk:
- **Before:** Loop skips every block group with `alloc_offset != 0`,
  then skips the first empty one (`first` flag), uses the second empty
  block group for relocation.
- **After:** Loop skips only non-empty block groups (`alloc_offset !=
  0`), immediately uses the first empty block group.
- **Also removed:** `bool first`, comment about “second one”,
  `ASSERT(!list_empty(...))` (invalid when only one empty BG exists),
  and `first = false` after chunk allocation.

**Step 2.3 – Bug mechanism**
Record: **Logic / correctness fix** in mount-time block-group
reservation. Stale algorithm from an earlier criterion (`used == 0` →
skip first) persisted after criterion changed to `alloc_offset == 0`,
causing failure to reserve relocation space on zone-constrained
filesystems with a single empty DATA block group.

**Step 2.4 – Fix quality**
Record: Fix is minimal and obviously correct — removes dead logic and an
assertion that assumed a second empty block group always exists. Low
regression risk; only changes which empty block group is chosen on
mount.

---

## PHASE 3: GIT HISTORY INVESTIGATION

**Step 3.1 – Blame**
Record: Buggy “skip first empty” logic introduced in `daa0fde32235`
(Naohiro Aota, 2025-07-16). Loop structure from `694ce5e143d6` (Johannes
Thumshirn, 2025-06-03). Both are in v6.18 and in this tree.

**Step 3.2 – Fixes: tag**
Record: N/A — no `Fixes:` tag. Author explicitly states this corrects
`daa0fde32235`, which is present in this tree.

**Step 3.3 – Related file history**
Record: Recent `fs/btrfs/zoned.c` changes in this tree include deadlock
fixes and zone pointer fixes; no duplicate fix for this issue found.

**Step 3.4 – Author context**
Record: Johannes Thumshirn is a btrfs zoned contributor; authored
`694ce5e143d6` (original mount-time reservation feature, with `Cc:
stable@vger.kernel.org # 6.6+`).

**Step 3.5 – Dependencies**
Record: **Standalone.** Patch is 3/5 in a series (“fix deadlock and
space reporting issues for zoned filesystems”), but only touches
`btrfs_zoned_reserve_data_reloc_bg()` and does not depend on patches
1/2/4/5. `git apply --check` confirms clean apply to 6.18.44.

---

## PHASE 4: MAILING LIST AND EXTERNAL RESEARCH

**Step 4.1 – Original discussion**
Record:
- `b4 dig -c 82fd26090ebd` → https://patch.msgid.link/20260522090247.274
  45-4-johannes.thumshirn@wdc.com (v3 submission)
- Series revisions: v1 (2026-05-13), v2 (2026-05-19), v3 (2026-05-22);
  committed version matches v3 (removes `first` entirely, not v2’s
  reorder-only approach)
- Boris Burkov review identified the concrete failure: after GC-heavy
  workload and remount, all non-empty BGs skipped, first empty BG also
  skipped, drive out of free zones → no relocation BG set

**Step 4.2 – Reviewers**
Record: `b4 dig -w` shows CC to `linux-btrfs@vger.kernel.org`, David
Sterba, Filipe Manana, Naohiro Aota, Boris Burkov, Christoph Hellwig,
Damien Le Moal.

**Step 4.3 – Bug report**
Record: No formal bugzilla/syzbot report. Failure scenario documented in
list discussion (remount after heavy GC on zone-limited device).

**Step 4.4 – Series context**
Record: Other patches in series cover tracepoints (1/2), statfs
accounting (4/5), deadlock (5/5) — separate issues; this patch is
independently backportable.

**Step 4.5 – Stable list**
Record: lore.kernel.org/stable search blocked (bot protection). Original
feature commit `694ce5e143d6` had explicit stable nomination (`Cc:
stable # 6.6+`).

---

## PHASE 5: CODE SEMANTIC ANALYSIS

**Step 5.1 – Key functions**
Record: `btrfs_zoned_reserve_data_reloc_bg()` modified.

**Step 5.2 – Callers**
Record: Called once from `btrfs_open_devices()` path in `fs/btrfs/disk-
io.c:3556` during filesystem mount, after `btrfs_read_block_groups()`.

**Step 5.3 – Callees**
Record: Block-group list iteration, space_info migration
(`list_del_init`, `btrfs_add_bg_to_space_info`), `btrfs_chunk_alloc()`
fallback, `btrfs_zone_activate()`.

**Step 5.4 – Reachability**
Record: Triggered on every read-write mount of a zoned btrfs filesystem
(`btrfs_is_zoned()`). Common operational path for zoned-storage users.

**Step 5.5 – Similar patterns**
Record: Treelog block-group reservation uses related but separate logic
in `extent-tree.c`. No other “skip first empty” pattern found for data
relocation.

---

## PHASE 6: CROSS-REFERENCING AGAINST LOCAL TREE

**Step 6.1 – Buggy code present?**
Record: **Yes.** Current `fs/btrfs/zoned.c:2760–2787` still has `bool
first = true`, comment “Take the second one”, and skip-first-empty
logic. Fix commit `82fd26090ebd` is **not** an ancestor of HEAD
(6.18.44).

**Step 6.2 – Backport complications**
Record: **Clean apply.** `git format-patch -1 82fd260 | git apply
--check` succeeds on current tree.

**Step 6.3 – Related fixes already present?**
Record: Prerequisites `694ce5e143d6` and `daa0fde32235` are in v6.18 and
this tree. No alternate fix for this issue found.

---

## PHASE 7: SUBSYSTEM AND MAINTAINER CONTEXT

**Step 7.1 – Subsystem**
Record: **btrfs / zoned mode** — IMPORTANT for zoned-btrfs deployments
(SMR/ZNS storage); not universal but operationally critical for that
subset.

**Step 7.2 – Activity**
Record: `fs/btrfs/zoned.c` actively maintained in 6.18.y with multiple
recent zoned fixes.

---

## PHASE 8: IMPACT AND RISK ASSESSMENT

**Step 8.1 – Who is affected**
Record: Users of **zoned btrfs** (`CONFIG_BTRFS_FS` + zoned devices).
Not all kernel users, but all zoned-btrfs users on affected versions.

**Step 8.2 – Trigger conditions**
Record: Mount after workload leaving one empty DATA block group and no
spare zones for new chunk allocation (e.g., remount after heavy GC).
Realistic on zone-limited SMR/ZNS hardware.

**Step 8.3 – Failure severity**
Record: `data_reloc_bg` remains 0 → garbage collection / data relocation
cannot reserve dedicated space → **ENOSPC under overwrite workloads**
(the exact problem `694ce5e143d6` was designed to prevent). Severity:
**HIGH** for affected configurations; mount succeeds silently so the
failure is deferred.

**Step 8.4 – Risk-benefit**
Record:
- **Benefit:** HIGH for zoned btrfs — restores guaranteed relocation
  block-group reservation
- **Risk:** LOW — 10-line deletion, reviewed by two btrfs developers,
  applies cleanly
- **Ratio:** Strongly favors backport

---

## PHASE 9: FINAL SYNTHESIS

**Step 9.1 – Evidence summary**

| FOR backport | AGAINST backport |
|---|---|
| Real bug with documented remount scenario | Narrow audience (zoned
btrfs only) |
| Can leave `data_reloc_bg` unset → ENOSPC/GC failure | Part of 5-patch
series (but this patch is standalone) |
| Small, surgical, reviewed fix | No syzbot/fuzzer report |
| Applies cleanly to 6.18.44 | Bug only exists since `daa0fde32235` (not
in older trees) |
| Fixes regression in already-stable code | |
| Original feature had stable CC | |

**Step 9.2 – Stable rules checklist**
1. Obviously correct and tested? **PASS** — logic fix reviewed by Boris
   Burkov and Naohiro Aota; applies cleanly
2. Fixes a real user-affecting bug? **PASS** — documented remount/zone-
   exhaustion scenario
3. Important issue? **PASS** — ENOSPC / broken GC on zoned btrfs (HIGH
   for affected users)
4. Small and contained? **PASS** — 1 file, net −9 lines
5. No new features/APIs? **PASS** — removes obsolete logic only
6. Can apply to local tree? **PASS** — verified with `git apply --check`

**Step 9.3 – Exception categories**
Record: None (not a quirk/DT/build/doc fix), but a straightforward logic
bug fix.

**Step 9.4 – Problem and why it matters**

On mount, zoned btrfs reserves a dedicated data-relocation block group
so garbage collection always has space under heavy overwrite. Commit
`daa0fde32235` changed the selection criterion to `alloc_offset == 0`
but kept the old “skip the first empty block group” rule. When a
filesystem has only one empty DATA block group and the device cannot
allocate a new chunk (common after GC-heavy use on zone-limited drives),
the mount path silently fails to set `data_reloc_bg`. The filesystem
then hits ENOSPC when GC is needed — defeating the purpose of the mount-
time reservation added in `694ce5e143d6`.

This fix simply picks the first empty DATA block group, which is correct
under the new criterion. It is minimal, self-contained, and directly
relevant to Linux 6.18.44 where both the feature and the regression are
present.

---

## Verification

- [Phase 1] Parsed subject, tags, body; no Fixes/Reported-by/Cc:stable
  on this commit
- [Phase 1] Identified hidden bug fix from message and list discussion
- [Phase 2] Diff: 1 file, `btrfs_zoned_reserve_data_reloc_bg()`, −10/+1
  lines
- [Phase 2] Mechanism: stale skip-first-empty logic after criterion
  change
- [Phase 3] `git blame -L 2777,2800 fs/btrfs/zoned.c`: skip logic from
  `daa0fde32235`
- [Phase 3] `git merge-base --is-ancestor`: `694ce5e143d6` and
  `daa0fde32235` both in HEAD and v6.18
- [Phase 3] `git show 694ce5e143d6`: original feature with `Cc: stable #
  6.6+`
- [Phase 3] `git show daa0fde32235`: introduced skip-first-empty with
  `alloc_offset == 0`
- [Phase 3] `git merge-base --is-ancestor 82fd260 HEAD`: fix **NOT** in
  6.18.44
- [Phase 4] `b4 dig -c 82fd260`: lore URL found
- [Phase 4] `b4 dig -a`: v1/v2/v3 series revisions confirmed; v3 is
  final form
- [Phase 4] `b4 dig -w`: btrfs maintainers/reviewers CC'd
- [Phase 4] WebFetch spinics msg165574: Boris documented remount + zone
  exhaustion scenario
- [Phase 5] `grep btrfs_zoned_reserve_data_reloc_bg`: caller at `disk-
  io.c:3556` on mount
- [Phase 5] Read `extent-tree.c:3870–3966`: `data_reloc_bg == 0` breaks
  relocation allocation paths
- [Phase 6] `git describe HEAD` / `make kernelversion`: tree is 6.18.44
- [Phase 6] Read `fs/btrfs/zoned.c:2752–2845`: buggy code confirmed
  present
- [Phase 6] `git format-patch -1 82fd260 | git apply --check`: clean
  apply
- [Phase 8] Failure mode: no relocation BG → ENOSPC on zoned btrfs,
  severity HIGH for affected configs

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

- The fix commit (`always set data_relocation_bg`) is **not** in
  6.18.44; the tree still has the skip-first-empty logic in
  `fs/btrfs/zoned.c`.
- The prerequisite commits are present: `694ce5e143d6` (reserve
  data_reloc BG on mount) and `daa0fde32235` (the regression this fix
  corrects).
- `b4 dig` on HEAD didn’t match (expected — the fix isn’t applied yet);
  digging by commit hash `82fd260` found the v3 lore thread.

**Verdict remains YES** for backporting to this 6.18.44 tree.

 fs/btrfs/zoned.c | 11 +----------
 1 file changed, 1 insertion(+), 10 deletions(-)

diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 6ca5816a67381..660f516cb9357 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2759,7 +2759,6 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
 	struct btrfs_block_group *bg;
 	struct list_head *bg_list;
 	u64 alloc_flags;
-	bool first = true;
 	bool did_chunk_alloc = false;
 	int index;
 	int ret;
@@ -2776,17 +2775,12 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
 	alloc_flags = btrfs_get_alloc_profile(fs_info, space_info->flags);
 	index = btrfs_bg_flags_to_raid_index(alloc_flags);
 
-	/* Scan the data space_info to find empty block groups. Take the second one. */
 again:
 	bg_list = &space_info->block_groups[index];
 	list_for_each_entry(bg, bg_list, list) {
-		if (bg->alloc_offset != 0)
-			continue;
 
-		if (first) {
-			first = false;
+		if (bg->alloc_offset != 0)
 			continue;
-		}
 
 		if (space_info == data_sinfo) {
 			/* Migrate the block group to the data relocation space_info. */
@@ -2798,8 +2792,6 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
 
 			down_write(&space_info->groups_sem);
 			list_del_init(&bg->list);
-			/* We can assume this as we choose the second empty one. */
-			ASSERT(!list_empty(&space_info->block_groups[index]));
 			up_write(&space_info->groups_sem);
 
 			spin_lock(&space_info->lock);
@@ -2843,7 +2835,6 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
 		 * We allocated a new block group in the data relocation space_info. We
 		 * can take that one.
 		 */
-		first = false;
 		did_chunk_alloc = true;
 		goto again;
 	}
-- 
2.53.0


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

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

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
     [not found] <20260831133314.4125787-1-sashal@kernel.org>
2026-08-31 13:20 ` [PATCH AUTOSEL 6.18-5.15] btrfs: protect sb_write_pointer() with invalidate lock Sasha Levin
2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: fix transaction abort logic in btrfs_fileattr_set() Sasha Levin
2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: tree-checker: validate INODE_REF's namelen Sasha Levin
2026-08-31 13:22 ` [PATCH AUTOSEL 6.18] btrfs: validate data reloc tree file extent item members Sasha Levin
2026-08-31 13:22 ` [PATCH AUTOSEL 6.18-5.15] btrfs: only account delalloc bytes for regular file inodes in btrfs_getattr() Sasha Levin
2026-08-31 13:23 ` [PATCH AUTOSEL 6.18-6.12] btrfs: derive f_fsid from on-disk fsid and dev_t Sasha Levin
2026-08-31 13:23 ` [PATCH AUTOSEL 6.18] btrfs: validate properties before setting them Sasha Levin
2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: balance: fix potential bg lookup failure in btrfs_may_alloc_data_chunk() Sasha Levin
2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use lockless read in nr_cached_objects shrinker callback Sasha Levin
2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix use-after-free on reloc root after error in insert_dirty_subvol() Sasha Levin
2026-08-31 13:24 ` [PATCH AUTOSEL 6.18-6.1] btrfs: tree-checker: validate names in ROOT_REF and ROOT_BACKREF Sasha Levin
2026-08-31 13:25 ` [PATCH AUTOSEL 6.18-5.10] btrfs: fix reloc root cleanup in merge_reloc_roots() Sasha Levin
2026-08-31 13:26 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_filter() Sasha Levin
2026-08-31 13:28 ` [PATCH AUTOSEL 6.18] btrfs: balance: fix potential bg lookup failure in chunk_usage_range_filter() Sasha Levin
2026-08-31 13:30 ` [PATCH AUTOSEL 6.18-6.12] btrfs: use on-disk uuid for s_uuid in temp_fsid mounts Sasha Levin
2026-08-31 13:31 ` [PATCH AUTOSEL 6.18] btrfs: zoned: always set data_relocation_bg Sasha Levin

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