Linux EXT4 FS development
 help / color / mirror / Atom feed
* [PATCH v2] ext4: fix race in ext4_mb_check_group_pa
@ 2026-07-20  6:54 rafad900
  2026-07-20 16:05 ` Andreas Dilger
  0 siblings, 1 reply; 3+ messages in thread
From: rafad900 @ 2026-07-20  6:54 UTC (permalink / raw)
  To: tytso, adilger.kernel, libaokun, jack; +Cc: linux-ext4, linux-kernel, rafad900

ext4_mb_check_group_pa drops the reference count on the previous
best PA using atomic_dec(&cpa->pa_count) without holding the
cpa->pa_lock.

This causes race with ext4_discard_preallocations() which checks
pa_count to decide whether a PA is still in use. If the pa_count
is dec between the check and the discard, the PA can be freed
while ext4_mb_check_group_pa() still holds a reference to
it.

Fix this by taking the cpa->pa_lock around the atomic_dec.
Similar to pa->pa_lock which is taken outside of the
ext4_mb_check_group_pa() function.

The race was found while testing a change related to a
Coccinelle warning from atomic_as_refcounter.cocci. The refcount
conversion was found to be incorrect but the change had revealed
the pre-exiting race condition.

Signed-off-by: rafad900 <rafad900@gmail.com>
---
Changes in v2:
- Identified correct race location: ext4_mb_check_group_pa rather
  than ext4_mb_use_preallocated (the inode PA path already holds
  pa_lock correctly)
- Dropped refcount_t conversion — incompatible with PA lifecycle
  where count=0 represents an idle but reusable PA
- Added spin_lock(&cpa->pa_lock) around atomic_dec in
  ext4_mb_check_group_pa

 fs/ext4/mballoc.c | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index ed1bd00e11cd..c9a118ae4658 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4833,7 +4833,9 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
 		return cpa;
 
 	/* drop the previous reference */
+	spin_lock(&cpa->pa_lock);
 	atomic_dec(&cpa->pa_count);
+	spin_unlock(&cpa->pa_lock);
 	atomic_inc(&pa->pa_count);
 	return pa;
 }
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH v2] ext4: fix race in ext4_mb_check_group_pa
  2026-07-20  6:54 [PATCH v2] ext4: fix race in ext4_mb_check_group_pa rafad900
@ 2026-07-20 16:05 ` Andreas Dilger
  2026-07-21  0:54   ` Rafael Alejandro Díaz Cruz
  0 siblings, 1 reply; 3+ messages in thread
From: Andreas Dilger @ 2026-07-20 16:05 UTC (permalink / raw)
  To: rafad900; +Cc: tytso, libaokun, jack, linux-ext4, linux-kernel

On Jul 20, 2026, at 00:54, rafad900 <rafad900@gmail.com> wrote:
> ext4_mb_check_group_pa drops the reference count on the previous
> best PA using atomic_dec(&cpa->pa_count) without holding the
> cpa->pa_lock.
> 
> This causes race with ext4_discard_preallocations() which checks
> pa_count to decide whether a PA is still in use. If the pa_count
> is dec between the check and the discard, the PA can be freed
> while ext4_mb_check_group_pa() still holds a reference to it.

Can you please explain this race condition further?  I don't see where
ext4_mb_check_group_pa() is using cpa after the reference is dropped.

> Fix this by taking the cpa->pa_lock around the atomic_dec.
> Similar to pa->pa_lock which is taken outside of the
> ext4_mb_check_group_pa() function.

At this point, it wouldn't be clear why `pa_count` needs to be an
atomic at all, if `pa_lock` is always held during inc/dec/check?

> diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
> index ed1bd00e11cd..c9a118ae4658 100644
> --- a/fs/ext4/mballoc.c
> +++ b/fs/ext4/mballoc.c
> @@ -4833,7 +4833,9 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
> return cpa;
> 
> 	/* drop the previous reference */
> +	spin_lock(&cpa->pa_lock);
>  	atomic_dec(&cpa->pa_count);
> +	spin_unlock(&cpa->pa_lock);
>  	atomic_inc(&pa->pa_count);
>  	return pa;
>  }

In ext4_mb_check_group_pa() there is no reference to `cpa` after the
refcount is dropped.  In its one caller ext4_mb_use_preallocated():

		list_for_each_entry_rcu(tmp_pa, &lg->lg_prealloc_list[i],
					pa_node.lg_list) {
			spin_lock(&tmp_pa->pa_lock);
			if (tmp_pa->pa_deleted == 0 &&
			    tmp_pa->pa_free >= ac->ac_o_ex.fe_len) {
				cpa = ext4_mb_check_group_pa(goal_block,
							     tmp_pa, cpa);
			}
			spin_unlock(&tmp_pa->pa_lock);
		}
		rcu_read_unlock();
	}
	if (cpa) {
		ext4_mb_use_group_pa(ac, cpa);
		return true;
	}
	return false;
}

It *looks* like 'cpa' is used after ext4_mb_check_group_pa(), but it is
replaced on the return by 'tmp_pa' in that case, so there is no further
use after the refcount is dropped AFAICS.  Even the list iteration is
using 'tmp_pa', so that couldn't be it either.

There may be a race condition somewhere, but the commit message doesn't
provide clear details of what it is.

Cheers, Andreas






^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v2] ext4: fix race in ext4_mb_check_group_pa
  2026-07-20 16:05 ` Andreas Dilger
