fs/jfs/jfs_dtree.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++ fs/jfs/jfs_dtree.h | 2 ++ fs/jfs/jfs_imap.c | 4 +++ 3 files changed, 89 insertions(+)
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>
---
fs/jfs/jfs_dtree.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++
fs/jfs/jfs_dtree.h | 2 ++
fs/jfs/jfs_imap.c | 4 +++
3 files changed, 89 insertions(+)
diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
index d9cf2071c41f..e1cbc3a04f3f 100644
--- a/fs/jfs/jfs_dtree.c
+++ b/fs/jfs/jfs_dtree.c
@@ -4316,3 +4316,86 @@ 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), so nextindex must be within this range
+ */
+ 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 194cb05b42b9..7056c8d6fda2 100644
--- a/fs/jfs/jfs_imap.c
+++ b/fs/jfs/jfs_imap.c
@@ -3111,6 +3111,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_err("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
--
2.34.1
On 11/20/25 9:43AM, Yun Zhou wrote:
> 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.
I really like the idea of these patches. I'm swamped with some other
work and have been out on vacation a bit, but I do intend to give these
a good review and hopefully integrate them. Thanks for your patience.
Shaggy
>
> 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>
> ---
> fs/jfs/jfs_dtree.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++
> fs/jfs/jfs_dtree.h | 2 ++
> fs/jfs/jfs_imap.c | 4 +++
> 3 files changed, 89 insertions(+)
>
> diff --git a/fs/jfs/jfs_dtree.c b/fs/jfs/jfs_dtree.c
> index d9cf2071c41f..e1cbc3a04f3f 100644
> --- a/fs/jfs/jfs_dtree.c
> +++ b/fs/jfs/jfs_dtree.c
> @@ -4316,3 +4316,86 @@ 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), so nextindex must be within this range
> + */
> + 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 194cb05b42b9..7056c8d6fda2 100644
> --- a/fs/jfs/jfs_imap.c
> +++ b/fs/jfs/jfs_imap.c
> @@ -3111,6 +3111,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_err("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
© 2016 - 2025 Red Hat, Inc.