lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Message-ID: <bf6d47da-4c2e-40e4-85c0-ae3061d6c39c@oracle.com>
Date: Mon, 1 Dec 2025 16:31:56 -0600
From: Dave Kleikamp <dave.kleikamp@...cle.com>
To: Yun Zhou <yun.zhou@...driver.com>
Cc: rand.sec96@...il.com, contact@...aud-lcm.com, kovalev@...linux.org,
        zheng.yu@...thwestern.edu, eadavis@...com,
        jfs-discussion@...ts.sourceforge.net, linux-kernel@...r.kernel.org
Subject: Re: [PATCH] jfs: add dtroot integrity check to prevent index
 out-of-bounds

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@...driver.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


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