@ 2026-07-21  0:54   ` Rafael Alejandro Díaz Cruz
  0 siblings, 0 replies; 3+ messages in thread
From: Rafael Alejandro Díaz Cruz @ 2026-07-21  0:54 UTC (permalink / raw)
  To: Andreas Dilger; +Cc: tytso, libaokun, jack, linux-ext4, linux-kernel

I think I misinterpreted what was going on after the change in the first patch.
When making the change from atomic_t to refcount_t, I get this kernel
error while
testing on my x86 QEMU:
 ------------[ cut here ]------------
[   12.954674] refcount_t: addition on 0; use-after-free.
[   12.956476] WARNING: lib/refcount.c:25 at
refcount_warn_saturate+0x6a/0x90, CPU#0: dhcpcd-run-hook/229
[   12.959648] Modules linked in:
[   12.960735] CPU: 0 UID: 0 PID: 229 Comm: dhcpcd-run-hook Not
tainted 7.2.0-rc4-00001-g453fc8a20cd2-dirty #6 PREEMPT(lazy)
[   12.964446] Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX,
arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[   12.968371] RIP: 0010:refcount_warn_saturate+0x6a/0x90
[   12.969892] Code: 00 48 8d 3d b8 92 71 01 67 48 0f b9 3a e9 2e cd
ad 00 48 8d 3d b7 92 71 01 67 48 0f b9 3a e9 1d cd ad 00 48 8d 3d b6
92 71 01 <67> 48 0f b9 3a e9 0c cd ad 00 48 8d 3d b5 92 71 01 67 48 0f
b9 3a
[   12.975114] RSP: 0018:ffffb4c9c03a7838 EFLAGS: 00010246
[   12.976742] RAX: 0000000000000001 RBX: ffff8c96fdc32688 RCX: 0000000000000000
[   12.979085] RDX: 0000000000000000 RSI: 0000000000000002 RDI: ffffffff8b703940
[   12.981349] RBP: ffff8c96848a1738 R08: 000000000000000c R09: 0000000000000200
[   12.983382] R10: ffff8c96848a1738 R11: 000000000000001f R12: ffff8c96848a3a10
[   12.985415] R13: 0000000000000000 R14: 0000000000000008 R15: ffff8c96848a3a48
[   12.987455] FS:  00007f2995026780(0000) GS:ffff8c9771c6f000(0000)
knlGS:0000000000000000
[   12.989751] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[   12.991296] CR2: 00005593d8eb6640 CR3: 0000000001919000 CR4: 0000000000350ef0
[   12.992879] Call Trace:
[   12.993483]  <TASK>
[   12.993982]  ext4_mb_use_preallocated.constprop.0+0x346/0x3d0
[   12.995293]  ext4_mb_new_blocks+0x15b/0xe80
[   12.996246]  ? srso_return_thunk+0x5/0x5f
[   12.997178]  ? ext4_find_extent+0xc8/0x320
[   12.998117]  ext4_ext_map_blocks+0x3f1/0x17e0
[   12.999121]  ? srso_return_thunk+0x5/0x5f
[   13.000067]  ? mpage_prepare_extent_to_map+0x47d/0x4c0
[   13.001224]  ext4_map_create_blocks+0x40/0x170
[   13.002239]  ext4_map_blocks+0x193/0x3c0
[   13.003142]  ext4_do_writepages+0x7e7/0xf20
[   13.004109]  ? ext4_writepages+0xbb/0x180
[   13.004694]  ext4_writepages+0xbb/0x180
[   13.005275]  do_writepages+0xb6/0x150
[   13.005826]  filemap_writeback+0xa1/0xc0
[   13.006435]  ext4_release_file+0x6c/0xa0
[   13.007041]  __fput+0xe1/0x2b0
[   13.007503]  task_work_run+0x57/0x80
[   13.008046]  exit_to_user_mode_loop+0x139/0x4e0
[   13.008705]  ? srso_return_thunk+0x5/0x5f
[   13.009319]  do_syscall_64+0x42c/0x540
[   13.009878]  ? srso_return_thunk+0x5/0x5f
[   13.010477]  entry_SYSCALL_64_after_hwframe+0x77/0x7f
[   13.011235] RIP: 0033:0x7f2995128887
[   13.011765] Code: 73 01 c3 48 8b 0d 89 55 0e 00 f7 d8 64 89 01 48
83 c8 ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 44 00 00 b8 21 00 00
00 0f 05 <48> 3d 00 f0 ff ff 77 01 c3 48 8b 15 59 55 0e 00 f7 d8 64 89
02 b8
[   13.014433] RSP: 002b:00007ffe0ef0ac48 EFLAGS: 00000246 ORIG_RAX:
0000000000000021
[   13.015542] RAX: 0000000000000001 RBX: 0000000000000001 RCX: 00007f2995128887
[   13.016591] RDX: 0000000000000000 RSI: 0000000000000001 RDI: 000000000000000c
[   13.017635] RBP: 00005593d8ebbef0 R08: 0000000000000000 R09: 0000000000000000
[   13.018685] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000
[   13.019737] R13: 0000000000000001 R14: 0000000000000000 R15: 00005593d8ebced0
[   13.020798]  </TASK>
[   13.021158] ---[ end trace 0000000000000000 ]---
After reading the code a little closer, I realized that pa_count is
intentionally decremented to 0.
This of course would trigger the warning above when using refcount_t
since it treats 0 as
"object is completely dead". I assumed it was because a lock was not
held and so I added the
locks around the atomic_inc(&cpa->pa_count);

The reference count is being dropped to 0 on purpose right?

-Rafael

^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-07-21  0:54 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-20  6:54 [PATCH v2] ext4: fix race in ext4_mb_check_group_pa rafad900
2026-07-20 16:05 ` Andreas Dilger
2026-07-21  0:54   ` Rafael Alejandro Díaz Cruz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox