| From df4bb5d128e2c44848aeb36b7ceceba3ac85080d Mon Sep 17 00:00:00 2001 |
| From: Dmitry Monakhov <dmtrmonakhov@yandex-team.ru> |
| Date: Thu, 31 Oct 2019 10:39:20 +0000 |
| Subject: quota: Check that quota is not dirty before release |
| |
| From: Dmitry Monakhov <dmtrmonakhov@yandex-team.ru> |
| |
| commit df4bb5d128e2c44848aeb36b7ceceba3ac85080d upstream. |
| |
| There is a race window where quota was redirted once we drop dq_list_lock inside dqput(), |
| but before we grab dquot->dq_lock inside dquot_release() |
| |
| TASK1 TASK2 (chowner) |
| ->dqput() |
| we_slept: |
| spin_lock(&dq_list_lock) |
| if (dquot_dirty(dquot)) { |
| spin_unlock(&dq_list_lock); |
| dquot->dq_sb->dq_op->write_dquot(dquot); |
| goto we_slept |
| if (test_bit(DQ_ACTIVE_B, &dquot->dq_flags)) { |
| spin_unlock(&dq_list_lock); |
| dquot->dq_sb->dq_op->release_dquot(dquot); |
| dqget() |
| mark_dquot_dirty() |
| dqput() |
| goto we_slept; |
| } |
| So dquot dirty quota will be released by TASK1, but on next we_sleept loop |
| we detect this and call ->write_dquot() for it. |
| XFSTEST: https://github.com/dmonakhov/xfstests/commit/440a80d4cbb39e9234df4d7240aee1d551c36107 |
| |
| Link: https://lore.kernel.org/r/[email protected] |
| CC: stable@vger.kernel.org |
| Signed-off-by: Dmitry Monakhov <dmtrmonakhov@yandex-team.ru> |
| Signed-off-by: Jan Kara <jack@suse.cz> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| |
| --- |
| fs/ocfs2/quota_global.c | 2 +- |
| fs/quota/dquot.c | 2 +- |
| include/linux/quotaops.h | 10 ++++++++++ |
| 3 files changed, 12 insertions(+), 2 deletions(-) |
| |
| --- a/fs/ocfs2/quota_global.c |
| +++ b/fs/ocfs2/quota_global.c |
| @@ -714,7 +714,7 @@ static int ocfs2_release_dquot(struct dq |
| |
| mutex_lock(&dquot->dq_lock); |
| /* Check whether we are not racing with some other dqget() */ |
| - if (atomic_read(&dquot->dq_count) > 1) |
| + if (dquot_is_busy(dquot)) |
| goto out; |
| /* Running from downconvert thread? Postpone quota processing to wq */ |
| if (current == osb->dc_task) { |
| --- a/fs/quota/dquot.c |
| +++ b/fs/quota/dquot.c |
| @@ -472,7 +472,7 @@ int dquot_release(struct dquot *dquot) |
| |
| mutex_lock(&dquot->dq_lock); |
| /* Check whether we are not racing with some other dqget() */ |
| - if (atomic_read(&dquot->dq_count) > 1) |
| + if (dquot_is_busy(dquot)) |
| goto out_dqlock; |
| mutex_lock(&dqopt->dqio_mutex); |
| if (dqopt->ops[dquot->dq_id.type]->release_dqblk) { |
| --- a/include/linux/quotaops.h |
| +++ b/include/linux/quotaops.h |
| @@ -54,6 +54,16 @@ static inline struct dquot *dqgrab(struc |
| atomic_inc(&dquot->dq_count); |
| return dquot; |
| } |
| + |
| +static inline bool dquot_is_busy(struct dquot *dquot) |
| +{ |
| + if (test_bit(DQ_MOD_B, &dquot->dq_flags)) |
| + return true; |
| + if (atomic_read(&dquot->dq_count) > 1) |
| + return true; |
| + return false; |
| +} |
| + |
| void dqput(struct dquot *dquot); |
| int dquot_scan_active(struct super_block *sb, |
| int (*fn)(struct dquot *dquot, unsigned long priv), |