[lvc-project] [PATCH] ocfs2: always run deallocs on copy-on-write completion

Joseph Qi joseph.qi at linux.alibaba.com
Tue Jul 21 13:08:52 MSK 2026



On 7/21/26 5:08 PM, Dmitry Antipov wrote:
> On 7/21/26 4:35 AM, Joseph Qi wrote:
> 
>> The identical idiom also exists in other places:
>>    ocfs2_attach_refcount_tree
>>    ocfs2_create_reflink_node
>>    ocfs2_reflink_remap_blocks
>>    ocfs2_reflink_xattrs
>>
>> So please check if it will leak the same way. If so, please apply the
>> same changes as well.
> 
> So the basic problem is to check whether 'ocfs2_cache_block_dealloc()'
> can be called from these functions as well. Since I have the only
> .syz reproducer that proves that 'ocfs2_cache_block_dealloc()' can
> be called from 'ocfs2_replace_cow()', time to try some static analysis.
> 
> 1. Collect LLVM bitcode of the relevant source code:
> 
> diff --git a/fs/ocfs2/Makefile b/fs/ocfs2/Makefile
> index cc9b32b9db7c..6163df78e873 100644
> --- a/fs/ocfs2/Makefile
> +++ b/fs/ocfs2/Makefile
> @@ -1,5 +1,5 @@
>  # SPDX-License-Identifier: GPL-2.0
> -ccflags-y := -I$(src)
> +ccflags-y := -I$(src) --save-temps=obj
> 
>  obj-$(CONFIG_OCFS2_FS) +=      \
>         ocfs2.o                 \
> 
> 2. Link them into a single unified bitcode and produce call graph:
> 
> $ llvm-link $(find fs/ocfs2 -name '*.bc') -o ocfs2.bc
> $ opt --passes=dot-callgraph ocfs2.bc
> 
> This produces ocfs2.bc.callgraph.dot.
> 
> 3. Use the following gvpr(1) (comes from graphviz) script:
> 
> // AI-assisted breadth-first search
> 
> BEGIN {
>     if (ARGC < 2) {
>         printf(2, "Error: Please provide source and target node names as arguments.\n");
>         printf(2, "Usage: gvpr -f reachable.gvpr -a [source] -a [target] input.gv\n");
>         exit(1);
>     }
>     string src = ARGV[0];
>     string tgt = ARGV[1];
> 
>     // Arrays for tracking BFS
>     node_t queue[];
>     int visited[string];
>     int Head = 0;
>     int Tail = 0;
> }
> 
> BEG_G {
>     node_t s = node($, src);
>     node_t t = node($, tgt);
> 
>     if (s == NULL || t == NULL) {
>         printf(2, "Error: One or both nodes do not exist in the graph.\n");
>         exit(1);
>     }
> 
>     if (s == t) {
>         print("yes (source is equal to target)");
>         exit(0);
>     }
> 
>     // Initialize BFS queue
>     queue[Tail++] = s;
>     visited[s.name] = 1;
> 
>     // Run BFS loop
>     while (Head < Tail) {
>         node_t curr = queue[Head++];
>         edge_t e;
> 
>         // Loop through all outgoing edges of the current node
>         for (e = fstedge(curr); e; e = nxtedge(e, curr)) {
>             if (e.head == curr) continue;
> 
>             node_t nextNode = e.head;
>             if (!visited[nextNode.name]) {
>                 if (nextNode == t) {
>                     print("yes");
>                     exit(0);
>                 }
>                 visited[nextNode.name] = 1;
>                 queue[Tail++] = nextNode;
>             }
>         }
>     }
> 
>     print("no");
> }
> 
> and simple shell script to check suspected functions:
> 
> #!/bin/bash
> 
> TARGET=ocfs2_cache_block_dealloc
> NODE_target=$(grep "{$TARGET}" ocfs2.bc.callgraph.dot  | awk '{print $1}')
> 
> for NAME in ocfs2_replace_cow \
>         ocfs2_attach_refcount_tree \
>         ocfs2_create_reflink_node \
>         ocfs2_reflink_remap_blocks \
>         ocfs2_reflink_xattrs
> do
>     NODE_check=$(grep "{$NAME}" ocfs2.bc.callgraph.dot  | awk '{print $1}')
>     echo -ne "Check whether $TARGET() is reachable from $NAME()..."
>     gvpr -f reachable.gvpr -a $NODE_check -a $NODE_target ocfs2.bc.callgraph.dot
> done
> 
> This produces the following output:
> 
> Check whether ocfs2_cache_block_dealloc() is reachable from ocfs2_replace_cow()...yes
> Check whether ocfs2_cache_block_dealloc() is reachable from ocfs2_attach_refcount_tree()...yes
> Check whether ocfs2_cache_block_dealloc() is reachable from ocfs2_create_reflink_node()...no
> Check whether ocfs2_cache_block_dealloc() is reachable from ocfs2_reflink_remap_blocks()...yes
> Check whether ocfs2_cache_block_dealloc() is reachable from ocfs2_reflink_xattrs()...no
> 
> So I would assume that 'ocfs2_create_reflink_node()' and 'ocfs2_reflink_xattrs()'
> are better to leave untouched.
> 
Reasons for ocfs2_create_reflink_node() and ocfs2_reflink_xattrs() are
marked unreachable:
ocfs2_create_reflink_node() - it's purely additive without remove blocks.
ocfs2_reflink_xattrs() - allocate new xattr blocks for the destination, with
the source's xattr tree untouched.

Since ocfs2_run_deallocs() is a no-op with an empty dealloc context, so it
seems marking it unconditional is safe. And this will keep the code pattern
consistent.

Thanks,
Joseph



More information about the lvc-project mailing list