Technical Analysis of Root Cause
1. Relevant Subsystem, Files, and Structs
2. Memory Layout of struct jfs_inode_info
In struct jfs_inode_info, the directory information union u.dir consists of:
struct dir_table_slot _table[12] (96 bytes, offset 656..751)
dtroot_t _dtroot (288 bytes, offset 752..1039)
dtroot_t consists of 9 slots of 32 bytes each (DTROOTMAXSLOT = 9). Slot 0 overlays the 32-byte header, while slots 1 through 8 hold directory entry data.
Immediately adjacent to the end of union u (byte offset 1040) is:
#ifdef CONFIG_QUOTA
struct dquot __rcu *i_dquot[MAXQUOTAS];
#endif
i_dquot[0] is located at byte offset 1040 of struct jfs_inode_info. When directory operations operate on the in-inode root page (BT_ROOT), i_dtroot is cast to (dtpage_t *)&jfs_ip->i_dtroot.
Accessing p->slot[9] corresponds to offset 752 + 9 * 32 = 1040 bytes, which overlays jfs_ip->i_dquot[0].
3. Bug Mechanism and Trigger Flow
-
Missing Freelist Bounds Validation in dtInsert() / dtInsertEntry():
Directory B+-tree pages manage available 32-byte slots via a free list: p->header.freelist contains the head slot index, and each free slot's next field links to the next available slot index.
In fs/jfs/jfs_dtree.c:
DT_GETPAGE() verifies p->header.nextindex, but does not validate p->header.freelist or p->header.freecnt.
dtInsert() only checks if (p->header.freelist == 0) return -EINVAL;. It never verifies that freelist or subsequent slot next pointers satisfy 1 <= fsi < DTROOTMAXSLOT (DTROOTMAXSLOT = 9) for BT_ROOT, or < p->header.maxslot for non-root pages.
-
Trigger via Multi-Slot Directory Insertion:
When creating a file in the directory (e.g. openat(..., "devices.list", O_CREAT) -> jfs_create() -> dtInsert()):
-
Crash at Inode Eviction:
- During filesystem unmount or task exit cleanup (
cleanup_mnt -> deactivate_locked_super -> generic_shutdown_super -> evict_inodes -> jfs_evict_inode), JFS calls dquot_drop(inode).
dquot_drop() reads i_dquot(inode) (jfs_ip->i_dquot). Expecting NULL for an uninitialized quota, it instead finds the corrupted pointer 0x00000000007400ff.
dquot_drop() proceeds to __dquot_drop() -> dqput_all() -> dqput(dquot).
dqput() attempts to read atomic_read(&dquot->dq_count). offsetof(struct dquot, dq_count) is 272 (0x110).
- Reading
0x00000000007400ff + 0x110 = 0x000000000074020f results in a wild read of user-space memory from kernel mode, triggering KASAN's user-memory-access detection (or an unhandled page fault / GPF crash on non-debug kernels).