mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 0/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp()
@ 2026-11-10 19:37 Aleksandr Panfilenko
  2026-11-10 19:37 ` [PATCH 1/1] " Aleksandr Panfilenko
  2026-11-10 19:44 ` [PATCH 0/1] " Matthew Wilcox
  0 siblings, 2 replies; 3+ messages in thread
From: Aleksandr Panfilenko @ 2026-11-10 19:37 UTC (permalink / raw)
  To: linux-ext4
  Cc: tytso, adilger, willy, stable, linux-kernel, Aleksandr Panfilenko

Hello,

We have hit a kernel NULL pointer dereference in ext4_mb_load_buddy_gfp()
twice in one day on a freshly installed server. Both crashes are identical
(same RIP and offset) and were captured by kdump. Patch 1 fixes it; this
cover letter contains the full crash data and the analysis.

Environment
-----------

Kernel:      6.1.192-1.el9.elrepo.x86_64 (vanilla linux-6.1.y built by ELRepo;
             the affected code is identical in current mainline)
Hardware:    ASUSTeK RS500-E9-PS4 (2 sockets, 64 GiB RAM)
Filesystem:  ext4 on LVM on md-raid1, 120 GiB, block size 4096,
             features: has_journal ext_attr resize_inode dir_index filetype
             extent 64bit flex_bg sparse_super large_file huge_file
             dir_nlink extra_isize metadata_csum
Mount:       rw,relatime
Workload:    fresh OS install followed by heavy package installation and
             database restore (lots of writes and page cache churn); both
             crashes happened in background writeback (kworker), not tied
             to any specific userspace task.

Crash 1 (kdump at 19:26:27, 4683s after boot)
---------------------------------------------

[ 4683.607255] BUG: kernel NULL pointer dereference, address: 0000000000000000
[ 4683.619530] #PF: supervisor read access in kernel mode
[ 4683.624757] #PF: error_code(0x0000) - not-present page
[ 4683.627387] Oops: 0000 [#1] PREEMPT SMP NOPTI
[ 4683.631834] CPU: 51 PID: 137989 Comm: kworker/u161:1 Kdump: loaded Tainted: G            E      6.1.192-1.el9.elrepo.x86_64 #1
[ 4683.652787] Workqueue: writeback wb_workfn (flush-253:1)
[ 4683.658194] RIP: 0010:ext4_mb_load_buddy_gfp+0xc0/0x3a0 [ext4]
[ 4683.664141] Code: 48 63 f0 89 54 24 14 ba 01 00 00 00 48 89 74 24 08 e8 44 49 ec f5 48 8b 74 24 08 48 3d 00 f0 ff ff 49 89 c7 0f 87 32 01 00 00 <48> 8b 00 a8 04 0f 84 1c 01 00 00 49 8b 07 a8 01 0f 85 11 01 00 00
[ 4683.688284] RAX: 0000000000000000 RBX: 00000000000000a2 RCX: 0000000000000022

[ 4683.765356] Call Trace:
[ 4683.767559]  <TASK>
[ 4683.773060]  ext4_mb_regular_allocator+0x1ba/0x600 [ext4]
[ 4683.777952]  ? ext4_find_extent+0x378/0x440 [ext4]
[ 4683.782848]  ext4_mb_new_blocks+0x3a9/0x530 [ext4]
[ 4683.787823]  ext4_ext_map_blocks+0x670/0x8a0 [ext4]
[ 4683.792460]  ext4_map_blocks+0x22b/0x5d0 [ext4]
[ 4683.797441]  mpage_map_one_extent+0x60/0x170 [ext4]
[ 4683.797441]  mpage_map_and_submit_extent+0x76/0x300 [ext4]
[ 4683.803020]  ext4_writepages+0x6f8/0x860 [ext4]
[ 4683.818105]  do_writepages+0xcc/0x1d0
[ 4683.821867]  __writeback_single_inode+0x41/0x270
[ 4683.826575]  writeback_sb_inodes+0x209/0x4c0
[ 4683.830937]  __writeback_inodes_wb+0x4c/0xe0
[ 4683.835300]  wb_writeback+0x1d7/0x2d0
[ 4683.839058]  wb_do_writeback+0x22a/0x2b0
[ 4683.843080]  wb_workfn+0x5d/0x250

Crash 2 (kdump at 20:19:39, 3005s after the following boot) is identical:
same RIP (ext4_mb_load_buddy_gfp+0xc0/0x3a0), same workqueue, same
instruction sequence, RAX=0, different block group (RBX=0xbe vs 0xa2)
and CPU.  (The only difference in the Code dump is the relative call
displacement - see the note under the disassembly below.)

Analysis
--------

The faulting instruction sequence decodes to (bytes from the Crash 1
dump above; in the Crash 2 dump the call displacement differs - the
call to __filemap_get_folio is stored as an R_X86_64_PLT32 relocation
against the vmlinux symbol in the module and is resolved at module
load time, so its encoded value depends on the kernel and module load
addresses of a given boot, while the instruction sequence is identical):

    e8 44 49 ec f5        call  __filemap_get_folio      ; fast path
    48 8b 74 24 08        mov   rsi, [rsp+0x8]
    48 3d 00 f0 ff ff     cmp   rax, 0xfffff000
    49 89 c7              mov   r15, rax
    0f 87 32 01 00 00     ja    err                      ; IS_ERR() only
    <48 8b 00>            mov   rax, [rax]               ; RAX == NULL -> #PF
    a8 04                 test  al, 0x4                  ; PG_uptodate
    0f 84 ...             jz    slow_path
    49 8b 07              mov   rax, [r15]
    a8 01                 test  al, 0x1                  ; PG_locked
    0f 85 ...             jnz   slow_path

This is the fast path in ext4_mb_load_buddy_gfp():

    folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_ACCESSED, 0);
    if (IS_ERR(folio) || !folio_test_uptodate(folio) || folio_test_locked(folio)) {
            ...
    }

