From: Johannes Thumshirn johannes.thumshirn@wdc.com
[ Upstream commit 3c44cd3c79fcb38a86836dea6ff8fec322a9e68c ]
Now that btrfs_zone_finish_endio_workfn() is directly calling do_zone_finish() the only caller of btrfs_zone_finish_endio() is btrfs_finish_one_ordered().
btrfs_finish_one_ordered() already has error handling in-place so btrfs_zone_finish_endio() can return an error if the block group lookup fails.
Also as btrfs_zone_finish_endio() already checks for zoned filesystems and returns early, there's no need to do this in the caller.
Reviewed-by: Damien Le Moal dlemoal@kernel.org 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:
## Backport Recommendation: **YES**
This commit should definitely be backported to stable kernel trees (and indeed has already been backported to this 6.17 stable tree).
---
## Detailed Analysis
### **1. Bug Being Fixed**
The commit fixes a critical NULL pointer dereference vulnerability in the btrfs zoned device feature. The problematic code at **fs/btrfs/zoned.c:2476** uses: ```c block_group = btrfs_lookup_block_group(fs_info, logical); ASSERT(block_group); ```
**Problems with this approach:** - `ASSERT()` can be compiled out in production kernels, leaving no runtime check - If `btrfs_lookup_block_group()` returns NULL, the code continues and dereferences the NULL pointer at line 2479 (`if (block_group->flags & BTRFS_BLOCK_GROUP_DATA)`) - This leads to a kernel crash or undefined behavior
### **2. Evidence of Real-World Impact**
My investigation found commit **d3cfa44164688** (from August 2023) which documents an actual crash from this exact issue:
``` KASAN: null-ptr-deref in range [0x0000000000000088-0x000000000000008f] RIP: 0010:btrfs_zone_finish_endio.part.0+0x34/0x160 [btrfs] ```
The crash report states:
"btrfs_lookup_block_group() in btrfs_zone_finish_endio() failed to
find a block group and will hit an assert or a null pointer dereference"
This occurred during data relocation when `ordered_extent->logical` was set to an invalid value, demonstrating that block group lookups **can and do fail** in production scenarios.
### **3. The Fix - Code Changes Analysis**
**In fs/btrfs/zoned.c (btrfs_zone_finish_endio function):** - **Line 2467**: Changed function signature from `void` to `int` to return error codes - **Line 2473**: Early return now returns `0` (success) instead of void - **Lines 2475-2477**: Replaced `ASSERT(block_group)` with: ```c if (WARN_ON_ONCE(!block_group)) return -ENOENT; ``` - `WARN_ON_ONCE()` cannot be compiled out - provides runtime detection - Returns `-ENOENT` error to caller instead of crashing - Uses `_ONCE` variant to avoid log spam if issue repeats
- **Line 2493**: Added `return 0` at normal exit path
**In fs/btrfs/inode.c (btrfs_finish_one_ordered function):** - **Lines 3110-3112** (before): Called `btrfs_zone_finish_endio()` only if zoned, as void function - **Lines 3110-3113** (after): - Unconditionally calls `btrfs_zone_finish_endio()` (the zoned check moved inside) - Captures return value in `ret` - Checks for error and jumps to `out` label for proper cleanup - The `out` label at line 3228 already has comprehensive error handling that calls `btrfs_mark_ordered_extent_error()` to properly handle the failure
**In fs/btrfs/zoned.h:** - **Line 66**: Updated function declaration from `void` to `int` - **Lines 217-221**: Updated the non-zoned stub to return `int` with value `0`
### **4. Why This is a Good Backport Candidate**
**Severity:** - Fixes a NULL pointer dereference that crashes the kernel - Affects users of btrfs zoned device feature - Has been confirmed to occur in real-world usage (reproducible with btrfs/028 test)
**Code Quality:** - Small, focused change (15 insertions, 9 deletions across 3 files) - Follows kernel best practices (WARN_ON instead of ASSERT) - Adds proper error handling rather than silently ignoring issues - Leverages existing error handling infrastructure
**Dependencies:** - Depends on preceding commit **3d16abf6c88ae** ("directly call do_zone_finish() from btrfs_zone_finish_endio_workfn()") - Both commits work together as a series and have been backported together - The preceding commit removes one caller, simplifying the code before adding error handling
**Risk Assessment:** - **Low regression risk**: The change is defensive - adds error handling where none existed - The caller (`btrfs_finish_one_ordered`) already had error handling infrastructure ready (the `out:` label and error path) - No subsequent fixes or reverts found - Reviewed-by tag from Damien Le Moal indicates peer review - No architectural changes - just improved error handling
### **5. Stable Tree Rules Compliance**
This commit follows all stable kernel tree rules: - ✅ Fixes an important bug (kernel crash) - ✅ Small and obviously correct - ✅ Minimal risk of regression - ✅ Confined to btrfs subsystem (specifically zoned device code) - ✅ Does not introduce new features - ✅ No major architectural changes
### **6. Backport Status**
**Already backported**: This commit has been backported to the 6.17 stable tree as commit **d7ca28dca807e** by stable tree maintainer Sasha Levin on October 1, 2025, confirming that kernel maintainers have already determined this is appropriate for stable backporting.
---
## Conclusion
**YES** - This commit is an excellent candidate for stable backporting. It fixes a confirmed kernel crash bug in btrfs zoned devices with minimal code changes and low regression risk. The fix follows best practices by replacing a potentially-compiled-out ASSERT with proper runtime error detection and handling. The commit has already been successfully backported to this stable tree, demonstrating maintainer confidence in its suitability.
fs/btrfs/inode.c | 7 ++++--- fs/btrfs/zoned.c | 8 +++++--- fs/btrfs/zoned.h | 9 ++++++--- 3 files changed, 15 insertions(+), 9 deletions(-)
diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 18db1053cdf08..4a745f43c895c 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -3107,9 +3107,10 @@ int btrfs_finish_one_ordered(struct btrfs_ordered_extent *ordered_extent) goto out; }
- if (btrfs_is_zoned(fs_info)) - btrfs_zone_finish_endio(fs_info, ordered_extent->disk_bytenr, - ordered_extent->disk_num_bytes); + ret = btrfs_zone_finish_endio(fs_info, ordered_extent->disk_bytenr, + ordered_extent->disk_num_bytes); + if (ret) + goto out;
if (test_bit(BTRFS_ORDERED_TRUNCATED, &ordered_extent->flags)) { truncated = true; diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c index f426276e2b6bf..6641b8e9c15c2 100644 --- a/fs/btrfs/zoned.c +++ b/fs/btrfs/zoned.c @@ -2464,16 +2464,17 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags) return ret; }
-void btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 length) +int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 length) { struct btrfs_block_group *block_group; u64 min_alloc_bytes;
if (!btrfs_is_zoned(fs_info)) - return; + return 0;
block_group = btrfs_lookup_block_group(fs_info, logical); - ASSERT(block_group); + if (WARN_ON_ONCE(!block_group)) + return -ENOENT;
/* No MIXED_BG on zoned btrfs. */ if (block_group->flags & BTRFS_BLOCK_GROUP_DATA) @@ -2490,6 +2491,7 @@ void btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 len
out: btrfs_put_block_group(block_group); + return 0; }
static void btrfs_zone_finish_endio_workfn(struct work_struct *work) diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h index 6e11533b8e14c..17c5656580dd9 100644 --- a/fs/btrfs/zoned.h +++ b/fs/btrfs/zoned.h @@ -83,7 +83,7 @@ int btrfs_sync_zone_write_pointer(struct btrfs_device *tgt_dev, u64 logical, bool btrfs_zone_activate(struct btrfs_block_group *block_group); int btrfs_zone_finish(struct btrfs_block_group *block_group); bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags); -void btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, +int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical, u64 length); void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg, struct extent_buffer *eb); @@ -234,8 +234,11 @@ static inline bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, return true; }
-static inline void btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, - u64 logical, u64 length) { } +static inline int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, + u64 logical, u64 length) +{ + return 0; +}
static inline void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg, struct extent_buffer *eb) { }