[PATCH AUTOSEL 6.19-6.1] fs/buffer: add alert in try_to_free_buffers() for folios without buffers

Sasha Levin
2026-02-10T23:31:47Z

From: Deepakkumar Karn <dkarn@redhat.com>

[ Upstream commit b68f91ef3b3fe82ad78c417de71b675699a8467c ]

try_to_free_buffers() can be called on folios with no buffers attached
when filemap_release_folio() is invoked on a folio belonging to a mapping
with AS_RELEASE_ALWAYS set but no release_folio operation defined.

In such cases, folio_needs_release() returns true because of the
AS_RELEASE_ALWAYS flag, but the folio has no private buffer data. This
causes try_to_free_buffers() to call drop_buffers() on a folio with no
buffers, leading to a null pointer dereference.

Adding a check in try_to_free_buffers() to return early if the folio has no
buffers attached, with WARN_ON_ONCE() to alert about the misconfiguration.
This provides defensive hardening.

Signed-off-by: Deepakkumar Karn <dkarn@redhat.com>
Link: https://patch.msgid.link/20251211131211.308021-1-dkarn@redhat.com
Reviewed-by: Jan Kara <jack@suse.cz>
Signed-off-by: Christian Brauner <brauner@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
---

LLM Generated explanations, may be completely bogus:

`folio_buffers()` exists in all relevant stable trees. The patch uses
only `WARN_ON_ONCE` and `folio_buffers`, both of which are available
everywhere.

## Comprehensive Analysis

### 1. COMMIT MESSAGE ANALYSIS

The commit describes a clear bug: `try_to_free_buffers()` can be called
on folios with no buffer_heads attached when `filemap_release_folio()`
is invoked on a folio belonging to a mapping with `AS_RELEASE_ALWAYS`
set but no `release_folio` operation defined. This causes a **NULL
pointer dereference** in `drop_buffers()`.

The commit is authored by Deepakkumar Karn (Red Hat), reviewed by Jan
Kara (SUSE, a very well-respected VFS/filesystem developer), and signed
off by Christian Brauner (the VFS subsystem maintainer).

### 2. CODE CHANGE ANALYSIS

The fix adds exactly 3 lines (plus a comment) to
`try_to_free_buffers()`:

```c
/* Misconfigured folio check */
if (WARN_ON_ONCE(!folio_buffers(folio)))
return true;
```

This is inserted after the writeback check and before the `mapping ==
NULL` check. The logic is:
- If the folio has no buffers (`folio_buffers()` returns NULL), emit a
warning (once) and return `true` (success — there's nothing to free)
- This prevents the NULL deref in `drop_buffers()` where `head =
folio_buffers(folio)` returns NULL and then `buffer_busy(bh)`
dereferences it

### 3. THE BUG MECHANISM

The call chain is:
1. Memory reclaim (`shrink_folio_list`) or truncation calls
`filemap_release_folio()`
2. `folio_needs_release()` returns `true` because `AS_RELEASE_ALWAYS` is
set
3. `mapping->a_ops->release_folio` is NULL (e.g., `afs_dir_aops` doesn't
define it in v6.14+)
4. Falls through to `try_to_free_buffers(folio)`
5. `drop_buffers()` dereferences `folio_buffers(folio)` which is NULL
6. **NULL pointer dereference crash**

### 4. AFFECTED VERSIONS

The specific trigger (AFS symlinks/directories with `afs_dir_aops`
lacking `release_folio`) only exists from **v6.14-rc1** onward,
introduced by commit `6dd80936618c` ("afs: Use netfslib for
directories"). Before v6.14, all filesystems that set
`AS_RELEASE_ALWAYS` also define `release_folio`.

This means:
- **v6.6.y**: NOT affected (afs_dir_aops has release_folio)
- **v6.12.y**: NOT affected (afs_dir_aops has release_folio)
- **v6.13.y**: NOT affected (afs_dir_aops has release_folio)
- **v6.14.y**: AFFECTED (afs_dir_aops stripped down)
- **v6.15.y+**: AFFECTED

### 5. RISK ASSESSMENT

**Risk: Very Low**
- The patch adds 3 lines - a NULL check with WARN_ON_ONCE and early
return
- It's completely self-contained, no dependencies
- `folio_buffers()` and `WARN_ON_ONCE` are available in all stable trees
- Returning `true` is correct: no buffers = nothing to free = success
- Reviewed by Jan Kara and Christian Brauner (top VFS experts)
- Could be considered pure defensive hardening in older kernels where
the trigger doesn't exist

**Benefit in affected trees (6.14+): High**
- Prevents a NULL pointer dereference crash reachable from normal memory
reclaim
- AFS symlinks with fscache enabled hitting memory pressure would crash

**Benefit in unaffected trees (6.6, 6.12): Low**
- Defensive hardening only - the specific trigger doesn't exist
- Protects against potential future similar misconfigurations if other
patches are backported

### 6. STABLE BACKPORT SUITABILITY

For trees where the bug is actually triggerable (6.14+), this is clearly
a YES - it fixes a NULL pointer dereference crash in a core kernel path
(memory reclaim). However, for older stable trees (6.6, 6.12), the bug
isn't reachable with the current code because all filesystems that set
`AS_RELEASE_ALWAYS` also properly define `release_folio`.

The fix itself is trivially small (3 lines), obviously correct, has zero
regression risk, and is well-reviewed. Even as defensive hardening, it's
the kind of patch that makes stable kernels more robust.

For the broader question of "should this be backported to stable trees":
The answer depends on which tree. For 6.14.y and later, it's an
unambiguous yes - it fixes a real crash. For 6.6.y and 6.12.y, it's
defensive hardening that prevents a crash scenario that currently isn't
reachable but could become reachable if other patches are backported.
Given the negligible risk and the fact that it hardens a core kernel
function against NULL dereference, even the older trees benefit.

**YES**

fs/buffer.c | 4 ++++
1 file changed, 4 insertions(+)

diff --git a/fs/buffer.c b/fs/buffer.c
index 838c0c5710229..28e4d53f17173 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -2948,6 +2948,10 @@ bool try_to_free_buffers(struct folio *folio)
if (folio_test_writeback(folio))
return false;

+ /* Misconfigured folio check */
+ if (WARN_ON_ONCE(!folio_buffers(folio)))
+ return true;
+
if (mapping == NULL) { /* can this still happen? */
ret = drop_buffers(folio, &buffers_to_free);
goto out;
--
2.51.0