mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	quota: Fix potential NULL pointer dereference
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>
This commit is contained in:
		
							parent
							
								
									a1e1b2beca
								
							
						
					
					
						commit
						d0aa72604f
					
				
					 1 changed files with 57 additions and 41 deletions
				
			
		| 
						 | 
					@ -399,15 +399,17 @@ int dquot_mark_dquot_dirty(struct dquot *dquot)
 | 
				
			||||||
EXPORT_SYMBOL(dquot_mark_dquot_dirty);
 | 
					EXPORT_SYMBOL(dquot_mark_dquot_dirty);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Dirtify all the dquots - this can block when journalling */
 | 
					/* 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;
 | 
						int ret, err, cnt;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ret = err = 0;
 | 
						ret = err = 0;
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						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 */
 | 
								/* Even in case of error we have to continue */
 | 
				
			||||||
			ret = mark_dquot_dirty(dquot[cnt]);
 | 
								ret = mark_dquot_dirty(dquot);
 | 
				
			||||||
		if (!err)
 | 
							if (!err)
 | 
				
			||||||
			err = ret;
 | 
								err = ret;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -1674,6 +1676,7 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
 | 
				
			||||||
	struct dquot_warn warn[MAXQUOTAS];
 | 
						struct dquot_warn warn[MAXQUOTAS];
 | 
				
			||||||
	int reserve = flags & DQUOT_SPACE_RESERVE;
 | 
						int reserve = flags & DQUOT_SPACE_RESERVE;
 | 
				
			||||||
	struct dquot **dquots;
 | 
						struct dquot **dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode)) {
 | 
						if (!inode_quota_active(inode)) {
 | 
				
			||||||
		if (reserve) {
 | 
							if (reserve) {
 | 
				
			||||||
| 
						 | 
					@ -1693,27 +1696,26 @@ int __dquot_alloc_space(struct inode *inode, qsize_t number, int flags)
 | 
				
			||||||
	index = srcu_read_lock(&dquot_srcu);
 | 
						index = srcu_read_lock(&dquot_srcu);
 | 
				
			||||||
	spin_lock(&inode->i_lock);
 | 
						spin_lock(&inode->i_lock);
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
				
			||||||
		if (!dquots[cnt])
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
							if (!dquot)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		if (reserve) {
 | 
							if (reserve) {
 | 
				
			||||||
			ret = dquot_add_space(dquots[cnt], 0, number, flags,
 | 
								ret = dquot_add_space(dquot, 0, number, flags, &warn[cnt]);
 | 
				
			||||||
					      &warn[cnt]);
 | 
					 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ret = dquot_add_space(dquots[cnt], number, 0, flags,
 | 
								ret = dquot_add_space(dquot, number, 0, flags, &warn[cnt]);
 | 
				
			||||||
					      &warn[cnt]);
 | 
					 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (ret) {
 | 
							if (ret) {
 | 
				
			||||||
			/* Back out changes we already did */
 | 
								/* Back out changes we already did */
 | 
				
			||||||
			for (cnt--; cnt >= 0; cnt--) {
 | 
								for (cnt--; cnt >= 0; cnt--) {
 | 
				
			||||||
				if (!dquots[cnt])
 | 
									dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
									if (!dquot)
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
				spin_lock(&dquots[cnt]->dq_dqb_lock);
 | 
									spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
				if (reserve)
 | 
									if (reserve)
 | 
				
			||||||
					dquot_free_reserved_space(dquots[cnt],
 | 
										dquot_free_reserved_space(dquot, number);
 | 
				
			||||||
								  number);
 | 
					 | 
				
			||||||
				else
 | 
									else
 | 
				
			||||||
					dquot_decr_space(dquots[cnt], number);
 | 
										dquot_decr_space(dquot, number);
 | 
				
			||||||
				spin_unlock(&dquots[cnt]->dq_dqb_lock);
 | 
									spin_unlock(&dquot->dq_dqb_lock);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			spin_unlock(&inode->i_lock);
 | 
								spin_unlock(&inode->i_lock);
 | 
				
			||||||
			goto out_flush_warn;
 | 
								goto out_flush_warn;
 | 
				
			||||||
| 
						 | 
					@ -1744,6 +1746,7 @@ int dquot_alloc_inode(struct inode *inode)
 | 
				
			||||||
	int cnt, ret = 0, index;
 | 
						int cnt, ret = 0, index;
 | 
				
			||||||
	struct dquot_warn warn[MAXQUOTAS];
 | 
						struct dquot_warn warn[MAXQUOTAS];
 | 
				
			||||||
	struct dquot * const *dquots;
 | 
						struct dquot * const *dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode))
 | 
						if (!inode_quota_active(inode))
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
| 
						 | 
					@ -1754,17 +1757,19 @@ int dquot_alloc_inode(struct inode *inode)
 | 
				
			||||||
	index = srcu_read_lock(&dquot_srcu);
 | 
						index = srcu_read_lock(&dquot_srcu);
 | 
				
			||||||
	spin_lock(&inode->i_lock);
 | 
						spin_lock(&inode->i_lock);
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
				
			||||||
		if (!dquots[cnt])
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
							if (!dquot)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		ret = dquot_add_inodes(dquots[cnt], 1, &warn[cnt]);
 | 
							ret = dquot_add_inodes(dquot, 1, &warn[cnt]);
 | 
				
			||||||
		if (ret) {
 | 
							if (ret) {
 | 
				
			||||||
			for (cnt--; cnt >= 0; cnt--) {
 | 
								for (cnt--; cnt >= 0; cnt--) {
 | 
				
			||||||
				if (!dquots[cnt])
 | 
									dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
									if (!dquot)
 | 
				
			||||||
					continue;
 | 
										continue;
 | 
				
			||||||
				/* Back out changes we already did */
 | 
									/* Back out changes we already did */
 | 
				
			||||||
				spin_lock(&dquots[cnt]->dq_dqb_lock);
 | 
									spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
				dquot_decr_inodes(dquots[cnt], 1);
 | 
									dquot_decr_inodes(dquot, 1);
 | 
				
			||||||
				spin_unlock(&dquots[cnt]->dq_dqb_lock);
 | 
									spin_unlock(&dquot->dq_dqb_lock);
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			goto warn_put_all;
 | 
								goto warn_put_all;
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
| 
						 | 
					@ -1786,6 +1791,7 @@ EXPORT_SYMBOL(dquot_alloc_inode);
 | 
				
			||||||
void dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
 | 
					void dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct dquot **dquots;
 | 
						struct dquot **dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
	int cnt, index;
 | 
						int cnt, index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode)) {
 | 
						if (!inode_quota_active(inode)) {
 | 
				
			||||||
| 
						 | 
					@ -1801,9 +1807,8 @@ void dquot_claim_space_nodirty(struct inode *inode, qsize_t number)
 | 
				
			||||||
	spin_lock(&inode->i_lock);
 | 
						spin_lock(&inode->i_lock);
 | 
				
			||||||
	/* Claim reserved quotas to allocated quotas */
 | 
						/* Claim reserved quotas to allocated quotas */
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
				
			||||||
		if (dquots[cnt]) {
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
			struct dquot *dquot = dquots[cnt];
 | 
							if (dquot) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			spin_lock(&dquot->dq_dqb_lock);
 | 
								spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
			if (WARN_ON_ONCE(dquot->dq_dqb.dqb_rsvspace < number))
 | 
								if (WARN_ON_ONCE(dquot->dq_dqb.dqb_rsvspace < number))
 | 
				
			||||||
				number = dquot->dq_dqb.dqb_rsvspace;
 | 
									number = dquot->dq_dqb.dqb_rsvspace;
 | 
				
			||||||
| 
						 | 
					@ -1828,6 +1833,7 @@ EXPORT_SYMBOL(dquot_claim_space_nodirty);
 | 
				
			||||||
void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number)
 | 
					void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	struct dquot **dquots;
 | 
						struct dquot **dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
	int cnt, index;
 | 
						int cnt, index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode)) {
 | 
						if (!inode_quota_active(inode)) {
 | 
				
			||||||
| 
						 | 
					@ -1843,9 +1849,8 @@ void dquot_reclaim_space_nodirty(struct inode *inode, qsize_t number)
 | 
				
			||||||
	spin_lock(&inode->i_lock);
 | 
						spin_lock(&inode->i_lock);
 | 
				
			||||||
	/* Claim reserved quotas to allocated quotas */
 | 
						/* Claim reserved quotas to allocated quotas */
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
				
			||||||
		if (dquots[cnt]) {
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
			struct dquot *dquot = dquots[cnt];
 | 
							if (dquot) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
			spin_lock(&dquot->dq_dqb_lock);
 | 
								spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
			if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number))
 | 
								if (WARN_ON_ONCE(dquot->dq_dqb.dqb_curspace < number))
 | 
				
			||||||
				number = dquot->dq_dqb.dqb_curspace;
 | 
									number = dquot->dq_dqb.dqb_curspace;
 | 
				
			||||||
| 
						 | 
					@ -1872,6 +1877,7 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
 | 
				
			||||||
	unsigned int cnt;
 | 
						unsigned int cnt;
 | 
				
			||||||
	struct dquot_warn warn[MAXQUOTAS];
 | 
						struct dquot_warn warn[MAXQUOTAS];
 | 
				
			||||||
	struct dquot **dquots;
 | 
						struct dquot **dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
	int reserve = flags & DQUOT_SPACE_RESERVE, index;
 | 
						int reserve = flags & DQUOT_SPACE_RESERVE, index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode)) {
 | 
						if (!inode_quota_active(inode)) {
 | 
				
			||||||
| 
						 | 
					@ -1892,17 +1898,18 @@ void __dquot_free_space(struct inode *inode, qsize_t number, int flags)
 | 
				
			||||||
		int wtype;
 | 
							int wtype;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		warn[cnt].w_type = QUOTA_NL_NOWARN;
 | 
							warn[cnt].w_type = QUOTA_NL_NOWARN;
 | 
				
			||||||
		if (!dquots[cnt])
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
							if (!dquot)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		spin_lock(&dquots[cnt]->dq_dqb_lock);
 | 
							spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
		wtype = info_bdq_free(dquots[cnt], number);
 | 
							wtype = info_bdq_free(dquot, number);
 | 
				
			||||||
		if (wtype != QUOTA_NL_NOWARN)
 | 
							if (wtype != QUOTA_NL_NOWARN)
 | 
				
			||||||
			prepare_warning(&warn[cnt], dquots[cnt], wtype);
 | 
								prepare_warning(&warn[cnt], dquot, wtype);
 | 
				
			||||||
		if (reserve)
 | 
							if (reserve)
 | 
				
			||||||
			dquot_free_reserved_space(dquots[cnt], number);
 | 
								dquot_free_reserved_space(dquot, number);
 | 
				
			||||||
		else
 | 
							else
 | 
				
			||||||
			dquot_decr_space(dquots[cnt], number);
 | 
								dquot_decr_space(dquot, number);
 | 
				
			||||||
		spin_unlock(&dquots[cnt]->dq_dqb_lock);
 | 
							spin_unlock(&dquot->dq_dqb_lock);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if (reserve)
 | 
						if (reserve)
 | 
				
			||||||
		*inode_reserved_space(inode) -= number;
 | 
							*inode_reserved_space(inode) -= number;
 | 
				
			||||||
| 
						 | 
					@ -1927,6 +1934,7 @@ void dquot_free_inode(struct inode *inode)
 | 
				
			||||||
	unsigned int cnt;
 | 
						unsigned int cnt;
 | 
				
			||||||
	struct dquot_warn warn[MAXQUOTAS];
 | 
						struct dquot_warn warn[MAXQUOTAS];
 | 
				
			||||||
	struct dquot * const *dquots;
 | 
						struct dquot * const *dquots;
 | 
				
			||||||
 | 
						struct dquot *dquot;
 | 
				
			||||||
	int index;
 | 
						int index;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (!inode_quota_active(inode))
 | 
						if (!inode_quota_active(inode))
 | 
				
			||||||
| 
						 | 
					@ -1937,16 +1945,16 @@ void dquot_free_inode(struct inode *inode)
 | 
				
			||||||
	spin_lock(&inode->i_lock);
 | 
						spin_lock(&inode->i_lock);
 | 
				
			||||||
	for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
						for (cnt = 0; cnt < MAXQUOTAS; cnt++) {
 | 
				
			||||||
		int wtype;
 | 
							int wtype;
 | 
				
			||||||
 | 
					 | 
				
			||||||
		warn[cnt].w_type = QUOTA_NL_NOWARN;
 | 
							warn[cnt].w_type = QUOTA_NL_NOWARN;
 | 
				
			||||||
		if (!dquots[cnt])
 | 
							dquot = srcu_dereference(dquots[cnt], &dquot_srcu);
 | 
				
			||||||
 | 
							if (!dquot)
 | 
				
			||||||
			continue;
 | 
								continue;
 | 
				
			||||||
		spin_lock(&dquots[cnt]->dq_dqb_lock);
 | 
							spin_lock(&dquot->dq_dqb_lock);
 | 
				
			||||||
		wtype = info_idq_free(dquots[cnt], 1);
 | 
							wtype = info_idq_free(dquot, 1);
 | 
				
			||||||
		if (wtype != QUOTA_NL_NOWARN)
 | 
							if (wtype != QUOTA_NL_NOWARN)
 | 
				
			||||||
			prepare_warning(&warn[cnt], dquots[cnt], wtype);
 | 
								prepare_warning(&warn[cnt], dquot, wtype);
 | 
				
			||||||
		dquot_decr_inodes(dquots[cnt], 1);
 | 
							dquot_decr_inodes(dquot, 1);
 | 
				
			||||||
		spin_unlock(&dquots[cnt]->dq_dqb_lock);
 | 
							spin_unlock(&dquot->dq_dqb_lock);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	spin_unlock(&inode->i_lock);
 | 
						spin_unlock(&inode->i_lock);
 | 
				
			||||||
	mark_all_dquot_dirty(dquots);
 | 
						mark_all_dquot_dirty(dquots);
 | 
				
			||||||
| 
						 | 
					@ -1973,7 +1981,7 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 | 
				
			||||||
	qsize_t rsv_space = 0;
 | 
						qsize_t rsv_space = 0;
 | 
				
			||||||
	qsize_t inode_usage = 1;
 | 
						qsize_t inode_usage = 1;
 | 
				
			||||||
	struct dquot *transfer_from[MAXQUOTAS] = {};
 | 
						struct dquot *transfer_from[MAXQUOTAS] = {};
 | 
				
			||||||
	int cnt, ret = 0;
 | 
						int cnt, index, ret = 0;
 | 
				
			||||||
	char is_valid[MAXQUOTAS] = {};
 | 
						char is_valid[MAXQUOTAS] = {};
 | 
				
			||||||
	struct dquot_warn warn_to[MAXQUOTAS];
 | 
						struct dquot_warn warn_to[MAXQUOTAS];
 | 
				
			||||||
	struct dquot_warn warn_from_inodes[MAXQUOTAS];
 | 
						struct dquot_warn warn_from_inodes[MAXQUOTAS];
 | 
				
			||||||
| 
						 | 
					@ -2062,8 +2070,16 @@ int __dquot_transfer(struct inode *inode, struct dquot **transfer_to)
 | 
				
			||||||
	spin_unlock(&inode->i_lock);
 | 
						spin_unlock(&inode->i_lock);
 | 
				
			||||||
	spin_unlock(&dq_data_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_from);
 | 
				
			||||||
	mark_all_dquot_dirty(transfer_to);
 | 
						mark_all_dquot_dirty(transfer_to);
 | 
				
			||||||
 | 
						srcu_read_unlock(&dquot_srcu, index);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	flush_warnings(warn_to);
 | 
						flush_warnings(warn_to);
 | 
				
			||||||
	flush_warnings(warn_from_inodes);
 | 
						flush_warnings(warn_from_inodes);
 | 
				
			||||||
	flush_warnings(warn_from_space);
 | 
						flush_warnings(warn_from_space);
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue