Commit 037d1ad6 authored by Wang Jianjian's avatar Wang Jianjian Committed by Baokun Li
Browse files

quota: Fix potential NULL pointer dereference

stable inclusion
from stable-v4.19.311
commit 8514899c1a4edf802f03c408db901063aa3f05a1
category: bugfix
bugzilla: https://gitee.com/src-openeuler/kernel/issues/I9HJX7
CVE: CVE-2024-26878

Reference: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=8514899c1a4edf802f03c408db901063aa3f05a1



--------------------------------

[ 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: default avatarWang Jianjian <wangjianjian3@huawei.com>
Signed-off-by: default avatarJan 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: default avatarSasha Levin <sashal@kernel.org>
Signed-off-by: default avatarBaokun Li <libaokun1@huawei.com>
parent c259c7c5
Loading
Loading
Loading
Loading
+57 −41
Original line number Diff line number Diff line
@@ -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;
	}
@@ -1671,6 +1673,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) {
@@ -1690,27 +1693,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;
@@ -1741,6 +1743,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;
@@ -1751,17 +1754,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;
		}
@@ -1783,6 +1788,7 @@ EXPORT_SYMBOL(dquot_alloc_inode);
int dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
{
	struct dquot **dquots;
	struct dquot *dquot;
	int cnt, index;

	if (!inode_quota_active(inode)) {
@@ -1798,9 +1804,8 @@ int 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;
@@ -1825,6 +1830,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)) {
@@ -1840,9 +1846,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;
@@ -1869,6 +1874,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)) {
@@ -1889,17 +1895,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;
@@ -1924,6 +1931,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))
@@ -1934,16 +1942,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);
@@ -1970,7 +1978,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];
@@ -2059,8 +2067,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);