Root Cause Analysis
1. Failure Manifestation and Address Arithmetic
The crash manifests as a KASAN slab out-of-bounds read inside xfs_trans_precommit_sort:
BUG: KASAN: slab-out-of-bounds in xfs_trans_precommit_sort+0x7a/0x160 fs/xfs/xfs_trans.c:-1
Read of size 8 at addr ffff8880458666c8 by task syz.0.0/5347
The object at ffff8880458665c8 was allocated by Task 5346 from the xfs_trans kmem cache:
Allocated by task 5346:
__xfs_trans_alloc+0x26/0x410 fs/xfs/xfs_trans.c:220
xfs_trans_alloc+0xcf/0x990 fs/xfs/xfs_trans.c:258
xfs_scrub_metadata+0xa74/0x1910 fs/xfs/scrub/scrub.c:709
xfs_ioc_scrubv_metadata+0x7ac/0xb70 fs/xfs/scrub/scrub.c:980
struct xfs_trans (other_tp) has a total size of 232 bytes (0xe8). Inside struct xfs_trans:
- The embedded transaction item list head
t_items (struct list_head) is located at byte offset 176 (0xb0).
- Therefore,
&other_tp->t_items is located at ffff8880458665c8 + 0xb0 = ffff888045866678.
Task 5347 runs xfs_create $\to$ __xfs_trans_commit $\to$ xfs_trans_run_precommits, which invokes:
list_sort(NULL, &tp->t_items, xfs_trans_precommit_sort);
During the merge sort, xfs_trans_precommit_sort is passed two list nodes a and b, expecting them to be embedded li_trans fields inside struct xfs_log_item:
struct xfs_log_item *lia = container_of(a, struct xfs_log_item, li_trans);
In struct xfs_log_item:
li_trans is at offset 16 (0x10).
li_ops is at offset 96 (0x60).
When pointer a is &other_tp->t_items (ffff888045866678):
$$\text{lia} = \text{a} - 16 = \text{ffff888045866668}\ (\text{other_tp} + \text{0xa0})$$
Accessing lia->li_ops reads 8 bytes at offset 96 (0x60) from lia:
$$\text{addr} = \text{lia} + \text{0x60} = \text{other_tp} + \text{0xa0} + \text{0x60} = \text{other_tp} + \text{0x100} = \text{ffff8880458666c8}$$
Because other_tp is only 232 bytes (0xe8), other_tp + 0x100 (256 bytes) is located exactly 24 bytes beyond the end of the allocated 232-byte region, perfectly matching the KASAN report.
2. Root Cause and Mechanism
The defect is caused by a violation of linked-list lifecycle invariants when an in-core metadata object (an inode or buffer) joined to a scrub transaction is prematurely unlocked or reused before the scrub transaction has released it:
-
Attachment in Scrub (other_tp):
Task 5346 invokes an online scrub/repair ioctl (xfs_ioc_scrubv_metadata). In xfs_scrub_metadata, sc->ops->setup(sc) allocates a transaction (other_tp) into sc->tp. During scrub/repair processing, an in-core metadata object (an inode via xfs_trans_ijoin(sc->tp, ip, 0) or an AG/metadata buffer via _xfs_trans_bjoin) is joined to other_tp->t_items.
Joining the log item (lip) attaches it to other_tp->t_items via list_add_tail(&lip->li_trans, &other_tp->t_items).
-
Premature Lock Release / Concurrent Join:
The metadata object is unlocked (or released) while lip->li_trans remains linked on other_tp->t_items (i.e. before other_tp is committed, rolled, or cancelled via xfs_trans_cancel).
Concurrently, Task 5347 enters xfs_create and attempts to modify the same metadata object. Task 5347 acquires the lock on the object and calls xfs_trans_ijoin(tp, ip, 0) (or _xfs_trans_bjoin), which invokes:
xfs_trans_add_item(tp, &lip->li_trans);
which runs:
list_add_tail(&lip->li_trans, &tp->t_items);
In non-debug kernels (or with assertions disabled), ASSERT(list_empty(&lip->li_trans)) does not abort. Because lip->li_trans was still linked within other_tp->t_items, adding lip->li_trans to tp->t_items without first unlinking it splices &other_tp->t_items into Task 5347's circular doubly-linked item list (tp->t_items).
-
Corruption Trigger:
When Task 5347 commits via xfs_trans_commit, xfs_trans_run_precommits calls list_sort(NULL, &tp->t_items, xfs_trans_precommit_sort). list_sort iterates over the nodes reachable from tp->t_items.next and encounters &other_tp->t_items. Passing this foreign list head to xfs_trans_precommit_sort triggers the out-of-bounds pointer calculation and read past the end of other_tp.
3. Incomplete / Unverified Aspects
- Specific Scrub Type Trigger: While it is established that
other_tp is the transaction allocated at fs/xfs/scrub/scrub.c:709 (sc->ops->setup(sc) tail-calling xchk_trans_alloc) and that an unlocked/unrelinquished log item was joined to both other_tp and Task 5347's transaction, the exact ioctl command parameter (sm_type) passed by syzkaller to XFS_IOC_SCRUBV_METADATA was not captured in the dmesg log. (Quota scrub was ruled out because quotas were disabled at mount due to quotacheck error -117).