Re: [PATCH] jfs: add dmaptree integrity check to prevent array-index-out-of-bounds
Slawomir Stepien <[email protected]> Wed, 29 Jul 2026 08:24:30 +0200
| Newsgroups | dev.linux.lists.syzbot,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <ammcnq9Z0SONR1UT@nr200> |
Hello A gentle reminder about this PATCH. On lip 06, 2026 11:36, syzbot wrote: > From: Slawomir Stepien <[email protected]> > > An array-index-out-of-bounds issue occurs in dbJoin() when the JFS > filesystem attempts to free blocks using a corrupted dmap structure read > from disk. > > The JFS filesystem uses a binary buddy system to manage free space. The > state of the buddy system is stored in dmap (for leaf levels) and dmapctl > (for upper levels) structures on disk. When a dmap is read from disk, its > dmaptree structure is not fully validated. Specifically, the leafidx field > (which indicates the index of the first leaf in the stree array) is read > directly from disk and used to compute pointers to the leaf nodes. > > If leafidx is corrupted (e.g., set to 0xffffffff), the computed pointer > will point out of bounds of the stree array. When dbFreeDmap() is called to > free blocks, it calls dbFreeBits(), which in turn calls dbJoin() to update > the buddy system. dbJoin() uses the corrupted leafidx to access the leaf > nodes, resulting in an out-of-bounds memory access. > > While a similar integrity check (check_dmapctl()) was recently added for > dmapctl structures, the dmap structures (dmaptree) were left unchecked. > > Introduce a check_dmaptree() function, similar to the existing > check_dmapctl() function, to validate the integrity of the dmaptree > structure when it is used. The function verifies that fields like nleafs, > l2nleafs, leafidx, height, and budmin are within their expected bounds and > internally consistent. It also ensures that the leaf nodes fit within the > stree array and have valid values. > > Call check_dmaptree() at the entry points of functions that operate on the > dmap structure (dbFreeDmap(), dbAllocNext(), dbAllocNear(), > dbAllocDmapLev(), dbAllocDmapBU(), and dbAllocDmap()). If the validation > fails, log an error and return -EIO to prevent further processing of the > corrupted dmap. This also replaces the existing partial checks for leafidx > in dbAllocNext() and dbAllocNear(). > > UBSAN: array-index-out-of-bounds in fs/jfs/jfs_dmap.c:2867:24 > index 4294967295 is out of range for type 's8[1365]' (aka 'signed > char[1365]') > CPU: 0 UID: 0 PID: 123 Comm: jfsCommit Not tainted > Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS > 1.16.3-debian-1.16.3-2 04/01/2014 > Call Trace: > <TASK> > dump_stack_lvl+0xe8/0x150 > ubsan_epilogue+0xa/0x30 > __ubsan_handle_out_of_bounds+0xe8/0xf0 > dbJoin+0xcc4/0xd60 > dbFreeBits+0x4a2/0xd70 > dbFreeDmap > dbFree+0x324/0x650 > txFreeMap+0x9e6/0xde0 > xtTruncate+0xd16/0x2eb0 > jfs_free_zero_link+0x35b/0x4c0 > jfs_evict_inode+0x356/0x430 > evict+0x624/0xb50 > txLazyCommit > jfs_lazycommit+0x44c/0xac0 > kthread+0x388/0x470 > ret_from_fork+0x514/0xb70 > ret_from_fork_asm+0x1a/0x30 > </TASK> > > Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2") > Assisted-by: good-balanced Gemini:gemini-3.1-pro-preview Gemini:gemini-3-flash-preview best-expensive syzbot > Reported-by: [email protected] > Closes: https://syzkaller.appspot.com/bug?extid=667a6d667592227b1452 > Link: https://syzkaller.appspot.com/ai_job?id=5ef1fb0c-3b76-46ae-b8c4-426995b84893 > Signed-off-by: Slawomir Stepien <[email protected]> > > --- > diff --git a/fs/jfs/jfs_dmap.c b/fs/jfs/jfs_dmap.c > index a841cf21d..357a34cb3 100644 > --- a/fs/jfs/jfs_dmap.c > +++ b/fs/jfs/jfs_dmap.c > @@ -220,6 +220,85 @@ static bool check_dmapctl(struct dmapctl *dcp) > return true; > } > > +/* > + * check_dmaptree - Validate integrity of a dmaptree structure > + * @dtp: Pointer to the dmaptree structure to check > + * > + * Return: true if valid, false if corrupted > + */ > +static bool check_dmaptree(struct dmaptree *dtp) > +{ > + u32 nleafs, l2nleafs, leafidx, height; > + int i; > + > + nleafs = le32_to_cpu(dtp->nleafs); > + /* Check basic field ranges */ > + if (unlikely(nleafs > LPERDMAP)) { > + jfs_err("dmaptree: invalid nleafs %u (max %u)", nleafs, > + LPERDMAP); > + return false; > + } > + > + l2nleafs = le32_to_cpu(dtp->l2nleafs); > + if (unlikely(l2nleafs > L2LPERDMAP)) { > + jfs_err("dmaptree: invalid l2nleafs %u (max %u)", l2nleafs, > + L2LPERDMAP); > + return false; > + } > + > + /* Verify nleafs matches l2nleafs (must be power of two) */ > + if (unlikely((1U << l2nleafs) != nleafs)) { > + jfs_err("dmaptree: nleafs %u != 2^%u", nleafs, l2nleafs); > + return false; > + } > + > + leafidx = le32_to_cpu(dtp->leafidx); > + /* Check leaf index matches expected position */ > + if (unlikely(leafidx != LEAFIND)) { > + jfs_err("dmaptree: invalid leafidx %u (expected %u)", leafidx, > + LEAFIND); > + return false; > + } > + > + height = le32_to_cpu(dtp->height); > + /* Check tree height is within valid range */ > + if (unlikely(height > 4)) { > + jfs_err("dmaptree: invalid height %u (max 4)", height); > + return false; > + } > + > + /* Check budmin is valid */ > + if (unlikely(dtp->budmin < BUDMIN)) { > + jfs_err("dmaptree: invalid budmin %d (min %d)", dtp->budmin, > + BUDMIN); > + return false; > + } > + > + /* Check leaf nodes fit within stree array */ > + if (unlikely(leafidx + nleafs > TREESIZE)) { > + jfs_err("dmaptree: leaf range exceeds stree size (end %u > %u)", > + leafidx + nleafs, TREESIZE); > + return false; > + } > + > + /* Check leaf nodes have valid values */ > + for (i = leafidx; i < leafidx + nleafs; i++) { > + s8 val = dtp->stree[i]; > + > + if (unlikely(val < NOFREE)) { > + jfs_err("dmaptree: invalid leaf value %d at index %d", > + val, i); > + return false; > + } else if (unlikely(val > 31)) { > + jfs_err("dmaptree: leaf value %d too large at index %d", > + val, i); > + return false; > + } > + } > + > + return true; > +} > + > /* > * NAME: dbMount() > * > @@ -1163,7 +1242,7 @@ static int dbAllocNext(struct bmap * bmp, struct dmap * dp, s64 blkno, > s8 *leaf; > u32 mask; > > - if (dp->tree.leafidx != cpu_to_le32(LEAFIND)) { > + if (unlikely(!check_dmaptree(&dp->tree))) { > jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > return -EIO; > } > @@ -1293,7 +1372,7 @@ dbAllocNear(struct bmap * bmp, > int word, lword, rc; > s8 *leaf; > > - if (dp->tree.leafidx != cpu_to_le32(LEAFIND)) { > + if (unlikely(!check_dmaptree(&dp->tree))) { > jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > return -EIO; > } > @@ -2046,6 +2125,11 @@ dbAllocDmapLev(struct bmap * bmp, > s64 blkno; > int leafidx, rc; > > + if (unlikely(!check_dmaptree(&dp->tree))) { > + jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > + return -EIO; > + } > + > /* can't be more than a dmaps worth of blocks */ > assert(l2nb <= L2BPERDMAP); > > @@ -2112,6 +2196,11 @@ static int dbAllocDmap(struct bmap * bmp, struct dmap * dp, s64 blkno, > s8 oldroot; > int rc; > > + if (unlikely(!check_dmaptree(&dp->tree))) { > + jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > + return -EIO; > + } > + > /* save the current value of the root (i.e. maximum free string) > * of the dmap tree. > */ > @@ -2167,6 +2256,11 @@ static int dbFreeDmap(struct bmap * bmp, struct dmap * dp, s64 blkno, > s8 oldroot; > int rc = 0, word; > > + if (unlikely(!check_dmaptree(&dp->tree))) { > + jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > + return -EIO; > + } > + > /* save the current value of the root (i.e. maximum free string) > * of the dmap tree. > */ > @@ -3338,6 +3432,11 @@ static int dbAllocDmapBU(struct bmap * bmp, struct dmap * dp, s64 blkno, > s8 oldroot; > struct dmaptree *tp = (struct dmaptree *) & dp->tree; > > + if (unlikely(!check_dmaptree(tp))) { > + jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmap page\n"); > + return -EIO; > + } > + > /* save the current value of the root (i.e. maximum free string) > * of the dmap tree. > */ > > > base-commit: dc59e4fea9d83f03bad6bddf3fa2e52491777482 -- Slawomir Stepien