From c83abc766aeb153e69cb46363bf7c9de0c9f3268 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 20 Nov 2025 23:43:50 +0800 Subject: jfs: add dtroot integrity check to prevent index out-of-bounds Add check_dtroot() to validate dtroot_t integrity, focusing on preventing index/pointer overflows from on-disk corruption. Key checks: - freecnt bounded by [0, DTROOTMAXSLOT-1] (slot[0] reserved for header). - freelist validity: -1 when freecnt=0; 1~DTROOTMAXSLOT-1 when non-zero, with linked list checks (no duplicates, proper termination via next=-1). - stbl bounds: nextindex within stbl array size; entries within 0~8, no duplicates (excluding idx=0). Invoked in copy_from_dinode() when loading directory inodes, catching corruption early before directory operations trigger out-of-bounds access. This fixes the following UBSAN warning. [ 101.832754][ T5960] ------------[ cut here ]------------ [ 101.832762][ T5960] UBSAN: array-index-out-of-bounds in fs/jfs/jfs_dtree.c:3713:8 [ 101.832792][ T5960] index -1 is out of range for type 'struct dtslot[128]' [ 101.832807][ T5960] CPU: 2 UID: 0 PID: 5960 Comm: 5f7f0caf9979e9d Tainted: G E 6.18.0-rc4-00250-g2603eb907f03 #119 PREEMPT_{RT,(full [ 101.832817][ T5960] Tainted: [E]=UNSIGNED_MODULE [ 101.832819][ T5960] Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [ 101.832823][ T5960] Call Trace: [ 101.832833][ T5960] [ 101.832838][ T5960] dump_stack_lvl+0x189/0x250 [ 101.832909][ T5960] ? __pfx_dump_stack_lvl+0x10/0x10 [ 101.832925][ T5960] ? __pfx__printk+0x10/0x10 [ 101.832934][ T5960] ? rt_mutex_slowunlock+0x493/0x8a0 [ 101.832959][ T5960] ubsan_epilogue+0xa/0x40 [ 101.832966][ T5960] __ubsan_handle_out_of_bounds+0xe9/0xf0 [ 101.833007][ T5960] dtInsertEntry+0x936/0x1430 [jfs] [ 101.833094][ T5960] dtSplitPage+0x2c8b/0x3ed0 [jfs] [ 101.833177][ T5960] ? __pfx_rt_mutex_slowunlock+0x10/0x10 [ 101.833193][ T5960] dtInsert+0x109b/0x6000 [jfs] [ 101.833283][ T5960] ? rt_mutex_slowunlock+0x493/0x8a0 [ 101.833296][ T5960] ? __pfx_rt_mutex_slowunlock+0x10/0x10 [ 101.833307][ T5960] ? rt_spin_unlock+0x161/0x200 [ 101.833315][ T5960] ? __pfx_dtInsert+0x10/0x10 [jfs] [ 101.833391][ T5960] ? txLock+0xaf9/0x1cb0 [jfs] [ 101.833477][ T5960] ? dtInitRoot+0x22a/0x670 [jfs] [ 101.833556][ T5960] jfs_mkdir+0x6ec/0xa70 [jfs] [ 101.833636][ T5960] ? __pfx_jfs_mkdir+0x10/0x10 [jfs] [ 101.833721][ T5960] ? generic_permission+0x2e5/0x690 [ 101.833760][ T5960] ? bpf_lsm_inode_mkdir+0x9/0x20 [ 101.833776][ T5960] vfs_mkdir+0x306/0x510 [ 101.833786][ T5960] do_mkdirat+0x247/0x590 [ 101.833795][ T5960] ? __pfx_do_mkdirat+0x10/0x10 [ 101.833804][ T5960] ? getname_flags+0x1e5/0x540 [ 101.833815][ T5960] __x64_sys_mkdir+0x6c/0x80 [ 101.833823][ T5960] do_syscall_64+0xfa/0xfa0 [ 101.833832][ T5960] ? lockdep_hardirqs_on+0x9c/0x150 [ 101.833840][ T5960] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f [ 101.833847][ T5960] ? exc_page_fault+0xab/0x100 [ 101.833856][ T5960] entry_SYSCALL_64_after_hwframe+0x77/0x7f Signed-off-by: Yun Zhou Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_dtree.c | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/jfs/jfs_dtree.h | 2 ++ fs/jfs/jfs_imap.c | 4 +++ 3 files changed, 92 insertions(+) diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index 9ab3f2fc61d1..8abd9c7663ea 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -4297,3 +4297,89 @@ int dtModify(tid_t tid, struct inode *ip, return 0; } + +bool check_dtroot(dtroot_t *p) +{ + DECLARE_BITMAP(bitmap, DTROOTMAXSLOT) = {0}; + int i; + + /* freecnt cannot be negative or exceed DTROOTMAXSLOT-1 + * (since slot[0] is occupied by the header). + */ + if (unlikely(p->header.freecnt < 0 || + p->header.freecnt > DTROOTMAXSLOT - 1)) { + jfs_err("Bad freecnt:%d in dtroot\n", p->header.freecnt); + return false; + } else if (p->header.freecnt == 0) { + /* No free slots: freelist must be -1 */ + if (unlikely(p->header.freelist != -1)) { + jfs_err("freecnt=0, but freelist=%d in dtroot\n", + p->header.freelist); + return false; + } + } else { + int fsi, i; + /* When there are free slots, freelist must be a valid slot index in + * 1~DTROOTMAXSLOT-1(since slot[0] is occupied by the header). + */ + if (unlikely(p->header.freelist < 1 || + p->header.freelist >= DTROOTMAXSLOT)) { + jfs_err("Bad freelist:%d in dtroot\n", p->header.freelist); + return false; + } + + /* Traverse the free list to check validity of all node indices */ + fsi = p->header.freelist; + for (i = 0; i < p->header.freecnt - 1; i++) { + /* Check for duplicate indices in the free list */ + if (unlikely(__test_and_set_bit(fsi, bitmap))) { + jfs_err("duplicate index%d in slot in dtroot\n", fsi); + return false; + } + fsi = p->slot[fsi].next; + + /* Ensure the next slot index in the free list is valid */ + if (unlikely(fsi < 1 || fsi >= DTROOTMAXSLOT)) { + jfs_err("Bad index:%d in slot in dtroot\n", fsi); + return false; + } + } + + /* The last node in the free list must terminate with next = -1 */ + if (unlikely(p->slot[fsi].next != -1)) { + jfs_err("Bad next:%d of the last slot in dtroot\n", + p->slot[fsi].next); + return false; + } + } + + /* Validate nextindex (next free entry index in stbl) + * stbl array has size 8 (indices 0~7). + * It may get set to 8 when the last free slot has been filled. + */ + if (unlikely(p->header.nextindex > ARRAY_SIZE(p->header.stbl))) { + jfs_err("Bad nextindex:%d in dtroot\n", p->header.nextindex); + return false; + } + + /* Validate index validity of stbl array (8 elements) + * Each entry in stbl is a slot index, with valid range: -1 (invalid) + * or 0~8 (slot[0]~slot[8]) + */ + for (i = 0; i < p->header.nextindex; i++) { + int idx = p->header.stbl[i]; + + if (unlikely(idx < 0 || idx >= 9)) { + jfs_err("Bad index:%d of stbl[%d] in dtroot\n", idx, i); + return false; /* stbl entry points out of slot array range */ + } + + /* Check for duplicate valid indices (skip check for idx=0) */ + if (unlikely(idx && __test_and_set_bit(idx, bitmap))) { + jfs_err("Duplicate index:%d in stbl in dtroot\n", idx); + return false; + } + } + + return true; +} diff --git a/fs/jfs/jfs_dtree.h b/fs/jfs/jfs_dtree.h index 1758289647a0..94dc16123c87 100644 --- a/fs/jfs/jfs_dtree.h +++ b/fs/jfs/jfs_dtree.h @@ -253,4 +253,6 @@ extern int dtModify(tid_t tid, struct inode *ip, struct component_name * key, ino_t * orig_ino, ino_t new_ino, int flag); extern int jfs_readdir(struct file *file, struct dir_context *ctx); + +extern bool check_dtroot(dtroot_t *p); #endif /* !_H_JFS_DTREE */ diff --git a/fs/jfs/jfs_imap.c b/fs/jfs/jfs_imap.c index 294a67327c73..fbb5f7966b75 100644 --- a/fs/jfs/jfs_imap.c +++ b/fs/jfs/jfs_imap.c @@ -3102,6 +3102,10 @@ static int copy_from_dinode(struct dinode * dip, struct inode *ip) if (S_ISDIR(ip->i_mode)) { memcpy(&jfs_ip->u.dir, &dip->u._dir, 384); + if (!check_dtroot(&jfs_ip->i_dtroot)) { + jfs_error(ip->i_sb, "Corrupt dtroot\n"); + return -EIO; + } } else if (S_ISREG(ip->i_mode) || S_ISLNK(ip->i_mode)) { memcpy(&jfs_ip->i_xtroot, &dip->di_xtroot, 288); } else -- cgit v1.2.3 From 119e448bb50a5b2626e248c01f439095766e248b Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Thu, 20 Nov 2025 23:44:00 +0800 Subject: jfs: add dtpage integrity check to prevent index/pointer overflows Add check_dtpage() to validate dtpage_t integrity, focusing on preventing index/pointer overflows from on-disk corruption. Key checks: - maxslot must be exactly DTPAGEMAXSLOT (128) as defined for dtpage slot array. - freecnt bounded by [0, DTPAGEMAXSLOT-1] (slot[0] reserved for header). - freelist validity: -1 when freecnt=0; 1~DTPAGEMAXSLOT-1 when non-zero, with linked list checks (no duplicates, proper termination via next=-1). - stblindex bounds: must be within range that avoids overlapping with stbl itself (stblindex < DTPAGEMAXSLOT - stblsize). - nextindex bounded by stbl size (stblsize << L2DTSLOTSIZE). stbl entries validity: within 1~DTPAGEMAXSLOT-1, no duplicates(excluding invalid entries marked as -1). Invoked when loading dtpage (in BT_GETPAGE macro context) to catch corruption early before directory operations trigger out-of-bounds access. Signed-off-by: Yun Zhou Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_dtree.c | 109 +++++++++++++++++++++++++++++++++++++++++++++++++++-- fs/jfs/jfs_dtree.h | 2 + 2 files changed, 107 insertions(+), 4 deletions(-) diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index 8abd9c7663ea..7669e268fe35 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -115,10 +115,7 @@ struct dtsplit { do { \ BT_GETPAGE(IP, BN, MP, dtpage_t, SIZE, P, RC, i_dtroot); \ if (!(RC)) { \ - if (((P)->header.nextindex > \ - (((BN) == 0) ? DTROOTMAXSLOT : (P)->header.maxslot)) || \ - ((BN) && (((P)->header.maxslot > DTPAGEMAXSLOT) || \ - ((P)->header.stblindex >= DTPAGEMAXSLOT)))) { \ + if ((BN) && !check_dtpage(P)) { \ BT_PUTPAGE(MP); \ jfs_error((IP)->i_sb, \ "DT_GETPAGE: dtree page corrupt\n"); \ @@ -4383,3 +4380,107 @@ bool check_dtroot(dtroot_t *p) return true; } + +bool check_dtpage(dtpage_t *p) +{ + DECLARE_BITMAP(bitmap, DTPAGEMAXSLOT) = {0}; + const int stblsize = ((PSIZE >> L2DTSLOTSIZE) + 31) >> L2DTSLOTSIZE; + int i; + + /* Validate maxslot (maximum number of slots in the page) + * dtpage_t slot array is defined to hold up to DTPAGEMAXSLOT (128) slots + */ + if (unlikely(p->header.maxslot != DTPAGEMAXSLOT)) { + jfs_err("Bad maxslot:%d in dtpage (expected %d)\n", + p->header.maxslot, DTPAGEMAXSLOT); + return false; + } + + /* freecnt cannot be negative or exceed DTPAGEMAXSLOT-1 + * (since slot[0] is occupied by the header). + */ + if (unlikely(p->header.freecnt < 0 || + p->header.freecnt > DTPAGEMAXSLOT - 1)) { + jfs_err("Bad freecnt:%d in dtpage\n", p->header.freecnt); + return false; + } else if (p->header.freecnt == 0) { + /* No free slots: freelist must be -1 */ + if (unlikely(p->header.freelist != -1)) { + jfs_err("freecnt=0 but freelist=%d in dtpage\n", + p->header.freelist); + return false; + } + } else { + int fsi; + /* When there are free slots, freelist must be a valid slot index in + * 1~DTROOTMAXSLOT-1(since slot[0] is occupied by the header). + */ + if (unlikely(p->header.freelist < 1 || + p->header.freelist >= DTPAGEMAXSLOT)) { + jfs_err("Bad freelist:%d in dtpage\n", p->header.freelist); + return false; + } + + /* Traverse the free list to check validity of all node indices */ + fsi = p->header.freelist; + for (i = 0; i < p->header.freecnt - 1; i++) { + /* Check for duplicate indices in the free list */ + if (unlikely(__test_and_set_bit(fsi, bitmap))) { + jfs_err("duplicate index%d in slot in dtpage\n", fsi); + return false; + } + fsi = p->slot[fsi].next; + + /* Ensure the next slot index in the free list is valid */ + if (unlikely(fsi < 1 || fsi >= DTPAGEMAXSLOT)) { + jfs_err("Bad index:%d in slot in dtpage\n", fsi); + return false; + } + } + + /* The last node in the free list must terminate with next = -1 */ + if (unlikely(p->slot[fsi].next != -1)) { + jfs_err("Bad next:%d of the last slot in dtpage\n", + p->slot[fsi].next); + return false; + } + } + + /* stbl must be little then DTPAGEMAXSLOT */ + if (unlikely(p->header.stblindex >= DTPAGEMAXSLOT - stblsize)) { + jfs_err("Bad stblindex:%d in dtpage (stbl size %d)\n", + p->header.stblindex, stblsize); + return false; + } + + /* nextindex must be little then stblsize*32 */ + if (unlikely(p->header.nextindex > (stblsize << L2DTSLOTSIZE))) { + jfs_err("Bad nextindex:%d in dtpage (stbl size %d)\n", + p->header.nextindex, stblsize); + return false; + } + + /* Validate stbl entries + * Each entry is a slot index, valid range: -1 (invalid) or + * [0, nextindex-1] (valid data slots) + * (stblindex and higher slots are reserved for stbl itself) + */ + for (i = 0; i < p->header.nextindex; i++) { + int idx = DT_GETSTBL(p)[i]; + + /* Check if index is out of valid data slot range */ + if (unlikely(idx < 1 || idx >= DTPAGEMAXSLOT)) { + jfs_err("Bad stbl[%d] index:%d (stblindex %d) in dtpage\n", + i, idx, p->header.stblindex); + return false; + } + + /* Check for duplicate valid indices (skip -1) */ + if (unlikely(__test_and_set_bit(idx, bitmap))) { + jfs_err("Duplicate index:%d in stbl of dtpage\n", idx); + return false; + } + } + + return true; +} diff --git a/fs/jfs/jfs_dtree.h b/fs/jfs/jfs_dtree.h index 94dc16123c87..dfc87b6690a9 100644 --- a/fs/jfs/jfs_dtree.h +++ b/fs/jfs/jfs_dtree.h @@ -255,4 +255,6 @@ extern int dtModify(tid_t tid, struct inode *ip, struct component_name * key, extern int jfs_readdir(struct file *file, struct dir_context *ctx); extern bool check_dtroot(dtroot_t *p); + +extern bool check_dtpage(dtpage_t *p); #endif /* !_H_JFS_DTREE */ -- cgit v1.2.3 From cce219b203c4b9cb445e910c7090d1f58af847c5 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Fri, 28 Nov 2025 23:51:50 +0800 Subject: jfs: add dmapctl integrity check to prevent invalid operations Add check_dmapctl() to validate dmapctl structure integrity, focusing on preventing invalid operations caused by on-disk corruption. Key checks: - nleafs bounded by [0, LPERCTL] (maximum leaf nodes per dmapctl). - l2nleafs bounded by [0, L2LPERCTL] and consistent with nleafs (nleafs must be 2^l2nleafs). - leafidx must be exactly CTLLEAFIND (expected leaf index position). - height bounded by [0, L2LPERCTL >> 1] (valid tree height range). - budmin validity: NOFREE only if nleafs=0; otherwise >= BUDMIN. - Leaf nodes fit within stree array (leafidx + nleafs <= CTLTREESIZE). - Leaf node values are either non-negative or NOFREE. Invoked in dbAllocAG(), dbFindCtl(), dbAdjCtl() and dbExtendFS() when accessing dmapctl pages, catching corruption early before dmap operations trigger invalid memory access or logic errors. This fixes the following UBSAN warning. [58245.668090][T14017] ------------[ cut here ]------------ [58245.668103][T14017] UBSAN: shift-out-of-bounds in fs/jfs/jfs_dmap.c:2641:11 [58245.668119][T14017] shift exponent 110 is too large for 32-bit type 'int' [58245.668137][T14017] CPU: 0 UID: 0 PID: 14017 Comm: 4c1966e88c28fa9 Tainted: G E 6.18.0-rc4-00253-g21ce5d4ba045-dirty #124 PREEMPT_{RT,(full)} [58245.668174][T14017] Tainted: [E]=UNSIGNED_MODULE [58245.668176][T14017] Hardware name: QEMU Ubuntu 25.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014 [58245.668184][T14017] Call Trace: [58245.668200][T14017] [58245.668208][T14017] dump_stack_lvl+0x189/0x250 [58245.668288][T14017] ? __pfx_dump_stack_lvl+0x10/0x10 [58245.668301][T14017] ? __pfx__printk+0x10/0x10 [58245.668315][T14017] ? lock_metapage+0x303/0x400 [jfs] [58245.668406][T14017] ubsan_epilogue+0xa/0x40 [58245.668422][T14017] __ubsan_handle_shift_out_of_bounds+0x386/0x410 [58245.668462][T14017] dbSplit+0x1f8/0x200 [jfs] [58245.668543][T14017] dbAdjCtl+0x34c/0xa20 [jfs] [58245.668628][T14017] dbAllocNear+0x2ee/0x3d0 [jfs] [58245.668710][T14017] dbAlloc+0x933/0xba0 [jfs] [58245.668797][T14017] ea_write+0x374/0xdd0 [jfs] [58245.668888][T14017] ? __pfx_ea_write+0x10/0x10 [jfs] [58245.668966][T14017] ? __jfs_setxattr+0x76e/0x1120 [jfs] [58245.669046][T14017] __jfs_setxattr+0xa01/0x1120 [jfs] [58245.669135][T14017] ? __pfx___jfs_setxattr+0x10/0x10 [jfs] [58245.669216][T14017] ? mutex_lock_nested+0x154/0x1d0 [58245.669252][T14017] ? __jfs_xattr_set+0xb9/0x170 [jfs] [58245.669333][T14017] __jfs_xattr_set+0xda/0x170 [jfs] [58245.669430][T14017] ? __pfx___jfs_xattr_set+0x10/0x10 [jfs] [58245.669509][T14017] ? xattr_full_name+0x6f/0x90 [58245.669546][T14017] ? jfs_xattr_set+0x33/0x60 [jfs] [58245.669636][T14017] ? __pfx_jfs_xattr_set+0x10/0x10 [jfs] [58245.669726][T14017] __vfs_setxattr+0x43c/0x480 [58245.669743][T14017] __vfs_setxattr_noperm+0x12d/0x660 [58245.669756][T14017] vfs_setxattr+0x16b/0x2f0 [58245.669768][T14017] ? __pfx_vfs_setxattr+0x10/0x10 [58245.669782][T14017] filename_setxattr+0x274/0x600 [58245.669795][T14017] ? __pfx_filename_setxattr+0x10/0x10 [58245.669806][T14017] ? getname_flags+0x1e5/0x540 [58245.669829][T14017] path_setxattrat+0x364/0x3a0 [58245.669840][T14017] ? __pfx_path_setxattrat+0x10/0x10 [58245.669859][T14017] ? __se_sys_chdir+0x1b9/0x280 [58245.669876][T14017] __x64_sys_lsetxattr+0xbf/0xe0 [58245.669888][T14017] do_syscall_64+0xfa/0xfa0 [58245.669901][T14017] ? lockdep_hardirqs_on+0x9c/0x150 [58245.669913][T14017] ? entry_SYSCALL_64_after_hwframe+0x77/0x7f [58245.669927][T14017] ? exc_page_fault+0xab/0x100 [58245.669937][T14017] entry_SYSCALL_64_after_hwframe+0x77/0x7f Reported-by: syzbot+4c1966e88c28fa96e053@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4c1966e88c28fa96e053 Signed-off-by: Yun Zhou Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_dmap.c | 114 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 111 insertions(+), 3 deletions(-) diff --git a/fs/jfs/jfs_dmap.c b/fs/jfs/jfs_dmap.c index 2abe8cc02ee6..a841cf21da7d 100644 --- a/fs/jfs/jfs_dmap.c +++ b/fs/jfs/jfs_dmap.c @@ -133,6 +133,93 @@ static const s8 budtab[256] = { 2, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, -1 }; +/* + * check_dmapctl - Validate integrity of a dmapctl structure + * @dcp: Pointer to the dmapctl structure to check + * + * Return: true if valid, false if corrupted + */ +static bool check_dmapctl(struct dmapctl *dcp) +{ + s8 budmin = dcp->budmin; + u32 nleafs, l2nleafs, leafidx, height; + int i; + + nleafs = le32_to_cpu(dcp->nleafs); + /* Check basic field ranges */ + if (unlikely(nleafs > LPERCTL)) { + jfs_err("dmapctl: invalid nleafs %u (max %u)", + nleafs, LPERCTL); + return false; + } + + l2nleafs = le32_to_cpu(dcp->l2nleafs); + if (unlikely(l2nleafs > L2LPERCTL)) { + jfs_err("dmapctl: invalid l2nleafs %u (max %u)", + l2nleafs, L2LPERCTL); + return false; + } + + /* Verify nleafs matches l2nleafs (must be power of two) */ + if (unlikely((1U << l2nleafs) != nleafs)) { + jfs_err("dmapctl: nleafs %u != 2^%u", + nleafs, l2nleafs); + return false; + } + + leafidx = le32_to_cpu(dcp->leafidx); + /* Check leaf index matches expected position */ + if (unlikely(leafidx != CTLLEAFIND)) { + jfs_err("dmapctl: invalid leafidx %u (expected %u)", + leafidx, CTLLEAFIND); + return false; + } + + height = le32_to_cpu(dcp->height); + /* Check tree height is within valid range */ + if (unlikely(height > (L2LPERCTL >> 1))) { + jfs_err("dmapctl: invalid height %u (max %u)", + height, L2LPERCTL >> 1); + return false; + } + + /* Check budmin is valid (cannot be NOFREE for non-empty tree) */ + if (budmin == NOFREE) { + if (unlikely(nleafs > 0)) { + jfs_err("dmapctl: budmin is NOFREE but nleafs %u", + nleafs); + return false; + } + } else if (unlikely(budmin < BUDMIN)) { + jfs_err("dmapctl: invalid budmin %d (min %d)", + budmin, BUDMIN); + return false; + } + + /* Check leaf nodes fit within stree array */ + if (unlikely(leafidx + nleafs > CTLTREESIZE)) { + jfs_err("dmapctl: leaf range exceeds stree size (end %u > %u)", + leafidx + nleafs, CTLTREESIZE); + return false; + } + + /* Check leaf nodes have valid values */ + for (i = leafidx; i < leafidx + nleafs; i++) { + s8 val = dcp->stree[i]; + + if (unlikely(val < NOFREE)) { + jfs_err("dmapctl: invalid leaf value %d at index %d", + val, i); + return false; + } else if (unlikely(val > 31)) { + jfs_err("dmapctl: leaf value %d too large at index %d", val, i); + return false; + } + } + + return true; +} + /* * NAME: dbMount() * @@ -1372,7 +1459,7 @@ dbAllocAG(struct bmap * bmp, int agno, s64 nblocks, int l2nb, s64 * results) dcp = (struct dmapctl *) mp->data; budmin = dcp->budmin; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); return -EIO; @@ -1702,7 +1789,7 @@ static int dbFindCtl(struct bmap * bmp, int l2nb, int level, s64 * blkno) dcp = (struct dmapctl *) mp->data; budmin = dcp->budmin; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); @@ -2485,7 +2572,7 @@ dbAdjCtl(struct bmap * bmp, s64 blkno, int newval, int alloc, int level) return -EIO; dcp = (struct dmapctl *) mp->data; - if (dcp->leafidx != cpu_to_le32(CTLLEAFIND)) { + if (unlikely(!check_dmapctl(dcp))) { jfs_error(bmp->db_ipbmap->i_sb, "Corrupt dmapctl page\n"); release_metapage(mp); return -EIO; @@ -3454,6 +3541,11 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) return -EIO; } l2dcp = (struct dmapctl *) l2mp->data; + if (unlikely(!check_dmapctl(l2dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + release_metapage(l2mp); + return -EIO; + } /* compute start L1 */ k = blkno >> L2MAXL1SIZE; @@ -3471,6 +3563,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) if (l1mp == NULL) goto errout; l1dcp = (struct dmapctl *) l1mp->data; + if (unlikely(!check_dmapctl(l1dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start L0 */ j = (blkno & (MAXL1SIZE - 1)) >> L2MAXL0SIZE; @@ -3484,6 +3580,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) goto errout; l1dcp = (struct dmapctl *) l1mp->data; + if (unlikely(!check_dmapctl(l1dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start L0 */ j = 0; @@ -3503,6 +3603,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) if (l0mp == NULL) goto errout; l0dcp = (struct dmapctl *) l0mp->data; + if (unlikely(!check_dmapctl(l0dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start dmap */ i = (blkno & (MAXL0SIZE - 1)) >> @@ -3518,6 +3622,10 @@ int dbExtendFS(struct inode *ipbmap, s64 blkno, s64 nblocks) goto errout; l0dcp = (struct dmapctl *) l0mp->data; + if (unlikely(!check_dmapctl(l0dcp))) { + jfs_error(ipbmap->i_sb, "Corrupt dmapctl page\n"); + goto errout; + } /* compute start dmap */ i = 0; -- cgit v1.2.3 From 3c778ec882084626ac915d6c6ec88aff87b82221 Mon Sep 17 00:00:00 2001 From: Yun Zhou Date: Sun, 9 Nov 2025 15:58:18 +0800 Subject: jfs: fix corrupted list in dbUpdatePMap This patch resolves the "list_add corruption. next is NULL" Oops reported by syzkaller in dbUpdatePMap(). The root cause is uninitialized synclist nodes in struct metapage and struct TxBlock, plus improper list node removal using list_del() (which leaves nodes in an invalid state). This fixes the following Oops reported by syzkaller. list_add corruption. next is NULL. ------------[ cut here ]------------ kernel BUG at lib/list_debug.c:28! Oops: invalid opcode: 0000 [#1] SMP KASAN PTI CPU: 1 UID: 0 PID: 122 Comm: jfsCommit Not tainted syzkaller #0 PREEMPT_{RT,(full)} Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 10/02/2025 RIP: 0010:__list_add_valid_or_report+0xc3/0x130 lib/list_debug.c:27 Code: 4c 89 f2 48 89 d9 e8 0c 88 a4 fc 90 0f 0b 48 c7 c7 20 de 3d 8b e8 fd 87 a4 fc 90 0f 0b 48 c7 c7 c0 de 3d 8b e8 ee 87 a4 fc 90 <0f> 0b 48 89 df e8 13 c3 7d fd 42 80 7c 2d 00 00 74 08 4c 89 e7 e8 RSP: 0018:ffffc9000395fa20 EFLAGS: 00010246 RAX: 0000000000000022 RBX: 0000000000000000 RCX: 270c5dfadb559700 RDX: 0000000000000000 RSI: 0000000000000000 RDI: 0000000000000000 RBP: 00000000000f0000 R08: 0000000000000000 R09: 0000000000000000 R10: dffffc0000000000 R11: fffff5200072bee9 R12: 0000000000000000 R13: dffffc0000000000 R14: 0000000000000004 R15: 1ffff92000632266 FS: 0000000000000000(0000) GS:ffff888126ef9000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 000056341fdb86c0 CR3: 0000000040a18000 CR4: 00000000003526f0 Call Trace: __list_add_valid include/linux/list.h:96 [inline] __list_add include/linux/list.h:158 [inline] list_add include/linux/list.h:177 [inline] dbUpdatePMap+0x7e4/0xeb0 fs/jfs/jfs_dmap.c:577 txAllocPMap+0x57d/0x6b0 fs/jfs/jfs_txnmgr.c:2426 txUpdateMap+0x81e/0x9c0 fs/jfs/jfs_txnmgr.c:2364 txLazyCommit fs/jfs/jfs_txnmgr.c:2665 [inline] jfs_lazycommit+0x3f1/0xa10 fs/jfs/jfs_txnmgr.c:2734 kthread+0x711/0x8a0 kernel/kthread.c:463 ret_from_fork+0x4bc/0x870 arch/x86/kernel/process.c:158 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 Modules linked in: ---[ end trace 0000000000000000 ]--- Reported-by: syzbot+4d0a0feb49c5138cac46@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=4d0a0feb49c5138cac46 Tested-by: syzbot+4d0a0feb49c5138cac46@syzkaller.appspotmail.com Signed-off-by: Yun Zhou Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_metapage.c | 3 ++- fs/jfs/jfs_txnmgr.c | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/fs/jfs/jfs_metapage.c b/fs/jfs/jfs_metapage.c index 64c6eaa7f3f2..78dd8a1b29b7 100644 --- a/fs/jfs/jfs_metapage.c +++ b/fs/jfs/jfs_metapage.c @@ -270,6 +270,7 @@ static inline struct metapage *alloc_metapage(gfp_t gfp_mask) mp->clsn = 0; mp->log = NULL; init_waitqueue_head(&mp->wait); + INIT_LIST_HEAD(&mp->synclist); } return mp; } @@ -379,7 +380,7 @@ static void remove_from_logsync(struct metapage *mp) mp->lsn = 0; mp->clsn = 0; log->count--; - list_del(&mp->synclist); + list_del_init(&mp->synclist); } LOGSYNC_UNLOCK(log, flags); } diff --git a/fs/jfs/jfs_txnmgr.c b/fs/jfs/jfs_txnmgr.c index c16578af3a77..083dbbb0c326 100644 --- a/fs/jfs/jfs_txnmgr.c +++ b/fs/jfs/jfs_txnmgr.c @@ -275,6 +275,7 @@ int txInit(void) for (k = 0; k < nTxBlock; k++) { init_waitqueue_head(&TxBlock[k].gcwait); init_waitqueue_head(&TxBlock[k].waitor); + INIT_LIST_HEAD(&TxBlock[k].synclist); } for (k = 1; k < nTxBlock - 1; k++) { @@ -974,7 +975,7 @@ static void txUnlock(struct tblock * tblk) if (tblk->lsn) { LOGSYNC_LOCK(log, flags); log->count--; - list_del(&tblk->synclist); + list_del_init(&tblk->synclist); LOGSYNC_UNLOCK(log, flags); } } -- cgit v1.2.3 From b15e4310633f90072d66cc9b6692acbf6b4d7d00 Mon Sep 17 00:00:00 2001 From: Edward Adam Davis Date: Tue, 16 Dec 2025 21:57:51 +0800 Subject: jfs: Set the lbmDone flag at the end of lbmIODone In lbmRead(), the I/O event waited for by wait_event() finishes before it goes to sleep, and the lbmIODone() prematurely sets the flag to lbmDONE, thus ending the wait. This causes wait_event() to return before lbmREAD is cleared (because lbmDONE was set first), the premature return of wait_event() leads to the release of lbuf before lbmIODone() returns, thus triggering the use-after-free vulnerability reported in [1]. Moving the operation of setting the lbmDONE flag to after clearing lbmREAD in lbmIODone() avoids the use-after-free vulnerability reported in [1]. [1] BUG: KASAN: slab-use-after-free in rt_spin_lock+0x88/0x3e0 kernel/locking/spinlock_rt.c:56 Call Trace: blk_update_request+0x57e/0xe60 block/blk-mq.c:1007 blk_mq_end_request+0x3e/0x70 block/blk-mq.c:1169 blk_complete_reqs block/blk-mq.c:1244 [inline] blk_done_softirq+0x10a/0x160 block/blk-mq.c:1249 Allocated by task 6101: lbmLogInit fs/jfs/jfs_logmgr.c:1821 [inline] lmLogInit+0x3d0/0x19e0 fs/jfs/jfs_logmgr.c:1269 open_inline_log fs/jfs/jfs_logmgr.c:1175 [inline] lmLogOpen+0x4e1/0xfa0 fs/jfs/jfs_logmgr.c:1069 jfs_mount_rw+0xe9/0x670 fs/jfs/jfs_mount.c:257 jfs_fill_super+0x754/0xd80 fs/jfs/super.c:532 Freed by task 6101: kfree+0x1bd/0x900 mm/slub.c:6876 lbmLogShutdown fs/jfs/jfs_logmgr.c:1864 [inline] lmLogInit+0x1137/0x19e0 fs/jfs/jfs_logmgr.c:1415 open_inline_log fs/jfs/jfs_logmgr.c:1175 [inline] lmLogOpen+0x4e1/0xfa0 fs/jfs/jfs_logmgr.c:1069 jfs_mount_rw+0xe9/0x670 fs/jfs/jfs_mount.c:257 jfs_fill_super+0x754/0xd80 fs/jfs/super.c:532 Reported-by: syzbot+1d38eedcb25a3b5686a7@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=1d38eedcb25a3b5686a7 Signed-off-by: Edward Adam Davis Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_logmgr.c | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/fs/jfs/jfs_logmgr.c b/fs/jfs/jfs_logmgr.c index ada00d5bc214..472b6500e817 100644 --- a/fs/jfs/jfs_logmgr.c +++ b/fs/jfs/jfs_logmgr.c @@ -2180,8 +2180,6 @@ static void lbmIODone(struct bio *bio) LCACHE_LOCK(flags); /* disable+lock */ - bp->l_flag |= lbmDONE; - if (bio->bi_status) { bp->l_flag |= lbmERROR; @@ -2196,12 +2194,10 @@ static void lbmIODone(struct bio *bio) if (bp->l_flag & lbmREAD) { bp->l_flag &= ~lbmREAD; - LCACHE_UNLOCK(flags); /* unlock+enable */ - /* wakeup I/O initiator */ LCACHE_WAKEUP(&bp->l_ioevent); - return; + goto out; } /* @@ -2225,8 +2221,7 @@ static void lbmIODone(struct bio *bio) if (bp->l_flag & lbmDIRECT) { LCACHE_WAKEUP(&bp->l_ioevent); - LCACHE_UNLOCK(flags); - return; + goto out; } tail = log->wqueue; @@ -2278,8 +2273,6 @@ static void lbmIODone(struct bio *bio) * leave buffer for i/o initiator to dispose */ if (bp->l_flag & lbmSYNC) { - LCACHE_UNLOCK(flags); /* unlock+enable */ - /* wakeup I/O initiator */ LCACHE_WAKEUP(&bp->l_ioevent); } @@ -2290,6 +2283,7 @@ static void lbmIODone(struct bio *bio) else if (bp->l_flag & lbmGC) { LCACHE_UNLOCK(flags); lmPostGC(bp); + LCACHE_LOCK(flags); /* disable+lock */ } /* @@ -2302,9 +2296,11 @@ static void lbmIODone(struct bio *bio) assert(bp->l_flag & lbmRELEASE); assert(bp->l_flag & lbmFREE); lbmfree(bp); - - LCACHE_UNLOCK(flags); /* unlock+enable */ } + +out: + bp->l_flag |= lbmDONE; + LCACHE_UNLOCK(flags); } int jfsIOWait(void *arg) -- cgit v1.2.3 From ca5848ae87d24886a7886f5a22278bd4045c15f8 Mon Sep 17 00:00:00 2001 From: Helen Koike Date: Fri, 27 Feb 2026 15:11:50 -0300 Subject: jfs: hold LOG_LOCK on umount to avoid null-ptr-deref write_special_inodes() function iterate through the log->sb_list and access the sbi fields, which can be set to NULL concurrently by umount. Fix concurrency issue by holding LOG_LOCK and checking for NULL. Reported-by: syzbot+e14b1036481911ae4d77@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=e14b1036481911ae4d77 Signed-off-by: Helen Koike Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_logmgr.c | 16 +++++++--------- fs/jfs/jfs_logmgr.h | 7 +++++++ fs/jfs/jfs_umount.c | 10 ++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/fs/jfs/jfs_logmgr.c b/fs/jfs/jfs_logmgr.c index 472b6500e817..306165e61438 100644 --- a/fs/jfs/jfs_logmgr.c +++ b/fs/jfs/jfs_logmgr.c @@ -74,12 +74,6 @@ static struct lbuf *log_redrive_list; static DEFINE_SPINLOCK(log_redrive_lock); -/* - * log read/write serialization (per log) - */ -#define LOG_LOCK_INIT(log) mutex_init(&(log)->loglock) -#define LOG_LOCK(log) mutex_lock(&((log)->loglock)) -#define LOG_UNLOCK(log) mutex_unlock(&((log)->loglock)) /* @@ -204,9 +198,13 @@ static void write_special_inodes(struct jfs_log *log, struct jfs_sb_info *sbi; list_for_each_entry(sbi, &log->sb_list, log_list) { - writer(sbi->ipbmap->i_mapping); - writer(sbi->ipimap->i_mapping); - writer(sbi->direct_inode->i_mapping); + /* These pointers can be NULL before list_del during umount */ + if (sbi->ipbmap) + writer(sbi->ipbmap->i_mapping); + if (sbi->ipimap) + writer(sbi->ipimap->i_mapping); + if (sbi->direct_inode) + writer(sbi->direct_inode->i_mapping); } } diff --git a/fs/jfs/jfs_logmgr.h b/fs/jfs/jfs_logmgr.h index 8b8994e48cd0..09e0ef6aecce 100644 --- a/fs/jfs/jfs_logmgr.h +++ b/fs/jfs/jfs_logmgr.h @@ -402,6 +402,13 @@ struct jfs_log { int no_integrity; /* 3: flag to disable journaling to disk */ }; +/* + * log read/write serialization (per log) + */ +#define LOG_LOCK_INIT(log) mutex_init(&(log)->loglock) +#define LOG_LOCK(log) mutex_lock(&((log)->loglock)) +#define LOG_UNLOCK(log) mutex_unlock(&((log)->loglock)) + /* * Log flag */ diff --git a/fs/jfs/jfs_umount.c b/fs/jfs/jfs_umount.c index 8ec43f53f686..18569f1eaabd 100644 --- a/fs/jfs/jfs_umount.c +++ b/fs/jfs/jfs_umount.c @@ -20,6 +20,7 @@ #include "jfs_superblock.h" #include "jfs_dmap.h" #include "jfs_imap.h" +#include "jfs_logmgr.h" #include "jfs_metapage.h" #include "jfs_debug.h" @@ -57,6 +58,12 @@ int jfs_umount(struct super_block *sb) */ jfs_flush_journal(log, 2); + /* + * Hold log lock so write_special_inodes (lmLogSync) cannot see + * this sbi with a NULL inode pointer while iterating log->sb_list. + */ + if (log) + LOG_LOCK(log); /* * close fileset inode allocation map (aka fileset inode) */ @@ -95,6 +102,9 @@ int jfs_umount(struct super_block *sb) */ filemap_write_and_wait(sbi->direct_inode->i_mapping); + if (log) + LOG_UNLOCK(log); + /* * ensure all file system file pages are propagated to their * home blocks on disk (and their in-memory buffer pages are -- cgit v1.2.3 From 679330e4a7af1d102d035b13b2b9d41bc1dfbbf7 Mon Sep 17 00:00:00 2001 From: João Paredes Date: Mon, 2 Mar 2026 02:55:39 +0000 Subject: JFS: always load filesystem UUID during mount MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The filesystem UUID was only being loaded into super_block sb when an external journal device was in use. When mounting without an external journal, the UUID remained unset, which prevented the computation of a filesystem ID (fsid), which could be confirmed via `stat -f -c "%i"` and thus user space could not use fanotify correctly. A missing filesystem ID causes fanotify to return ENODEV when marking the filesystem for events like FAN_CREATE, FAN_DELETE, FAN_MOVED_TO, and FAN_MOVED_FROM. As a result, applications relying on fanotify could not monitor these events on JFS filesystems without an external journal. Moved the UUID initialization so it is always performed during mount, ensuring the superblock UUID is consistently available. Signed-off-by: João Paredes Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_mount.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fs/jfs/jfs_mount.c b/fs/jfs/jfs_mount.c index 52e6b58c5dbd..dac822f15070 100644 --- a/fs/jfs/jfs_mount.c +++ b/fs/jfs/jfs_mount.c @@ -378,11 +378,12 @@ static int chkSuper(struct super_block *sb) sbi->nbperpage = PSIZE >> sbi->l2bsize; sbi->l2nbperpage = L2PSIZE - sbi->l2bsize; sbi->l2niperblk = sbi->l2bsize - L2DISIZE; + uuid_copy(&sbi->uuid, &j_sb->s_uuid); + if (sbi->mntflag & JFS_INLINELOG) sbi->logpxd = j_sb->s_logpxd; else { sbi->logdev = new_decode_dev(le32_to_cpu(j_sb->s_logdev)); - uuid_copy(&sbi->uuid, &j_sb->s_uuid); uuid_copy(&sbi->loguuid, &j_sb->s_loguuid); } sbi->fsckpxd = j_sb->s_fsckpxd; -- cgit v1.2.3 From dad98c5b2a05ef744af4c884c97066a3c8cdad61 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 16 Mar 2026 15:42:51 +0100 Subject: jfs: avoid -Wtautological-constant-out-of-range-compare warning again The comparison of an __s8 value against DTPAGEMAXSLOT is still trivially true, causing a harmless (default disabled) warning with clang: fs/jfs/jfs_dtree.c:4419:25: error: result of comparison of constant 128 with expression of type 's8' (aka 'signed char') is always false [-Werror,-Wtautological-constant-out-of-range-compare] 4419 | p->header.freelist >= DTPAGEMAXSLOT)) { | ~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~~~~~ I previously worked around two of these in commit 7833570dae83 ("jfs: avoid -Wtautological-constant-out-of-range-compare warning"), but now a new one has come up, so address the same way by dropping the redundant range check. Fixes: 119e448bb50a ("jfs: add dtpage integrity check to prevent index/pointer overflows") Signed-off-by: Arnd Bergmann Signed-off-by: Dave Kleikamp --- fs/jfs/jfs_dtree.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c index 7669e268fe35..ac0f79fafaca 100644 --- a/fs/jfs/jfs_dtree.c +++ b/fs/jfs/jfs_dtree.c @@ -4412,11 +4412,8 @@ bool check_dtpage(dtpage_t *p) } } else { int fsi; - /* When there are free slots, freelist must be a valid slot index in - * 1~DTROOTMAXSLOT-1(since slot[0] is occupied by the header). - */ - if (unlikely(p->header.freelist < 1 || - p->header.freelist >= DTPAGEMAXSLOT)) { + + if (unlikely(p->header.freelist < 1)) { jfs_err("Bad freelist:%d in dtpage\n", p->header.freelist); return false; } -- cgit v1.2.3