KASAN: slab-out-of-bounds Read in xfs_trans_precommit_sort

Status: moderation: reported on 2026/11/09 23:16
Subsystems: xfs
Labels: prio:normal
[Documentation on labels]
Reported-by: syzbot+e25cbad71c11a54eee75@syzkaller.appspotmail.com
First crash: 1h25m, last: 1h25m
✨ AI-generated root cause summary:

A shared in-core log item (inode or buffer) attached to an active XFS scrub transaction (allocated in xfs_scrub_metadata via sc->ops->setup) is accessed and joined by a concurrent transaction (xfs_create) via xfs_trans_add_item while still linked on the scrub transaction's tp->t_items list. This violates list-head lifecycle invariants and cross-links the two transactions' t_items lists, causing the scrub transaction's list head (&other_tp->t_items) to be present as an element in the committing transaction's item list. When __xfs_trans_commit calls list_sort with xfs_trans_precommit_sort, container_of(a, struct xfs_log_item, li_trans) is incorrectly evaluated on the list head &other_tp->t_items, resulting in a slab out-of-bounds read 24 bytes past the 232-byte xfs_trans slab object when accessing lia->li_ops.

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit 7475ea8b5861:

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:

  1. 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).

  2. 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).

  3. 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).
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
ba4f111d assessment-security DenialOfService: ✅ Exploitable: ✅ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ✅ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ KASAN: slab-out-of-bounds Read in xfs_trans_precommit_sort 2026/11/09 22:22 2026/11/09 22:23 2026/11/09 23:08 5cecaf1f

Sample crash report:
loop0: detected capacity change from 0 to 65536
XFS (loop0): Mounting V5 Filesystem 9b7348e5-2fa0-41a5-9526-c53a678b01f3
XFS (loop0): Ending clean mount
XFS (loop0): Quotacheck needed: Please wait.
XFS (loop0): Quotacheck: Unsuccessful (Error -117): Disabling quotas.
loop0: lost filesystem error report for type 5 error -117
==================================================================
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

CPU: 0 UID: 0 PID: 5347 Comm: syz.0.0 Not tainted syzkaller #0 PREEMPT(full) 
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
Call Trace:
 <TASK>
 dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
 print_address_description+0x55/0x1e0 mm/kasan/report.c:378
 print_report+0x58/0x70 mm/kasan/report.c:482
 kasan_report+0x117/0x150 mm/kasan/report.c:595
 xfs_trans_precommit_sort+0x7a/0x160 fs/xfs/xfs_trans.c:-1
 merge lib/list_sort.c:20 [inline]
 list_sort+0x222/0x7b0 lib/list_sort.c:219
 xfs_trans_run_precommits fs/xfs/xfs_trans.c:790 [inline]
 __xfs_trans_commit+0x21b/0xc20 fs/xfs/xfs_trans.c:845
 xfs_trans_commit+0x140/0x1c0 fs/xfs/xfs_trans.c:930
 xfs_create+0x7ac/0xa60 fs/xfs/xfs_inode.c:747
 xfs_generic_create+0x410/0xb30 fs/xfs/xfs_iops.c:216
 xfs_vn_mkdir+0x31/0x40 fs/xfs/xfs_iops.c:308
 vfs_mkdir+0x411/0x660 fs/namei.c:5428
 filename_mkdirat+0x284/0x550 fs/namei.c:5461
 __do_sys_mkdirat fs/namei.c:5482 [inline]
 __se_sys_mkdirat+0x35/0x150 fs/namei.c:5479
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
 entry_SYSCALL_64_after_hwframe+0x77/0x7f
RIP: 0033:0x7f6ed499fb19
Code: ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
RSP: 002b:00007f6ed57e6fe8 EFLAGS: 00000246 ORIG_RAX: 0000000000000102
RAX: ffffffffffffffda RBX: 00007f6ed4c26090 RCX: 00007f6ed499fb19
RDX: 00000000000001ff RSI: 0000200000000340 RDI: ffffffffffffff9c
RBP: 00007f6ed4a37b70 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
R13: 00007f6ed4c26128 R14: 00007f6ed4c26090 R15: 00007ffcbe56a758
 </TASK>

Allocated by task 5346:
 kasan_save_stack mm/kasan/common.c:57 [inline]
 kasan_save_track+0x3e/0x80 mm/kasan/common.c:78
 unpoison_slab_object mm/kasan/common.c:351 [inline]
 __kasan_slab_alloc+0x6c/0x80 mm/kasan/common.c:377
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4720 [inline]
 slab_alloc_node mm/slub.c:5033 [inline]
 kmem_cache_alloc_noprof+0x2b4/0x600 mm/slub.c:5047
 __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
 xfs_file_ioctl+0x6f9/0xf40 fs/xfs/xfs_ioctl.c:1520
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl+0xfc/0x170 fs/ioctl.c:583
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

The buggy address belongs to the object at ffff8880458665c8
 which belongs to the cache xfs_trans of size 232
The buggy address is located 24 bytes to the right of
 allocated 232-byte region [ffff8880458665c8, ffff8880458666b0)

