Skip to content

Commit

Permalink
quota: Fix potential NULL pointer dereference
Browse files Browse the repository at this point in the history
[ Upstream commit d0aa72604fbd80c8aabb46eda00535ed35570f1f ]

Below race may cause NULL pointer dereference

P1					P2
dquot_free_inode			quota_off
					  drop_dquot_ref
					   remove_dquot_ref
					   dquots = i_dquot(inode)
  dquots = i_dquot(inode)
  srcu_read_lock
  dquots[cnt]) != NULL (1)
					     dquots[type] = NULL (2)
  spin_lock(&dquots[cnt]->dq_dqb_lock) (3)
   ....

If dquot_free_inode(or other routines) checks inode's quota pointers (1)
before quota_off sets it to NULL(2) and use it (3) after that, NULL pointer
dereference will be triggered.

So let's fix it by using a temporary pointer to avoid this issue.

Signed-off-by: Wang Jianjian <wangjianjian3@huawei.com>
Signed-off-by: Jan Kara <jack@suse.cz>
Message-Id: <20240202081852.2514092-1-wangjianjian3@huawei.com>
Stable-dep-of: 179b8c97ebf6 ("quota: Fix rcu annotations of inode dquot pointers")
Signed-off-by: Sasha Levin <sashal@kernel.org>
  • Loading branch information
Wang Jianjian authored and Sasha Levin committed Mar 26, 2024
1 parent 29635d1 commit 6afc9f4
Showing 1 changed file with 57 additions and 41 deletions.
98 changes: 57 additions & 41 deletions fs/quota/dquot.c
Expand Up @@ -399,15 +399,17 @@ int dquot_mark_dquot_dirty(struct dquot *dquot)
EXPORT_SYMBOL(dquot_mark_dquot_dirty);

