git: d6915bffb7b6 - main - nullfs: close a race when syncing inotify flags from the lower vnode
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 19 Jul 2026 15:13:58 UTC
The branch main has been updated by netchild:
URL: https://cgit.FreeBSD.org/src/commit/?id=d6915bffb7b68d9b55fa3db4e5709463549c379e
commit d6915bffb7b68d9b55fa3db4e5709463549c379e
Author: Alexander Leidinger <netchild@FreeBSD.org>
AuthorDate: 2026-07-19 07:38:52 +0000
Commit: Alexander Leidinger <netchild@FreeBSD.org>
CommitDate: 2026-07-19 15:13:40 +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)
---
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 50d359c9b909..41823d0c6202 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);
}
/*