git: 00c2ff02342d - stable/14 - nullfs: close a race when syncing inotify flags from the lower vnode

Mark Johnston <[email protected]>
Newsgroups gmane.os.freebsd.devel.cvs.src
Message-ID <6a7353df.44fdf.47c4b60f__15250.0580459431$1785943275$gmane$org@gitrepo.freebsd.org>
The branch stable/14 has been updated by markj:

URL: https://cgit.FreeBSD.org/src/commit/?id=00c2ff02342d3f211c97f8b4ace9a5a96d955ea4

commit 00c2ff02342d3f211c97f8b4ace9a5a96d955ea4
Author:     Alexander Leidinger <[email protected]>
AuthorDate: 2026-07-19 07:38:52 +0000
Commit:     Mark Johnston <[email protected]>
CommitDate: 2026-08-05 15:15:16 +0000

    nullfs: close a race when syncing inotify flags from the lower vnode
    
    After a bypassed VOP, nullfs mirrors the lower vnode's inotify state
    onto the upper vnode.  The flags were checked with lockless reads
    before being updated with the asserting flag set/unset primitives, so
    two threads syncing the same vnode concurrently (or a sync racing a
    watch being established) could both decide to make the same change;
    the loser then trips the "flags already set" assertion on an
    INVARIANTS kernel.  On other kernels the race is harmless.
    
    Keep the lockless check as the fast path, but re-make the decision
    under the vnode interlock before actually changing the flags.
    
    Reproduced in a 4-CPU VM with one thread cycling an inotify watch on
    a lower-filesystem file while several threads stat(2) the same file
    through a nullfs mount: the unpatched INVARIANTS kernel panics under
    this load, the patched kernel runs it to completion.
    
    Fixes:                  f1f230439fa4 ("vfs: Initial revision of inotify")
    MFC after:              2 weeks
    Differential Revision:  D58344
    Reviewed by:            markj
    Assisted-by:            Claude Code (Fable 5)
    
    (cherry picked from commit d6915bffb7b68d9b55fa3db4e5709463549c379e)
---
 sys/fs/nullfs/null_vnops.c | 19 ++++++++++++++-----
 1 file changed, 14 insertions(+), 5 deletions(-)

diff --git a/sys/fs/nullfs/null_vnops.c b/sys/fs/nullfs/null_vnops.c
index 6fe5dc64b4d7..9108cd338a45 100644
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -200,17 +200,26 @@ SYSCTL_INT(_debug, OID_AUTO, nullfs_bug_bypass, CTLFLAG_RW,
  *   VOP_INOTIFY.
  * - If the lower vnode is watched, then the upper vnode should go through
  *   VOP_INOTIFY, so copy the flag up.
+ *
+ * The lockless check is only a fast path: the decision to change a flag
+ * is re-made under the upper vnode's interlock, since another thread may
+ * set or clear the flag concurrently.
  */
 static void
 null_copy_inotify(struct vnode *vp, struct vnode *lvp, short flag)
 {
+	if (__predict_true((vn_irflag_read(vp) & flag) ==
+	    (vn_irflag_read(lvp) & flag)))
+		return;
+	VI_LOCK(vp);
 	if ((vn_irflag_read(vp) & flag) != 0) {
-		if (__predict_false((vn_irflag_read(lvp) & flag) == 0))
-			vn_irflag_unset(vp, flag);
-	} else if ((vn_irflag_read(lvp) & flag) != 0) {
-		if (__predict_false((vn_irflag_read(vp) & flag) == 0))
-			vn_irflag_set(vp, flag);
+		if ((vn_irflag_read(lvp) & flag) == 0)
+			vn_irflag_unset_locked(vp, flag);
+	} else {
+		if ((vn_irflag_read(lvp) & flag) != 0)
+			vn_irflag_set_locked(vp, flag);
 	}
+	VI_UNLOCK(vp);
 }
 
 /*
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.