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

Dmitry Antipov dmantipov at yandex.ru
Tue Jul 21 12:08:57 MSK 2026


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.

Dmitry




More information about the lvc-project mailing list