The kernel can deadlock if it tries to allocate blocks for a quota data file that is stored in the same filesystem that has the quotas enabled. This happens because of recursion within the quota subsystem:
sleep_finish(ffff8000225d16c0,1) at sleep_finish+0xfe tsleep(ffff8000007f0880,9,ffffffff81f8b8d8,0) at tsleep+0xb2 # waits for the struct dquot lock that was acquired earlier ufs_quota_alloc_blocks2(fffffd82631dbbc0,4,fffffd843f7e3c60,0) at ufs_quota_alloc_blocks2+0x149 ffs_alloc(fffffd82631dbbc0,0,ce5,800,fffffd843f7e3c60,ffff8000225d1940) at ffs_alloc+0x114 ffs2_balloc(fffffd82631dbbc0,0,20,fffffd843f7e3c60,1,ffff8000225d1a28) at ffs2_balloc+0x50d ffs_write(ffff8000225d1aa8) at ffs_write+0x229 VOP_WRITE(fffffd83357662f8,ffff8000225d1b18,0,fffffd843f7e3c60) at VOP_WRITE+0x6a # has acquired struct dquot lock dqsync(fffffd83357662f8,ffff8000007f0880) at dqsync+0x133 qsync_vnode(fffffd83357662f8,0) at qsync_vnode+0x68 vfs_mount_foreach_vnode(ffff8000006ae000,ffffffff81a92120,0) at vfs_mount_foreach_vnode+0x4a qsync(ffff8000006ae000) at qsync+0x37 ffs_sync(ffff8000006ae000,3,0,fffffd843f7e3ea0,ffff80002259b508) at ffs_sync+0x114 sync_fsync(ffff8000225d1d60) at sync_fsync+0x144 VOP_FSYNC(fffffd8406af5bc8,fffffd843f7e3ea0,3,ffff80002259b508) at VOP_FSYNC+0x83 syncer_thread(ffff80002259b508) at syncer_thread+0x1b2 One way to fix this is to exclude quota data files from the accounting, as shown in the following patch. The quota files are root's property and special to the system, so the omission should not be a problem in general. A slight variation of the patch is to skip only the files that are used to store quotas in the same filesystem. However, I think the categorical skipping is clearer. The patch makes quotacheck(8) ignore the quota data files in the same-filesystem case. Note that there is no adjustment for externally stored quota files. External storage probably does not use quotas. Also, the VSYSTEM flag should be cleared when quotas are disabled. (One might still invoke curious patterns by setting up a filesystem with quotas on a vnd device that is backed by a sparse file on a quota-enabled filesystem. However, I think this is a slightly different thing that relates to the subsystem's shared data structures.) OK? Index: sbin/quotacheck/quotacheck.c =================================================================== RCS file: src/sbin/quotacheck/quotacheck.c,v retrieving revision 1.41 diff -u -p -r1.41 quotacheck.c --- sbin/quotacheck/quotacheck.c 28 Jun 2019 13:32:45 -0000 1.41 +++ sbin/quotacheck/quotacheck.c 25 Jun 2022 10:46:22 -0000 @@ -263,10 +263,13 @@ int chkquota(const char *vfstype, const char *fsname, const char *mntpt, void *auxarg, pid_t *pidp) { + struct stat sb; struct quotaname *qnp = auxarg; struct fileusage *fup; union dinode *dp; int cg, i, mode, errs = 0, status; + dev_t groupdev = 0, userdev = 0, dev = 0; + ino_t groupino = 0, userino = 0; ino_t ino, inosused; pid_t pid; char *cp; @@ -276,8 +279,20 @@ chkquota(const char *vfstype, const char warn("fork"); return 1; case 0: /* child */ + if ((qnp->flags & HASGRP) != 0 && + stat(qnp->grpqfname, &sb) == 0) { + groupdev = sb.st_dev; + groupino = sb.st_ino; + } + if ((qnp->flags & HASUSR) != 0 && + stat(qnp->usrqfname, &sb) == 0) { + userdev = sb.st_dev; + userino = sb.st_ino; + } if ((fi = opendev(fsname, O_RDONLY, 0, NULL)) == -1) err(1, "%s", fsname); + if (stat(mntpt, &sb) == 0) + dev = sb.st_dev; sync(); for (i = 0; sblock_try[i] != -1; i++) { bread(sblock_try[i] / DEV_BSIZE, (char *)&sblock, @@ -332,6 +347,13 @@ chkquota(const char *vfstype, const char ino < ROOTINO || (mode = DIP(dp, di_mode) & IFMT) == 0) continue; + /* + * Skip the quota data files because + * the kernel excludes them from accounting. + */ + if ((ino == groupino && dev == groupdev) || + (ino == userino && dev == userdev)) + continue; if (qnp->flags & HASGRP) { fup = addid(DIP(dp, di_gid), GRPQUOTA, NULL); Index: sys/ufs/ufs/ufs_quota.c =================================================================== RCS file: src/sys/ufs/ufs/ufs_quota.c,v retrieving revision 1.47 diff -u -p -r1.47 ufs_quota.c --- sys/ufs/ufs/ufs_quota.c 24 Jun 2020 22:03:45 -0000 1.47 +++ sys/ufs/ufs/ufs_quota.c 25 Jun 2022 10:46:22 -0000 @@ -156,6 +156,13 @@ getinoquota(struct inode *ip) ump = ip->i_ump; /* + * Do not apply quotas to quota files. + * The system will deadlock if the quota files are + * on the same filesystem that has quotas enabled. + */ + if (vp->v_flag & VSYSTEM) + return (0); + /* * Set up the user quota based on file uid. * EINVAL means that quotas are not enabled. */ @@ -439,7 +446,15 @@ chkdquot(struct inode *ip) if (!VOP_ISLOCKED(vp)) panic ("chkdquot: vnode is not locked"); - + + /* + * Do not apply quotas to quota files. + * The system will deadlock if the quota files are + * on the same filesystem that has quotas enabled. + */ + if (vp->v_flag & VSYSTEM) + return; + for (i = 0; i < MAXQUOTAS; i++) { if (ump->um_quotas[i] == NULLVP || (ump->um_qflags[i] & (QTF_OPENING|QTF_CLOSING))) @@ -600,6 +615,7 @@ quotaoff(struct proc *p, struct mount *m qa.type = type; vfs_mount_foreach_vnode(mp, quotaoff_vnode, &qa); + qvp->v_flag &= ~VSYSTEM; error = vn_close(qvp, FREAD|FWRITE, p->p_ucred, p); ump->um_quotas[type] = NULLVP; crfree(ump->um_cred[type]);