git: a5a16ba5f179 - stable/15 - nullfs: close a race when syncing inotify flags from the lower vnode
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 02 Aug 2026 12:12:51 UTC
The branch stable/15 has been updated by netchild:
URL: https://cgit.FreeBSD.org/src/commit/?id=a5a16ba5f1791e1be87fede24887b469a9a0d911
commit a5a16ba5f1791e1be87fede24887b469a9a0d911
Author: Alexander Leidinger <netchild@FreeBSD.org>
AuthorDate: 2026-07-19 07:38:52 +0000
Commit: Alexander Leidinger <netchild@FreeBSD.org>
CommitDate: 2026-08-02 12:12:07 +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 ee7d89db6b05..27ccb54c2a4f 100644
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -196,17 +196,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);
}
/*