Commit 0c7273e4 authored by Dave Chinner's avatar Dave Chinner Committed by Darrick J. Wong
Browse files

xfs: quotacheck failure can race with background inode inactivation



The background inode inactivation can attached dquots to inodes, but
this can race with a foreground quotacheck failure that leads to
disabling quotas and freeing the mp->m_quotainfo structure. The
background inode inactivation then tries to allocate a quota, tries
to dereference mp->m_quotainfo, and crashes like so:

XFS (loop1): Quotacheck: Unsuccessful (Error -5): Disabling quotas.
xfs filesystem being mounted at /root/syzkaller.qCVHXV/0/file0 supports timestamps until 2038 (0x7fffffff)
BUG: kernel NULL pointer dereference, address: 00000000000002a8
....
CPU: 0 PID: 161 Comm: kworker/0:4 Not tainted 6.2.0-c9c3395d #1
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS rel-1.16.0-0-gd239552ce722-prebuilt.qemu.org 04/01/2014
Workqueue: xfs-inodegc/loop1 xfs_inodegc_worker
RIP: 0010:xfs_dquot_alloc+0x95/0x1e0
....
Call Trace:
 <TASK>
 xfs_qm_dqread+0x46/0x440
 xfs_qm_dqget_inode+0x154/0x500
 xfs_qm_dqattach_one+0x142/0x3c0
 xfs_qm_dqattach_locked+0x14a/0x170
 xfs_qm_dqattach+0x52/0x80
 xfs_inactive+0x186/0x340
 xfs_inodegc_worker+0xd3/0x430
 process_one_work+0x3b1/0x960
 worker_thread+0x52/0x660
 kthread+0x161/0x1a0
 ret_from_fork+0x29/0x50
 </TASK>
....

Prevent this race by flushing all the queued background inode
inactivations pending before purging all the cached dquots when
quotacheck fails.

Reported-by: default avatarPengfei Xu <pengfei.xu@intel.com>
Signed-off-by: default avatarDave Chinner <dchinner@redhat.com>
Reviewed-by: default avatarDarrick J. Wong <djwong@kernel.org>
Signed-off-by: default avatarDarrick J. Wong <djwong@kernel.org>
parent fe15c26e
Loading
Loading
Loading
Loading
+26 −14
Original line number Original line Diff line number Diff line
@@ -1321,15 +1321,14 @@ xfs_qm_quotacheck(


	error = xfs_iwalk_threaded(mp, 0, 0, xfs_qm_dqusage_adjust, 0, true,
	error = xfs_iwalk_threaded(mp, 0, 0, xfs_qm_dqusage_adjust, 0, true,
			NULL);
			NULL);
	if (error) {

	/*
	/*
		 * The inode walk may have partially populated the dquot
	 * On error, the inode walk may have partially populated the dquot
		 * caches.  We must purge them before disabling quota and
	 * caches.  We must purge them before disabling quota and tearing down
		 * tearing down the quotainfo, or else the dquots will leak.
	 * the quotainfo, or else the dquots will leak.
	 */
	 */
		xfs_qm_dqpurge_all(mp);
	if (error)
		goto error_return;
		goto error_purge;
	}


	/*
	/*
	 * We've made all the changes that we need to make incore.  Flush them
	 * We've made all the changes that we need to make incore.  Flush them
@@ -1363,10 +1362,8 @@ xfs_qm_quotacheck(
	 * and turn quotaoff. The dquots won't be attached to any of the inodes
	 * and turn quotaoff. The dquots won't be attached to any of the inodes
	 * at this point (because we intentionally didn't in dqget_noattach).
	 * at this point (because we intentionally didn't in dqget_noattach).
	 */
	 */
	if (error) {
	if (error)
		xfs_qm_dqpurge_all(mp);
		goto error_purge;
		goto error_return;
	}


	/*
	/*
	 * If one type of quotas is off, then it will lose its
	 * If one type of quotas is off, then it will lose its
@@ -1395,6 +1392,21 @@ xfs_qm_quotacheck(
	} else
	} else
		xfs_notice(mp, "Quotacheck: Done.");
		xfs_notice(mp, "Quotacheck: Done.");
	return error;
	return error;

error_purge:
	/*
	 * On error, we may have inodes queued for inactivation. This may try
	 * to attach dquots to the inode before running cleanup operations on
	 * the inode and this can race with the xfs_qm_destroy_quotainfo() call
	 * below that frees mp->m_quotainfo. To avoid this race, flush all the
	 * pending inodegc operations before we purge the dquots from memory,
	 * ensuring that background inactivation is idle whilst we turn off
	 * quotas.
	 */
	xfs_inodegc_flush(mp);
	xfs_qm_dqpurge_all(mp);
	goto error_return;

}
}


/*
/*