* [PATCH] jfs: add dmap integrity check to prevent out-of-bounds tree access
2026-11-03 4:33 [syzbot] Monthly jfs report (Nov 2026) syzbot
@ 2026-11-10 10:48 ` Shuai Liu
0 siblings, 0 replies; 2+ messages in thread
From: Shuai Liu @ 2026-11-10 10:48 UTC (permalink / raw)
To: jfs-discussion
Cc: shaggy, sj, syzbot+ab18fa9c959320611727, linux-kernel, Shuai Liu
Add check_dmap() to validate dmap structure integrity, mirroring
check_dmapctl() which already guards the dmapctl pages.
A corrupted on-disk dmap page (e.g. a fuzzed filesystem image) carries a
garbage dmaptree header. The free path does not validate it before use:
dbFreeBits() -> dbJoin() reads tp->dmt_leafidx straight from the on-disk
tree header to locate the leaves, and dbAdjTree() then computes
lp = leafno + dmt_leafidx into the stree[] array. With the observed
corruption leafidx = 0x5ff20055 (expected LEAFIND = 85) the computed
index reaches 0x5ff20056, far beyond TREESIZE (341), triggering the
WARN_ON_ONCE added to dbAdjTree() by commit a174706ba4da ("jfs: add a
check to prevent array-index-out-of-bounds in dbAdjTree") -- a
mitigation, not a fix. The alloc path got a leafidx sanity check in
dbFindLeaf() by commit d64ff0d23067 ("jfs: check if leafidx greater
than num leaves per dmap tree") and the dmapctl pages are verified by
check_dmapctl() from commit cce219b203c4 ("jfs: add dmapctl integrity
check to prevent invalid operations"), but the dmap leaf pages
themselves are still trusted.
Validate the dmap tree header (nleafs, l2nleafs, leafidx, height,
budmin and the leaf values) in check_dmap() and call it when a dmap
page is about to be modified: dbAllocDmap(), dbFreeDmap() and
dbAllocDmapBU(). Return -EIO on corruption so the caller aborts the
map update instead of operating on a bogus tree.
Verified with the syzbot reproducer under QEMU (x86_64_defconfig +
CONFIG_JFS_FS=y): without the fix the jfsCommit thread reliably
triggers
WARNING: fs/jfs/jfs_dmap.c:2962 at dbAdjTree+0x2fa/0x3f0, CPU#1: jfsCommit
dbJoin+0x24b/0x2b0
dbFreeBits+0x170/0x8d0
dbFreeDmap+0x62/0x1c0
dbFree+0x266/0x550
txFreeMap+0x2d3/0xe10
xtTruncate+0x24fe/0x2e20
jfs_free_zero_link+0x262/0x4c0
jfs_evict_inode+0x40a/0x4a0
with R12 = 0x5ff20056 (= 1 + corrupted dmt_leafidx), reproducing on
every run; with the check in place three consecutive runs complete the
mount and file operations with no WARNING, Oops or jfs_error report.
Reported-by: syzbot+ab18fa9c959320611727@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=ab18fa9c959320611727
Signed-off-by: Shuai Liu <847807867@qq.com>
---
fs/jfs/jfs_dmap.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 77 insertions(+)
diff --git a/fs/jfs/jfs_dmap.c b/fs/jfs/jfs_dmap.c
index a841cf21da7de..4616ded254ba6 100644
--- a/fs/jfs/jfs_dmap.c
+++ b/fs/jfs/jfs_dmap.c
@@ -220,6 +220,74 @@ static bool check_dmapctl(struct dmapctl *dcp)
return true;
}
+/*
+ * check_dmap - Validate integrity of a dmap structure
+ * @dp: Pointer to the dmap structure to check
+ *
+ * Return: true if valid, false if corrupted
+ */
+static bool check_dmap(struct dmap *dp)
+{
+ s8 budmin = dp->tree.budmin;
+ u32 nleafs, l2nleafs, leafidx, height;
+ int i;
+
+ nleafs = le32_to_cpu(dp->tree.nleafs);
+ /* A dmap tree always covers LPERDMAP leaves */
+ if (unlikely(nleafs != LPERDMAP)) {
+ jfs_err("dmap: invalid nleafs %u (expected %u)",
+ nleafs, LPERDMAP);
+ return false;
+ }
+
+ l2nleafs = le32_to_cpu(dp->tree.l2nleafs);
+ if (unlikely(l2nleafs != L2LPERDMAP)) {
+ jfs_err("dmap: invalid l2nleafs %u (expected %u)",
+ l2nleafs, L2LPERDMAP);
+ return false;
+ }
+
+ leafidx = le32_to_cpu(dp->tree.leafidx);
+ /* Check leaf index matches expected position */
+ if (unlikely(leafidx != LEAFIND)) {
+ jfs_err("dmap: invalid leafidx %u (expected %u)",
+ leafidx, LEAFIND);
+ return false;
+ }
+
+ height = le32_to_cpu(dp->tree.height);
+ /* Check tree height is within valid range */
+ if (unlikely(height > (L2LPERDMAP >> 1))) {
+ jfs_err("dmap: invalid height %u (max %u)",
+ height, L2LPERDMAP >> 1);
+ return false;
+ }
+
+ /* Check budmin is valid (cannot be NOFREE for a full tree) */
+ if (unlikely(budmin < BUDMIN)) {
+ jfs_err("dmap: invalid budmin %d (min %d)",
+ budmin, BUDMIN);
+ return false;
+ }
+
+ /* Check leaf nodes have valid values */
+ for (i = leafidx; i < leafidx + nleafs; i++) {
+ s8 val = dp->tree.stree[i];
+
+ if (unlikely(val < NOFREE)) {
+ jfs_err("dmap: invalid leaf value %d at index %d",
+ val, i);
+ return false;
+ } else if (unlikely(val > 31)) {
+ jfs_err("dmap: leaf value %d too large at index %d",
+ val, i);
+ return false;
+ }
+ }
+
+ return true;
+}
+
/*
* NAME: dbMount()
*
@@ -2112,6 +2180,9 @@ static int dbAllocDmap(struct bmap * bmp, struct dmap * dp, s64 blkno,
s8 oldroot;
int rc;
+ if (unlikely(!check_dmap(dp)))
+ return -EIO;
+
/* save the current value of the root (i.e. maximum free string)
* of the dmap tree.
*/
@@ -2167,6 +2238,9 @@ static int dbFreeDmap(struct bmap * bmp, struct dmap * dp, s64 blkno,
s8 oldroot;
int rc = 0, word;
+ if (unlikely(!check_dmap(dp)))
+ return -EIO;
+
/* save the current value of the root (i.e. maximum free string)
* of the dmap tree.
*/
@@ -3338,6 +3412,9 @@ static int dbAllocDmapBU(struct bmap * bmp, struct dmap * dp, s64 blkno,
s8 oldroot;
struct dmaptree *tp = (struct dmaptree *) & dp->tree;
+ if (unlikely(!check_dmap(dp)))
+ return -EIO;
+
/* save the current value of the root (i.e. maximum free string)
* of the dmap tree.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 2+ messages in thread