fs/ext4/mballoc.c | 22 +++++++++++----------- fs/ext4/mballoc.h | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-)
The pa_count field in struct ext4_prealloc_space is
reference counter for preallocation descriptors.
Converting it from atomic_t to refcount_t provides
underflow and overflow protection.
This was found using Coccinelle script
atomic_as_refcounter.cocci
Testing was done on a QEMU x86 VM revealing a
pre-existing race condition leading to a
use-after-free.
Signed-off-by: rafad900 <rafad900@gmail.com>
---
fs/ext4/mballoc.c | 22 +++++++++++-----------
fs/ext4/mballoc.h | 2 +-
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/fs/ext4/mballoc.c b/fs/ext4/mballoc.c
index ed1bd00e11cd..59b3d4abaab0 100644
--- a/fs/ext4/mballoc.c
+++ b/fs/ext4/mballoc.c
@@ -4823,7 +4823,7 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
ext4_fsblk_t cur_distance, new_distance;
if (cpa == NULL) {
- atomic_inc(&pa->pa_count);
+ refcount_inc(&pa->pa_count);
return pa;
}
cur_distance = abs(goal_block - cpa->pa_pstart);
@@ -4833,8 +4833,8 @@ ext4_mb_check_group_pa(ext4_fsblk_t goal_block,
return cpa;
/* drop the previous reference */
- atomic_dec(&cpa->pa_count);
- atomic_inc(&pa->pa_count);
+ refcount_dec(&cpa->pa_count);
+ refcount_inc(&pa->pa_count);
return pa;
}
@@ -4993,7 +4993,7 @@ ext4_mb_use_preallocated(struct ext4_allocation_context *ac)
}
if (tmp_pa->pa_free && likely(ext4_mb_pa_goal_check(ac, tmp_pa))) {
- atomic_inc(&tmp_pa->pa_count);
+ refcount_inc(&tmp_pa->pa_count);
ext4_mb_use_inode_pa(ac, tmp_pa);
spin_unlock(&tmp_pa->pa_lock);
read_unlock(&ei->i_prealloc_lock);
@@ -5139,7 +5139,7 @@ static void ext4_mb_mark_pa_deleted(struct super_block *sb,
static inline void ext4_mb_pa_free(struct ext4_prealloc_space *pa)
{
BUG_ON(!pa);
- BUG_ON(atomic_read(&pa->pa_count));
+ BUG_ON(refcount_read(&pa->pa_count));
BUG_ON(pa->pa_deleted == 0);
kmem_cache_free(ext4_pspace_cachep, pa);
}
@@ -5165,7 +5165,7 @@ static void ext4_mb_put_pa(struct ext4_allocation_context *ac,
/* in this short window concurrent discard can set pa_deleted */
spin_lock(&pa->pa_lock);
- if (!atomic_dec_and_test(&pa->pa_count) || pa->pa_free != 0) {
+ if (!refcount_dec_and_test(&pa->pa_count) || pa->pa_free != 0) {
spin_unlock(&pa->pa_lock);
return;
}
@@ -5535,7 +5535,7 @@ ext4_mb_discard_group_preallocations(struct super_block *sb,
list_for_each_entry_safe(pa, tmp,
&grp->bb_prealloc_list, pa_group_list) {
spin_lock(&pa->pa_lock);
- if (atomic_read(&pa->pa_count)) {
+ if (refcount_read(&pa->pa_count)) {
spin_unlock(&pa->pa_lock);
*busy = 1;
continue;
@@ -5637,7 +5637,7 @@ void ext4_discard_preallocations(struct inode *inode)
BUG_ON(pa->pa_node_lock.inode_lock != &ei->i_prealloc_lock);
spin_lock(&pa->pa_lock);
- if (atomic_read(&pa->pa_count)) {
+ if (refcount_read(&pa->pa_count)) {
/* this shouldn't happen often - nobody should
* use preallocation while we're discarding it */
spin_unlock(&pa->pa_lock);
@@ -5720,7 +5720,7 @@ static int ext4_mb_pa_alloc(struct ext4_allocation_context *ac)
pa = kmem_cache_zalloc(ext4_pspace_cachep, GFP_NOFS);
if (!pa)
return -ENOMEM;
- atomic_set(&pa->pa_count, 1);
+ refcount_set(&pa->pa_count, 1);
ac->ac_pa = pa;
return 0;
}
@@ -5731,7 +5731,7 @@ static void ext4_mb_pa_put_free(struct ext4_allocation_context *ac)
BUG_ON(!pa);
ac->ac_pa = NULL;
- WARN_ON(!atomic_dec_and_test(&pa->pa_count));
+ WARN_ON(!refcount_dec_and_test(&pa->pa_count));
/*
* current function is only called due to an error or due to
* len of found blocks < len of requested blocks hence the PA has not
@@ -5948,7 +5948,7 @@ ext4_mb_discard_lg_preallocations(struct super_block *sb,
pa_node.lg_list,
lockdep_is_held(&lg->lg_prealloc_lock)) {
spin_lock(&pa->pa_lock);
- if (atomic_read(&pa->pa_count)) {
+ if (refcount_read(&pa->pa_count)) {
/*
* This is the pa that we just used
* for block allocation. So don't
diff --git a/fs/ext4/mballoc.h b/fs/ext4/mballoc.h
index 39333ce72cbd..f708d303228e 100644
--- a/fs/ext4/mballoc.h
+++ b/fs/ext4/mballoc.h
@@ -126,7 +126,7 @@ struct ext4_prealloc_space {
struct rcu_head pa_rcu;
} u;
spinlock_t pa_lock;
- atomic_t pa_count;
+ refcount_t pa_count;
unsigned pa_deleted;
ext4_fsblk_t pa_pstart; /* phys. block */
ext4_lblk_t pa_lstart; /* log. block */
--
2.43.0
syzbot ci has tested the following series [v1] ext4: convert pa_count from atomic_t to refcount_t https://lore.kernel.org/all/20260720034441.3900050-1-rafad900@gmail.com * [PATCH] ext4: convert pa_count from atomic_t to refcount_t and found the following issue: WARNING: refcount bug in ext4_mb_use_preallocated Full report is available here: https://ci.syzbot.org/series/34fda797-e979-409c-bc2d-7af3e163df14 *** WARNING: refcount bug in ext4_mb_use_preallocated tree: torvalds URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/torvalds/linux base: 1590cf0329716306e948a8fc29f1d3ee87d3989f arch: amd64 compiler: Debian clang version 22.1.6 (++20260514074242+fc4aad7b5db3-1~exp1~20260514074407.73), Debian LLD 22.1.6 config: https://ci.syzbot.org/builds/05fa6c1d-a594-4691-ace3-ded3fc2ca4dc/config ------------[ cut here ]------------ refcount_t: addition on 0; use-after-free. WARNING: lib/refcount.c:25 at refcount_warn_saturate+0x9f/0x110, CPU#0: kworker/u9:2/51 Modules linked in: CPU: 0 UID: 0 PID: 51 Comm: kworker/u9:2 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 Workqueue: writeback wb_workfn (flush-8:0) RIP: 0010:refcount_warn_saturate+0x9f/0x110 Code: eb 66 85 db 74 3e 83 fb 01 75 4c e8 8b 73 0c fd 48 8d 3d 34 58 84 0b 67 48 0f b9 3a eb 4a e8 78 73 0c fd 48 8d 3d 31 58 84 0b <67> 48 0f b9 3a eb 37 e8 65 73 0c fd 48 8d 3d 2e 58 84 0b 67 48 0f RSP: 0018:ffffc90000bb6590 EFLAGS: 00010293 RAX: ffffffff84b9ee08 RBX: 0000000000000002 RCX: ffff8881072a8000 RDX: 0000000000000000 RSI: ffffffff8f126800 RDI: ffffffff903e4640 RBP: ffff888112257dd0 R08: ffff8881072a8000 R09: 0000000000000005 R10: 0000000000000004 R11: 0000000000000000 R12: dffffc0000000000 R13: ffff888112257d98 R14: ffff888112257e10 R15: 0000000000000008 FS: 0000000000000000(0000) GS:ffff88818dc15000(0000) knlGS:0000000000000000 CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033 CR2: 00007fc831b2c080 CR3: 0000000112724000 CR4: 00000000000006f0 Call Trace: <TASK> ext4_mb_use_preallocated+0xaa0/0x14e0 ext4_mb_new_blocks+0x5a0/0x4560 ext4_ext_map_blocks+0x1534/0x5850 ext4_map_create_blocks+0x116/0x530 ext4_map_blocks+0x7e4/0x1240 ext4_do_writepages+0x23d3/0x47a0 ext4_writepages+0x241/0x3b0 do_writepages+0x338/0x560 __writeback_single_inode+0x12e/0xf90 writeback_sb_inodes+0x9de/0x1b00 __writeback_inodes_wb+0x114/0x240 wb_writeback+0x42f/0xad0 wb_workfn+0x980/0x10f0 process_scheduled_works+0xa8e/0x14e0 worker_thread+0xa47/0xfb0 kthread+0x388/0x470 ret_from_fork+0x514/0xb70 ret_from_fork_asm+0x1a/0x30 </TASK> *** If these findings have caused you to resend the series or submit a separate fix, please add the following tag to your commit message: Tested-by: syzbot@syzkaller.appspotmail.com --- This report is generated by a bot. It may contain errors. syzbot ci engineers can be reached at syzkaller@googlegroups.com. To test a patch for this bug, please reply with `#syz test` (should be on a separate line). The patch should be attached to the email. Note: arguments like custom git repos and branches are not supported.
© 2016 - 2026 Red Hat, Inc.