__filemap_get_folio() without FGP_CREAT legitimately returns NULL when
the folio is not in the page cache ("if (!folio) goto no_page;" in
mm/filemap.c).  The fast-path condition only checks IS_ERR(folio) before
dereferencing the folio, so a NULL return results in a NULL deref.

The ext4_mb_init_group() call at the top of the function is skipped for
groups that are already initialized (EXT4_MB_GRP_NEED_INIT not set), so
nothing guarantees the buddy cache folio is still in the page cache at
this point: the folios of the s_buddy_cache inode are clean and can be
reclaimed under memory pressure.  The folio conversion series dropped
the explicit NULL check that the old find_get_page_flags()-based code
had ("page == NULL ||" was the first condition) in both lookups: the
bitmap lookup was converted by commit 99b150d84e49 ("ext4: convert
bd_bitmap_page to bd_bitmap_folio") and the buddy lookup by commit
5eea586b47f0 ("ext4: convert bd_buddy_page to bd_buddy_folio"), which
is where the regression comes from.

The error-path fix from commit c6a6c9694aad ("ext4: fix error pointer
dereference in ext4_mb_load_buddy_gfp()") does not cover this case.

The patch switches the fast-path conditions (and the two folio_put()
guards) to IS_ERR_OR_NULL(), so a NULL folio falls back to the FGP_CREAT
path which re-creates it.

Deterministic reproducer
------------------------

Once the eviction mechanism was understood, the crash turned out to be
trivially reproducible on a test VM (same 6.1.192 kernel, stock ext4
module):

    # fallocate -l 1G /root/t.img
    # mkfs.ext4 -F /root/t.img
    # mount -o loop /root/t.img /mnt/t
    # dd if=/dev/urandom of=/mnt/t/f1 bs=1M count=200
    # sync
    # echo 3 > /proc/sys/vm/drop_caches
    # dd if=/dev/urandom of=/mnt/t/f2 bs=1M count=200

The drop_caches evicts the clean s_buddy_cache folios, and the next
block allocation in the writeback worker hits the NULL fast path within
seconds:

    BUG: kernel NULL pointer dereference, address: 0000000000000000
    CPU: 0 PID: 446 Comm: kworker/u4:4 Kdump: loaded
    Workqueue: writeback wb_workfn (flush-7:0)
    RIP: 0010:ext4_mb_load_buddy_gfp+0xc0/0x3a0 [ext4]
    Code: ... 48 3d 00 f0 ff ff 0f 87 ... <48> 8b 00 a8 04 ...
    RAX: 0000000000000000

Same RIP and the same instruction sequence as in both production crashes.

Verification
------------

I rebuilt the ext4 module against the same kernel and config with only
this fix applied (verified via srcversion that the patched module was
the one loaded) and re-ran the reproducer above for three full
write/drop_caches/write cycles: no oops, no ext4 warnings in dmesg.
The unpatched module panics on the first cycle.

Additional data
---------------

vmcores of both production crashes (captured by kdump) and of the
reproduced crash on the test VM are available on request.  No ext4
errors or warnings preceded the production oopses; the log simply ends
a few minutes before the kdump timestamp.

Aleksandr Panfilenko (1):
  ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp()

 fs/ext4/mballoc.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

-- 
2.34.1


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

* [PATCH 1/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp()
  2026-11-10 19:37 [PATCH 0/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp() Aleksandr Panfilenko
@ 2026-11-10 19:37 ` Aleksandr Panfilenko
  2026-11-10 19:44 ` [PATCH 0/1] " Matthew Wilcox
  1 sibling, 0 replies; 3+ messages in thread
From: Aleksandr Panfilenko @ 2026-11-10 19:37 UTC (permalink / raw)
  To: linux-ext4
  Cc: tytso, adilger, willy, stable, linux-kernel, Aleksandr Panfilenko

__filemap_get_folio() called without FGP_CREAT returns NULL, not an error
pointer, when the folio is not present in the page cache.  The fast path
in ext4_mb_load_buddy_gfp() only checks IS_ERR(folio) before evaluating
folio_test_uptodate(folio), so a NULL return leads to a NULL pointer
dereference in the folio flags test.

The buddy cache folios can in fact disappear from the page cache: they
are clean pages of the s_buddy_cache inode and can be reclaimed under
memory pressure, while nothing sets EXT4_MB_GRP_NEED_INIT again, so
ext4_mb_init_group() is not called to reload them.  We have hit this
twice in one day on 6.1.192 during background writeback on a server
under heavy page cache churn (fresh OS install + database restore):

    BUG: kernel NULL pointer dereference, address: 0000000000000000
    Oops: 0000 [#1] PREEMPT SMP NOPTI
    CPU: 51 PID: 137989 Comm: kworker/u161:1 Kdump: loaded
    Workqueue: writeback wb_workfn (flush-253:1)
    RIP: 0010:ext4_mb_load_buddy_gfp+0xc0/0x3a0 [ext4]
    Code: ... 48 3d 00 f0 ff ff 0f 87 ... <48> 8b 00 a8 04 ...
    RAX: 0000000000000000
    Call Trace:
     ext4_mb_regular_allocator+0x1ba/0x600 [ext4]
     ext4_mb_new_blocks+0x3a9/0x530 [ext4]
     ext4_ext_map_blocks+0x670/0x8a0 [ext4]
     ext4_map_blocks+0x22b/0x5d0 [ext4]
     mpage_map_one_extent+0x60/0x170 [ext4]
     mpage_map_and_submit_extent+0x76/0x300 [ext4]
     ext4_writepages+0x6f8/0x860 [ext4]
     ...

The faulting instruction is the folio flags load (mov rax,[rax] with
RAX == 0) between the IS_ERR() check and the PG_uptodate test.

Before the folio conversion this was handled correctly: the old code used
find_get_page_flags() and explicitly tested "page == NULL" first, in both
the bitmap lookup (converted by 99b150d84e49) and the buddy lookup
(converted by 5eea586b47f0).  Treat a NULL folio the same way as the
"not uptodate / locked" cases and fall back to the FGP_CREAT path, which
will re-create the folio.  Also switch the two folio_put() guards to
!IS_ERR_OR_NULL() so that we do not call folio_put(NULL) after taking
the slow path with a NULL folio.

The error-path fix in c6a6c9694aad ("ext4: fix error pointer dereference
in ext4_mb_load_buddy_gfp()") does not cover this case.

The bug reproduces deterministically on 6.1.192: loop-mount a fresh
ext4 filesystem, write ~200 MB of files, sync, then run
"echo 3 > /proc/sys/vm/drop_caches" and write more files.  The drop
evicts the buddy cache folios, and the next allocation in the writeback
worker hits the NULL fast path within seconds:

    RIP: 0010:ext4_mb_load_buddy_gfp+0xc0/0x3a0 [ext4]
    Workqueue: writeback wb_workfn (flush-7:0)
    RAX: 0000000000000000
    Call Trace:
     ext4_mb_regular_allocator+0x1ba/0x600 [ext4]
     ext4_mb_new_blocks+0x3a9/0x530 [ext4]
     ...

With this patch applied (the ext4 module rebuilt against the same
kernel and config), the same sequence completes cleanly over multiple
cycles, with no oops and no ext4 warnings in dmesg.

Fixes: 99b150d84e49 ("ext4: convert bd_bitmap_page to bd_bitmap_folio")
Fixes: 5eea586b47f0 ("ext4: convert bd_buddy_page to bd_buddy_folio")
Cc: stable@vger.kernel.org
Reported-by: Aleksandr Panfilenko <lexpasss@inbox.lv>
Signed-off-by: Aleksandr Panfilenko <lexpasss@inbox.lv>
---
 fs/ext4/mballoc.c | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index a15285224..11ca5a148 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -1715,7 +1715,8 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group,
 
 	/* Avoid locking the folio in the fast path ... */
 	folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_ACCESSED, 0);
-	if (IS_ERR(folio) || !folio_test_uptodate(folio) || folio_test_locked(folio)) {
+	if (IS_ERR_OR_NULL(folio) || !folio_test_uptodate(folio) ||
+	    folio_test_locked(folio)) {
 		/*
 		 * folio_test_locked is employed to detect ongoing folio
 		 * migrations, since concurrent migrations can lead to
@@ -1725,7 +1726,7 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group,
 		 * try to get the folio with lock in both cases to avoid
 		 * concurrency.
 		 */
-		if (!IS_ERR(folio))
+		if (!IS_ERR_OR_NULL(folio))
 			folio_put(folio);
 		folio = __filemap_get_folio(inode->i_mapping, pnum,
 				FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
@@ -1774,8 +1775,9 @@ ext4_mb_load_buddy_gfp(struct super_block *sb, ext4_group_t group,
 
 	/* we need another folio for the buddy */
 	folio = __filemap_get_folio(inode->i_mapping, pnum, FGP_ACCESSED, 0);
-	if (IS_ERR(folio) || !folio_test_uptodate(folio) || folio_test_locked(folio)) {
-		if (!IS_ERR(folio))
+	if (IS_ERR_OR_NULL(folio) || !folio_test_uptodate(folio) ||
+	    folio_test_locked(folio)) {
+		if (!IS_ERR_OR_NULL(folio))
 			folio_put(folio);
 		folio = __filemap_get_folio(inode->i_mapping, pnum,
 				FGP_LOCK | FGP_ACCESSED | FGP_CREAT, gfp);
-- 
2.34.1


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

* Re: [PATCH 0/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp()
  2026-11-10 19:37 [PATCH 0/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp() Aleksandr Panfilenko
  2026-11-10 19:37 ` [PATCH 1/1] " Aleksandr Panfilenko
@ 2026-11-10 19:44 ` Matthew Wilcox
  1 sibling, 0 replies; 3+ messages in thread
From: Matthew Wilcox @ 2026-11-10 19:44 UTC (permalink / raw)
  To: Aleksandr Panfilenko; +Cc: linux-ext4, tytso, adilger, stable, linux-kernel

On Tue, Nov 10, 2026 at 10:37:10PM +0300, Aleksandr Panfilenko wrote:
> We have hit a kernel NULL pointer dereference in ext4_mb_load_buddy_gfp()

https://lore.kernel.org/linux-ext4/20261102-ext4-ntfs3-null-folio-v1-0-4fd91ee23a4a@elliotjb.com/T/#t

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

end of thread, other threads:[~2026-11-10 19:44 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-10 19:37 [PATCH 0/1] ext4: fix NULL pointer dereference in ext4_mb_load_buddy_gfp() Aleksandr Panfilenko
2026-11-10 19:37 ` [PATCH 1/1] " Aleksandr Panfilenko
2026-11-10 19:44 ` [PATCH 0/1] " Matthew Wilcox

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

all inboxes | Powered by JetHome®