--- 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 #include #include +#include #include #include #include @@ -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);