| 0/0 |
2026/09/10 00:00 |
flow |
moderation |
1h02m
Results: map[Actionable:true Explanation:This KASAN report describes a valid, self-consistent, and actionable slab
use-after-free bug.
### Analysis of the Stacks and Object
1. **Allocated Object**:
- **Type / Cache**: `struct gfs2_glock_aspace` (slab cache
`gfs2_glock(aspace)`, size 1176 bytes).
- **Allocation Stack**: Allocated by task 7723 during filesystem mount /
inode lookup in `gfs2_glock_get()` (`fs/gfs2/glock.c:1074`) called from
`gfs2_inode_lookup()`.
2. **Free Stack**:
- **Freeing Path**: Task 7773 frees the glock via RCU callback
`gfs2_glock_dealloc()` (`fs/gfs2/glock.c:134`).
- The auxiliary work creation stack shows `glock_work_func()` dropped the
glock's final reference after demoting it to `LM_ST_UNLOCKED`
(`__gfs2_glock_put()` calling `lm_put_lock()` $\rightarrow$
`gfs2_glock_free()` $\rightarrow$ `call_rcu()`).
3. **Bad Access Stack**:
- **Accessing Path**: Task 5768 (`kworker/0:6`) running
`gfs2_withdraw_func()` calls `do_withdraw()` $\rightarrow$ `gfs2_ail_drain()`
$\rightarrow$ `gfs2_ail_empty_tr()` $\rightarrow$ `gfs2_remove_from_ail()`
(`fs/gfs2/log.c:79`).
- In `gfs2_remove_from_ail(bd)`, `list_del_init(&bd->bd_ail_gl_list)` is
called. The list `bd->bd_ail_gl_list` is linked into `gl->gl_ail_list`.
- **Accessed Offsets**:
- `struct gfs2_glock` embedded at offset 0 of `struct gfs2_glock_aspace`
has field `gl_ail_list` at offset 280 bytes (`0x118`).
- Offset 280 (`gl_ail_list.next`) is read and offset 288
(`gl_ail_list.prev`) is read/written by `__list_del_entry_valid_or_report`
/ `__list_del` when removing `bd` from `gl_ail_list`.
- The report specifically flags reads and writes at offsets 280 and 288
within the freed 1176-byte `gfs2_glock(aspace)` object.
### Bug Mechanism & Consistency
- All three stack traces and the auxiliary work creation traces are in the
`fs/gfs2` subsystem and refer to the exact same object (`struct
gfs2_glock_aspace`).
- The glock was demoted to `LM_ST_UNLOCKED` and had its refcount drop to 0,
which triggered glock release and RCU-freeing. However, buffer data objects
(`struct gfs2_bufdata *bd`) were still linked on the glock's `gl_ail_list` and
in the filesystem's AIL list (`sd_ail1_list` / `sd_ail2_list`).
- When the filesystem withdrawal workqueue worker subsequently drained the AIL
in `gfs2_ail_drain()`, it invoked `gfs2_remove_from_ail()`, which attempted to
unlink the buffer from the already-freed glock's `gl_ail_list`, resulting in the
reported slab use-after-free.
Because the report contains all three requisite stack traces, all in the same
subsystem referencing the exact same structure and offsets, the report is
**actionable**.]
|
| 1/1 |
2026/09/10 00:00 |
action |
kernel-checkouter |
1m
Results: map[KernelSrc:/app/workdir/cache/src/340af6a94893d94be96d352b0303cca83ee32d79]
|
| 2/1 |
2026/09/10 00:01 |
action |
kernel-builder |
27m
Results: map[KernelObj:/app/workdir/cache/build/ac20b4b997bcbd54988794f46cd895c9c5723d8c]
|
| 3/1 |
2026/09/10 00:29 |
action |
codesearch-prepare |
21m
Results: map[Index:codesearch-index]
|
| 4/1 |
2026/09/10 00:50 |
agent |
expert |
12m
Model:
Results:
Instruction: You are an experienced Linux kernel developer tasked with determining if the given kernel bug
report is actionable or not. Actionable means that it contains enough info to root cause
the underlying bug, and that the report is self-consistent and makes sense, rather than
e.g. a one-off nonsensical crash induced by a previous memory corruption.
The bug report is about a use-after-free bug generated by KASAN tool.
It should contain 3 stack traces: the bad memory access stack, the heap block allocation stack,
and the heap block free stack. If the report does not contain 3 stacks, it's not actionable.
All 3 stack traces should be related to the same object type,
and usually be in the same kernel subsystem (at least leaf stack frames).
An example of an actionable and consistent report would be: first access stack relates
to an access to a field of struct Foo, allocation/free stacks relate to allocation/free
of the struct Foo.
In inconsistent/nonsensical reports an access may be to a struct Foo, but allocation
stack allocates a different structure in a different subsystem.
Look for other suspicious signals/inconsistencies that can make this report hard to
debug/understand.
In the final reply explain why you think the report is self-consistent and actionable,
or why it's inconsistent and/or not actionable.
Use the provided tools to confirm any assumptions, variables/fields being accessed, etc.
Don't make assumptions about the kernel source code (it may be different from what you assume it is).
Extensively use the provided code access tools (codesearch-*, git-*, grepper, etc)
to examine the actual source code, and confirm any assumptions.
Prefer calling several tools at the same time to save round-trips.
Use set-results tool to provide results of the analysis.
It must be called exactly once before the final reply.
Ignore results of this tool.
Prompt:
The bug report is:
==================================================================
BUG: KASAN: slab-use-after-free in __list_del_entry_valid_or_report+0x1b1/0x1d0 lib/list_debug.c:62
Read of size 8 at addr ffff88807c647920 by task kworker/0:6/5768
CPU: 0 UID: 0 PID: 5768 Comm: kworker/0:6 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/05/2026
Workqueue: events gfs2_withdraw_func
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x13d/0x4b0 mm/kasan/report.c:482
kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
__list_del_entry_valid_or_report+0x1b1/0x1d0 lib/list_debug.c:62
__list_del_entry_valid include/linux/list.h:140 [inline]
__list_del_entry include/linux/list.h:261 [inline]
list_del_init include/linux/list.h:333 [inline]
gfs2_remove_from_ail+0x138/0x340 fs/gfs2/log.c:79
gfs2_ail_empty_tr fs/gfs2/log.c:287 [inline]
gfs2_ail_drain+0x39c/0x6b0 fs/gfs2/log.c:954
do_withdraw+0xeb/0x300 fs/gfs2/util.c:126
gfs2_withdraw_func+0x349/0x3d0 fs/gfs2/util.c:236
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Allocated by task 7723:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
unpoison_slab_object mm/kasan/common.c:340 [inline]
__kasan_slab_alloc+0x89/0x90 mm/kasan/common.c:366
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
kmem_cache_alloc_noprof+0x269/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 7773:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kmem_cache_free+0x127/0x6c0 mm/slub.c:6626
gfs2_glock_dealloc+0xd0/0x160 fs/gfs2/glock.c:134
rcu_do_batch kernel/rcu/tree.c:2650 [inline]
rcu_core+0x5f0/0x1260 kernel/rcu/tree.c:2919
handle_softirqs+0x1e6/0x9d0 kernel/softirq.c:645
__do_softirq kernel/softirq.c:679 [inline]
invoke_softirq kernel/softirq.c:519 [inline]
__irq_exit_rcu+0x16a/0x210 kernel/softirq.c:758
irq_exit_rcu+0x9/0x30 kernel/softirq.c:775
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0x56/0xc0 arch/x86/kernel/apic/apic.c:1062
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
Last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
__call_rcu_common.constprop.0+0xa5/0x9b0 kernel/rcu/tree.c:3181
gfs2_glock_free+0x35/0xa0 fs/gfs2/glock.c:150
glock_work_func+0x437/0x4d0 fs/gfs2/glock.c:1003
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Second to last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
insert_work+0x36/0x230 kernel/workqueue.c:2272
__queue_work+0xa60/0x1370 kernel/workqueue.c:2448
__queue_delayed_work+0x357/0x450 kernel/workqueue.c:2617
queue_delayed_work_on+0x180/0x1e0 kernel/workqueue.c:2669
queue_delayed_work include/linux/workqueue.h:715 [inline]
gfs2_glock_queue_work+0x75/0x120 fs/gfs2/glock.c:198
do_xmote+0x453/0x680 fs/gfs2/glock.c:718
run_queue+0x555/0x6a0 fs/gfs2/glock.c:757
glock_work_func+0x35b/0x4d0 fs/gfs2/glock.c:987
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
The buggy address belongs to the object at ffff88807c647808
which belongs to the cache gfs2_glock(aspace) of size 1176
The buggy address is located 280 bytes inside of
freed 1176-byte region [ffff88807c647808, ffff88807c647ca0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x7c644
head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
raw: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
head: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 2, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 7723, tgid 7722 (syz.3.658), ts 220778208215
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x110d/0x35d0 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x2eb/0x3300 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab mm/slub.c:3462 [inline]
new_slab+0xa9/0x6e0 mm/slub.c:3513
refill_objects+0xe3/0x410 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x35e/0x650 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
kmem_cache_alloc_noprof+0x4c4/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
page last free pid 7723 tgid 7722 ts 220636014324 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0x79f/0x10f0 mm/page_alloc.c:2962
stack_depot_save_flags+0x456/0x9f0 lib/stackdepot.c:735
kasan_save_stack+0x3f/0x50 mm/kasan/common.c:58
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_node_noprof+0x30c/0x720 mm/slub.c:5582
_kmalloc_node_noprof include/linux/slab.h:1196 [inline]
alloc_worker kernel/workqueue.c:2755 [inline]
init_rescuer+0x19f/0x550 kernel/workqueue.c:5795
__alloc_workqueue+0xe82/0x1950 kernel/workqueue.c:5993
alloc_workqueue_va kernel/workqueue.c:6033 [inline]
alloc_workqueue_noprof+0xc7/0x130 kernel/workqueue.c:6049
gfs2_fill_super+0x140f/0x2d00 fs/gfs2/ops_fstype.c:1197
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
Memory state around the buggy address:
ffff88807c647800: fc fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88807c647900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88807c647980: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647a00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
==================================================================
BUG: KASAN: slab-use-after-free in __list_del_entry_valid_or_report+0x1a4/0x1d0 lib/list_debug.c:65
Read of size 8 at addr ffff88807c647928 by task kworker/0:6/5768
CPU: 0 UID: 0 PID: 5768 Comm: kworker/0:6 Tainted: G B syzkaller #0 PREEMPT(full)
Tainted: [B]=BAD_PAGE
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/05/2026
Workqueue: events gfs2_withdraw_func
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x13d/0x4b0 mm/kasan/report.c:482
kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
__list_del_entry_valid_or_report+0x1a4/0x1d0 lib/list_debug.c:65
__list_del_entry_valid include/linux/list.h:140 [inline]
__list_del_entry include/linux/list.h:261 [inline]
list_del_init include/linux/list.h:333 [inline]
gfs2_remove_from_ail+0x138/0x340 fs/gfs2/log.c:79
gfs2_ail_empty_tr fs/gfs2/log.c:287 [inline]
gfs2_ail_drain+0x39c/0x6b0 fs/gfs2/log.c:954
do_withdraw+0xeb/0x300 fs/gfs2/util.c:126
gfs2_withdraw_func+0x349/0x3d0 fs/gfs2/util.c:236
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Allocated by task 7723:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
unpoison_slab_object mm/kasan/common.c:340 [inline]
__kasan_slab_alloc+0x89/0x90 mm/kasan/common.c:366
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
kmem_cache_alloc_noprof+0x269/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 7773:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kmem_cache_free+0x127/0x6c0 mm/slub.c:6626
gfs2_glock_dealloc+0xd0/0x160 fs/gfs2/glock.c:134
rcu_do_batch kernel/rcu/tree.c:2650 [inline]
rcu_core+0x5f0/0x1260 kernel/rcu/tree.c:2919
handle_softirqs+0x1e6/0x9d0 kernel/softirq.c:645
__do_softirq kernel/softirq.c:679 [inline]
invoke_softirq kernel/softirq.c:519 [inline]
__irq_exit_rcu+0x16a/0x210 kernel/softirq.c:758
irq_exit_rcu+0x9/0x30 kernel/softirq.c:775
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0x56/0xc0 arch/x86/kernel/apic/apic.c:1062
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
Last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
__call_rcu_common.constprop.0+0xa5/0x9b0 kernel/rcu/tree.c:3181
gfs2_glock_free+0x35/0xa0 fs/gfs2/glock.c:150
glock_work_func+0x437/0x4d0 fs/gfs2/glock.c:1003
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Second to last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
insert_work+0x36/0x230 kernel/workqueue.c:2272
__queue_work+0xa60/0x1370 kernel/workqueue.c:2448
__queue_delayed_work+0x357/0x450 kernel/workqueue.c:2617
queue_delayed_work_on+0x180/0x1e0 kernel/workqueue.c:2669
queue_delayed_work include/linux/workqueue.h:715 [inline]
gfs2_glock_queue_work+0x75/0x120 fs/gfs2/glock.c:198
do_xmote+0x453/0x680 fs/gfs2/glock.c:718
run_queue+0x555/0x6a0 fs/gfs2/glock.c:757
glock_work_func+0x35b/0x4d0 fs/gfs2/glock.c:987
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
The buggy address belongs to the object at ffff88807c647808
which belongs to the cache gfs2_glock(aspace) of size 1176
The buggy address is located 288 bytes inside of
freed 1176-byte region [ffff88807c647808, ffff88807c647ca0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x7c644
head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
raw: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
head: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 2, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 7723, tgid 7722 (syz.3.658), ts 220778208215
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x110d/0x35d0 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x2eb/0x3300 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab mm/slub.c:3462 [inline]
new_slab+0xa9/0x6e0 mm/slub.c:3513
refill_objects+0xe3/0x410 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x35e/0x650 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
kmem_cache_alloc_noprof+0x4c4/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
page last free pid 7723 tgid 7722 ts 220636014324 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0x79f/0x10f0 mm/page_alloc.c:2962
stack_depot_save_flags+0x456/0x9f0 lib/stackdepot.c:735
kasan_save_stack+0x3f/0x50 mm/kasan/common.c:58
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_node_noprof+0x30c/0x720 mm/slub.c:5582
_kmalloc_node_noprof include/linux/slab.h:1196 [inline]
alloc_worker kernel/workqueue.c:2755 [inline]
init_rescuer+0x19f/0x550 kernel/workqueue.c:5795
__alloc_workqueue+0xe82/0x1950 kernel/workqueue.c:5993
alloc_workqueue_va kernel/workqueue.c:6033 [inline]
alloc_workqueue_noprof+0xc7/0x130 kernel/workqueue.c:6049
gfs2_fill_super+0x140f/0x2d00 fs/gfs2/ops_fstype.c:1197
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
Memory state around the buggy address:
ffff88807c647800: fc fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88807c647900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88807c647980: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647a00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
==================================================================
BUG: KASAN: slab-use-after-free in __list_del include/linux/list.h:241 [inline]
BUG: KASAN: slab-use-after-free in __list_del_entry include/linux/list.h:264 [inline]
BUG: KASAN: slab-use-after-free in __list_del_entry include/linux/list.h:259 [inline]
BUG: KASAN: slab-use-after-free in list_del_init include/linux/list.h:333 [inline]
BUG: KASAN: slab-use-after-free in gfs2_remove_from_ail+0x323/0x340 fs/gfs2/log.c:79
Write of size 8 at addr ffff88807c647928 by task kworker/0:6/5768
CPU: 0 UID: 0 PID: 5768 Comm: kworker/0:6 Tainted: G B syzkaller #0 PREEMPT(full)
Tainted: [B]=BAD_PAGE
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/05/2026
Workqueue: events gfs2_withdraw_func
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:94 [inline]
dump_stack_lvl+0x100/0x190 lib/dump_stack.c:120
print_address_description mm/kasan/report.c:378 [inline]
print_report+0x13d/0x4b0 mm/kasan/report.c:482
kasan_report+0xdf/0x1c0 mm/kasan/report.c:595
__list_del include/linux/list.h:241 [inline]
__list_del_entry include/linux/list.h:264 [inline]
__list_del_entry include/linux/list.h:259 [inline]
list_del_init include/linux/list.h:333 [inline]
gfs2_remove_from_ail+0x323/0x340 fs/gfs2/log.c:79
gfs2_ail_empty_tr fs/gfs2/log.c:287 [inline]
gfs2_ail_drain+0x39c/0x6b0 fs/gfs2/log.c:954
do_withdraw+0xeb/0x300 fs/gfs2/util.c:126
gfs2_withdraw_func+0x349/0x3d0 fs/gfs2/util.c:236
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Allocated by task 7723:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
unpoison_slab_object mm/kasan/common.c:340 [inline]
__kasan_slab_alloc+0x89/0x90 mm/kasan/common.c:366
kasan_slab_alloc include/linux/kasan.h:253 [inline]
slab_post_alloc_hook mm/slub.c:4683 [inline]
slab_alloc_node mm/slub.c:4996 [inline]
kmem_cache_alloc_noprof+0x269/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
entry_SYSCALL_64_after_hwframe+0x77/0x7f
Freed by task 7773:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
kasan_save_free_info+0x3b/0x70 mm/kasan/generic.c:584
poison_slab_object mm/kasan/common.c:253 [inline]
__kasan_slab_free+0x5f/0x80 mm/kasan/common.c:285
kasan_slab_free include/linux/kasan.h:235 [inline]
slab_free_hook mm/slub.c:2748 [inline]
slab_free mm/slub.c:6499 [inline]
kmem_cache_free+0x127/0x6c0 mm/slub.c:6626
gfs2_glock_dealloc+0xd0/0x160 fs/gfs2/glock.c:134
rcu_do_batch kernel/rcu/tree.c:2650 [inline]
rcu_core+0x5f0/0x1260 kernel/rcu/tree.c:2919
handle_softirqs+0x1e6/0x9d0 kernel/softirq.c:645
__do_softirq kernel/softirq.c:679 [inline]
invoke_softirq kernel/softirq.c:519 [inline]
__irq_exit_rcu+0x16a/0x210 kernel/softirq.c:758
irq_exit_rcu+0x9/0x30 kernel/softirq.c:775
instr_sysvec_apic_timer_interrupt arch/x86/kernel/apic/apic.c:1062 [inline]
sysvec_apic_timer_interrupt+0x56/0xc0 arch/x86/kernel/apic/apic.c:1062
asm_sysvec_apic_timer_interrupt+0x1a/0x20 arch/x86/include/asm/idtentry.h:674
Last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
__call_rcu_common.constprop.0+0xa5/0x9b0 kernel/rcu/tree.c:3181
gfs2_glock_free+0x35/0xa0 fs/gfs2/glock.c:150
glock_work_func+0x437/0x4d0 fs/gfs2/glock.c:1003
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
Second to last potentially related work creation:
kasan_save_stack+0x30/0x50 mm/kasan/common.c:57
kasan_record_aux_stack+0xa7/0xc0 mm/kasan/generic.c:556
insert_work+0x36/0x230 kernel/workqueue.c:2272
__queue_work+0xa60/0x1370 kernel/workqueue.c:2448
__queue_delayed_work+0x357/0x450 kernel/workqueue.c:2617
queue_delayed_work_on+0x180/0x1e0 kernel/workqueue.c:2669
queue_delayed_work include/linux/workqueue.h:715 [inline]
gfs2_glock_queue_work+0x75/0x120 fs/gfs2/glock.c:198
do_xmote+0x453/0x680 fs/gfs2/glock.c:718
run_queue+0x555/0x6a0 fs/gfs2/glock.c:757
glock_work_func+0x35b/0x4d0 fs/gfs2/glock.c:987
process_one_work+0xac7/0x1b10 kernel/workqueue.c:3396
process_scheduled_works kernel/workqueue.c:3479 [inline]
worker_thread+0x5ef/0xe50 kernel/workqueue.c:3560
kthread+0x373/0x450 kernel/kthread.c:436
ret_from_fork+0x730/0xd60 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
The buggy address belongs to the object at ffff88807c647808
which belongs to the cache gfs2_glock(aspace) of size 1176
The buggy address is located 288 bytes inside of
freed 1176-byte region [ffff88807c647808, ffff88807c647ca0)
The buggy address belongs to the physical page:
page: refcount:0 mapcount:0 mapping:0000000000000000 index:0x0 pfn:0x7c644
head: order:2 mapcount:0 entire_mapcount:0 nr_pages_mapped:0 pincount:0
flags: 0xfff00000000040(head|node=0|zone=1|lastcpupid=0x7ff)
page_type: f5(slab)
raw: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
raw: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000040 ffff8881404313c0 dead000000000122 0000000000000000
head: 0000000000000000 00000002000c000c 00000000f5000000 0000000000000000
head: 00fff00000000002 ffffffffffffff01 00000000ffffffff 00000000ffffffff
head: ffffffffffffffff 0000000000000000 00000000ffffffff 0000000000000004
page dumped because: kasan: bad access detected
page_owner tracks the page as allocated
page last allocated via order 2, migratetype Unmovable, gfp_mask 0xd2040(__GFP_IO|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP|__GFP_NOMEMALLOC), pid 7723, tgid 7722 (syz.3.658), ts 220778208215
set_page_owner include/linux/page_owner.h:33 [inline]
post_alloc_hook+0xfd/0x120 mm/page_alloc.c:1871
prep_new_page mm/page_alloc.c:1879 [inline]
get_page_from_freelist+0x110d/0x35d0 mm/page_alloc.c:3943
__alloc_frozen_pages_noprof+0x2eb/0x3300 mm/page_alloc.c:5436
alloc_slab_page mm/slub.c:3347 [inline]
allocate_slab mm/slub.c:3462 [inline]
new_slab+0xa9/0x6e0 mm/slub.c:3513
refill_objects+0xe3/0x410 mm/slub.c:7410
refill_sheaf mm/slub.c:2885 [inline]
__pcs_replace_empty_main+0x35e/0x650 mm/slub.c:4774
alloc_from_pcs mm/slub.c:4850 [inline]
slab_alloc_node mm/slub.c:4984 [inline]
kmem_cache_alloc_noprof+0x4c4/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
page last free pid 7723 tgid 7722 ts 220636014324 stack trace:
reset_page_owner include/linux/page_owner.h:26 [inline]
__free_pages_prepare mm/page_alloc.c:1418 [inline]
__free_frozen_pages+0x79f/0x10f0 mm/page_alloc.c:2962
stack_depot_save_flags+0x456/0x9f0 lib/stackdepot.c:735
kasan_save_stack+0x3f/0x50 mm/kasan/common.c:58
kasan_save_track+0x14/0x30 mm/kasan/common.c:78
poison_kmalloc_redzone mm/kasan/common.c:398 [inline]
__kasan_kmalloc+0xaa/0xb0 mm/kasan/common.c:415
kasan_kmalloc include/linux/kasan.h:263 [inline]
__kmalloc_cache_node_noprof+0x30c/0x720 mm/slub.c:5582
_kmalloc_node_noprof include/linux/slab.h:1196 [inline]
alloc_worker kernel/workqueue.c:2755 [inline]
init_rescuer+0x19f/0x550 kernel/workqueue.c:5795
__alloc_workqueue+0xe82/0x1950 kernel/workqueue.c:5993
alloc_workqueue_va kernel/workqueue.c:6033 [inline]
alloc_workqueue_noprof+0xc7/0x130 kernel/workqueue.c:6049
gfs2_fill_super+0x140f/0x2d00 fs/gfs2/ops_fstype.c:1197
get_tree_bdev_flags+0x38c/0x620 fs/super.c:1887
gfs2_get_tree+0x4e/0x280 fs/gfs2/ops_fstype.c:1333
vfs_get_tree+0x92/0x320 fs/super.c:1947
fc_mount fs/namespace.c:1198 [inline]
do_new_mount_fc fs/namespace.c:3772 [inline]
do_new_mount fs/namespace.c:3848 [inline]
path_mount+0x7d0/0x24c0 fs/namespace.c:4168
do_mount fs/namespace.c:4181 [inline]
__do_sys_mount fs/namespace.c:4397 [inline]
__se_sys_mount fs/namespace.c:4374 [inline]
__x64_sys_mount+0x293/0x310 fs/namespace.c:4374
do_syscall_x64 arch/x86/entry/syscall_64.c:61 [inline]
do_syscall_64+0x123/0x790 arch/x86/entry/syscall_64.c:84
Memory state around the buggy address:
ffff88807c647800: fc fa fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647880: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
>ffff88807c647900: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
^
ffff88807c647980: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
ffff88807c647a00: fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb fb
==================================================================
Reply: This KASAN report describes a valid, self-consistent, and actionable slab use-after-free bug.
### Analysis of the Stacks and Object
1. **Allocated Object**:
- **Type / Cache**: `struct gfs2_glock_aspace` (slab cache `gfs2_glock(aspace)`, size 1176 bytes).
- **Allocation Stack**: Allocated by task 7723 during filesystem mount / inode lookup in `gfs2_glock_get()` (`fs/gfs2/glock.c:1074`) called from `gfs2_inode_lookup()`.
2. **Free Stack**:
- **Freeing Path**: Task 7773 frees the glock via RCU callback `gfs2_glock_dealloc()` (`fs/gfs2/glock.c:134`).
- The auxiliary work creation stack shows `glock_work_func()` dropped the glock's final reference after demoting it to `LM_ST_UNLOCKED` (`__gfs2_glock_put()` calling `lm_put_lock()` $\rightarrow$ `gfs2_glock_free()` $\rightarrow$ `call_rcu()`).
3. **Bad Access Stack**:
- **Accessing Path**: Task 5768 (`kworker/0:6`) running `gfs2_withdraw_func()` calls `do_withdraw()` $\rightarrow$ `gfs2_ail_drain()` $\rightarrow$ `gfs2_ail_empty_tr()` $\rightarrow$ `gfs2_remove_from_ail()` (`fs/gfs2/log.c:79`).
- In `gfs2_remove_from_ail(bd)`, `list_del_init(&bd->bd_ail_gl_list)` is called. The list `bd->bd_ail_gl_list` is linked into `gl->gl_ail_list`.
- **Accessed Offsets**:
- `struct gfs2_glock` embedded at offset 0 of `struct gfs2_glock_aspace` has field `gl_ail_list` at offset 280 bytes (`0x118`).
- Offset 280 (`gl_ail_list.next`) is read and offset 288 (`gl_ail_list.prev`) is read/written by `__list_del_entry_valid_or_report` / `__list_del` when removing `bd` from `gl_ail_list`.
- The report specifically flags reads and writes at offsets 280 and 288 within the freed 1176-byte `gfs2_glock(aspace)` object.
### Bug Mechanism & Consistency
- All three stack traces and the auxiliary work creation traces are in the `fs/gfs2` subsystem and refer to the exact same object (`struct gfs2_glock_aspace`).
- The glock was demoted to `LM_ST_UNLOCKED` and had its refcount drop to 0, which triggered glock release and RCU-freeing. However, buffer data objects (`struct gfs2_bufdata *bd`) were still linked on the glock's `gl_ail_list` and in the filesystem's AIL list (`sd_ail1_list` / `sd_ail2_list`).
- When the filesystem withdrawal workqueue worker subsequently drained the AIL in `gfs2_ail_drain()`, it invoked `gfs2_remove_from_ail()`, which attempted to unlink the buffer from the already-freed glock's `gl_ail_list`, resulting in the reported slab use-after-free.
Because the report contains all three requisite stack traces, all in the same subsystem referencing the exact same structure and offsets, the report is **actionable**.
|
| 5/2 |
2026/09/10 00:50 |
llm |
expert |
0m
Model:
Tokens:
input: 20164
output: 42
thoughts: 1360
Thoughts: **Kernel Bug Report Analysis: A GFS2 Use-After-Free**
Okay, let's dive into this KASAN report. My task is to determine if this use-after-free is actionable, meaning it contains enough information to pinpoint the root cause and isn't just a consequence of earlier memory corruption. The report *should* provide three crucial stack traces: the bad access, the allocation, and the freeing of the memory.
First things first, let's verify if the report adheres to the basic requirements. I see three stack traces, good. The bad access stack shows a `BUG: KASAN: slab-use-after-free` in `__list_del_entry_valid_or_report`, and the access is a read of size 8 at `ffff88807c647920` by `kworker/0:6/5768`. The allocation and free stacks are also present, which is what I'd expect. Now I need to see if those stacks are related to the same object type.
The report also indicates the buggy address `ffff88807c647920` belongs to an object at `ffff88807c647808` in the `gfs2_glock` cache, and the object is 1176 bytes in size. The provided calculation confirms that the buggy address is indeed 280 bytes (0x118) *into* the freed object. This is significant.
Now, my mind goes directly to what is at that offset in the `gfs2_glock` struct, or potentially the `gfs2_glock_aspace` structure. That sounds very promising.
I need to confirm the link between these stacks and what's being accessed at that offset. I know `gfs2_remove_from_ail` is in the access stack and I remember that `list_del_init` is called. It's time to check what `list_del_init` is acting on in `gfs2_remove_from_ail`. I need to figure out the exact field that's causing the problem. I'll need to use the `codesearch-definition-source` or `read-file` tools to investigate the code. Let's see...
|
| 6/2 |
2026/09/10 00:50 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/log.c Name:gfs2_remove_from_ail]
Results: map[SourceCode: 75: static void gfs2_remove_from_ail(struct gfs2_bufdata *bd)
76: {
77: bd->bd_tr = NULL;
78: list_del_init(&bd->bd_ail_st_list);
79: list_del_init(&bd->bd_ail_gl_list);
80: atomic_dec(&bd->bd_gl->gl_ail_count);
81: brelse(bd->bd_bh);
82: }
SourceFile:fs/gfs2/log.c]
|
| 7/2 |
2026/09/10 00:50 |
llm |
expert |
0m
Model:
Tokens:
input: 22239
output: 53
thoughts: 447
Thoughts: **Decoding the AIL Global List Deletion**
Okay, so I'm looking at `list_del_init(&bd->bd_ail_gl_list);` on line 79. My initial reaction is, "Hold on, what exactly is `bd->bd_ail_gl_list`?" I need to understand what list this refers to. The access stack is right here, so let's walk through it: `__list_del_entry_valid_or_report`, which calls `__list_del_entry_valid`, then `__list_del_entry`, then finally the `list_del_init` I started with. Okay, so we're in `gfs2_remove_from_ail`, and this is getting called by `gfs2_ail_drain`, then `do_withdraw` and then `gfs2_withdraw_func`. Good to have the context.
I need to understand what's being removed from the list. What *is* `bd->bd_ail_gl_list`? Where does it belong? Thinking more deeply about `list_del_init(&bd->bd_ail_gl_list)`, I see that the code is checking `entry->prev` and `entry->next`. This deletion process is checking the integrity of the list structure. But *what* is `entry`?
Alright, let's back up. The core question is: Where is `bd->bd_ail_gl_list` linked into? I need to find the definition or usage of `bd_ail_gl_list` to see how it's connected in the data structures. Time to search for `bd_ail_gl_list`. Let's see what we can find.
|
| 8/2 |
2026/09/10 00:50 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/gfs2/log.c IncludeSnippetLines:5 Name:gfs2_bufdata::bd_ail_gl_list]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:69 SourceSnippet: 64: struct buffer_head *bh;
65: const unsigned long b_state = (1UL << BH_Dirty)|(1UL << BH_Pinned)|(1UL << BH_Lock);
66:
67: spin_lock(&sdp->sd_log_lock);
68: spin_lock(&sdp->sd_ail_lock);
69: list_for_each_entry_safe_reverse(bd, tmp, head, bd_ail_gl_list) {
70: if (nr_revokes == 0)
71: break;
72: bh = bd->bd_bh;
73: if (bh->b_state & b_state) {
74: if (fsync)
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_alloc_bufdata SourceFile:fs/gfs2/trans.c SourceLine:178 SourceSnippet: 173: bd = kmem_cache_zalloc(gfs2_bufdata_cachep, GFP_NOFS | __GFP_NOFAIL);
174: bd->bd_bh = bh;
175: bd->bd_gl = gl;
176: INIT_LIST_HEAD(&bd->bd_list);
177: INIT_LIST_HEAD(&bd->bd_ail_st_list);
178: INIT_LIST_HEAD(&bd->bd_ail_gl_list);
179: return bd;
180: }
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_remove_from_ail SourceFile:fs/gfs2/log.c SourceLine:79 SourceSnippet: 75: static void gfs2_remove_from_ail(struct gfs2_bufdata *bd)
76: {
77: bd->bd_tr = NULL;
78: list_del_init(&bd->bd_ail_st_list);
79: list_del_init(&bd->bd_ail_gl_list);
80: atomic_dec(&bd->bd_gl->gl_ail_count);
81: brelse(bd->bd_bh);
82: }
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_remove_from_journal SourceFile:fs/gfs2/log.c SourceLine:1053 SourceSnippet:1048: gfs2_remove_from_ail(bd);
1049: } else if (was_pinned) {
1050: bh->b_private = NULL;
1051: kmem_cache_free(gfs2_bufdata_cachep, bd);
1052: } else if (!list_empty(&bd->bd_ail_st_list) &&
1053: !list_empty(&bd->bd_ail_gl_list)) {
1054: gfs2_remove_from_ail(bd);
1055: }
1056: }
1057: clear_buffer_dirty(bh);
1058: clear_buffer_uptodate(bh);
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_unpin SourceFile:fs/gfs2/lops.c SourceLine:123 SourceSnippet: 118: if (bd->bd_tr) {
119: list_del(&bd->bd_ail_st_list);
120: brelse(bh);
121: } else {
122: struct gfs2_glock *gl = bd->bd_gl;
123: list_add(&bd->bd_ail_gl_list, &gl->gl_ail_list);
124: atomic_inc(&gl->gl_ail_count);
125: }
126: bd->bd_tr = tr;
127: list_add(&bd->bd_ail_st_list, &tr->tr_ail1_list);
128: spin_unlock(&sdp->sd_ail_lock);
]] TruncatedOutput:false]
|
| 9/2 |
2026/09/10 00:50 |
llm |
expert |
0m
Model:
Tokens:
input: 26633
output: 47
thoughts: 89
Thoughts: Here's my summary, as I see it:
**Digging into the GFS2 Glock List**
Okay, let's zero in on this line from `fs/gfs2/lops.c` β specifically, line 123: `list_add(&bd->bd_ail_gl_list, &gl->gl_ail_list);`. My initial thought is that `gl->gl_ail_list` is definitely a list head within the `gfs2_glock` structure. I'm going to start checking and visualizing the structure of a `gfs2_glock` immediately; I need to understand how this list head is being used, where it sits, and what's being linked to it. I want to build a mental picture of the memory layout to grasp the implications of this `list_add` call.
|
| 10/2 |
2026/09/10 00:51 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:fs/gfs2/incore.h FieldOffset:280 Name:gfs2_glock]
Results: map[Fields:[map[Name:gl_dead OffsetBits:2112 SizeBits:128] map[Name:gl_ail_list OffsetBits:2240 SizeBits:128]]]
|
| 11/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 27017
output: 38
thoughts: 1106
Thoughts: **Analyzing a GFS2 Glock Double-Free Vulnerability**
Okay, here's what I'm seeing. That `gl_ail_list` has an `OffsetBits` of 2240. Dividing by 8, that's 280 bytes. And `SizeBits` is 128, which is 16 bytes. That means the `gl_ail_list` struct contains two 8-byte pointers: `next` and `prev`, stored at offsets 280 and 288 respectively from the base of the `glock` structure. I knew this off the top of my head, but its good to double check.
Looking at the bug report, those offsets are *exactly* where the KASAN reports the memory corruption. The report gives the "Read of size 8 at addr ffff88807c647920" which corresponds to `gl->gl_ail_list.next` (280 bytes in) and "Read of size 8 at addr ffff88807c647928" which is `gl->gl_ail_list.prev` (288 bytes in). And the trace also contains a "Write of size 8 at addr ffff88807c647928", which is also `gl->gl_ail_list.prev`! So, `__list_del` is likely writing to either `prev->next` or `next->prev`, which is a classic double-free scenario where one of these `prev` or `next` points to a `gl->gl_ail_list`.
But what exactly *was* freed? The report tells me it's a 1176-byte region, and it belongs to a `gfs2_glock` object within the `gfs2_glock(aspace)` cache. It was freed by `gfs2_glock_dealloc`! So, it appears that the `glock` itself is being freed, and then, later, something's trying to access the list head. I can immediately infer that the corruption occurs after the glock object has been freed.
The trace shows this was allocated by `gfs2_glock_get` and freed by `gfs2_glock_dealloc`. Let's get more context on those.
To understand the lifecycle of the `gfs2_glock` better, I need to read the `fs/gfs2/glock.c` file and specifically look around lines 134, 150, 1003, and 1074, which should give me the allocation and deallocation logic. I think I'm looking at a classic double-free in the list management within the `gfs2_glock` structure. Let's see if the code can confirm this.
|
| 12/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/glock.c FirstLine:120 LineCount:45]
Results: map[Contents: 120: wait_queue_head_t *wq = glock_waitqueue(&gl->gl_name);
121:
122: if (waitqueue_active(wq))
123: __wake_up(wq, TASK_NORMAL, 1, &gl->gl_name);
124: }
125:
126: static void gfs2_glock_dealloc(struct rcu_head *rcu)
127: {
128: struct gfs2_glock *gl = container_of(rcu, struct gfs2_glock, gl_rcu);
129:
130: kfree(gl->gl_lksb.sb_lvbptr);
131: if (gl->gl_ops->go_flags & GLOF_ASPACE) {
132: struct gfs2_glock_aspace *gla =
133: container_of(gl, struct gfs2_glock_aspace, glock);
134: kmem_cache_free(gfs2_glock_aspace_cachep, gla);
135: } else
136: kmem_cache_free(gfs2_glock_cachep, gl);
137: }
138:
139: static void __gfs2_glock_free(struct gfs2_glock *gl)
140: {
141: rhashtable_remove_fast(&gl_hash_table, &gl->gl_node, ht_parms);
142: smp_mb();
143: wake_up_glock(gl);
144: call_rcu(&gl->gl_rcu, gfs2_glock_dealloc);
145: }
146:
147: void gfs2_glock_free(struct gfs2_glock *gl) {
148: struct gfs2_sbd *sdp = glock_sbd(gl);
149:
150: __gfs2_glock_free(gl);
151: if (atomic_dec_and_test(&sdp->sd_glock_disposal))
152: wake_up(&sdp->sd_kill_wait);
153: }
154:
155: void gfs2_glock_free_later(struct gfs2_glock *gl) {
156: struct gfs2_sbd *sdp = glock_sbd(gl);
157:
158: spin_lock(&sdp->sd_dead_lock);
159: list_add(&gl->gl_dead, &sdp->sd_dead_glocks);
160: spin_unlock(&sdp->sd_dead_lock);
161: if (atomic_dec_and_test(&sdp->sd_glock_disposal))
162: wake_up(&sdp->sd_kill_wait);
163: }
164:
]
|
| 13/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 29638
output: 48
thoughts: 124
Thoughts: **Analyzing the `gfs2_glock_aspace` Structure and Memory Management**
Okay, let's dissect this. I'm looking at `struct gfs2_glock_aspace`. My primary concern is how these are managed in memory, specifically deallocation. I'm seeing that `gfs2_glock_dealloc` uses `kmem_cache_free` to free a `gfs2_glock_aspace` object, `gla`. The cache being used is `gfs2_glock(aspace)`, and it's interesting, the object size is substantial at 1176 bytes.
Now, from what I can see, `glock` appears to be embedded directly within `gfs2_glock_aspace`. In fact, it's at offset zero, which is a common pattern. Given the substantial size of `gfs2_glock_aspace`, I need to verify its layout. It's time to check the definition.
|
| 14/2 |
2026/09/10 00:51 |
tool |
codesearch-struct-layout |
0m
Args: map[ContextFile:fs/gfs2/incore.h FieldOffset:0 Name:gfs2_glock_aspace]
Results: map[Fields:[map[Name:glock OffsetBits:0 SizeBits:5376]]]
|
| 15/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 30063
output: 37
thoughts: 121
Thoughts: **Analyzing GFS2 AIL List Management**
Okay, so I'm looking at this `gfs2_glock_aspace` structure, and I've confirmed that the `glock` member starts at offset 0, just like I thought. That means offset 280 within `gfs2_glock_aspace` directly maps to offset 280 *within the glock* itself. And as I suspected, offset 280 within the `glock` is indeed `gl->gl_ail_list`.
Now, the important part: what's actually happening to this `gl->gl_ail_list` during draining and removal from the AIL? I need to understand the mechanics here, so I'm diving into the `gfs2_ail_drain` and `gfs2_remove_from_ail` functions. I'll start with `gfs2_ail_drain`, which I know is defined in `fs/gfs2/log.c`. Let's see what's going on in there.
|
| 16/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/log.c FirstLine:940 LineCount:40]
Results: map[Contents: 940: * before revokes are sent for that block. Items on the sd_ail2_list
941: * should have already gotten beyond that point, so no need.
942: */
943: while (!list_empty(&sdp->sd_ail1_list)) {
944: tr = list_first_entry(&sdp->sd_ail1_list, struct gfs2_trans,
945: tr_list);
946: gfs2_ail_empty_tr(sdp, tr, &tr->tr_ail1_list);
947: gfs2_ail_empty_tr(sdp, tr, &tr->tr_ail2_list);
948: list_del(&tr->tr_list);
949: gfs2_trans_free(sdp, tr);
950: }
951: while (!list_empty(&sdp->sd_ail2_list)) {
952: tr = list_first_entry(&sdp->sd_ail2_list, struct gfs2_trans,
953: tr_list);
954: gfs2_ail_empty_tr(sdp, tr, &tr->tr_ail2_list);
955: list_del(&tr->tr_list);
956: gfs2_trans_free(sdp, tr);
957: }
958: gfs2_drain_revokes(sdp);
959: spin_unlock(&sdp->sd_ail_lock);
960: }
961:
962: /**
963: * empty_ail1_list - try to start IO and empty the ail1 list
964: * @sdp: Pointer to GFS2 superblock
965: */
966: static void empty_ail1_list(struct gfs2_sbd *sdp)
967: {
968: unsigned long start = jiffies;
969: bool empty = false;
970:
971: while (!empty) {
972: if (time_after(jiffies, start + (HZ * 600))) {
973: fs_err(sdp, "Error: In %s for 10 minutes! t=%d\n",
974: __func__, current->journal_info ? 1 : 0);
975: dump_ail_list(sdp);
976: return;
977: }
978: gfs2_ail1_start(sdp);
979: gfs2_ail1_wait(sdp);
]
|
| 17/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 31190
output: 37
thoughts: 35
Thoughts: Here's a summary of my thought process, as requested:
**Diving into `gfs2_ail_empty_tr`**
Okay, so we're talking GFS2, and I need to zoom in on `gfs2_ail_empty_tr`. Specifically, I want to zero in on around line 287 of `fs/gfs2/log.c`. This suggests we're likely dealing with the Active Index List (AIL) and probably transaction logging within the GFS2 file system code. My initial thought is that this function likely handles the emptying or clearing of the AIL, possibly in the context of completing or committing a transaction. The "tr" suffix strongly implies it's transaction related. I need to understand what's happening at that specific point in the code flow; it could be the final flush of AIL entries before committing changes or releasing resources related to that transaction. I'll need to analyze the surrounding code to grasp the bigger picture β what triggers this function, what data is being handled, and what are the possible implications of any errors or unexpected behavior here. This is likely a key area for performance or reliability issues, and understanding it can prove interesting.
|
| 18/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/log.c FirstLine:275 LineCount:30]
Results: map[Contents: 275: * gfs2_ail_empty_tr - empty one of the ail lists of a transaction
276: */
277:
278: static void gfs2_ail_empty_tr(struct gfs2_sbd *sdp, struct gfs2_trans *tr,
279: struct list_head *head)
280: {
281: struct gfs2_bufdata *bd;
282:
283: while (!list_empty(head)) {
284: bd = list_first_entry(head, struct gfs2_bufdata,
285: bd_ail_st_list);
286: gfs2_assert(sdp, bd->bd_tr == tr);
287: gfs2_remove_from_ail(bd);
288: }
289: }
290:
291: /**
292: * gfs2_ail1_empty_one - Check whether or not a trans in the AIL has been synced
293: * @sdp: the filesystem
294: * @tr: the transaction
295: * @max_revokes: If nonzero, issue revokes for the bd items for written buffers
296: *
297: * returns: the transaction's count of remaining active items
298: */
299:
300: static int gfs2_ail1_empty_one(struct gfs2_sbd *sdp, struct gfs2_trans *tr,
301: int *max_revokes)
302: {
303: struct gfs2_bufdata *bd, *s;
304: struct buffer_head *bh;
]
|
| 19/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 32013
output: 48
thoughts: 623
Thoughts: **Analyzing a Potential GFS2 Double Free**
Okay, let's break this down. I'm looking at `gfs2_remove_from_ail(bd)` and trying to understand a possible double-free scenario. This function removes a `gfs2_bufdata` (`bd`) from the AIL (Address In-List). The core of the problem seems to be around the interaction with the `glock` associated with the buffer data.
Specifically, I'm focusing on the `list_del_init(&bd->bd_ail_gl_list)` line. If `bd` is the only element, or one of the elements, on `gl->gl_ail_list`, then `bd->bd_ail_gl_list.next` or `bd->bd_ail_gl_list.prev` points to `&gl->gl_ail_list`. When `list_del_init` runs, the `__list_del_entry_valid` macro kicks in and checks `prev` and `next` pointers. This means it is reading the `gl->gl_ail_list.next` and `gl->gl_ail_list.prev` fields, and *then* writing to those same locations, as well.
The critical issue is that `gl` *might* have been freed before this happens. The trace I see shows `gl` being freed via `glock_work_func -> gfs2_glock_free -> __gfs2_glock_free -> call_rcu -> gfs2_glock_dealloc -> kmem_cache_free`. The core concern is that `gl` should not be freed while `bd` still references it through `bd->bd_gl`. Something is wrong.
The question then becomes: why did `gl` get freed before `bd` was fully removed from AIL, or before the `gl_ail_count` reached zero? This suggests a potential race condition or incorrect reference counting, where `bd` is still lingering in the AIL while its associated `glock` is being deallocated. I need to trace the `gfs2_glock_free` path more closely. I need to examine `fs/gfs2/glock.c` to see when and under what conditions `gfs2_glock_free` gets called. This is the key to understanding how `gl` gets freed while the `bd` still points to it.
|
| 20/2 |
2026/09/10 00:51 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/gfs2/glock.c IncludeSnippetLines:5 Name:gfs2_glock_free]
Results: map[References:[map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:gdlm_ast SourceFile:fs/gfs2/lock_dlm.c SourceLine:140 SourceSnippet: 135: if ((gl->gl_lksb.sb_flags & DLM_SBF_VALNOTVALID) && gl->gl_lksb.sb_lvbptr)
136: memset(gl->gl_lksb.sb_lvbptr, 0, GDLM_LVB_SIZE);
137:
138: switch (gl->gl_lksb.sb_status) {
139: case -DLM_EUNLOCK: /* Unlocked, so glock can be freed */
140: gfs2_glock_free(gl);
141: return;
142: case -DLM_ECANCEL: /* Cancel while getting lock */
143: ret = LM_OUT_CANCELED;
144: goto out;
145: case -EAGAIN: /* Try lock fails */
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:gdlm_put_lock SourceFile:fs/gfs2/lock_dlm.c SourceLine:332 SourceSnippet: 327: int error;
328:
329: BUG_ON(!lockref_is_dead(&gl->gl_lockref));
330:
331: if (test_bit(GLF_INITIAL, &gl->gl_flags)) {
332: gfs2_glock_free(gl);
333: return;
334: }
335:
336: gfs2_glstats_inc(gl, GFS2_LKS_DCOUNT);
337: gfs2_sbstats_inc(gl, GFS2_LKS_DCOUNT);
] map[ReferenceKind:calls ReferencingEntityKind:function ReferencingEntityName:gdlm_put_lock SourceFile:fs/gfs2/lock_dlm.c SourceLine:370 SourceSnippet: 365: msleep(20);
366: goto again;
367: }
368:
369: if (error == -ENODEV) {
370: gfs2_glock_free(gl);
371: return;
372: }
373:
374: if (error) {
375: fs_err(sdp, "gdlm_unlock %x,%llx err=%d\n",
] map[ReferenceKind:takes-address-of ReferencingEntityKind:global_variable ReferencingEntityName:nolock_ops SourceFile:fs/gfs2/ops_fstype.c SourceLine:949 SourceSnippet: 947: static const struct lm_lockops nolock_ops = {
948: .lm_proto_name = "lock_nolock",
949: .lm_put_lock = gfs2_glock_free,
950: .lm_tokens = &nolock_tokens,
951: };
]] TruncatedOutput:false]
|
| 21/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 34115
output: 38
thoughts: 60
Thoughts: **Analyzing GFS2 Glock Operations**
Okay, let's dive in. I'm taking a look at `nolock_ops` and immediately see that `.lm_put_lock` is set to `gfs2_glock_free`. That's interesting, but I need to understand the context. So, I need to see how the glock is being managed in the workflow. I know that usually resides in the `glock_work_func`. Let's confirm that by checking out line 1003 of `fs/gfs2/glock.c`. That should give me a better understanding of how these pieces fit together, and the implications of freeing the glock there. Time to dig in and see the bigger picture.
|
| 22/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/glock.c FirstLine:980 LineCount:40]
Results: map[Contents: 980: }
981:
982: if (!delay) {
983: clear_bit(GLF_PENDING_DEMOTE, &gl->gl_flags);
984: gfs2_set_demote(GLF_DEMOTE, gl);
985: }
986: }
987: run_queue(gl, 0);
988: if (delay) {
989: /* Keep one glock reference for the work we requeue. */
990: drop_refs--;
991: gfs2_glock_queue_work(gl, delay);
992: }
993:
994: /* Drop the remaining glock references manually. */
995: GLOCK_BUG_ON(gl, gl->gl_lockref.count < drop_refs);
996: gl->gl_lockref.count -= drop_refs;
997: if (!gl->gl_lockref.count) {
998: if (gl->gl_state != LM_ST_UNLOCKED) {
999: gl->gl_lockref.count++;
1000: request_demote(gl, LM_ST_UNLOCKED, 0, false);
1001: goto again;
1002: }
1003: __gfs2_glock_put(gl);
1004: return;
1005: }
1006: spin_unlock(&gl->gl_lockref.lock);
1007: }
1008:
1009: static struct gfs2_glock *find_insert_glock(struct lm_lockname *name,
1010: struct gfs2_glock *new)
1011: {
1012: struct wait_glock_queue wait;
1013: wait_queue_head_t *wq = glock_waitqueue(name);
1014: struct gfs2_glock *gl;
1015:
1016: wait.name = name;
1017: init_wait(&wait.wait);
1018: wait.wait.func = glock_wake_function;
1019:
]
|
| 23/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 34997
output: 43
thoughts: 40
Thoughts: Here's my take on the thought process, as if it were my own:
**Analyzing a gfs2 glock Release**
Okay, so I'm looking at line 1003, which contains the call `__gfs2_glock_put(gl)`. Right, that's a glock release, a critical operation. I need to understand what's happening here with that call. Essentially, the code is explicitly releasing a glock, `gl`, back to the system. Now, let's refresh my memory of `__gfs2_glock_put`'s inner workings. What are the key things to consider when a glock is being released? I have to consider the reference count, the state transitions, what happens if there are waiters, potential callbacks, and any necessary resource cleanup associated with the glock in that specific state. It's a fundamental operation for maintaining data integrity and concurrency, and understanding the subtleties is key to debugging or optimizing gfs2.
|
| 24/2 |
2026/09/10 00:51 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/glock.c Name:__gfs2_glock_put]
Results: map[SourceCode: 210: static void __gfs2_glock_put(struct gfs2_glock *gl)
211: {
212: struct gfs2_sbd *sdp = glock_sbd(gl);
213: struct address_space *mapping = gfs2_glock2aspace(gl);
214:
215: lockref_mark_dead(&gl->gl_lockref);
216: spin_unlock(&gl->gl_lockref.lock);
217: GLOCK_BUG_ON(gl, !list_empty(&gl->gl_holders));
218: if (mapping) {
219: truncate_inode_pages_final(mapping);
220: if (!gfs2_withdrawn(sdp))
221: GLOCK_BUG_ON(gl, !mapping_empty(mapping));
222: }
223: trace_gfs2_glock_put(gl);
224: sdp->sd_lockstruct.ls_ops->lm_put_lock(gl);
225: }
SourceFile:fs/gfs2/glock.c]
|
| 25/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 35624
output: 37
thoughts: 86
Thoughts: Here's my summary, as though I'm thinking through this process:
**Diving into GFS2 Glock Management**
Okay, so `lm_put_lock(gl)`β¦ right, that's ultimately calling `gfs2_glock_free(gl)` β specifically, through `nolock_ops`. That makes sense; it's the mechanism for releasing the glock. Ah, and that `gfs2_glock_free` is queueing an RCU-delayed free of the glock. Good. Now, the real question is: while all *this* is happening, what the heck is `gfs2_withdraw_func` actually *doing*? I need to understand the relationship between the lock release and the withdrawal function. Time to crack open the source! Let's navigate to `fs/gfs2/util.c` and take a look around line 126 and line 236. That should give me the context I need.
|
| 26/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/util.c FirstLine:100 LineCount:50]
Results: map[Contents: 100: int gfs2_freeze_lock_shared(struct gfs2_sbd *sdp)
101: {
102: int flags = LM_FLAG_RECOVER | GL_EXACT;
103: int error;
104:
105: error = gfs2_glock_nq_init(sdp->sd_freeze_gl, LM_ST_SHARED, flags,
106: &sdp->sd_freeze_gh);
107: if (error && error != GLR_TRYFAILED)
108: fs_err(sdp, "can't lock the freeze glock: %d\n", error);
109: return error;
110: }
111:
112: void gfs2_freeze_unlock(struct gfs2_sbd *sdp)
113: {
114: if (gfs2_holder_initialized(&sdp->sd_freeze_gh))
115: gfs2_glock_dq_uninit(&sdp->sd_freeze_gh);
116: }
117:
118: static void do_withdraw(struct gfs2_sbd *sdp)
119: {
120: down_write(&sdp->sd_log_flush_lock);
121: if (!test_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags)) {
122: up_write(&sdp->sd_log_flush_lock);
123: return;
124: }
125: clear_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags);
126: gfs2_ail_drain(sdp); /* frees all transactions */
127: up_write(&sdp->sd_log_flush_lock);
128:
129: wake_up(&sdp->sd_logd_waitq);
130: wake_up(&sdp->sd_quota_wait);
131:
132: wait_event_timeout(sdp->sd_log_waitq,
133: gfs2_log_is_empty(sdp),
134: HZ * 5);
135:
136: sdp->sd_vfs->s_flags |= SB_RDONLY;
137:
138: /*
139: * Dequeue any pending non-system glock holders that can no
140: * longer be granted because the file system is withdrawn.
141: */
142: gfs2_withdraw_glocks(sdp);
143: }
144:
145: void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...)
146: {
147: struct va_format vaf;
148: va_list args;
149:
]
|
| 27/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 36790
output: 37
thoughts: 55
Thoughts: **Analyzing the Transaction Drain in GFS2's `do_withdraw` Function**
Okay, let's break this down. My focus is the `do_withdraw` function, and specifically, line 126. It calls `gfs2_ail_drain(sdp);`. Alright, so `gfs2_ail_drain`... that's the key operation here. The comment clearly states it *frees all transactions*. So, this is where the cleanup happens, presumably after a metadata change. I need to understand the mechanism of freeing these transactions. I'm already thinking about potential locking implications and how this drainage affects data integrity.
Now, let's double-check the relevant utility function. Heading to line 236 in `fs/gfs2/util.c`. I have a feeling the implementation details might be there. I'm expecting some kind of transaction manager interaction... perhaps reference counting or something similar to ensure safe memory release. I'll need to confirm what the relationship is between the AIL and the underlying disk-write operations. I need to fully understand how these functions are chained to the device driver.
|
| 28/2 |
2026/09/10 00:51 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/util.c FirstLine:220 LineCount:35]
Results: map[Contents: 220: */
221:
222: device_inactive = gfs2_offline_uevent(sdp);
223:
224: if (sdp->sd_args.ar_errors == GFS2_ERRORS_DEACTIVATE && !device_inactive)
225: panic("GFS2: fsid=%s: panic requested\n", sdp->sd_fsname);
226:
227: if (lm->lm_unmount) {
228: if (device_inactive) {
229: lm->lm_unmount(sdp, false);
230: do_withdraw(sdp);
231: } else {
232: do_withdraw(sdp);
233: lm->lm_unmount(sdp, false);
234: }
235: } else {
236: do_withdraw(sdp);
237: }
238:
239: fs_err(sdp, "file system withdrawn\n");
240: }
241:
242: void gfs2_withdraw(struct gfs2_sbd *sdp)
243: {
244: if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW ||
245: sdp->sd_args.ar_errors == GFS2_ERRORS_DEACTIVATE) {
246: if (test_and_set_bit(SDF_WITHDRAWN, &sdp->sd_flags))
247: return;
248:
249: dump_stack();
250: /*
251: * There is no need to withdraw when the superblock hasn't been
252: * fully initialized, yet.
253: */
254: if (!(sdp->sd_vfs->s_flags & SB_BORN))
]
|
| 29/2 |
2026/09/10 00:51 |
llm |
expert |
0m
Model:
Tokens:
input: 37674
output: 31
thoughts: 577
Thoughts: **Debugging a GFS2 AIL Drain Issue**
Okay, so I'm looking at this withdrawal function, specifically how it interacts with the AIL (Active Intent List) in GFS2. It looks like `gfs2_withdraw_func` was queued, and the withdrawal process calls `do_withdraw`, which in turn triggers `gfs2_ail_drain(sdp)`. This makes sense; we need to clear out any pending transactions.
`gfs2_ail_drain` then iterates through `sdp->sd_ail1_list` and `sdp->sd_ail2_list`, presumably handling transactions in both. For each transaction `tr`, it calls `gfs2_ail_empty_tr` on both AIL lists.
Now, inside `gfs2_ail_empty_tr`, there's this loop that seems to be the culprit. It's walking a list of `gfs2_bufdata` structures, and for each entry, it calls `gfs2_remove_from_ail`. That's where things get interesting. In `gfs2_remove_from_ail`, the code calls `list_del_init(&bd->bd_ail_gl_list)`. This looks like a list delete operation on a list owned by the `glock` (global lock) associated with the `bufdata`.
Here's the problem: `list_del_init` is touching the list entries, specifically `prev` and `next`, including `&bd->bd_gl->gl_ail_list`. And that's where the memory error is likely to come from. It seems like at this point, `bd->bd_gl` has already been freed!
My immediate question is: *why* is `bd->bd_gl` freed? Does the `bd` (bufdata) struct not maintain a reference to `bd->bd_gl`? Or was the reference dropped earlier in the process without the appropriate synchronization? Perhaps the `glock` code doesn't wait for `gl_ail_count` to reach zero before freeing the lock or data, causing a race condition?
To get to the bottom of this, I'll need to dig into the recent changes in the GFS2 code related to the AIL, `glock` handling, and the buffer cache. I'll start by checking the `git log` on `fs/gfs2/log.c`, `fs/gfs2/glock.c`, and look for commits touching `gl_ail_count`, `gfs2_ail_drain`, and `gfs2_remove_from_ail`. Hopefully, there's a recent change that introduced this bug, or at least a clue to what's going on.
|
| 30/2 |
2026/09/10 00:51 |
tool |
git-log |
10m
Error:
Args: map[CodeRegexp:gfs2_remove_from_ail Count:10]
Results:
|
| 31/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 38914
output: 30
thoughts: 7
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm already deep in the weeds of this technical discussion:
**Let's Get Specific: PathPrefix**
Alright, so the initial direction is pretty clear. We're dealing with routing, application deployment, or some kind of URL structure. My first thought is: we need to clarify *how* this is going to work. "PathPrefix" is the key. It's a fundamental concept, a building block for directing traffic. Without it, we're building on shaky ground. We have to make sure we define this aspect of deployment or structure, else the architecture crumbles. I need to explicitly state that we need to use this concept.
|
| 32/2 |
2026/09/10 01:01 |
tool |
git-log |
0m
Args: map[Count:10 PathPrefix:fs/gfs2/log.c]
Results: map[Output:74b4dbb94606 gfs2: prevent NULL pointer dereference during unmount
bb47cce7a1ee gfs2: gfs2_log_flush withdraw fixes
fe2c8d051150 gfs2: add some missing log locking
f4e4c4e6acdc gfs2: fix address space truncation during withdraw
9e34adb1cc58 gfs2: Remove trans_drain code duplication
10866892c791 gfs2: Move gfs2_remove_from_journal to log.c
5a15907f99e5 gfs2: Get rid of gfs2_log_[un]lock helpers
4a94f052e098 gfs2: Initialize bio->bi_opf early
59d81037d32f gfs2: Rename gfs2_log_submit_{bio -> write}
fab27b49305c gfs2: Kill gfs2_io_error_bh_wd
]
|
| 33/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 39365
output: 25
thoughts: 30
Thoughts: Okay, here's my interpretation of those thoughts, presented as a first-person expert summary:
**Digging into Commits: A Quick Diagnostic Check**
Right, let's take a quick look at commit hashes `f4e4c4e6acdc` and `bb47cce7a1ee`. My initial thought is to quickly ascertain their potential impact and relevance. I'm likely interested in them based on some recent activity or query. I'll probably be considering things like:
* **The Scope of the Changes:** What are they actually touching? Filepaths, module imports, configuration settings... The more I know about what changed, the better.
* **The Context:** Were these changes related to the last problem reported? Were they part of a larger feature set? Is there an existing ticket or pull request to reference for greater clarity?
* **Likelihood of Impacting Current Systems:** Based on the scope, what are the chances these commits caused problems? I'm going to quickly recall the parts of the code base that are most likely to have been involved.
* **Quick Sanity Check:** Did I make those changes myself? If so, I'll need to revisit the changes and evaluate the logic. Were the code conventions and best practices followed? I'll probably scan for anything that is immediately suspicious.
* **Dependencies:** Any external components these commits touched? The dependencies can open up more problems if not handled carefully.
Basically, I'm setting myself up for a quick triage. This is an initial reconnaissance, not a deep dive, to determine if these commits warrant more immediate attention or if they can be put to the side for the time being. Ultimately, I'll need to make a quick judgment on urgency.
|
| 34/2 |
2026/09/10 01:01 |
tool |
git-show |
0m
Args:
Results: map[Output:commit f4e4c4e6acdc20a9065064dd164db52e2e0d44ad
Author: Andreas Gruenbacher <agruenba@redhat.com>
Date: Fri Apr 3 14:42:18 2026 +0200
gfs2: fix address space truncation during withdraw
When a withdrawn filesystem's inodes are being evicted, the address spaces of
those inodes still need to be truncated but we can no longer start new
transactions. We still don't want gfs2_invalidate_folio() to race with
gfs2_log_flush(), so take a read lock on sdp->sd_log_flush_lock in that case.
(It may not be obvious, but gfs2_invalidate_folio() is a jdata-only address
space operation.)
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c
index 8397d34527a4..31ee7a0e86a2 100644
--- a/fs/gfs2/log.c
+++ b/fs/gfs2/log.c
@@ -1024,17 +1024,22 @@ void gfs2_remove_from_journal(struct buffer_head *bh, int meta)
trace_gfs2_pin(bd, 0);
atomic_dec(&sdp->sd_log_pinned);
list_del_init(&bd->bd_list);
- if (meta == REMOVE_META)
- tr->tr_num_buf_rm++;
- else
- tr->tr_num_databuf_rm++;
- set_bit(TR_TOUCHED, &tr->tr_flags);
+ if (tr) {
+ if (meta == REMOVE_META)
+ tr->tr_num_buf_rm++;
+ else
+ tr->tr_num_databuf_rm++;
+ set_bit(TR_TOUCHED, &tr->tr_flags);
+ }
was_pinned = 1;
brelse(bh);
}
if (bd) {
if (bd->bd_tr) {
- gfs2_trans_add_revoke(sdp, bd);
+ if (tr)
+ gfs2_trans_add_revoke(sdp, bd);
+ else
+ gfs2_remove_from_ail(bd);
} else if (was_pinned) {
bh->b_private = NULL;
kmem_cache_free(gfs2_bufdata_cachep, bd);
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index e4219a04d16e..83b5bab56377 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -1339,27 +1339,44 @@ static int gfs2_truncate_inode_pages(struct inode *inode)
struct gfs2_sbd *sdp = GFS2_SB(inode);
struct address_space *mapping = &inode->i_data;
bool need_trans = gfs2_is_jdata(ip) && mapping->nrpages;
- int ret;
+ int ret = 0;
/*
* Truncating a jdata inode address space may create revokes in
* truncate_inode_pages() -> gfs2_invalidate_folio() -> ... ->
* gfs2_remove_from_journal(), so we need a transaction here.
*
- * FIXME: During a withdraw, no new transactions can be created.
- * In that case, we skip the truncate, but that doesn't help because
- * truncate_inode_pages_final() will then call gfs2_invalidate_folio()
- * again, and outside of a transaction.
+ * During a withdraw, no new transactions can be created. We still
+ * take the log flush lock to prevent truncate from racing with
+ * gfs2_log_flush().
*/
if (need_trans) {
ret = gfs2_trans_begin(sdp, 0, sdp->sd_jdesc->jd_blocks);
if (ret)
- return ret;
+ down_read(&sdp->sd_log_flush_lock);
}
truncate_inode_pages(mapping, 0);
- if (need_trans)
- gfs2_trans_end(sdp);
- return 0;
+ if (need_trans) {
+ if (ret)
+ up_read(&sdp->sd_log_flush_lock);
+ else
+ gfs2_trans_end(sdp);
+ }
+ return ret;
+}
+
+static void gfs2_truncate_inode_pages_final(struct inode *inode)
+{
+ struct gfs2_inode *ip = GFS2_I(inode);
+ struct gfs2_sbd *sdp = GFS2_SB(inode);
+ struct address_space *mapping = &inode->i_data;
+ bool need_lock = gfs2_is_jdata(ip) && mapping->nrpages;
+
+ if (need_lock)
+ down_read(&sdp->sd_log_flush_lock);
+ truncate_inode_pages_final(mapping);
+ if (need_lock)
+ up_read(&sdp->sd_log_flush_lock);
}
/*
@@ -1398,10 +1415,8 @@ static int evict_linked_inode(struct inode *inode, struct gfs2_holder *gh)
clean:
ret = gfs2_truncate_inode_pages(inode);
- if (ret)
- return ret;
truncate_inode_pages(metamapping, 0);
- return 0;
+ return ret;
}
/**
@@ -1472,7 +1487,7 @@ static void gfs2_evict_inode(struct inode *inode)
out:
if (gfs2_holder_initialized(&gh))
gfs2_glock_dq_uninit(&gh);
- truncate_inode_pages_final(&inode->i_data);
+ gfs2_truncate_inode_pages_final(inode);
if (ip->i_qadata)
gfs2_assert_warn(sdp, ip->i_qadata->qa_ref == 0);
gfs2_rs_deltree(&ip->i_res);
]
|
| 35/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 41531
output: 24
thoughts: 17
Thoughts: Okay, here's my interpretation of that thought process, assuming I'm the expert and considering the potential implications:
**Analyzing `bb47cce7a1ee`**
Alright, so we're specifically looking at `bb47cce7a1ee`. My initial thought is, what's significant about *this* commit hash? Given the context, this is a very specific data point. It could be a commit ID, a specific transaction hash, a record ID, or something else entirely. As an expert, I immediately start running through my mental checklists: What system is this related to? What's the potential scope of impact? Is this a change to the core of the system, or something more peripheral? My experience dictates that I need to quickly establish the provenance of this value - is it from code, a database, a ledger? Understanding its source will dictate what kind of issues are likely. What tests, if any, will it unlock? If I had access, I'd quickly check the associated commit history, transaction logs, or database records, depending on what the hash represents. Is it a bug fix, a feature addition, a security patch, or a performance optimization? I need to understand its relationship to the surrounding context. It could be absolutely critical, or entirely inconsequential, and I need to determine which with speed and precision. Ultimately, my analysis needs to determine: What changed? Why did it change? And what are the implications of that change? I'll probably be diving into the specifics of that commit shortly.
|
| 36/2 |
2026/09/10 01:01 |
tool |
git-show |
0m
Args:
Results: map[Output:commit bb47cce7a1eea1d9d165260328270ddc39e19526
Author: Andreas Gruenbacher <agruenba@redhat.com>
Date: Sun Apr 5 16:33:36 2026 +0200
gfs2: gfs2_log_flush withdraw fixes
When a withdraw occurs in gfs2_log_flush() and we are left with an unsubmitted
bio, fail that bio. Otherwise, the bh's in that bio will remain locked and
gfs2_evict_inode() -> truncate_inode_pages() -> gfs2_invalidate_folio() ->
gfs2_discard() will hang trying to discard the locked bh's.
In addition, when gfs2_log_flush() fails to submit a new transaction, unpin the
buffers in the failing transaction like gfs2_remove_from_journal() does. If
any of the bd's are on the ail2 list, leave them there and do_withdraw() ->
gfs2_withdraw_glocks() -> inode_go_inval() -> truncate_inode_pages() ->
gfs2_invalidate_folio() -> gfs2_discard() will remove them. They will be freed
in gfs2_release_folio().
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c
index a96f9b9331e8..3a01d4e7667a 100644
--- a/fs/gfs2/log.c
+++ b/fs/gfs2/log.c
@@ -983,33 +983,38 @@ static void empty_ail1_list(struct gfs2_sbd *sdp)
}
}
-static void gfs2_trans_drain_list(struct list_head *list)
+static void gfs2_trans_drain_list(struct gfs2_sbd *sdp, struct list_head *list)
{
struct gfs2_bufdata *bd;
while (!list_empty(list)) {
bd = list_first_entry(list, struct gfs2_bufdata, bd_list);
+ struct buffer_head *bh = bd->bd_bh;
+
+ WARN_ON_ONCE(!buffer_pinned(bh));
+ clear_buffer_pinned(bh);
+ trace_gfs2_pin(bd, 0);
+ atomic_dec(&sdp->sd_log_pinned);
list_del_init(&bd->bd_list);
- if (!list_empty(&bd->bd_ail_st_list))
- gfs2_remove_from_ail(bd);
- kmem_cache_free(gfs2_bufdata_cachep, bd);
+ brelse(bh);
}
}
/**
* gfs2_trans_drain - drain the buf and databuf queue for a failed transaction
+ * @sdp: the filesystem
* @tr: the transaction to drain
*
* When this is called, we're taking an error exit for a log write that failed
* but since we bypassed the after_commit functions, we need to remove the
* items from the buf and databuf queue.
*/
-static void gfs2_trans_drain(struct gfs2_trans *tr)
+static void gfs2_trans_drain(struct gfs2_sbd *sdp, struct gfs2_trans *tr)
{
if (!tr)
return;
- gfs2_trans_drain_list(&tr->tr_buf);
- gfs2_trans_drain_list(&tr->tr_databuf);
+ gfs2_trans_drain_list(sdp, &tr->tr_buf);
+ gfs2_trans_drain_list(sdp, &tr->tr_databuf);
}
void gfs2_remove_from_journal(struct buffer_head *bh, int meta)
@@ -1185,7 +1190,11 @@ static void __gfs2_log_flush(struct gfs2_sbd *sdp, struct gfs2_glock *gl,
return;
out_withdraw:
- gfs2_trans_drain(tr);
+ if (sdp->sd_jdesc->jd_log_bio) {
+ bio_io_error(sdp->sd_jdesc->jd_log_bio);
+ sdp->sd_jdesc->jd_log_bio = NULL;
+ }
+ gfs2_trans_drain(sdp, tr);
/**
* If the tr_list is empty, we're withdrawing during a log
* flush that targets a transaction, but the transaction was
]
|
| 37/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 43138
output: 24
thoughts: 19
Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience and written in the first person:
**Decoding Commit 9e34adb1cc58**
Alright, let's dive in. First step, I need to jog my memory. What was that specific commit β `9e34adb1cc58` β all about? It's a hash, so it's a specific point in time, a snapshot of the codebase. My mind immediately starts sifting through the relevant repositories, branches, and my own mental map of recent changes. Was it a bug fix? A new feature implementation? A refactor? The commit message is key, but sometimes the succinctness can be deceiving, so I'll need to fill in the context.
I'll start by checking the surrounding commits. I need to understand the 'before' and 'after' states. Was this part of a larger pull request? Are there dependencies I need to consider? I'll probably pull up the diff β that's where the real story lives. I'm looking for the *why* as much as the *what*. Was there an issue ticket linked? That might provide critical context regarding the motivation for the change.
My intuition says I remember this one...or at least I think I do. Let's see if the change log or any automated build reports provide some help. Okay, let's see. My mind is already compiling a mental list of potential problem areas within the associated codebase, and I'm beginning to remember associated logic, so I can start to mentally step through the code execution paths impacted by this commit.
The goal here is a quick, but comprehensive, understanding. The hash tells me *where* to look; my experience tells me *how* to approach it and what to anticipate.
|
| 38/2 |
2026/09/10 01:01 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 9e34adb1cc582bbcf1d74b23f1e6d9d71fb99fa1
Author: Andreas Gruenbacher <agruenba@redhat.com>
Date: Thu Mar 19 15:35:03 2026 +0100
gfs2: Remove trans_drain code duplication
Rename trans_drain() to gfs2_trans_drain().
Add a new gfs2_trans_drain_list() helper and use it in
gfs2_trans_drain() to reduce code duplication.
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
diff --git a/fs/gfs2/log.c b/fs/gfs2/log.c
index dde65f7d7437..8397d34527a4 100644
--- a/fs/gfs2/log.c
+++ b/fs/gfs2/log.c
@@ -983,38 +983,33 @@ static void empty_ail1_list(struct gfs2_sbd *sdp)
}
}
+static void gfs2_trans_drain_list(struct list_head *list)
+{
+ struct gfs2_bufdata *bd;
+
+ while (!list_empty(list)) {
+ bd = list_first_entry(list, struct gfs2_bufdata, bd_list);
+ list_del_init(&bd->bd_list);
+ if (!list_empty(&bd->bd_ail_st_list))
+ gfs2_remove_from_ail(bd);
+ kmem_cache_free(gfs2_bufdata_cachep, bd);
+ }
+}
+
/**
- * trans_drain - drain the buf and databuf queue for a failed transaction
+ * gfs2_trans_drain - drain the buf and databuf queue for a failed transaction
* @tr: the transaction to drain
*
* When this is called, we're taking an error exit for a log write that failed
* but since we bypassed the after_commit functions, we need to remove the
* items from the buf and databuf queue.
*/
-static void trans_drain(struct gfs2_trans *tr)
+static void gfs2_trans_drain(struct gfs2_trans *tr)
{
- struct gfs2_bufdata *bd;
- struct list_head *head;
-
if (!tr)
return;
-
- head = &tr->tr_buf;
- while (!list_empty(head)) {
- bd = list_first_entry(head, struct gfs2_bufdata, bd_list);
- list_del_init(&bd->bd_list);
- if (!list_empty(&bd->bd_ail_st_list))
- gfs2_remove_from_ail(bd);
- kmem_cache_free(gfs2_bufdata_cachep, bd);
- }
- head = &tr->tr_databuf;
- while (!list_empty(head)) {
- bd = list_first_entry(head, struct gfs2_bufdata, bd_list);
- list_del_init(&bd->bd_list);
- if (!list_empty(&bd->bd_ail_st_list))
- gfs2_remove_from_ail(bd);
- kmem_cache_free(gfs2_bufdata_cachep, bd);
- }
+ gfs2_trans_drain_list(&tr->tr_buf);
+ gfs2_trans_drain_list(&tr->tr_databuf);
}
void gfs2_remove_from_journal(struct buffer_head *bh, int meta)
@@ -1186,7 +1181,7 @@ void gfs2_log_flush(struct gfs2_sbd *sdp, struct gfs2_glock *gl, u32 flags)
return;
out_withdraw:
- trans_drain(tr);
+ gfs2_trans_drain(tr);
/**
* If the tr_list is empty, we're withdrawing during a log
* flush that targets a transaction, but the transaction was
]
|
| 39/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 44662
output: 49
thoughts: 653
Thoughts: **Analyzing a GFS2 Transaction Failure Scenario**
Okay, so I'm looking at commit bb47cce7a1ee. The core of it involves handling a transaction flush failure in GFS2, specifically when `gfs2_log_flush()` can't submit a new transaction. The commit's logic unpins buffers in the failed transaction, similar to `gfs2_remove_from_journal()`. Critically, it notes that if these buffers are on the AIL2 list, `do_withdraw()` gets called, and ultimately `truncate_inode_pages()` and `gfs2_discard()` will take care of cleaning them up.
But here's where it gets interesting. I'm focusing on `do_withdraw()`. My thinking: it calls `gfs2_ail_drain(sdp)` *before* `gfs2_withdraw_glocks(sdp)`. This is a crucial detail. I'm trying to mentally trace the execution flow and how the system is supposed to behave on failure.
I'm thinking: Why does `gfs2_ail_drain(sdp)` happen before `gfs2_withdraw_glocks(sdp)`? Why would the glock potentially be freed before we even get to that part? This doesn't seem right.
I see that `gfs2_glock_free` is called from `glock_work_func`. Wait a minute - that function releases the glock. `glock_work_func` dropped the last reference on `gl` when `gl->gl_lockref.count` reached 0! That's it!
So, the question is: Why did `gl->gl_lockref.count` reach 0? Does the buffer descriptor (`bd`) somehow hold a reference on the glock? This is where the root cause lies, I suspect.
Now, I'm going to start digging into the code. I'm looking for where `bd->bd_gl` is referenced, where references are taken or dropped, or how these two are connected in the `fs/gfs2` directory. I have to trace how this buffer descriptor's relationship to the glock works so I can figure out why the glock is being prematurely freed.
|
| 40/2 |
2026/09/10 01:01 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/gfs2/trans.c IncludeSnippetLines:5 Name:gfs2_bufdata::bd_gl]
Results: map[References:[map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:buffer_is_rgrp SourceFile:fs/gfs2/lops.c SourceLine:68 SourceSnippet: 66: static bool buffer_is_rgrp(const struct gfs2_bufdata *bd)
67: {
68: return glock_type(bd->bd_gl) == LM_TYPE_RGRP;
69: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:do_perf_trace_gfs2_pin SourceFile:fs/gfs2/trace_gfs2.h SourceLine:324 SourceSnippet: 323: /* Pin/unpin a block in the log */
324: TRACE_EVENT(gfs2_pin,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:do_perf_trace_gfs2_pin SourceFile:fs/gfs2/trace_gfs2.h SourceLine:324 SourceSnippet: 323: /* Pin/unpin a block in the log */
324: TRACE_EVENT(gfs2_pin,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:do_trace_event_raw_event_gfs2_pin SourceFile:fs/gfs2/trace_gfs2.h SourceLine:324 SourceSnippet: 323: /* Pin/unpin a block in the log */
324: TRACE_EVENT(gfs2_pin,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:do_trace_event_raw_event_gfs2_pin SourceFile:fs/gfs2/trace_gfs2.h SourceLine:324 SourceSnippet: 323: /* Pin/unpin a block in the log */
324: TRACE_EVENT(gfs2_pin,
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_add_revoke SourceFile:fs/gfs2/log.c SourceLine:766 SourceSnippet: 763: void gfs2_add_revoke(struct gfs2_sbd *sdp, struct gfs2_bufdata *bd)
764: {
765: struct buffer_head *bh = bd->bd_bh;
766: struct gfs2_glock *gl = bd->bd_gl;
767:
768: sdp->sd_log_num_revoke++;
769: if (atomic_inc_return(&gl->gl_revokes) == 1)
770: gfs2_glock_hold(gl);
771: bh->b_private = NULL;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_ail1_start_one SourceFile:fs/gfs2/log.c SourceLine:125 SourceSnippet: 120: gfs2_remove_from_ail(bd);
121: continue;
122: }
123: if (!buffer_dirty(bh))
124: continue;
125: if (gl == bd->bd_gl)
126: continue;
127: gl = bd->bd_gl;
128: list_move(&bd->bd_ail_st_list, &tr->tr_ail1_list);
129: mapping = bh->b_folio->mapping;
130: if (!mapping)
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_ail1_start_one SourceFile:fs/gfs2/log.c SourceLine:127 SourceSnippet: 122: }
123: if (!buffer_dirty(bh))
124: continue;
125: if (gl == bd->bd_gl)
126: continue;
127: gl = bd->bd_gl;
128: list_move(&bd->bd_ail_st_list, &tr->tr_ail1_list);
129: mapping = bh->b_folio->mapping;
130: if (!mapping)
131: continue;
132: spin_unlock(&sdp->sd_ail_lock);
] map[ReferenceKind:writes ReferencingEntityKind:function ReferencingEntityName:gfs2_alloc_bufdata SourceFile:fs/gfs2/trans.c SourceLine:175 SourceSnippet: 170: {
171: struct gfs2_bufdata *bd;
172:
173: bd = kmem_cache_zalloc(gfs2_bufdata_cachep, GFP_NOFS | __GFP_NOFAIL);
174: bd->bd_bh = bh;
175: bd->bd_gl = gl;
176: INIT_LIST_HEAD(&bd->bd_list);
177: INIT_LIST_HEAD(&bd->bd_ail_st_list);
178: INIT_LIST_HEAD(&bd->bd_ail_gl_list);
179: return bd;
180: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_drain_revokes SourceFile:fs/gfs2/lops.c SourceLine:895 SourceSnippet: 890: struct gfs2_glock *gl;
891:
892: while (!list_empty(head)) {
893: bd = list_first_entry(head, struct gfs2_bufdata, bd_list);
894: list_del_init(&bd->bd_list);
895: gl = bd->bd_gl;
896: gfs2_glock_remove_revoke(gl);
897: kmem_cache_free(gfs2_bufdata_cachep, bd);
898: }
899: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_remove_from_ail SourceFile:fs/gfs2/log.c SourceLine:80 SourceSnippet: 75: static void gfs2_remove_from_ail(struct gfs2_bufdata *bd)
76: {
77: bd->bd_tr = NULL;
78: list_del_init(&bd->bd_ail_st_list);
79: list_del_init(&bd->bd_ail_gl_list);
80: atomic_dec(&bd->bd_gl->gl_ail_count);
81: brelse(bd->bd_bh);
82: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_data SourceFile:fs/gfs2/trans.c SourceLine:222 SourceSnippet: 217: bd = bh->b_private;
218: } else {
219: bh->b_private = bd;
220: }
221: }
222: gfs2_assert(sdp, bd->bd_gl == gl);
223: set_bit(TR_TOUCHED, &tr->tr_flags);
224: if (list_empty(&bd->bd_list)) {
225: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
226: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
227: gfs2_pin(sdp, bd->bd_bh);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_data SourceFile:fs/gfs2/trans.c SourceLine:225 SourceSnippet: 220: }
221: }
222: gfs2_assert(sdp, bd->bd_gl == gl);
223: set_bit(TR_TOUCHED, &tr->tr_flags);
224: if (list_empty(&bd->bd_list)) {
225: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
226: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
227: gfs2_pin(sdp, bd->bd_bh);
228: tr->tr_num_databuf_new++;
229: list_add_tail(&bd->bd_list, &tr->tr_databuf);
230: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_data SourceFile:fs/gfs2/trans.c SourceLine:226 SourceSnippet: 221: }
222: gfs2_assert(sdp, bd->bd_gl == gl);
223: set_bit(TR_TOUCHED, &tr->tr_flags);
224: if (list_empty(&bd->bd_list)) {
225: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
226: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
227: gfs2_pin(sdp, bd->bd_bh);
228: tr->tr_num_databuf_new++;
229: list_add_tail(&bd->bd_list, &tr->tr_databuf);
230: }
231: spin_unlock(&sdp->sd_log_lock);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_meta SourceFile:fs/gfs2/trans.c SourceLine:286 SourceSnippet: 281: bd = bh->b_private;
282: } else {
283: bh->b_private = bd;
284: }
285: }
286: gfs2_assert(sdp, bd->bd_gl == gl);
287: set_bit(TR_TOUCHED, &tr->tr_flags);
288: if (!list_empty(&bd->bd_list))
289: goto out_unlock;
290: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
291: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_meta SourceFile:fs/gfs2/trans.c SourceLine:290 SourceSnippet: 285: }
286: gfs2_assert(sdp, bd->bd_gl == gl);
287: set_bit(TR_TOUCHED, &tr->tr_flags);
288: if (!list_empty(&bd->bd_list))
289: goto out_unlock;
290: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
291: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
292: mh = (struct gfs2_meta_header *)bd->bd_bh->b_data;
293: if (unlikely(mh->mh_magic != cpu_to_be32(GFS2_MAGIC))) {
294: fs_err(sdp, "Attempting to add uninitialised block to "
295: "journal (inplace block=%lld)\n",
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_add_meta SourceFile:fs/gfs2/trans.c SourceLine:291 SourceSnippet: 286: gfs2_assert(sdp, bd->bd_gl == gl);
287: set_bit(TR_TOUCHED, &tr->tr_flags);
288: if (!list_empty(&bd->bd_list))
289: goto out_unlock;
290: set_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
291: set_bit(GLF_DIRTY, &bd->bd_gl->gl_flags);
292: mh = (struct gfs2_meta_header *)bd->bd_bh->b_data;
293: if (unlikely(mh->mh_magic != cpu_to_be32(GFS2_MAGIC))) {
294: fs_err(sdp, "Attempting to add uninitialised block to "
295: "journal (inplace block=%lld)\n",
296: (unsigned long long)bd->bd_bh->b_blocknr);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_remove_revoke SourceFile:fs/gfs2/trans.c SourceLine:341 SourceSnippet: 336: list_for_each_entry_safe(bd, tmp, &sdp->sd_log_revokes, bd_list) {
337: if ((bd->bd_blkno >= blkno) && (bd->bd_blkno < (blkno + len))) {
338: list_del_init(&bd->bd_list);
339: gfs2_assert_withdraw(sdp, sdp->sd_log_num_revoke);
340: sdp->sd_log_num_revoke--;
341: if (bd->bd_gl)
342: gfs2_glock_remove_revoke(bd->bd_gl);
343: kmem_cache_free(gfs2_bufdata_cachep, bd);
344: gfs2_log_release_revokes(sdp, 1);
345: if (--n == 0)
346: break;
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_trans_remove_revoke SourceFile:fs/gfs2/trans.c SourceLine:342 SourceSnippet: 337: if ((bd->bd_blkno >= blkno) && (bd->bd_blkno < (blkno + len))) {
338: list_del_init(&bd->bd_list);
339: gfs2_assert_withdraw(sdp, sdp->sd_log_num_revoke);
340: sdp->sd_log_num_revoke--;
341: if (bd->bd_gl)
342: gfs2_glock_remove_revoke(bd->bd_gl);
343: kmem_cache_free(gfs2_bufdata_cachep, bd);
344: gfs2_log_release_revokes(sdp, 1);
345: if (--n == 0)
346: break;
347: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_unpin SourceFile:fs/gfs2/lops.c SourceLine:122 SourceSnippet: 117: spin_lock(&sdp->sd_ail_lock);
118: if (bd->bd_tr) {
119: list_del(&bd->bd_ail_st_list);
120: brelse(bh);
121: } else {
122: struct gfs2_glock *gl = bd->bd_gl;
123: list_add(&bd->bd_ail_gl_list, &gl->gl_ail_list);
124: atomic_inc(&gl->gl_ail_count);
125: }
126: bd->bd_tr = tr;
127: list_add(&bd->bd_ail_st_list, &tr->tr_ail1_list);
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:gfs2_unpin SourceFile:fs/gfs2/lops.c SourceLine:130 SourceSnippet: 125: }
126: bd->bd_tr = tr;
127: list_add(&bd->bd_ail_st_list, &tr->tr_ail1_list);
128: spin_unlock(&sdp->sd_ail_lock);
129:
130: clear_bit(GLF_LFLUSH, &bd->bd_gl->gl_flags);
131: trace_gfs2_pin(bd, 0);
132: unlock_buffer(bh);
133: atomic_dec(&sdp->sd_log_pinned);
134: }
] map[ReferenceKind:reads ReferencingEntityKind:function ReferencingEntityName:maybe_release_space SourceFile:fs/gfs2/lops.c SourceLine:73 SourceSnippet: 71: static void maybe_release_space(struct gfs2_bufdata *bd)
72: {
73: struct gfs2_glock *gl = bd->bd_gl;
74: struct gfs2_sbd *sdp = glock_sbd(gl);
75: struct gfs2_rgrpd *rgd = gfs2_glock2rgrp(gl);
76: unsigned int index = bd->bd_bh->b_blocknr - glock_number(gl);
77: struct gfs2_bitmap *bi = rgd->rd_bits + index;
78:
]] TruncatedOutput:false]
|
| 41/2 |
2026/09/10 01:01 |
llm |
expert |
0m
Model:
Tokens:
input: 50781
output: 37
thoughts: 688
Thoughts: **Analyzing `gfs2_unpin` and Buffer Data Management**
Okay, so I'm looking at this `gfs2_unpin` function, specifically this section here: I can see it's dealing with some buffer data structure (`bd`). The code's either removing this `bd` from a list and releasing the buffer, or, if it's not being used by a transaction, adding it to a list associated with a glock (`gl`) and incrementing a count. The critical question here, and this is where I need to be precise, is the reference counting of the `glock`.
My immediate concern is whether `bd` *holds* a reference on `gl`. If it does not, then we need to know who *does* hold that reference, and where it is incremented and decremented. If it *does* hold a reference, then the code could be correct as-is.
So, let's trace this back. Where is `bd` allocated? Ah, `gfs2_alloc_bufdata`. Okay, and the relevant lines are:
```c
bd = kmem_cache_zalloc(gfs2_bufdata_cachep, GFP_NOFS | __GFP_NOFAIL);
bd->bd_bh = bh;
bd->bd_gl = gl;
INIT_LIST_HEAD(&bd->bd_list);
INIT_LIST_HEAD(&bd->bd_ail_st_list);
INIT_LIST_HEAD(&bd->bd_ail_gl_list);
```
This is good. It's a simple allocation, setting the `bd_gl` pointer to the `gl` that was passed to the function. It doesn't look like `gfs2_alloc_bufdata` itself is taking a reference. So, the question is now, where *does* the reference come from?
Alright, let's step back again. Where does this `gl` come from? I need to analyze the allocation stack to be sure. It looks like it comes from the allocation stack:
```
Allocated by task 7723:
kmem_cache_alloc_noprof+0x269/0x6a0 mm/slub.c:5010
gfs2_glock_get+0x1e5/0x1260 fs/gfs2/glock.c:1074
gfs2_inode_lookup+0x2e0/0x920 fs/gfs2/inode.c:149
gfs2_lookup_root+0x57/0xe0 fs/gfs2/ops_fstype.c:428
init_sb+0xa17/0xfd0 fs/gfs2/ops_fstype.c:497
gfs2_fill_super+0x1acb/0x2d00 fs/gfs2/ops_fstype.c:1219
```
So it looks like the `gl` is coming from `gfs2_glock_get` in `gfs2_inode_lookup`, in line 149 of that function. That's where I need to look to understand the glock lifecycle and its reference counting. I need to understand whether `gfs2_glock_get` itself takes a reference on the `gl`. If it does, then the `atomic_inc` is possibly correct. If it doesn't, we are in trouble.
|
| 42/2 |
2026/09/10 01:02 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/inode.c FirstLine:140 LineCount:30]
Results: map[Contents: 140:
141: ip = GFS2_I(inode);
142:
143: if (inode_state_read_once(inode) & I_NEW) {
144: struct gfs2_sbd *sdp = GFS2_SB(inode);
145: struct gfs2_glock *io_gl;
146: int extra_flags = 0;
147:
148: gfs2_setup_inode(inode);
149: error = gfs2_glock_get(sdp, no_addr, &gfs2_inode_glops, CREATE,
150: &ip->i_gl);
151: if (unlikely(error))
152: goto fail;
153:
154: error = gfs2_glock_get(sdp, no_addr, &gfs2_iopen_glops, CREATE,
155: &io_gl);
156: if (unlikely(error))
157: goto fail;
158:
159: /*
160: * The only caller that sets @blktype to GFS2_BLKST_UNLINKED is
161: * delete_work_func(). Make sure not to cancel the delete work
162: * from within itself here.
163: */
164: if (blktype == GFS2_BLKST_UNLINKED)
165: extra_flags |= LM_FLAG_TRY;
166: else
167: gfs2_cancel_delete_work(io_gl);
168: error = gfs2_glock_nq_init(io_gl, LM_ST_SHARED,
169: GL_EXACT | GL_NOPID | extra_flags,
]
|
| 43/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 52807
output: 42
thoughts: 36
Thoughts: Here's my take on the thought process, as if it were my own:
**Investigating the GFS2 Inode Global Operations**
Okay, so `gfs2_inode_glops` has that `GLOF_ASPACE` flag β that immediately piques my interest. It means the inode's global operations likely interact with the address space, and that usually has significant implications for how data is handled, cached, and flushed. Let's dig into the definition of `gfs2_inode_glops` and see what's what.
|
| 44/2 |
2026/09/10 01:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/glops.c Name:gfs2_inode_glops]
Results: map[SourceCode: 651: const struct gfs2_glock_operations gfs2_inode_glops = {
652: .go_sync = inode_go_sync,
653: .go_inval = inode_go_inval,
654: .go_instantiate = inode_go_instantiate,
655: .go_held = inode_go_held,
656: .go_dump = inode_go_dump,
657: .go_type = LM_TYPE_INODE,
658: .go_flags = GLOF_ASPACE | GLOF_LVB,
659: };
SourceFile:fs/gfs2/glops.c]
|
| 45/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 53212
output: 38
thoughts: 120
Thoughts: **My GFS2 Inode Allocation and Deallocation Thoughts**
Okay, so I'm digging into how GFS2 manages its inodes, and something just clicked. I see that `gfs2_inode_glops` has that `GLOF_ASPACE` flag. This flag tells me it's allocating memory from `gfs2_glock_aspace_cachep`, which is, essentially, the `gfs2_glock(aspace)` cache. That's fine, but now the crucial question is when is that specific glock freed? Memory leaks are the enemy!
Alright, it looks like the freeing happens in `gfs2_evict_inode`, which is called as part of `evict_inode`. This makes sense; it seems like when an inode is evicted, it drops `ip->i_gl`, the glock. So, to really understand this lifecycle, I need to get a clear picture of what's happening in `gfs2_evict_inode` itself. Let's head over to `fs/gfs2/super.c` and take a look at the function, and see what the specifics are of how this deallocation happens.
|
| 46/2 |
2026/09/10 01:02 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/super.c FirstLine:1460 LineCount:45]
Results: map[Contents:1460: * system files without having an active journal to write to. In that
1461: * case, skip the filesystem evict.
1462: */
1463: if (!sdp->sd_jdesc)
1464: goto out;
1465:
1466: behavior = evict_should_delete(inode, &gh);
1467: if (behavior == EVICT_SHOULD_DEFER_DELETE &&
1468: !test_bit(SDF_KILL, &sdp->sd_flags)) {
1469: struct gfs2_glock *io_gl = ip->i_iopen_gh.gh_gl;
1470:
1471: if (io_gl) {
1472: gfs2_glock_hold(io_gl);
1473: if (!gfs2_queue_verify_delete(io_gl, true))
1474: gfs2_glock_put(io_gl);
1475: goto out;
1476: }
1477: behavior = EVICT_SHOULD_SKIP_DELETE;
1478: }
1479: if (behavior == EVICT_SHOULD_DELETE)
1480: ret = evict_unlinked_inode(inode, &gh);
1481: else
1482: ret = evict_linked_inode(inode, &gh);
1483:
1484: if (gfs2_rs_active(&ip->i_res))
1485: gfs2_rs_deltree(&ip->i_res);
1486:
1487: if (ret && !gfs2_withdrawn(sdp) && ret != -EROFS)
1488: fs_warn(sdp, "gfs2_evict_inode: %d\n", ret);
1489: out:
1490: if (gfs2_holder_initialized(&gh))
1491: gfs2_glock_dq_uninit(&gh);
1492: gfs2_truncate_inode_pages_final(inode);
1493: if (ip->i_qadata)
1494: gfs2_assert_warn(sdp, ip->i_qadata->qa_ref == 0);
1495: gfs2_rs_deltree(&ip->i_res);
1496: gfs2_ordered_del_inode(ip);
1497: clear_inode(inode);
1498: gfs2_dir_hash_inval(ip);
1499: if (gfs2_holder_initialized(&ip->i_iopen_gh)) {
1500: struct gfs2_glock *gl = ip->i_iopen_gh.gh_gl;
1501:
1502: glock_clear_object(gl, ip);
1503: gfs2_glock_hold(gl);
1504: ip->i_iopen_gh.gh_flags |= GL_NOCACHE;
]
|
| 47/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 54464
output: 38
thoughts: 17
Thoughts: Okay, here's my interpretation of what's going on, framed as a first-person expert perspective:
**Diving Deep into `gfs2_evict_inode`**
Alright, let's take a closer look at what's happening within `gfs2_evict_inode`. I need to understand its role in the file system lifecycle, particularly concerning inode eviction. I know this is a critical function for managing memory and ensuring the file system remains performant and stable under resource constraints. I'm focusing on the specifics, understanding where it's called and the typical context it runs in. The goal is to identify any potential bottlenecks, race conditions, or areas where optimization might be possible. I'll need to dissect its interactions with the buffer cache, locking mechanisms (which are always key in a distributed environment like GFS2), and the journal to fully grasp its operational flow. This could be about memory pressure, metadata management, or even transaction commit sequencing. Time to get my hands dirty.
|
| 48/2 |
2026/09/10 01:02 |
tool |
read-file |
0m
Args: map[File:fs/gfs2/super.c FirstLine:1505 LineCount:25]
Results: map[Contents:1505: gfs2_glock_dq_uninit(&ip->i_iopen_gh);
1506: gfs2_glock_put_eventually(gl);
1507: }
1508: if (ip->i_gl) {
1509: glock_clear_object(ip->i_gl, ip);
1510: wait_on_bit_io(&ip->i_flags, GIF_GLOP_PENDING, TASK_UNINTERRUPTIBLE);
1511: gfs2_glock_put_eventually(ip->i_gl);
1512: rcu_assign_pointer(ip->i_gl, NULL);
1513: }
1514: }
1515:
1516: static struct inode *gfs2_alloc_inode(struct super_block *sb)
1517: {
1518: struct gfs2_inode *ip;
1519:
1520: ip = alloc_inode_sb(sb, gfs2_inode_cachep, GFP_KERNEL);
1521: if (!ip)
1522: return NULL;
1523: ip->i_no_addr = 0;
1524: ip->i_no_formal_ino = 0;
1525: ip->i_flags = 0;
1526: ip->i_gl = NULL;
1527: gfs2_holder_mark_uninitialized(&ip->i_iopen_gh);
1528: memset(&ip->i_res, 0, sizeof(ip->i_res));
1529: RB_CLEAR_NODE(&ip->i_res.rs_node);
]
|
| 49/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 55199
output: 52
thoughts: 643
Thoughts: **My GlOck Deallocation Thoughts**
Okay, so I'm tracing the glock release path here. The function `gfs2_glock_put_eventually(ip->i_gl)` is the starting point, specifically dropping the glock reference. When the reference count hits zero, `glock_work_func` gets invoked to do the actual release. I see that within `glock_work_func`, the code manually drops the remaining references. If the glock isn't unlocked, it requests a demotion and retries. But if it *is* unlocked, then `__gfs2_glock_put(gl)` is called.
Inside `__gfs2_glock_put(gl)`, the function `nolock_ops->lm_put_lock(gl)` is invoked, which then leads to `gfs2_glock_free(gl)`. This, in turn, calls `call_rcu(&gl->gl_rcu, gfs2_glock_dealloc)`. Ah, RCU is used to free the `gl` structure itself via `kmem_cache_free`. This seems standard, RCU is how we deal with concurrent accesses.
Wait a second⦠does `__gfs2_glock_put` *actually* check `gl->gl_ail_count`? I need to double-check that function again. Okay, re-reading `__gfs2_glock_put`, I see the relevant code. There's truncation of the associated mapping and some debugging checks, but I don't see any direct check on `gl->gl_ail_count`.
Hold on! What's the deal with `gl->gl_ail_count` and `gl->gl_ail_list`? Is this glock *supposed* to be freed while it still has outstanding AIL entries? That's a potential concurrency issue, isn't it? Better search the codebase for every use of `gl_ail_count` to ensure there are no race conditions here.
|
| 50/2 |
2026/09/10 01:02 |
tool |
codesearch-find-references |
0m
Args: map[ContextFile:fs/gfs2/incore.h IncludeSnippetLines:5 Name:gfs2_glock::gl_ail_count]
Results: map[References:[map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:__gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:81 SourceSnippet: 76: gfs2_ail_error(gl, bh);
77: }
78: gfs2_trans_add_revoke(sdp, bd);
79: nr_revokes--;
80: }
81: GLOCK_BUG_ON(gl, !fsync && atomic_read(&gl->gl_ail_count));
82: spin_unlock(&sdp->sd_ail_lock);
83: spin_unlock(&sdp->sd_log_lock);
84: }
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_ail_empty_gl SourceFile:fs/gfs2/glops.c SourceLine:94 SourceSnippet: 89: struct gfs2_sbd *sdp = glock_sbd(gl);
90: struct gfs2_trans tr;
91: unsigned int revokes;
92: int ret = 0;
93:
94: revokes = atomic_read(&gl->gl_ail_count);
95:
96: if (!revokes) {
97: bool have_revokes;
98: bool log_in_flight;
99:
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_ail_flush SourceFile:fs/gfs2/glops.c SourceLine:143 SourceSnippet: 140: void gfs2_ail_flush(struct gfs2_glock *gl, bool fsync)
141: {
142: struct gfs2_sbd *sdp = glock_sbd(gl);
143: unsigned int revokes = atomic_read(&gl->gl_ail_count);
144: int ret;
145:
146: if (!revokes)
147: return;
148:
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_dump_glock SourceFile:fs/gfs2/glock.c SourceLine:2177 SourceSnippet:2172: glock_type(gl),
2173: (unsigned long long) glock_number(gl),
2174: gflags2str(gflags_buf, gl),
2175: state2str(gl->gl_target),
2176: state2str(gl->gl_demote_state), dtime,
2177: atomic_read(&gl->gl_ail_count),
2178: atomic_read(&gl->gl_revokes),
2179: (int)gl->gl_lockref.count, gl->gl_hold_time, nrpages);
2180:
2181: list_for_each_entry(gh, &gl->gl_holders, gh_list)
2182: dump_holder(seq, gh, fs_id_buf);
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_init_glock_once SourceFile:fs/gfs2/main.c SourceLine:56 SourceSnippet: 51: {
52: struct gfs2_glock *gl = foo;
53:
54: INIT_LIST_HEAD(&gl->gl_holders);
55: INIT_LIST_HEAD(&gl->gl_ail_list);
56: atomic_set(&gl->gl_ail_count, 0);
57: atomic_set(&gl->gl_revokes, 0);
58: }
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_remove_from_ail SourceFile:fs/gfs2/log.c SourceLine:80 SourceSnippet: 75: static void gfs2_remove_from_ail(struct gfs2_bufdata *bd)
76: {
77: bd->bd_tr = NULL;
78: list_del_init(&bd->bd_ail_st_list);
79: list_del_init(&bd->bd_ail_gl_list);
80: atomic_dec(&bd->bd_gl->gl_ail_count);
81: brelse(bd->bd_bh);
82: }
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:gfs2_unpin SourceFile:fs/gfs2/lops.c SourceLine:124 SourceSnippet: 119: list_del(&bd->bd_ail_st_list);
120: brelse(bh);
121: } else {
122: struct gfs2_glock *gl = bd->bd_gl;
123: list_add(&bd->bd_ail_gl_list, &gl->gl_ail_list);
124: atomic_inc(&gl->gl_ail_count);
125: }
126: bd->bd_tr = tr;
127: list_add(&bd->bd_ail_st_list, &tr->tr_ail1_list);
128: spin_unlock(&sdp->sd_ail_lock);
129:
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:inode_go_inval SourceFile:fs/gfs2/glops.c SourceLine:362 SourceSnippet: 358: static void inode_go_inval(struct gfs2_glock *gl, int flags)
359: {
360: struct gfs2_inode *ip = gfs2_glock2inode(gl);
361:
362: gfs2_assert_withdraw(glock_sbd(gl), !atomic_read(&gl->gl_ail_count));
363:
364: if (flags & DIO_METADATA) {
365: struct address_space *mapping = gfs2_glock2aspace(gl);
366: truncate_inode_pages(mapping, 0);
367: if (ip) {
] map[ReferenceKind:takes-address-of ReferencingEntityKind:function ReferencingEntityName:rgrp_go_inval SourceFile:fs/gfs2/glops.c SourceLine:235 SourceSnippet: 230: return;
231: start = (rgd->rd_addr * bsize) & PAGE_MASK;
232: end = PAGE_ALIGN((rgd->rd_addr + rgd->rd_length) * bsize) - 1;
233: gfs2_rgrp_brelse(rgd);
234: WARN_ON_ONCE(!(flags & DIO_METADATA));
235: gfs2_assert_withdraw(sdp, !atomic_read(&gl->gl_ail_count));
236: truncate_inode_pages_range(mapping, start, end);
237: }
]] TruncatedOutput:false]
|
| 51/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 58382
output: 42
thoughts: 92
Thoughts: Here's my thought process, as an expert in this area:
**Deconstructing a Potential GFS2 Inode Invalidation Issue**
Okay, let's dive into this `inode_go_inval` situation. First, I see this critical `gfs2_assert_withdraw` call: `gfs2_assert_withdraw(glock_sbd(gl), !atomic_read(&gl->gl_ail_count));`. Right away, my radar goes up. This assert is crucial; it's designed to prevent something bad from happening, likely related to the Active Inode List (AIL) count. It's saying, "Hey, if this AIL count isn't zero, we shouldn't be here."
The code is telling me this call is failing. If the `gfs2_assert_withdraw` condition is false, meaning the `gl->gl_ail_count` is *not* zero, we have a problem. That's a serious inconsistency. Time to understand the implications of this failure. What happens when this assert *actually* fails? What's the fallout? I need to understand the control flow here.
I need to see what's *inside* the function. I'll search for `gfs2_assert_withdraw`. Time to dig deeper and see what `gfs2_assert_withdraw` does when its condition is false. Understanding that will guide my next steps in diagnosing the root cause.
|
| 52/2 |
2026/09/10 01:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/util.h Name:gfs2_assert_withdraw_i]
Results: map[SourceCode: 269: void gfs2_assert_withdraw_i(struct gfs2_sbd *sdp, char *assertion,
270: const char *function, char *file, unsigned int line)
271: {
272: if (gfs2_withdrawn(sdp))
273: return;
274:
275: fs_err(sdp,
276: "fatal: assertion \"%s\" failed - "
277: "function = %s, file = %s, line = %u\n",
278: assertion, function, file, line);
279:
280: gfs2_withdraw(sdp);
281: dump_stack();
282: }
SourceFile:fs/gfs2/util.c]
|
| 53/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 59074
output: 38
thoughts: 76
Thoughts: **Analyzing the `gfs2_withdraw` Function**
Okay, let's dive into this. I'm focusing on `gfs2_withdraw` and what triggers its execution. It looks like the key is the call to `queue_delayed_work`. Specifically, the function either schedules the `sdp->sd_control_work` with a delay on `gfs2_control_wq` or executes `gfs2_withdraw_func` directly. I need to understand the relationship between these two mechanisms. Now, time to crack open the definition of `gfs2_withdraw` itself and get a better grip on its purpose within the system. Let's see what the source code reveals.
|
| 54/2 |
2026/09/10 01:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/util.c Name:gfs2_withdraw]
Results: map[SourceCode: 242: void gfs2_withdraw(struct gfs2_sbd *sdp)
243: {
244: if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW ||
245: sdp->sd_args.ar_errors == GFS2_ERRORS_DEACTIVATE) {
246: if (test_and_set_bit(SDF_WITHDRAWN, &sdp->sd_flags))
247: return;
248:
249: dump_stack();
250: /*
251: * There is no need to withdraw when the superblock hasn't been
252: * fully initialized, yet.
253: */
254: if (!(sdp->sd_vfs->s_flags & SB_BORN))
255: return;
256: fs_err(sdp, "about to withdraw this file system\n");
257: schedule_work(&sdp->sd_withdraw_work);
258: return;
259: }
260:
261: if (sdp->sd_args.ar_errors == GFS2_ERRORS_PANIC)
262: panic("GFS2: fsid=%s: panic requested\n", sdp->sd_fsname);
263: }
SourceFile:fs/gfs2/util.c]
|
| 55/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 59744
output: 38
thoughts: 2518
Thoughts: **Analyzing a GFS2 KASAN Use-After-Free Report**
Okay, let's break this down. First, I see `schedule_work` targeting `sdp->sd_withdraw_work`, which in turn triggers `gfs2_withdraw_func`. This function calls `do_withdraw(sdp)` which eventually reaches `gfs2_ail_drain(sdp)`. Inside that, it calls `gfs2_remove_from_ail(bd)`, where the problem lies: `list_del_init(&bd->bd_ail_gl_list)`. This is operating on a list node (`bd->bd_ail_gl_list`) that was linked to `gl->gl_ail_list`. The key issue is that `gl` was freed earlier by `glock_work_func -> gfs2_glock_free -> call_rcu -> gfs2_glock_dealloc -> kmem_cache_free`.
Now, looking at the bug report, this is a KASAN use-after-free. It's got three key stacks: an access stack (where the access to the freed memory occurs), an allocation stack (where the `gfs2_glock_aspace` was allocated by `gfs2_glock_get`), and a free stack (where `gfs2_glock_dealloc` freed it). There are also aux stacks for work queue events, and the all three tracebacks point into the same subsystem (`fs/gfs2`)
The report shows the object type is `gfs2_glock(aspace)`, and the specific offset being accessed (280 bytes) corresponds to a `gl->gl_ail_list.next` member of `struct gfs2_glock_aspace`. This is consistent with what I'm seeing. Moreover, there's not just one, but three KASAN reports: a read at offset 288 and a write at offset 288, which confirms a list corruption in `__list_del_entry_valid_or_report` and `__list_del`.
The core of the problem here is that `gl` is being deallocated while `bd->bd_ail_gl_list` still points to it (or buffers were still on `gl_ail_list`). My hypothesis is that the glock has been deallocated before the AIL operations are completed! Let's examine the `gfs2_glock` lifecycle when `gl_ail_count` is non-zero, especially within `glock_work_func`. Specifically, this snippet:
```c
/* Drop the remaining glock references manually. */
GLOCK_BUG_ON(gl, gl->gl_lockref.count < drop_refs);
gl->gl_lockref.count -= drop_refs;
if (!gl->gl_lockref.count) {
if (gl->gl_state != LM_ST_UNLOCKED) {
gl->gl_lockref.count++;
request_demote(gl, LM_ST_UNLOCKED, 0, false);
goto again;
}
__gfs2_glock_put(gl);
return;
}
```
Could `gl->gl_ail_count` be *incorrectly* preventing the glock from being unlocked or demoted and freed at the right time? I need to check how `request_demote` and `run_queue` and `do_xmote` in `fs/gfs2/glock.c` interact with the AIL.
|
| 56/2 |
2026/09/10 01:02 |
tool |
codesearch-definition-source |
0m
Args: map[ContextFile:fs/gfs2/glock.c Name:do_xmote]
Results: map[SourceCode: 629: static void do_xmote(struct gfs2_glock *gl, struct gfs2_holder *gh,
630: unsigned int target, bool may_cancel)
631: __releases(&gl->gl_lockref.lock)
632: __acquires(&gl->gl_lockref.lock)
633: {
634: const struct gfs2_glock_operations *glops = gl->gl_ops;
635: struct gfs2_sbd *sdp = glock_sbd(gl);
636: struct lm_lockstruct *ls = &sdp->sd_lockstruct;
637: int ret;
638:
639: /*
640: * When a filesystem is withdrawing, the remaining cluster nodes will
641: * take care of recovering the withdrawing node's journal. We only
642: * need to make sure that once we trigger remote recovery, we won't
643: * write to the shared block device anymore. This means that here,
644: *
645: * - no new writes to the filesystem must be triggered (->go_sync()).
646: *
647: * - any cached data should be discarded by calling ->go_inval(), dirty
648: * or not and journaled or unjournaled.
649: *
650: * - no more dlm locking operations should be issued (->lm_lock()).
651: */
652:
653: GLOCK_BUG_ON(gl, gl->gl_state == target);
654: GLOCK_BUG_ON(gl, gl->gl_state == gl->gl_target);
655:
656: if (!glops->go_inval || !glops->go_sync)
657: goto skip_inval;
658:
659: spin_unlock(&gl->gl_lockref.lock);
660: if (!gfs2_withdrawn(sdp)) {
661: ret = glops->go_sync(gl);
662: if (ret) {
663: if (cmpxchg(&sdp->sd_log_error, 0, ret)) {
664: fs_err(sdp, "Error %d syncing glock\n", ret);
665: gfs2_dump_glock(NULL, gl, true);
666: gfs2_withdraw(sdp);
667: }
668: }
669: }
670:
671: if (target == LM_ST_UNLOCKED || target == LM_ST_DEFERRED)
672: glops->go_inval(gl, target == LM_ST_DEFERRED ? 0 : DIO_METADATA);
673: spin_lock(&gl->gl_lockref.lock);
674:
675: skip_inval:
676: if (gfs2_withdrawn(sdp)) {
677: if (target != LM_ST_UNLOCKED)
678: target = LM_OUT_ERROR;
679: goto out;
680: }
681:
682: if (ls->ls_ops->lm_lock) {
683: spin_unlock(&gl->gl_lockref.lock);
684: ret = ls->ls_ops->lm_lock(gl, target, gh ? gh->gh_flags : 0);
685: spin_lock(&gl->gl_lockref.lock);
686:
687: if (!ret) {
688: if (may_cancel) {
689: set_bit(GLF_MAY_CANCEL, &gl->gl_flags);
690: smp_mb__after_atomic();
691: wake_up_bit(&gl->gl_flags, GLF_LOCK);
692: }
693: /* The operation will be completed asynchronously. */
694: gl->gl_lockref.count++;
695: return;
696: }
697:
698: if (ret == -ENODEV) {
699: /*
700: * The lockspace has been released and the lock has
701: * been unlocked implicitly.
702: */
703: if (target != LM_ST_UNLOCKED) {
704: target = LM_OUT_ERROR;
705: goto out;
706: }
707: } else {
708: fs_err(sdp, "lm_lock ret %d\n", ret);
709: GLOCK_BUG_ON(gl, !gfs2_withdrawn(sdp));
710: return;
711: }
712: }
713:
714: out:
715: /* Complete the operation now. */
716: finish_xmote(gl, target);
717: gl->gl_lockref.count++;
718: gfs2_glock_queue_work(gl, 0);
719: }
SourceFile:fs/gfs2/glock.c]
|
| 57/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 64573
output: 39
thoughts: 1325
Thoughts: **The Glaring GFS2 Glock UAF Vulnerability**
Okay, let's break this down. My initial observation is focused on `do_xmote`. The auxiliary stack trace immediately points to `gfs2_glock_queue_work` being invoked within `do_xmote`. Diving deeper into `do_xmote`, I see `glops->go_inval` and I'm instantly reminded of the role of `inode_go_inval`. Specifically, the `gfs2_assert_withdraw` check that's triggered when `gl->gl_ail_count` isn't zero during a demotion to `LM_ST_UNLOCKED` state. This leads directly to `gfs2_withdraw` if the assertion fails.
If `gfs2_withdraw` is triggered, and `gfs2_withdrawn(sdp)` is true, the target is set to `LM_OUT_ERROR` if not already `LM_ST_UNLOCKED`. This will happen inside `do_xmote` before we exit the function to unlock the lock (`gl->gl_state = LM_ST_UNLOCKED`) with `finish_xmote(gl, target);`. Then the work queued by `gfs2_glock_queue_work` runs, which is `glock_work_func`. And this is where things get interesting.
In `glock_work_func`, the lockref count hits zero, which in turn calls `__gfs2_glock_put(gl)`. This then calls `lm_put_lock(gl)` which, via RCU, frees the glock. The RCU grace period completes, and `gfs2_glock_dealloc` reclaims the glock back to `gfs2_glock_aspace_cachep`.
Now, here's the crucial part: in parallel, `gfs2_withdraw_func` (queued by `gfs2_withdraw`) runs. This runs `do_withdraw -> gfs2_ail_drain -> gfs2_ail_empty_tr -> gfs2_remove_from_ail`. In `gfs2_remove_from_ail(bd)`, it attempts to remove `bd` from `gl->gl_ail_list` using `list_del_init` and decrement the `gl_ail_count`. The problem is, `bd->bd_gl` is *already freed* at this point. Thus, accessing the list entries from the already freed glock structure.
The complete picture is now crystal clear to me:
1. During demotion and unlocking, `inode_go_inval` detects a non-zero `gl->gl_ail_count` and triggers a withdraw via `gfs2_withdraw`.
2. The glock is unlocked, its refcount drops to 0, which triggers `glock_work_func` and then `__gfs2_glock_put(gl)`.
3. `__gfs2_glock_put` then calls `lm_put_lock` -> `gfs2_glock_free`, freeing the glock through RCU.
4. After the grace period, `gfs2_glock_dealloc` releases the `gfs2_glock_aspace` object.
5. In parallel / subsequently, the `gfs2_withdraw_func` worker runs, draining the AIL.
6. `gfs2_ail_drain` traverses transactions and drains them via `gfs2_remove_from_ail(bd)`.
7. `gfs2_remove_from_ail` calls `list_del_init(&bd->bd_ail_gl_list)`, which dereferences the now-freed `gl->gl_ail_list`, causing the KASAN slab-use-after-free read and write.
This is a clear use-after-free vulnerability. The next step is to see if there's any existing documentation, fix, or discussion of this specific issue in the upstream Linux kernel history or related bug reports.
|
| 58/2 |
2026/09/10 01:02 |
tool |
git-log |
0m
Args: map[CodeRegexp:gfs2_ail_drain Count:5 PathPrefix:fs/gfs2]
Results: map[Output:0b2355fe91ac gfs2: No longer use 'extern' in function declarations
f5456b5d67cf gfs2: Clean up revokes on normal withdraws
]
|
| 59/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 66894
output: 38
thoughts: 18
Thoughts: Here's my summary, as though I were thinking through the process:
**Reviewing Recent Git Activity for Withdrawal-Related Changes**
Okay, first things first, I need to understand what's been happening recently related to withdrawals. Given my expertise in this domain, a quick scan of the `git log` is in order. I'm going to leverage MessageRegexps for a more focused search. I'll craft a regular expression that'll catch any commit messages mentioning "withdraw," "ail," or any closely related terms I can think of. This method allows me to quickly identify commits potentially impacting the withdrawal process. I need to be efficient and pinpoint the relevant commits. This should quickly give me a sense of what has been going on recently.
|
| 60/2 |
2026/09/10 01:02 |
tool |
git-log |
0m
Args: map[Count:10 MessageRegexps:[ail withdraw] PathPrefix:fs/gfs2]
Results: map[Output:bb47cce7a1ee gfs2: gfs2_log_flush withdraw fixes
7d2da6ed1726 gfs2: drain ail under sd_log_flush_lock
da6f5bbc2e79 gfs2: fix memory leaks in gfs2_fill_super error path
bbbf1529ea9b gfs2: New gfs2_withdraw_helper
655531c95be3 Revert "gfs2: fix infinite loop when checking ail item count before go_inval"
af572efef10a Revert "gfs2: Allow some glocks to be used during withdraw"
41ad1f7c8b0a Revert "gfs2: Check for log write errors before telling dlm to unlock"
6bb7c1bf5a62 Revert "gfs2: fix a deadlock on withdraw-during-mount"
dcc42d554176 Revert "gfs2: Force withdraw to replay journals and wait for it to finish" (6/6)
406058184c59 Revert "gfs2: Force withdraw to replay journals and wait for it to finish" (5/6)
]
|
| 61/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 67408
output: 25
thoughts: 15
Thoughts: Okay, I'm ready to dive into that commit. Let's see what we've got.
**Dissecting Commit 7d2da6ed1726**
Right, let's take a look at what commit `7d2da6ed1726` is bringing to the table. Based on the context, I'm going to assume this is a significant point of work, given the request for a detailed summary. I need to understand the intent, the changes, and the potential impact. I'm immediately looking for key lines of code, focusing on anything that seems novel or particularly complex. I'm running through my mental checklists: data structures, algorithms, dependencies, and any potential security implications. Is this a performance enhancement? A bug fix? A feature addition? The commit message better be clear, or I'm going to have to dig even deeper. I'm anticipating some potentially subtle interactions between the changes and the existing codebase, so I'll be keeping an eye out for any regressions. Finally, I'll be evaluating the code's clarity and maintainability - are these changes going to make the code easier to work with moving forward? Let's get to the details.
|
| 62/2 |
2026/09/10 01:02 |
tool |
git-show |
0m
Args:
Results: map[Output:commit 7d2da6ed172680a7ef06acfe3e44a2326977573f
Author: Andreas Gruenbacher <agruenba@redhat.com>
Date: Wed Apr 1 18:18:38 2026 +0200
gfs2: drain ail under sd_log_flush_lock
When a withdraw is carried out, call gfs2_ail_drain() under the
sdp->sd_log_flush_lock. This isn't strictly necessary but should be easier to
read, and more robust against possible future bugs.
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c
index 02603200846d..83b8bb6446e5 100644
--- a/fs/gfs2/util.c
+++ b/fs/gfs2/util.c
@@ -123,9 +123,8 @@ static void do_withdraw(struct gfs2_sbd *sdp)
return;
}
clear_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags);
- up_write(&sdp->sd_log_flush_lock);
-
gfs2_ail_drain(sdp); /* frees all transactions */
+ up_write(&sdp->sd_log_flush_lock);
wake_up(&sdp->sd_logd_waitq);
wake_up(&sdp->sd_quota_wait);
]
|
| 63/2 |
2026/09/10 01:02 |
llm |
expert |
0m
Model:
Tokens:
input: 68144
output: 15
thoughts: 2110
Thoughts: **Analysis of a GFS2 Glck Use-After-Free Bug During Withdrawal**
Okay, let's break this down. My initial thought was to examine the interactions of `sd_glock_disposal`, glock destruction, and unmounting, specifically focusing on how glocks are handled during filesystem withdrawal. The `do_withdraw` function's call to `gfs2_withdraw_glocks` drew my attention. I was initially thrown by the timing β withdrawal happens while the filesystem is still mounted. Then, it clicked: `gfs2_withdraw` itself doesn't wait for glocks, it just initiates the process. The bug is related to a glock that was unlocked and freed because there were no holders and its lockref hit 0, due to an inode eviction or demotion.
I started digging deeper into how the `bd` (buffer descriptor) is related to the glock (`gl`) in the AIL (active inode list). The core question: does `bd` hold a reference on `gl`? The code clearly showed it does *not*. `atomic_inc(&gl->gl_ail_count)` is crucial - it increments a count associated with the AIL, but it *doesn't* touch the glock's reference count (`gl->gl_lockref`). This count is used in the context of file system withdrawal.
The `__gfs2_glock_put` function, responsible for freeing the glock, is the key here. It *doesn't* check `gl->gl_ail_count` or `list_empty(&gl->gl_ail_list)`! It only checks for active holders (`gl->gl_holders`). This immediately points to a potential race condition.
Why would `gl->gl_ail_count` be non-zero *before* the glock is freed? The answer lies in the normal glock demotion process (`go_inval` or `go_sync`). `inode_go_inval` includes an assertion, `gfs2_assert_withdraw`, that checks if `gl_ail_count` is zero. *However*, this assertion just triggers a withdrawal and *continues* the demotion process, leaving the glock vulnerable to being unlocked. It doesn't halt the unlock! When we have `gl_ail_count` != 0, it calls `gfs2_assert_withdraw`. This is a crucial detail. It doesn't stop the glock from demoting! Then `do_xmote` sees `gfs2_withdrawn(sdp)` and ultimately sets the glock state to `LM_ST_UNLOCKED`. The function `finish_xmote(gl, LM_ST_UNLOCKED);` set the state. Then `glock_work_func` sees that the state is `LM_ST_UNLOCKED` and there is no more active reference, so it calls `__gfs2_glock_put`.
Now it's clear: The glock gets freed via `__gfs2_glock_put`, while `bd` remains in the AIL list, but `bd->bd_gl` now points to freed memory, and the list `gl->gl_ail_list` is still containing `bd->bd_ail_gl_list` on it. Later, the withdrawal workqueue worker (`gfs2_withdraw_func`) runs `gfs2_ail_drain()`, calling `gfs2_remove_from_ail(bd)`, which tries to manipulate the already-freed `gl->gl_ail_list`. Bingo! Use-after-free.
The bug is in the exact address: `ffff88807c647920` (offset 280 / 0x118) is `gl->gl_ail_list.next`, and `ffff88807c647928` (offset 288 / 0x120) is `gl->gl_ail_list.prev`. It's accessing a freed glock, that's allocated in a slab cache `gfs2_glock(aspace)` of size 1176 via the function: `gfs2_glock_get`. It will be deallocated using the functions `gfs2_glock_dealloc` via RCU from `gfs2_glock_free` in `glock_work_func`. And accessed by the function `gfs2_remove_from_ail` in `fs/gfs2/log.c`.
The root cause chain is precise: a glock demotion with pending AIL items (`gl_ail_count >
|
| 64/2 |
2026/09/10 01:03 |
tool |
set-results |
0m
Args:
Results:
|
| 65/2 |
2026/09/10 01:03 |
llm |
expert |
0m
Model:
Tokens:
input: 71307
output: 870
thoughts: 34
Thoughts: Okay, I'm ready. Let's dive in. Here's my understanding, presented as though I'm thinking through the analysis:
**Deconstructing the Vulnerability: A Deep Dive**
Alright, let's break this down. From what I'm seeing, we have a classic use-after-free scenario. My initial assessment is focused on the data structures and memory management involved.
First, I need to understand the relationship between the stacks, the object, and where the memory corruption is happening. I need to know:
* **The object**: What type is it? What are its key members and how are they used. This is probably critical to understanding the crash or potential exploit. I'm looking for anything that involves pointers, dynamic allocation, or reference counts (which are ripe for off-by-one errors)
* **The stacks**: which stack is being used for the attack. It is likely the attacker is using the stack to overwrite a value pointing to a heap value in order to trigger the use-after-free
* **The offsets**: Knowing exactly which bytes are overwritten is going to be key to exploit development. It tells me where to look for interesting data and pointers. I'm looking for where a deallocation happens, and then where a subsequent access occurs, using what was previously deallocated.
* **Use-After-Free**: After the object gets freed, there is still a pointer to it that causes it to be used again.
I'm mentally mapping the memory layout. The goal is to identify a freed memory region, and then observe how a dangling pointer is still being used, leading to the vulnerability. I'm focusing on the timing of allocation and deallocation. What's the sequence of events here? Is there a race condition? Is the freed object reallocated by the time it is accessed?
My next step is to examine the instructions. I need to trace the pointer manipulation and the conditional logic. Where is the object freed? Where is the pointer to it still being used? What's the context? What happens immediately before and after the memory corruption?
Once I have a clear mental model of the vulnerable code, I'll need to consider how the attacker could trigger this condition. What input is necessary? Are there specific sequences of operations that lead to the use-after-free? This will allow me to formulate the exploit strategy. It's time to refine my attack vector.
|
| 66/1 |
2026/09/10 01:03 |
action |
format-explanation |
0m
Results: map[Explanation:This KASAN report describes a valid, self-consistent, and actionable slab
use-after-free bug.
### Analysis of the Stacks and Object
1. **Allocated Object**:
- **Type / Cache**: `struct gfs2_glock_aspace` (slab cache
`gfs2_glock(aspace)`, size 1176 bytes).
- **Allocation Stack**: Allocated by task 7723 during filesystem mount /
inode lookup in `gfs2_glock_get()` (`fs/gfs2/glock.c:1074`) called from
`gfs2_inode_lookup()`.
2. **Free Stack**:
- **Freeing Path**: Task 7773 frees the glock via RCU callback
`gfs2_glock_dealloc()` (`fs/gfs2/glock.c:134`).
- The auxiliary work creation stack shows `glock_work_func()` dropped the
glock's final reference after demoting it to `LM_ST_UNLOCKED`
(`__gfs2_glock_put()` calling `lm_put_lock()` $\rightarrow$
`gfs2_glock_free()` $\rightarrow$ `call_rcu()`).
3. **Bad Access Stack**:
- **Accessing Path**: Task 5768 (`kworker/0:6`) running
`gfs2_withdraw_func()` calls `do_withdraw()` $\rightarrow$ `gfs2_ail_drain()`
$\rightarrow$ `gfs2_ail_empty_tr()` $\rightarrow$ `gfs2_remove_from_ail()`
(`fs/gfs2/log.c:79`).
- In `gfs2_remove_from_ail(bd)`, `list_del_init(&bd->bd_ail_gl_list)` is
called. The list `bd->bd_ail_gl_list` is linked into `gl->gl_ail_list`.
- **Accessed Offsets**:
- `struct gfs2_glock` embedded at offset 0 of `struct gfs2_glock_aspace`
has field `gl_ail_list` at offset 280 bytes (`0x118`).
- Offset 280 (`gl_ail_list.next`) is read and offset 288
(`gl_ail_list.prev`) is read/written by `__list_del_entry_valid_or_report`
/ `__list_del` when removing `bd` from `gl_ail_list`.
- The report specifically flags reads and writes at offsets 280 and 288
within the freed 1176-byte `gfs2_glock(aspace)` object.
### Bug Mechanism & Consistency
- All three stack traces and the auxiliary work creation traces are in the
`fs/gfs2` subsystem and refer to the exact same object (`struct
gfs2_glock_aspace`).
- The glock was demoted to `LM_ST_UNLOCKED` and had its refcount drop to 0,
which triggered glock release and RCU-freeing. However, buffer data objects
(`struct gfs2_bufdata *bd`) were still linked on the glock's `gl_ail_list` and
in the filesystem's AIL list (`sd_ail1_list` / `sd_ail2_list`).
- When the filesystem withdrawal workqueue worker subsequently drained the AIL
in `gfs2_ail_drain()`, it invoked `gfs2_remove_from_ail()`, which attempted to
unlink the buffer from the already-freed glock's `gl_ail_list`, resulting in the
reported slab use-after-free.
Because the report contains all three requisite stack traces, all in the same
subsystem referencing the exact same structure and offsets, the report is
**actionable**.]
|