Skip to content

Commit e93ab40

Browse files
committed
quota: Fix race of dquot_scan_active() with quota deactivation
dquot_scan_active() can race with quota deactivation in quota_release_workfn() like: CPU0 (quota_release_workfn) CPU1 (dquot_scan_active) ============================== ============================== spin_lock(&dq_list_lock); list_replace_init( &releasing_dquots, &rls_head); /* dquot X on rls_head, dq_count == 0, DQ_ACTIVE_B still set */ spin_unlock(&dq_list_lock); synchronize_srcu(&dquot_srcu); spin_lock(&dq_list_lock); list_for_each_entry(dquot, &inuse_list, dq_inuse) { /* finds dquot X */ dquot_active(X) -> true atomic_inc(&X->dq_count); } spin_unlock(&dq_list_lock); spin_lock(&dq_list_lock); dquot = list_first_entry(&rls_head); WARN_ON_ONCE(atomic_read(&dquot->dq_count)); The problem is not only a cosmetic one as under memory pressure the caller of dquot_scan_active() can end up working on freed dquot. Fix the problem by making sure the dquot is removed from releasing list when we acquire a reference to it. Fixes: 869b6ea ("quota: Fix slow quotaoff") Reported-by: Sam Sun <[email protected]> Link: https://lore.kernel.org/all/CAEkJfYPTt3uP1vAYnQ5V2ZWn5O9PLhhGi5HbOcAzyP9vbXyjeg@mail.gmail.com Signed-off-by: Jan Kara <[email protected]>
1 parent 08841b0 commit e93ab40

2 files changed

Lines changed: 31 additions & 16 deletions

File tree

fs/quota/dquot.c

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,31 @@ static inline int dquot_active(struct dquot *dquot)
363363
return test_bit(DQ_ACTIVE_B, &dquot->dq_flags);
364364
}
365365

366+
static struct dquot *__dqgrab(struct dquot *dquot)
367+
{
368+
lockdep_assert_held(&dq_list_lock);
369+
if (!atomic_read(&dquot->dq_count))
370+
remove_free_dquot(dquot);
371+
atomic_inc(&dquot->dq_count);
372+
return dquot;
373+
}
374+
375+
/*
376+
* Get reference to dquot when we got pointer to it by some other means. The
377+
* dquot has to be active and the caller has to make sure it cannot get
378+
* deactivated under our hands.
379+
*/
380+
struct dquot *dqgrab(struct dquot *dquot)
381+
{
382+
spin_lock(&dq_list_lock);
383+
WARN_ON_ONCE(!dquot_active(dquot));
384+
dquot = __dqgrab(dquot);
385+
spin_unlock(&dq_list_lock);
386+
387+
return dquot;
388+
}
389+
EXPORT_SYMBOL_GPL(dqgrab);
390+
366391
static inline int dquot_dirty(struct dquot *dquot)
367392
{
368393
return test_bit(DQ_MOD_B, &dquot->dq_flags);
@@ -641,15 +666,14 @@ int dquot_scan_active(struct super_block *sb,
641666
continue;
642667
if (dquot->dq_sb != sb)
643668
continue;
644-
/* Now we have active dquot so we can just increase use count */
645-
atomic_inc(&dquot->dq_count);
669+
__dqgrab(dquot);
646670
spin_unlock(&dq_list_lock);
647671
dqput(old_dquot);
648672
old_dquot = dquot;
649673
/*
650674
* ->release_dquot() can be racing with us. Our reference
651-
* protects us from new calls to it so just wait for any
652-
* outstanding call and recheck the DQ_ACTIVE_B after that.
675+
* protects us from dquot_release() proceeding so just wait for
676+
* any outstanding call and recheck the DQ_ACTIVE_B after that.
653677
*/
654678
wait_on_dquot(dquot);
655679
if (dquot_active(dquot)) {
@@ -717,7 +741,7 @@ int dquot_writeback_dquots(struct super_block *sb, int type)
717741
/* Now we have active dquot from which someone is
718742
* holding reference so we can safely just increase
719743
* use count */
720-
dqgrab(dquot);
744+
__dqgrab(dquot);
721745
spin_unlock(&dq_list_lock);
722746
err = dquot_write_dquot(dquot);
723747
if (err && !ret)
@@ -963,9 +987,7 @@ struct dquot *dqget(struct super_block *sb, struct kqid qid)
963987
spin_unlock(&dq_list_lock);
964988
dqstats_inc(DQST_LOOKUPS);
965989
} else {
966-
if (!atomic_read(&dquot->dq_count))
967-
remove_free_dquot(dquot);
968-
atomic_inc(&dquot->dq_count);
990+
__dqgrab(dquot);
969991
spin_unlock(&dq_list_lock);
970992
dqstats_inc(DQST_CACHE_HITS);
971993
dqstats_inc(DQST_LOOKUPS);

include/linux/quotaops.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -44,14 +44,7 @@ int dquot_initialize(struct inode *inode);
4444
bool dquot_initialize_needed(struct inode *inode);
4545
void dquot_drop(struct inode *inode);
4646
struct dquot *dqget(struct super_block *sb, struct kqid qid);
47-
static inline struct dquot *dqgrab(struct dquot *dquot)
48-
{
49-
/* Make sure someone else has active reference to dquot */
50-
WARN_ON_ONCE(!atomic_read(&dquot->dq_count));
51-
WARN_ON_ONCE(!test_bit(DQ_ACTIVE_B, &dquot->dq_flags));
52-
atomic_inc(&dquot->dq_count);
53-
return dquot;
54-
}
47+
struct dquot *dqgrab(struct dquot *dquot);
5548

5649
static inline bool dquot_is_busy(struct dquot *dquot)
5750
{

0 commit comments

Comments
 (0)