[PATCH] jfs: fix use-after-free in jfs_readdir on legacy non-indexed directories

From: Shuai Liu

Date: Thu Nov 12 2026 - 07:53:09 EST


From: Jenny Guanni Qu <qguanni@xxxxxxxxx>

In the legacy (non-indexed) readdir path, jfs_readdir calls dtReadNext
to advance through the directory's dtree. When dtReadNext reaches the
last leaf page, it frees the metapage via DT_PUTPAGE(mp), sets bn = -1,
and falls through to the out label, which stores the stale mp pointer
into btstack->top->mp.

Back in jfs_readdir, DT_GETSEARCH dereferences btstack.top->mp to
extract the dtpage pointer. It guards this with "if (BN)", intending to
distinguish root (bn=0) from non-root pages, but the -1 EOF sentinel
also passes this check, causing a read from the freed metapage slab
object.

The existing code already had the correct EOF check ("offset beyond
directory eof?") immediately after DT_GETSEARCH, but the dereference
inside the macro fires first. Fix this by moving the bn < 0 check
before the DT_GETSEARCH call, reading btstack.top->bn directly since
the local bn variable is not yet populated.

Also null out the stale mp pointer in dtReadNext when bn is -1, so the
btstack does not retain a dangling pointer to freed memory.

The buggy ordering predates the git history; the EOF check has followed
DT_GETSEARCH since the initial JFS merge, so there is no specific
commit to reference in a Fixes: tag.

Link: https://lore.kernel.org/all/20260612191204.95876-1-qguanni@xxxxxxxxx/
Reported-by: syzbot+a98891ce2318fe7baf05@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=a98891ce2318fe7baf05
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Jenny Guanni Qu <qguanni@xxxxxxxxx>
Signed-off-by: Shuai Liu <847807867@xxxxxx>
---

(Takeover resend) This is Jenny Guanni Qu's patch from:
https://lore.kernel.org/all/20260612191204.95876-1-qguanni@xxxxxxxxx/
Posted five months ago without any response, while the bug keeps
crashing on current kernels (latest syzbot crash on 2026-11-11).
Keeping the original authorship and analysis.

Independently verified on v7.4-rc2 with the syzbot C reproducer:
baseline kernel hits "BUG: KASAN: slab-use-after-free Read in
jfs_readdir" (object cache: jfs_mp, allocated in dtSplitRoot,
freed by release_metapage) within one repro loop; with this patch
the same reproducer runs 2900+ loop iterations over 300 seconds
with zero KASAN reports.

fs/jfs/jfs_dtree.c | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
index 8ce6e4458cc25..acc5f1f9029ad 100644
--- a/fs/jfs/jfs_dtree.c
+++ b/fs/jfs/jfs_dtree.c
@@ -2874,14 +2874,14 @@ int jfs_readdir(struct file *file, struct dir_context *ctx)
ctx->pos = DIREND;
return 0;
}
- /* get start leaf page and index */
- DT_GETSEARCH(ip, btstack.top, bn, mp, p, index);
-
/* offset beyond directory eof ? */
- if (bn < 0) {
+ if (btstack.top->bn < 0) {
ctx->pos = DIREND;
return 0;
}
+
+ /* get start leaf page and index */
+ DT_GETSEARCH(ip, btstack.top, bn, mp, p, index);
}

dirent_buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
@@ -3293,7 +3293,7 @@ static int dtReadNext(struct inode *ip, loff_t * offset,
btsp = btstack->top;
btsp->bn = bn;
btsp->index = dtoffset->index;
- btsp->mp = mp;
+ btsp->mp = (bn == -1) ? NULL : mp;

return 0;
}
--
2.43.0