Re: [PATCH v2] ocfs2: always run deallocs on copy-on-write completion
Joseph Qi <[email protected]> Tue, 21 Jul 2026 19:47:47 +0800
| Newsgroups | dev.linux.lists.ocfs2-devel |
|---|---|
| Message-ID | <[email protected]> |
On 7/21/26 6:28 PM, Dmitry Antipov wrote: > Local fuzzing of 6.12.94 has found the following memory leak > caused by doing 'copy_file_range()' within the same filesystem: > > unreferenced object 0xffff88812192c980 (size 32): > comm "syz.0.49", pid 12095, jiffies 4294964143 > hex dump (first 32 bytes): > 00 00 00 00 00 00 00 00 08 00 00 00 00 00 00 00 ................ > c0 c5 92 21 81 88 ff ff 00 02 00 00 00 06 00 00 ...!............ > backtrace (crc 7068d63f): > kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] > slab_post_alloc_hook mm/slub.c:4152 [inline] > slab_alloc_node mm/slub.c:4197 [inline] > __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 > kmalloc_noprof include/linux/slab.h:878 [inline] > ocfs2_find_per_slot_free_list fs/ocfs2/alloc.c:6618 [inline] > ocfs2_cache_block_dealloc+0x155/0x4b0 fs/ocfs2/alloc.c:6786 > ocfs2_cache_extent_block_free fs/ocfs2/alloc.c:6819 [inline] > ocfs2_unlink_path+0x286/0x450 fs/ocfs2/alloc.c:2613 > ocfs2_rotate_subtree_left fs/ocfs2/alloc.c:2779 [inline] > __ocfs2_rotate_tree_left+0x1f6f/0x2da0 fs/ocfs2/alloc.c:2985 > ocfs2_rotate_tree_left+0x283/0xe00 fs/ocfs2/alloc.c:3237 > ocfs2_try_to_merge_extent+0xf56/0x1a20 fs/ocfs2/alloc.c:3825 > ocfs2_split_extent+0x15f4/0x2940 fs/ocfs2/alloc.c:5138 > ocfs2_clear_ext_refcount+0x2f6/0x550 fs/ocfs2/refcounttree.c:3098 > ocfs2_replace_clusters fs/ocfs2/refcounttree.c:3131 [inline] > ocfs2_make_clusters_writable fs/ocfs2/refcounttree.c:3255 [inline] > ocfs2_replace_cow+0x991/0x1660 fs/ocfs2/refcounttree.c:3349 > ocfs2_refcount_cow_hunk fs/ocfs2/refcounttree.c:3427 [inline] > ocfs2_refcount_cow+0x5e1/0x9f0 fs/ocfs2/refcounttree.c:3470 > ocfs2_prepare_inode_for_write fs/ocfs2/file.c:2340 [inline] > ocfs2_file_write_iter+0xbda/0x1880 fs/ocfs2/file.c:2451 > iter_file_splice_write+0x890/0xf60 fs/splice.c:743 > do_splice_from fs/splice.c:944 [inline] > direct_splice_actor+0x232/0x480 fs/splice.c:1167 > splice_direct_to_actor+0x4b4/0xb60 fs/splice.c:1111 > do_splice_direct_actor fs/splice.c:1210 [inline] > do_splice_direct+0x10f/0x1c0 fs/splice.c:1236 > do_sendfile+0x430/0xbf0 fs/read_write.c:1388 > > unreferenced object 0xffff88812192c5c0 (size 32): > comm "syz.0.49", pid 12095, jiffies 4294964143 > hex dump (first 32 bytes): > 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ > 29 70 00 00 00 00 00 00 19 00 00 00 00 00 00 00 )p.............. > backtrace (crc afec850f): > kmemleak_alloc_recursive include/linux/kmemleak.h:42 [inline] > slab_post_alloc_hook mm/slub.c:4152 [inline] > slab_alloc_node mm/slub.c:4197 [inline] > __kmalloc_cache_noprof+0x168/0x2c0 mm/slub.c:4358 > kmalloc_noprof include/linux/slab.h:878 [inline] > kzalloc_noprof include/linux/slab.h:1014 [inline] > ocfs2_cache_block_dealloc+0x25c/0x4b0 fs/ocfs2/alloc.c:6793 > ocfs2_cache_extent_block_free fs/ocfs2/alloc.c:6819 [inline] > ocfs2_unlink_path+0x286/0x450 fs/ocfs2/alloc.c:2613 > ocfs2_rotate_subtree_left fs/ocfs2/alloc.c:2779 [inline] > __ocfs2_rotate_tree_left+0x1f6f/0x2da0 fs/ocfs2/alloc.c:2985 > ocfs2_rotate_tree_left+0x283/0xe00 fs/ocfs2/alloc.c:3237 > ocfs2_try_to_merge_extent+0xf56/0x1a20 fs/ocfs2/alloc.c:3825 > ocfs2_split_extent+0x15f4/0x2940 fs/ocfs2/alloc.c:5138 > ocfs2_clear_ext_refcount+0x2f6/0x550 fs/ocfs2/refcounttree.c:3098 > ocfs2_replace_clusters fs/ocfs2/refcounttree.c:3131 [inline] > ocfs2_make_clusters_writable fs/ocfs2/refcounttree.c:3255 [inline] > ocfs2_replace_cow+0x991/0x1660 fs/ocfs2/refcounttree.c:3349 > ocfs2_refcount_cow_hunk fs/ocfs2/refcounttree.c:3427 [inline] > ocfs2_refcount_cow+0x5e1/0x9f0 fs/ocfs2/refcounttree.c:3470 > ocfs2_prepare_inode_for_write fs/ocfs2/file.c:2340 [inline] > ocfs2_file_write_iter+0xbda/0x1880 fs/ocfs2/file.c:2451 > iter_file_splice_write+0x890/0xf60 fs/splice.c:743 > do_splice_from fs/splice.c:944 [inline] > direct_splice_actor+0x232/0x480 fs/splice.c:1167 > splice_direct_to_actor+0x4b4/0xb60 fs/splice.c:1111 > do_splice_direct_actor fs/splice.c:1210 [inline] > do_splice_direct+0x10f/0x1c0 fs/splice.c:1236 > do_sendfile+0x430/0xbf0 fs/read_write.c:1388 > > This happens when 'ocfs2_cache_block_dealloc()' called from > 'ocfs2_cache_extent_block_free()' uses the suballocator to > schedule extent removal, so 'ocfs2_run_deallocs()' should > be run unconditionally to complete the removal with > 'ocfs2_free_cached_blocks()'. An extra semi-automated static > analysis [1] suspects that the same scenario looks possible in > 'ocfs2_attach_refcount_tree()' and 'ocfs2_reflink_remap_blocks()' > as well, but, since 'ocfs2_run_deallocs()' is a safe no-op for > an empty dealloc context, 'ocfs2_create_reflink_node()' and > 'ocfs2_reflink_xattrs()' may be adjusted in the same way too, > thus keeping the code pattern consistent. > > [1] https://lore.kernel.org/ocfs2-devel/[email protected] > > Fixes: 6f70fa519976 ("ocfs2: Add CoW support.") > Suggested-by: Joseph Qi <[email protected]> > Signed-off-by: Dmitry Antipov <[email protected]> Looks fine. Reviewed-by: Joseph Qi <[email protected]> > --- > fs/ocfs2/refcounttree.c | 20 ++++++++------------ > fs/ocfs2/xattr.c | 5 ++--- > 2 files changed, 10 insertions(+), 15 deletions(-) > > diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c > index 7323bde70caa..b36f6143a0f6 100644 > --- a/fs/ocfs2/refcounttree.c > +++ b/fs/ocfs2/refcounttree.c > @@ -3360,10 +3360,9 @@ static int ocfs2_replace_cow(struct ocfs2_cow_context *context) > cow_start += num_clusters; > } > > - if (ocfs2_dealloc_has_cluster(&context->dealloc)) { > + if (ocfs2_dealloc_has_cluster(&context->dealloc)) > ocfs2_schedule_truncate_log_flush(osb, 1); > - ocfs2_run_deallocs(osb, &context->dealloc); > - } > + ocfs2_run_deallocs(osb, &context->dealloc); > > return ret; > } > @@ -3846,10 +3845,9 @@ static int ocfs2_attach_refcount_tree(struct inode *inode, > ocfs2_unlock_refcount_tree(osb, ref_tree, 1); > brelse(ref_root_bh); > > - if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) { > + if (!ret && ocfs2_dealloc_has_cluster(&dealloc)) > ocfs2_schedule_truncate_log_flush(osb, 1); > - ocfs2_run_deallocs(osb, &dealloc); > - } > + ocfs2_run_deallocs(osb, &dealloc); > out: > /* > * Empty the extent map so that we may get the right extent > @@ -4135,10 +4133,9 @@ static int ocfs2_create_reflink_node(struct inode *s_inode, > ocfs2_unlock_refcount_tree(osb, ref_tree, 1); > brelse(ref_root_bh); > out: > - if (ocfs2_dealloc_has_cluster(&dealloc)) { > + if (ocfs2_dealloc_has_cluster(&dealloc)) > ocfs2_schedule_truncate_log_flush(osb, 1); > - ocfs2_run_deallocs(osb, &dealloc); > - } > + ocfs2_run_deallocs(osb, &dealloc); > > return ret; > } > @@ -4691,10 +4688,9 @@ loff_t ocfs2_reflink_remap_blocks(struct inode *s_inode, > } > > out: > - if (ocfs2_dealloc_has_cluster(&dealloc)) { > + if (ocfs2_dealloc_has_cluster(&dealloc)) > ocfs2_schedule_truncate_log_flush(osb, 1); > - ocfs2_run_deallocs(osb, &dealloc); > - } > + ocfs2_run_deallocs(osb, &dealloc); > > return ret; > } > diff --git a/fs/ocfs2/xattr.c b/fs/ocfs2/xattr.c > index fcddd3c13acd..412e647334ad 100644 > --- a/fs/ocfs2/xattr.c > +++ b/fs/ocfs2/xattr.c > @@ -7235,10 +7235,9 @@ int ocfs2_reflink_xattrs(struct inode *old_inode, > ref_tree, 1); > brelse(ref_root_bh); > > - if (ocfs2_dealloc_has_cluster(&dealloc)) { > + if (ocfs2_dealloc_has_cluster(&dealloc)) > ocfs2_schedule_truncate_log_flush(OCFS2_SB(old_inode->i_sb), 1); > - ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc); > - } > + ocfs2_run_deallocs(OCFS2_SB(old_inode->i_sb), &dealloc); > > out: > return ret;