/* Dirtify all the dquots - this can block when journalling */
static inline int mark_all_dquot_dirty(struct dquot * const *dquot)
static inline int mark_all_dquot_dirty(struct dquot * const *dquots)
{
int ret, err, cnt;
struct dquot *dquot;

ret = err = 0;
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (dquot[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (dquot)
/* Even in case of error we have to continue */
ret = mark_dquot_dirty(dquot[cnt]);
ret = mark_dquot_dirty(dquot);
if (!err)
err = ret;
}
Expand Down Expand Up @@ -1678,6 +1680,7 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
struct dquot_warn warn[MAXQUOTAS];
int reserve = flags & DQUOT_SPACE_RESERVE;
struct dquot **dquots;
struct dquot *dquot;

if (!inode_quota_active(inode)) {
if (reserve) {
Expand All @@ -1697,27 +1700,26 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
index = srcu_read_lock(&dquot_srcu);
spin_lock(&inode->i_lock);
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
if (reserve) {
ret = dquot_add_space(dquots[cnt], 0, number, flags,
&warn[cnt]);
ret = dquot_add_space(dquot, 0, number, flags, &warn[cnt]);
} else {
ret = dquot_add_space(dquots[cnt], number, 0, flags,
&warn[cnt]);
ret = dquot_add_space(dquot, number, 0, flags, &warn[cnt]);
}
if (ret) {
/* Back out changes we already did */
for (cnt--; cnt >= 0; cnt--) {
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
spin_lock(&dquots[cnt]->dq_dqb_lock);
spin_lock(&dquot->dq_dqb_lock);
if (reserve)
dquot_free_reserved_space(dquots[cnt],
number);
dquot_free_reserved_space(dquot, number);
else
dquot_decr_space(dquots[cnt], number);
spin_unlock(&dquots[cnt]->dq_dqb_lock);
dquot_decr_space(dquot, number);
spin_unlock(&dquot->dq_dqb_lock);
}
spin_unlock(&inode->i_lock);
goto out_flush_warn;
Expand Down Expand Up @@ -1748,6 +1750,7 @@ int dquot_alloc_inode(struct inode *inode)
int cnt, ret = 0, index;
struct dquot_warn warn[MAXQUOTAS];
struct dquot * const *dquots;
struct dquot *dquot;

if (!inode_quota_active(inode))
return 0;
Expand All @@ -1758,17 +1761,19 @@ int dquot_alloc_inode(struct inode *inode)
index = srcu_read_lock(&dquot_srcu);
spin_lock(&inode->i_lock);
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
ret = dquot_add_inodes(dquots[cnt], 1, &warn[cnt]);
ret = dquot_add_inodes(dquot, 1, &warn[cnt]);
if (ret) {
for (cnt--; cnt >= 0; cnt--) {
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
/* Back out changes we already did */
spin_lock(&dquots[cnt]->dq_dqb_lock);
dquot_decr_inodes(dquots[cnt], 1);
spin_unlock(&dquots[cnt]->dq_dqb_lock);
spin_lock(&dquot->dq_dqb_lock);
dquot_decr_inodes(dquot, 1);
spin_unlock(&dquot->dq_dqb_lock);
}
goto warn_put_all;
}
Expand All @@ -1790,6 +1795,7 @@ EXPORT_SYMBOL(dquot_alloc_inode);
void dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
{
struct dquot **dquots;
struct dquot *dquot;
int cnt, index;

if (!inode_quota_active(inode)) {
Expand All @@ -1805,9 +1811,8 @@ void dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
spin_lock(&inode->i_lock);
/* Claim reserved quotas to allocated quotas */
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (dquots[cnt]) {
struct dquot *dquot = dquots[cnt];

dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (dquot) {
spin_lock(&dquot->dq_dqb_lock);
if (WARN_ON_ONCE(dquot->dq_dqb.dqb_rsvspace < number))
number = dquot->dq_dqb.dqb_rsvspace;
Expand All @@ -1832,6 +1837,7 @@ EXPORT_SYMBOL(dquot_claim_space_nodirty);
void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number)
{
struct dquot **dquots;
struct dquot *dquot;
int cnt, index;

if (!inode_quota_active(inode)) {
Expand All @@ -1847,9 +1853,8 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number)
spin_lock(&inode->i_lock);
/* Claim reserved quotas to allocated quotas */
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
if (dquots[cnt]) {
struct dquot *dquot = dquots[cnt];

dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (dquot) {
spin_lock(&dquot->dq_dqb_lock);
if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number))
number = dquot->dq_dqb.dqb_curspace;
Expand All @@ -1876,6 +1881,7 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
unsigned int cnt;
struct dquot_warn warn[MAXQUOTAS];
struct dquot **dquots;
struct dquot *dquot;
int reserve = flags & DQUOT_SPACE_RESERVE, index;

if (!inode_quota_active(inode)) {
Expand All @@ -1896,17 +1902,18 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
int wtype;

warn[cnt].w_type = QUOTA_NL_NOWARN;
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
spin_lock(&dquots[cnt]->dq_dqb_lock);
wtype = info_bdq_free(dquots[cnt], number);
spin_lock(&dquot->dq_dqb_lock);
wtype = info_bdq_free(dquot, number);
if (wtype != QUOTA_NL_NOWARN)
prepare_warning(&warn[cnt], dquots[cnt], wtype);
prepare_warning(&warn[cnt], dquot, wtype);
if (reserve)
dquot_free_reserved_space(dquots[cnt], number);
dquot_free_reserved_space(dquot, number);
else
dquot_decr_space(dquots[cnt], number);
spin_unlock(&dquots[cnt]->dq_dqb_lock);
dquot_decr_space(dquot, number);
spin_unlock(&dquot->dq_dqb_lock);
}
if (reserve)
*inode_reserved_space(inode) -= number;
Expand All @@ -1931,6 +1938,7 @@ void dquot_free_inode(struct inode *inode)
unsigned int cnt;
struct dquot_warn warn[MAXQUOTAS];
struct dquot * const *dquots;
struct dquot *dquot;
int index;

if (!inode_quota_active(inode))
Expand All @@ -1941,16 +1949,16 @@ void dquot_free_inode(struct inode *inode)
spin_lock(&inode->i_lock);
for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
int wtype;

warn[cnt].w_type = QUOTA_NL_NOWARN;
if (!dquots[cnt])
dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
if (!dquot)
continue;
spin_lock(&dquots[cnt]->dq_dqb_lock);
wtype = info_idq_free(dquots[cnt], 1);
spin_lock(&dquot->dq_dqb_lock);
wtype = info_idq_free(dquot, 1);
if (wtype != QUOTA_NL_NOWARN)
prepare_warning(&warn[cnt], dquots[cnt], wtype);
dquot_decr_inodes(dquots[cnt], 1);
spin_unlock(&dquots[cnt]->dq_dqb_lock);
prepare_warning(&warn[cnt], dquot, wtype);
dquot_decr_inodes(dquot, 1);
spin_unlock(&dquot->dq_dqb_lock);
}
spin_unlock(&inode->i_lock);
mark_all_dquot_dirty(dquots);
Expand All @@ -1977,7 +1985,7 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
qsize_t rsv_space = 0;
qsize_t inode_usage = 1;
struct dquot *transfer_from[MAXQUOTAS] = {};
int cnt, ret = 0;
int cnt, index, ret = 0;
char is_valid[MAXQUOTAS] = {};
struct dquot_warn warn_to[MAXQUOTAS];
struct dquot_warn warn_from_inodes[MAXQUOTAS];
Expand Down Expand Up @@ -2066,8 +2074,16 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
spin_unlock(&inode->i_lock);
spin_unlock(&dq_data_lock);

/*
* These arrays are local and we hold dquot references so we don't need
* the srcu protection but still take dquot_srcu to avoid warning in
* mark_all_dquot_dirty().
*/
index = srcu_read_lock(&dquot_srcu);
mark_all_dquot_dirty(transfer_from);
mark_all_dquot_dirty(transfer_to);
srcu_read_unlock(&dquot_srcu, index);

flush_warnings(warn_to);
flush_warnings(warn_from_inodes);
flush_warnings(warn_from_space);
Expand Down

0 comments on commit 6afc9f4

Please sign in to comment.