The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x45866
flags: 0x4fff00000000000(node=1|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 04fff00000000000 ffff888033f4ca00 dead000000000122 0000000000000000
raw: 0000000000000000 00000002000d000d 00000000f5000000 0000000000000000
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 0, migratetype Unmovable, gfp_mask 0xd2cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 5347, tgid 5345 (syz.0.0), ts 104116130165
 set_page_owner include/linux/page_owner.h:33 [inline]
 post_alloc_hook+0x1f9/0x250 mm/page_alloc.c:1859
 prep_new_page mm/page_alloc.c:1867 [inline]
 get_page_from_freelist+0x2224/0x2290 mm/page_alloc.c:3940
 __alloc_frozen_pages_noprof+0x1fe/0x410 mm/page_alloc.c:5464
 alloc_slab_page mm/slub.c:3384 [inline]
 allocate_slab+0x7d/0x5e0 mm/slub.c:3499
 new_slab mm/slub.c:3550 [inline]
 refill_objects+0x2d5/0x350 mm/slub.c:7539
 refill_sheaf mm/slub.c:2917 [inline]
 __pcs_replace_empty_main+0x2c8/0x6c0 mm/slub.c:4811
 alloc_from_pcs mm/slub.c:4887 [inline]
 slab_alloc_node mm/slub.c:5021 [inline]
 kmem_cache_alloc_noprof+0x394/0x600 mm/slub.c:5047
 __xfs_trans_alloc fs/xfs/xfs_trans.c:220 [inline]
 xfs_trans_alloc_empty+0x30/0x210 fs/xfs/xfs_trans.c:307
 xfs_rtmount_inodes+0x2c/0x7b0 fs/xfs/xfs_rtalloc.c:1713
 xfs_mountfs+0x1254/0x2380 fs/xfs/xfs_mount.c:1115
 xfs_fs_fill_super+0x115f/0x15a0 fs/xfs/xfs_super.c:1987
 get_tree_bdev_flags+0x436/0x500 fs/super.c:1821
 vfs_get_tree+0x92/0x2a0 fs/super.c:1881
 fc_mount fs/namespace.c:1282 [inline]
 do_new_mount_fc fs/namespace.c:3910 [inline]
 do_new_mount+0x325/0xb00 fs/namespace.c:3986
 do_mount fs/namespace.c:4350 [inline]
 __do_sys_mount fs/namespace.c:4565 [inline]
 __se_sys_mount+0x31d/0x420 fs/namespace.c:4542
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
page last free pid 5294 tgid 5294 ts 104114802358 stack trace:
 reset_page_owner include/linux/page_owner.h:26 [inline]
 __free_pages_prepare mm/page_alloc.c:1403 [inline]
 __free_frozen_pages+0xc97/0xd90 mm/page_alloc.c:2957
 __slab_free+0x269/0x2c0 mm/slub.c:5926
 qlink_free mm/kasan/quarantine.c:163 [inline]
 qlist_free_all+0x99/0x100 mm/kasan/quarantine.c:179
 kasan_quarantine_reduce+0x148/0x160 mm/kasan/quarantine.c:286
 __kasan_slab_alloc+0x22/0x80 mm/kasan/common.c:361
 kasan_slab_alloc include/linux/kasan.h:253 [inline]
 slab_post_alloc_hook mm/slub.c:4720 [inline]
 slab_alloc_node mm/slub.c:5033 [inline]
 kmem_cache_alloc_noprof+0x2b4/0x600 mm/slub.c:5047
 vm_area_dup+0x32/0x8e0 mm/vma_init.c:128
 __split_vma+0x2db/0x8a0 mm/vma.c:549
 vms_gather_munmap_vmas+0x322/0x1370 mm/vma.c:1524
 do_vmi_align_munmap+0x2b4/0x4b0 mm/vma.c:1692
 do_vmi_munmap+0x252/0x2d0 mm/vma.c:1749
 __vm_munmap+0x240/0x420 mm/vma.c:3600
 __do_sys_munmap mm/mmap.c:1094 [inline]
 __se_sys_munmap mm/mmap.c:1091 [inline]
 __x64_sys_munmap+0x60/0x70 mm/mmap.c:1091
 do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
 do_syscall_64+0x16a/0x530 arch/x86/entry/syscall_64.c:89
 entry_SYSCALL_64_after_hwframe+0x77/0x7f

Memory state around the buggy address:
 ffff888045866580: fc fc fc fc fc fc fc fc fc 00 00 00 00 00 00 00
 ffff888045866600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
>ffff888045866680: 00 00 00 00 00 00 fc fc fc fc fc fc fc fc 00 00
                                              ^
 ffff888045866700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
 ffff888045866780: 00 00 00 00 00 00 00 00 00 00 00 fc fc fc fc fc
==================================================================

Crashes (1):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/11/09 22:22 upstream 7475ea8b5861 04b726fe .config console log report [disk image (non-bootable)] [vmlinux] [kernel image] ci-snapshot-upstream-root KASAN: slab-out-of-bounds Read in xfs_trans_precommit_sort
* Struck through repros no longer work on HEAD.