* [PATCH] ocfs2: fix stale extent map cache during COW operations
@ 2025-10-08 4:23 Deepanshu Kartikey
2025-10-09 9:14 ` Joseph Qi
0 siblings, 1 reply; 7+ messages in thread
From: Deepanshu Kartikey @ 2025-10-08 4:23 UTC (permalink / raw)
To: mark, jlbec, joseph.qi
Cc: ocfs2-devel, linux-kernel, Deepanshu Kartikey,
syzbot+6fdd8fa3380730a4b22c
The extent map cache can become stale during COW operations, causing
ocfs2_refcount_cal_cow_clusters() to see an outdated extent state.
The problem occurs when:
1. ocfs2_get_clusters() reads and caches an extent with OCFS2_EXT_REFCOUNTED
2. ocfs2_refcount_cow_hunk() performs COW, clearing the REFCOUNTED flag
3. The extent map cache still contains the stale REFCOUNTED flag
4. Subsequent access on the same extent via the cache gets incorrect flags,
triggering BUG_ON(!(rec->e_flags & OCFS2_EXT_REFCOUNTED))
Fix by clearing the extent map cache at the start of COW operations.
This ensures that ocfs2_get_clusters() always reads fresh extent data
from disk during COW instead of using stale cached data.
Reported-by: syzbot+6fdd8fa3380730a4b22c@syzkaller.appspotmail.com
Tested-by: syzbot+6fdd8fa3380730a4b22c@syzkaller.appspotmail.com
Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
---
fs/ocfs2/refcounttree.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
index 267b50e8e42e..e8c8fcdc3dd9 100644
--- a/fs/ocfs2/refcounttree.c
+++ b/fs/ocfs2/refcounttree.c
@@ -3451,7 +3451,8 @@ int ocfs2_refcount_cow(struct inode *inode,
int ret = 0;
u32 p_cluster, num_clusters;
unsigned int ext_flags;
-
+ /* Clear extent map cache before COW operations to avoid stale data */
+ ocfs2_extent_map_trunc(inode, 0);
while (write_len) {
ret = ocfs2_get_clusters(inode, cpos, &p_cluster,
&num_clusters, &ext_flags);
--
2.43.0
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
2025-10-08 4:23 [PATCH] ocfs2: fix stale extent map cache during COW operations Deepanshu Kartikey
@ 2025-10-09 9:14 ` Joseph Qi
0 siblings, 0 replies; 7+ messages in thread
From: Joseph Qi @ 2025-10-09 9:14 UTC (permalink / raw)
To: Deepanshu Kartikey, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi,
On 2025/10/8 12:23, Deepanshu Kartikey wrote:
> The extent map cache can become stale during COW operations, causing
> ocfs2_refcount_cal_cow_clusters() to see an outdated extent state.
>
> The problem occurs when:
> 1. ocfs2_get_clusters() reads and caches an extent with OCFS2_EXT_REFCOUNTED
> 2. ocfs2_refcount_cow_hunk() performs COW, clearing the REFCOUNTED flag
> 3. The extent map cache still contains the stale REFCOUNTED flag
> 4. Subsequent access on the same extent via the cache gets incorrect flags,
> triggering BUG_ON(!(rec->e_flags & OCFS2_EXT_REFCOUNTED))
>
At the end of ocfs2_refcount_cow_hunk(), it has:
/*
* truncate the extent map here since no matter whether we meet with
* any error during the action, we shouldn't trust cached extent map
* any more.
*/
ocfs2_extent_map_trunc(inode, cow_start);
It seems the cached extent record has already been forgotten. So how
does the above step 3 happen?
Thanks,
Joseph
> Fix by clearing the extent map cache at the start of COW operations.
> This ensures that ocfs2_get_clusters() always reads fresh extent data
> from disk during COW instead of using stale cached data.
>
> Reported-by: syzbot+6fdd8fa3380730a4b22c@syzkaller.appspotmail.com
> Tested-by: syzbot+6fdd8fa3380730a4b22c@syzkaller.appspotmail.com
> Signed-off-by: Deepanshu Kartikey <kartikey406@gmail.com>
> ---
> fs/ocfs2/refcounttree.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git a/fs/ocfs2/refcounttree.c b/fs/ocfs2/refcounttree.c
> index 267b50e8e42e..e8c8fcdc3dd9 100644
> --- a/fs/ocfs2/refcounttree.c
> +++ b/fs/ocfs2/refcounttree.c
> @@ -3451,7 +3451,8 @@ int ocfs2_refcount_cow(struct inode *inode,
> int ret = 0;
> u32 p_cluster, num_clusters;
> unsigned int ext_flags;
> -
> + /* Clear extent map cache before COW operations to avoid stale data */
> + ocfs2_extent_map_trunc(inode, 0);
> while (write_len) {
> ret = ocfs2_get_clusters(inode, cpos, &p_cluster,
> &num_clusters, &ext_flags);
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
2025-10-11 8:32 Deepanshu Kartikey
@ 2025-10-11 12:27 ` Heming Zhao
0 siblings, 0 replies; 7+ messages in thread
From: Heming Zhao @ 2025-10-11 12:27 UTC (permalink / raw)
To: Deepanshu Kartikey, joseph.qi, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi Deepanshu,
Good catch! With following explanation, your patch looks good to me.
Thanks,
Heming
On 10/11/25 16:32, Deepanshu Kartikey wrote:
> Hi Heming,
>
> Thank you for the detailed analysis and feedback.
>
> After further investigation, I discovered that the ioctl call in the reproducer
> (0x40406f06) is NOT FITRIM as the syzbot comment suggested. Decoding the ioctl
> number shows:
> - Type: 'o' (0x6f)
> - Number: 6
> - This is OCFS2_IOC_MOVE_EXT (defined in fs/ocfs2/ocfs2_ioctl.h:222)
>
> So the actual call path in the reproducer is:
> 1. copy_file_range() - creates reflinked extent with flags=0x2
> 2. ioctl(OCFS2_IOC_MOVE_EXT) - calls ocfs2_move_extents() ->
> __ocfs2_move_extents_range()
> 3. write() - triggers BUG_ON
>
> Inside __ocfs2_move_extents_range(), the while loop:
> - Calls ocfs2_get_clusters() which caches the extent with flags=0x2
> - Then calls ocfs2_move_extent() -> __ocfs2_move_extent()
> - __ocfs2_move_extent() at line 50 clears the refcount flag on disk:
> replace_rec.e_flags = ext_flags & ~OCFS2_EXT_REFCOUNTED;
> - But the extent cache is not invalidated
>
> This is exactly what my v2 patch fixes by adding ocfs2_extent_map_trunc()
> after the move operation completes.
>
> Thanks for your time!
>
> Best regards,
> Deepanshu
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
@ 2025-10-11 8:32 Deepanshu Kartikey
2025-10-11 12:27 ` Heming Zhao
0 siblings, 1 reply; 7+ messages in thread
From: Deepanshu Kartikey @ 2025-10-11 8:32 UTC (permalink / raw)
To: heming.zhao, joseph.qi, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi Heming,
Thank you for the detailed analysis and feedback.
After further investigation, I discovered that the ioctl call in the reproducer
(0x40406f06) is NOT FITRIM as the syzbot comment suggested. Decoding the ioctl
number shows:
- Type: 'o' (0x6f)
- Number: 6
- This is OCFS2_IOC_MOVE_EXT (defined in fs/ocfs2/ocfs2_ioctl.h:222)
So the actual call path in the reproducer is:
1. copy_file_range() - creates reflinked extent with flags=0x2
2. ioctl(OCFS2_IOC_MOVE_EXT) - calls ocfs2_move_extents() ->
__ocfs2_move_extents_range()
3. write() - triggers BUG_ON
Inside __ocfs2_move_extents_range(), the while loop:
- Calls ocfs2_get_clusters() which caches the extent with flags=0x2
- Then calls ocfs2_move_extent() -> __ocfs2_move_extent()
- __ocfs2_move_extent() at line 50 clears the refcount flag on disk:
replace_rec.e_flags = ext_flags & ~OCFS2_EXT_REFCOUNTED;
- But the extent cache is not invalidated
This is exactly what my v2 patch fixes by adding ocfs2_extent_map_trunc()
after the move operation completes.
Thanks for your time!
Best regards,
Deepanshu
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
@ 2025-10-11 7:42 Deepanshu Kartikey
0 siblings, 0 replies; 7+ messages in thread
From: Deepanshu Kartikey @ 2025-10-11 7:42 UTC (permalink / raw)
To: heming.zhao, joseph.qi, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi Heming,
Thank you for the detailed analysis and feedback.
I appreciate your review. However, I'm trying to understand your explanation
better. You mentioned that step 5 (write with zeros) cleans the file data
and causes the refcount flag mismatch.
Looking at the C reproducer, I see:
- Step 7: copy_file_range() creates reflinked extent (flags=0x2)
- Step 8: ioctl(fd, 0x40406f06, ...) which is FITRIM
- Step 9: write() triggers the BUG_ON
In my analysis, step 8 (FITRIM) calls ocfs2_move_extents() ->
__ocfs2_move_extents_range() -> ocfs2_move_extent() -> __ocfs2_move_extent().
Inside __ocfs2_move_extent() at line 50, I found:
replace_rec.e_flags = ext_flags & ~OCFS2_EXT_REFCOUNTED;
This explicitly clears the OCFS2_EXT_REFCOUNTED flag when writing to disk,
but the extent cache is not invalidated afterward.
Could you help me understand:
1. How does the write operation in step 5 clear the refcount flag on disk?
2. Are you suggesting there might be two separate bugs - one in the FITRIM
path (which v2 fixes) and another in a different path (which v1 would fix)?
My v2 patch has been merged into linux-next. If you believe v1 addresses a
different bug scenario, I'm happy to submit it as an additional patch.
Thanks for your time and expertise!
Best regards,
Deepanshu
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
2025-10-09 14:29 Deepanshu Kartikey
@ 2025-10-11 4:50 ` Heming Zhao
0 siblings, 0 replies; 7+ messages in thread
From: Heming Zhao @ 2025-10-11 4:50 UTC (permalink / raw)
To: Deepanshu Kartikey, joseph.qi, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi Deepanshu and Joseph,
On 10/9/25 22:29, Deepanshu Kartikey wrote:
>
> Hi Joseph,
>
> Thank you for the review. You are absolutely right - the cache clearing at the end of ocfs2_refcount_cow_hunk() should handle the COW path correctly.
>
> After further investigation with the syzbot reproducer and extensive debugging, I found the real issue is in the FITRIM/move_extents code path. The bug occurs when:
>
> 1. copy_file_range() creates a reflinked extent with flags=0x2 (OCFS2_EXT_REFCOUNTED)
> 2. ioctl(FITRIM) is called, which triggers ocfs2_move_extents()
> 3. In __ocfs2_move_extents_range(), the while loop:
> - Calls ocfs2_get_clusters() which reads extent with flags=0x2 and caches it
> - Then calls ocfs2_move_extent() or ocfs2_defrag_extent()
> - Both eventually call __ocfs2_move_extent() which contains:
> replace_rec.e_flags = ext_flags & ~OCFS2_EXT_REFCOUNTED;
> - This clears the refcount flag and writes to disk with flags=0x0
> 4. However, the extent map cache is NOT cleared after the move operation
> 5. Cache still contains stale flags=0x2 while disk has flags=0x0
> 6. Later, when write() triggers COW, ocfs2_refcount_cal_cow_clusters() reads:
> - From cache: flags=0x2 (stale)
> - From disk extent tree: flags=0x0 (correct)
> 7. The mismatch triggers: BUG_ON(!(rec->e_flags & OCFS2_EXT_REFCOUNTED))
>
> The proper fix should be in __ocfs2_move_extents_range() to clear the extent cache after each move/defrag operation completes. I will send a v2 patch with this fix.
>
> Thanks,
> Deepanshu
>
let's look at the syzbot page [1].
the following analysis is based on the c code from "2025/10/03 12:11" [2].
(btw, syzbot never calls __ocfs2_move_extents_range().)
The test code mainly involves 9 steps:
1. create img data and mount
2. one time open() the file (0x200000000080ul), return fd: r[0]
3. two times open the file (0x200000000280ul), return fds: r[1] r[2]
4. call ioctl F_SETFL 0 on r[1]
5. write r[2] with "0x0000000000000000" len:0xfea0ul //clean data job
6. call r[3] = dup(r[1])
7. do copy_file_range(), copy from r[1] to r[3] len=0xd8c2ul
//creates OCFS2_EXT_REFCOUNTED and create extent cache.
//check ocfs2_remap_file_range() => ocfs2_reflink_remap_extent()
8. trim r[0]
9. write on r[1] //crash.
the root cause is that, in step <9>, it calls ocfs2_refcount_cow():
- the input parameter di_bh is created by the caller via
ocfs2_prepare_inode_for_write() => ocfs2_inode_lock_for_extent_tree() =>
ocfs2_inode_lock_update(), which reads file data from disk.
The extent is without OCFS2_EXT_REFCOUNTED flag because r[1] & r[2] point
to the same file, and step <5> cleanup the file data.
- ocfs2_refcount_cow() then calls ocfs2_get_clusters to retrieve the extent
from cache, which does contain OCFS2_EXT_REFCOUNTED (cooked by step <7>).
- this difference leads to it calling ocfs2_refcount_cow_hunk(), which
triggers a BUG_ON().
I suspect step <7> needs some time to write back the COW data, and syzbot
starts step <9> too quickly before the write-back job start.
how to fix:
the v1 patch is reasonable, but the commit log needs to be revised.
for Joseph's question: (I copied here)
> At the end of ocfs2_refcount_cow_hunk(), it has:
>
> /*
> * truncate the extent map here since no matter whether we meet with
> * any error during the action, we shouldn't trust cached extent map
> * any more.
> */
> ocfs2_extent_map_trunc(inode, cow_start);
>
> It seems the cached extent record has already been forgotten. So how
> does the above step 3 happen?
my answer:
the crash only happens on the first call to ocfs2_refcount_cow_hunk().
ocfs2_extent_map_trunc() does the cleanup later, but the malicious
extent block is cooked before ocfs2_refcount_cow_hunk() is called.
[1] https://syzkaller.appspot.com/bug?extid=6fdd8fa3380730a4b22c
[2] https://syzkaller.appspot.com/text?tag=ReproC&x=163c9214580000
- Heming
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH] ocfs2: fix stale extent map cache during COW operations
@ 2025-10-09 14:29 Deepanshu Kartikey
2025-10-11 4:50 ` Heming Zhao
0 siblings, 1 reply; 7+ messages in thread
From: Deepanshu Kartikey @ 2025-10-09 14:29 UTC (permalink / raw)
To: joseph.qi, mark, jlbec
Cc: ocfs2-devel, linux-kernel, syzbot+6fdd8fa3380730a4b22c
Hi Joseph,
Thank you for the review. You are absolutely right - the cache clearing at the end of ocfs2_refcount_cow_hunk() should handle the COW path correctly.
After further investigation with the syzbot reproducer and extensive debugging, I found the real issue is in the FITRIM/move_extents code path. The bug occurs when:
1. copy_file_range() creates a reflinked extent with flags=0x2 (OCFS2_EXT_REFCOUNTED)
2. ioctl(FITRIM) is called, which triggers ocfs2_move_extents()
3. In __ocfs2_move_extents_range(), the while loop:
- Calls ocfs2_get_clusters() which reads extent with flags=0x2 and caches it
- Then calls ocfs2_move_extent() or ocfs2_defrag_extent()
- Both eventually call __ocfs2_move_extent() which contains:
replace_rec.e_flags = ext_flags & ~OCFS2_EXT_REFCOUNTED;
- This clears the refcount flag and writes to disk with flags=0x0
4. However, the extent map cache is NOT cleared after the move operation
5. Cache still contains stale flags=0x2 while disk has flags=0x0
6. Later, when write() triggers COW, ocfs2_refcount_cal_cow_clusters() reads:
- From cache: flags=0x2 (stale)
- From disk extent tree: flags=0x0 (correct)
7. The mismatch triggers: BUG_ON(!(rec->e_flags & OCFS2_EXT_REFCOUNTED))
The proper fix should be in __ocfs2_move_extents_range() to clear the extent cache after each move/defrag operation completes. I will send a v2 patch with this fix.
Thanks,
Deepanshu
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-10-11 12:27 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2025-10-08 4:23 [PATCH] ocfs2: fix stale extent map cache during COW operations Deepanshu Kartikey
2025-10-09 9:14 ` Joseph Qi
2025-10-09 14:29 Deepanshu Kartikey
2025-10-11 4:50 ` Heming Zhao
2025-10-11 7:42 Deepanshu Kartikey
2025-10-11 8:32 Deepanshu Kartikey
2025-10-11 12:27 ` Heming Zhao
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®