]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
jfs: add dtroot integrity check to prevent index out-of-bounds
authorYun Zhou <yun.zhou@windriver.com>
Thu, 20 Nov 2025 15:43:50 +0000 (23:43 +0800)
committerDave Kleikamp <dave.kleikamp@oracle.com>
Wed, 11 Mar 2026 15:57:21 +0000 (10:57 -0500)
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]  <TASK>
[  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 <yun.zhou@windriver.com>
Signed-off-by: Dave Kleikamp <dave.kleikamp@oracle.com>
fs/jfs/jfs_dtree.c
fs/jfs/jfs_dtree.h
fs/jfs/jfs_imap.c

index 9ab3f2fc61d17698e6a4ad542e8be3dee5029707..8abd9c7663ea4879c0edebcc553fb8a72513b488 100644 (file)
@@ -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;
+}
index 1758289647a0e0a8aaa56ff7badced54d7101d67..94dc16123c87e631cc8970b7937f8c5a1308df12 100644 (file)
@@ -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 */
index 294a67327c735fb9cbe074078ed72e872862d710..fbb5f7966b75400d9afa4771112228ef8df21e7b 100644 (file)
@@ -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