From 7eacc7139c040c5989911a038c2ac230fd3f39fb Mon Sep 17 00:00:00 2001 From: Auke Kok Date: Thu, 9 Apr 2026 12:27:08 -0700 Subject: [PATCH] Hold transaction in scoutfs_quota_mod_rule to prevent alloc corruption. scoutfs_quota_mod_rule calls scoutfs_item_create/delete which use the transaction allocator but it never held it. Without the hold, a concurrent transaction commit can call scoutfs_alloc_init to reinitialize the allocator while dirty_alloc_blocks is in the middle of setting up the freed list block. This overwrites alloc->freed with the server's fresh (empty) state, causing a blkno mismatch BUG_ON in list_block_add. Reproduced by stressing concurrent quota add/del operations across mounts. Crashdump analysis confirms dirty_list_block COW'd a freed block (fr_old=9842, new blkno=9852) but by the time list_block_add ran, freed.ref.blkno was 0 with first_nr=0 and total_nr=0: the freed list head had been zeroed by a concurrent alloc_init. Fix by adding scoutfs_hold_trans/scoutfs_release_trans around the item modification in scoutfs_quota_mod_rule, preventing transaction commit from racing with the allocator use. Rename the 'unlock' label to 'release' since 'out' now directly does the unlock. The unlock safely handles a NULL lock. Signed-off-by: Auke Kok --- kmod/src/quota.c | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/kmod/src/quota.c b/kmod/src/quota.c index 3163ee8c..fc2cd461 100644 --- a/kmod/src/quota.c +++ b/kmod/src/quota.c @@ -34,6 +34,7 @@ #include "totl.h" #include "util.h" #include "quota.h" +#include "trans.h" #include "counters.h" #include "scoutfs_trace.h" @@ -1086,6 +1087,10 @@ int scoutfs_quota_mod_rule(struct super_block *sb, bool is_add, if (ret < 0) goto out; + ret = scoutfs_hold_trans(sb, true); + if (ret < 0) + goto out; + down_write(&qtinf->rwsem); if (is_add) { @@ -1095,28 +1100,30 @@ int scoutfs_quota_mod_rule(struct super_block *sb, bool is_add, else if (ret == 0) ret = -EEXIST; if (ret < 0) - goto unlock; + goto release; rule_to_rule_val(&rv, &rule); ret = scoutfs_item_create(sb, &key, &rv, sizeof(rv), lock); if (ret < 0) - goto unlock; + goto release; } else { ret = find_rule(sb, &rule, &key, lock) ?: scoutfs_item_delete(sb, &key, lock); if (ret < 0) - goto unlock; + goto release; } scoutfs_quota_invalidate(sb); ret = 0; -unlock: +release: up_write(&qtinf->rwsem); - scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE); + scoutfs_release_trans(sb); out: + scoutfs_unlock(sb, lock, SCOUTFS_LOCK_WRITE); + if (is_add) trace_scoutfs_quota_add_rule(sb, &rule, ret); else