⬢ DragonFlyBSD Kernel Audit
DF-3120 / fix.diff
← back to finding ↓ download raw
--- a/sys/vfs/ufs/ufs_quota.c	2026-09-06 18:44:20.307903596 +0000
+++ b/sys/vfs/ufs/ufs_quota.c	2026-09-06 20:02:29.851777128 +0000
@@ -408,6 +408,7 @@
  * Q_QUOTAON - set up a quota file for a particular filesystem.
  */
 static int ufs_quotaon_scan(struct mount *mp, struct vnode *vp, void *data);
+static int ufs_quotaoff_locked(struct mount *mp, int type);
 
 int
 ufs_quotaon(struct ucred *cred, struct mount *mp, int type, caddr_t fname)
@@ -433,9 +434,20 @@
 	nd.nl_open_vp = NULL;
 	nlookup_done(&nd);
 
+	/*
+	 * DF-3120: serialize the per-mount quota state machine so two
+	 * quotaon/quotaoff cannot interleave (double vn_close, crfree of
+	 * NOCRED, um_quotas clobbering of a concurrent re-quotaon).
+	 *
+	 * NOTE: vp must be unlocked BEFORE taking um_quota_lock: quotaon's
+	 * scan inside the lock vgets vnodes, and holding vp's lock here
+	 * against a quotaoff holding um_quota_lock and vget()ing the same
+	 * vnode would deadlock.
+	 */
 	vn_unlock(vp);
+	lockmgr(&ump->um_quota_lock, LK_EXCLUSIVE);
 	if (*vpp != vp)
-		ufs_quotaoff(mp, type);
+		ufs_quotaoff_locked(mp, type);
 	ump->um_qflags[type] |= QTF_OPENING;
 	mp->mnt_flag |= MNT_QUOTA;
 	vsetflags(vp, VSYSTEM);
@@ -470,7 +482,8 @@
 	}
 	ump->um_qflags[type] &= ~QTF_OPENING;
 	if (error)
-		ufs_quotaoff(mp, type);
+		ufs_quotaoff_locked(mp, type);
+	lockmgr(&ump->um_quota_lock, LK_RELEASE);
 	return (error);
 }
 
@@ -495,6 +508,26 @@
 int
 ufs_quotaoff(struct mount *mp, int type)
 {
+	struct ufsmount *ump = VFSTOUFS(mp);
+	int error;
+
+	/*
+	 * DF-3120: before this lock, two concurrent Q_QUOTAOFFs could both
+	 * pass the um_quotas[type] == NULLVP test and double-execute the
+	 * close path: double vn_close(qvp) (vnode refcount underflow / UAF),
+	 * crfree() of um_cred[type] after it was reset to NOCRED (NULL
+	 * deref in crfree), and clearing um_quotas[type] of a concurrent
+	 * re-quotaon (panic "dqsync: file" / VOP_WRITE on a closed vp).
+	 */
+	lockmgr(&ump->um_quota_lock, LK_EXCLUSIVE);
+	error = ufs_quotaoff_locked(mp, type);
+	lockmgr(&ump->um_quota_lock, LK_RELEASE);
+	return (error);
+}
+
+static int
+ufs_quotaoff_locked(struct mount *mp, int type)
+{
 	struct vnode *qvp;
 	struct ufsmount *ump = VFSTOUFS(mp);
 	int error;
--- a/sys/vfs/ufs/ufsmount.h	2026-09-06 18:44:20.307903596 +0000
+++ b/sys/vfs/ufs/ufsmount.h	2026-09-06 18:44:20.311903544 +0000
@@ -61,6 +61,7 @@
 struct inode;
 struct timeval;
 struct ucred;
+struct lock;
 struct uio;
 struct vnode;
 struct netexport;
@@ -80,6 +81,7 @@
 	ufs_time_t um_btime[MAXQUOTAS];		/* block quota time limit */
 	ufs_time_t um_itime[MAXQUOTAS];		/* inode quota time limit */
 	char	um_qflags[MAXQUOTAS];		/* quota specific flags */
+	struct	lock um_quota_lock;		/* serializes quotaon/quotaoff */
 	struct	netexport um_export;		/* export information */
 	int64_t	um_savedmaxfilesize;		/* XXX - limit maxfilesize */
 	struct malloc_type *um_malloctype;	/* The inodes malloctype */
--- a/sys/vfs/ufs/ffs_vfsops.c	2026-09-06 18:44:20.311903544 +0000
+++ b/sys/vfs/ufs/ffs_vfsops.c	2026-09-06 19:14:02.113130845 +0000
@@ -35,6 +35,7 @@
 #include <sys/disk.h>
 #include <sys/param.h>
 #include <sys/systm.h>
+#include <sys/lock.h>
 #include <sys/proc.h>
 #include <sys/nlookup.h>
 #include <sys/kernel.h>
@@ -668,6 +669,7 @@
 	ump = kmalloc(sizeof *ump, M_UFSMNT, M_WAITOK | M_ZERO);
 	ump->um_malloctype = mtype;
 	ump->um_i_effnlink_valid = 1;
+	lockinit(&ump->um_quota_lock, "ufsqta", 0, 0);
 	ump->um_fs = kmalloc((u_long)fs->fs_sbsize, M_UFSMNT,
 	    M_WAITOK);
 	bcopy(bp->b_data, ump->um_fs, (uint)fs->fs_sbsize);