Module Name:    src
Committed By:   martin
Date:           Sun Jul 30 12:09:51 UTC 2023

Modified Files:
        src/sys/kern [netbsd-10]: kern_descrip.c

Log Message:
Pull up following revision(s) (requested by riastradh in ticket #262):

        sys/kern/kern_descrip.c: revision 1.252
        sys/kern/kern_descrip.c: revision 1.253
        sys/kern/kern_descrip.c: revision 1.254

kern_descrip.c: Fix membars around reference count decrement.

In general, the `last one out hit the lights' style of reference
counting (as opposed to the `whoever's destroying must wait for
pending users to finish' style) requires memory barriers like so:
        ... usage of resources associated with object ...
        membar_release();
        if (atomic_dec_uint_nv(&obj->refcnt) != 0)
                return;
        membar_acquire();
        ... freeing of resources associated with object ...

This way, all usage happens-before all freeing.  This fixes several
errors:
- fd_close failed to ensure whatever its caller did would
  happen-before the freeing, in the case where another thread is
  concurrently trying to close the fd (ff->ff_file == NULL).
  Fix: Add membar_release before atomic_dec_uint(&ff->ff_refcnt) in
  that branch.
- fd_close failed to ensure all loads its caller had issued will have
  happened-before the freeing, in the case where the fd is still in
  use by another thread (fdp->fd_refcnt > 1 and ff->ff_refcnt-- > 0).
  Fix: Change membar_producer to membar_release before
  atomic_dec_uint(&ff->ff_refcnt).
- fd_close failed to ensure that any usage of fp by other callers
  would happen-before any freeing it does.
  Fix: Add membar_acquire after atomic_dec_uint_nv(&ff->ff_refcnt).
- fd_free failed to ensure that any usage of fdp by other callers
  would happen-before any freeing it does.
  Fix: Add membar_acquire after atomic_dec_uint_nv(&fdp->fd_refcnt).

While here, change membar_exit -> membar_release.  No semantic
change, just updating away from the legacy API.

kern_descrip.c: Use atomic_store_relaxed/release for ff->ff_file.
1. atomic_store_relaxed in fd_close avoids the appearance of race in
   sanitizers (minor bug).
2. atomic_store_release in fd_affix is necessary because the lock
   activity was not, in fact, enough to guarantee ordering (real bug
   some architectures like aarch64).
   The premise appears to have been that the mutex_enter/exit earlier
   in fd_affix is enough to guarantee that initialization of fp (A)
   happens before use of fp by a user once fp is published (B):
        fp->f_... = ...;                // A
        /* fd_affix */
        mutex_enter(&fp->f_lock);
        fp->f_count++;
        mutex_exit(&fp->f_lock);
        ...
        ff->ff_file = fp;               // B
   But actually mutex_enter/exit allow the following reordering by
   the CPU:
        mutex_enter(&fp->f_lock);
        ff->ff_file = fp;               // B
        fp->f_count++;
        fp->f_... = ...;                // A
        mutex_exit(&fp->f_lock);
   The only constraints they imply are:
        1. fp->f_count++ and B cannot precede mutex_enter
        2. mutex_exit cannot precede A and fp->f_count++
   They imply no constraint on the relative ordering of A, B, and
   fp->f_count++ amongst each other, however.
   This affects any architecture that has a native load-acquire or
   store-release operation in mutex_enter/exit, like aarch64, instead
   of explicit load-before-load/store and load/store-before-store
   barrier.

No need for atomic_store_* in fd_copy or fd_free because we have
exclusive access to ff as is.

kern_descrip.c: Change membar_enter to membar_acquire in fd_getfile.
membar_acquire is cheaper on many CPUs, and unlikely to be costlier
on any CPUs, than the legacy membar_enter.
Add a long comment explaining the interaction between fd_getfile and
fd_close and why membar_acquire is safe.


To generate a diff of this commit:
cvs rdiff -u -r1.251 -r1.251.10.1 src/sys/kern/kern_descrip.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/kern/kern_descrip.c
diff -u src/sys/kern/kern_descrip.c:1.251 src/sys/kern/kern_descrip.c:1.251.10.1
--- src/sys/kern/kern_descrip.c:1.251	Tue Jun 29 22:40:53 2021
+++ src/sys/kern/kern_descrip.c	Sun Jul 30 12:09:51 2023
@@ -1,4 +1,4 @@
-/*	$NetBSD: kern_descrip.c,v 1.251 2021/06/29 22:40:53 dholland Exp $	*/
+/*	$NetBSD: kern_descrip.c,v 1.251.10.1 2023/07/30 12:09:51 martin Exp $	*/
 
 /*-
  * Copyright (c) 2008, 2009 The NetBSD Foundation, Inc.
@@ -70,7 +70,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: kern_descrip.c,v 1.251 2021/06/29 22:40:53 dholland Exp $");
+__KERNEL_RCSID(0, "$NetBSD: kern_descrip.c,v 1.251.10.1 2023/07/30 12:09:51 martin Exp $");
 
 #include <sys/param.h>
 #include <sys/systm.h>
@@ -392,10 +392,45 @@ fd_getfile(unsigned fd)
 		 * Multi threaded: issue a memory barrier to ensure that we
 		 * acquire the file pointer _after_ adding a reference.  If
 		 * no memory barrier, we could fetch a stale pointer.
+		 *
+		 * In particular, we must coordinate the following four
+		 * memory operations:
+		 *
+		 *	A. fd_close store ff->ff_file = NULL
+		 *	B. fd_close refcnt = atomic_dec_uint_nv(&ff->ff_refcnt)
+		 *	C. fd_getfile atomic_inc_uint(&ff->ff_refcnt)
+		 *	D. fd_getfile load fp = ff->ff_file
+		 *
+		 * If the order is D;A;B;C:
+		 *
+		 *	1. D: fp = ff->ff_file
+		 *	2. A: ff->ff_file = NULL
+		 *	3. B: refcnt = atomic_dec_uint_nv(&ff->ff_refcnt)
+		 *	4. C: atomic_inc_uint(&ff->ff_refcnt)
+		 *
+		 * then fd_close determines that there are no more
+		 * references and decides to free fp immediately, at
+		 * the same that fd_getfile ends up with an fp that's
+		 * about to be freed.  *boom*
+		 *
+		 * By making B a release operation in fd_close, and by
+		 * making C an acquire operation in fd_getfile, since
+		 * they are atomic operations on the same object, which
+		 * has a total modification order, we guarantee either:
+		 *
+		 *	- B happens before C.  Then since A is
+		 *	  sequenced before B in fd_close, and C is
+		 *	  sequenced before D in fd_getfile, we
+		 *	  guarantee A happens before D, so fd_getfile
+		 *	  reads a null fp and safely fails.
+		 *
+		 *	- C happens before B.  Then fd_getfile may read
+		 *	  null or nonnull, but either way, fd_close
+		 *	  will safely wait for references to drain.
 		 */
 		atomic_inc_uint(&ff->ff_refcnt);
 #ifndef __HAVE_ATOMIC_AS_MEMBAR
-		membar_enter();
+		membar_acquire();
 #endif
 	}
 
@@ -451,7 +486,7 @@ fd_putfile(unsigned fd)
 	 * CPU.
 	 */
 #ifndef __HAVE_ATOMIC_AS_MEMBAR
-	membar_exit();
+	membar_release();
 #endif
 
 	/*
@@ -602,6 +637,9 @@ fd_close(unsigned fd)
 		 * waiting for other users of the file to drain.  Release
 		 * our reference, and wake up the closer.
 		 */
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+		membar_release();
+#endif
 		atomic_dec_uint(&ff->ff_refcnt);
 		cv_broadcast(&ff->ff_closing);
 		mutex_exit(&fdp->fd_lock);
@@ -621,7 +659,7 @@ fd_close(unsigned fd)
 	 * will prevent them from adding additional uses to this file
 	 * while we are closing it.
 	 */
-	ff->ff_file = NULL;
+	atomic_store_relaxed(&ff->ff_file, NULL);
 	ff->ff_exclose = false;
 
 	/*
@@ -637,9 +675,12 @@ fd_close(unsigned fd)
 	} else {
 		/* Multi threaded. */
 #ifndef __HAVE_ATOMIC_AS_MEMBAR
-		membar_producer();
+		membar_release();
 #endif
 		refcnt = atomic_dec_uint_nv(&ff->ff_refcnt);
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+		membar_acquire();
+#endif
 	}
 	if (__predict_false(refcnt != 0)) {
 		/*
@@ -1146,12 +1187,6 @@ fd_affix(proc_t *p, file_t *fp, unsigned
 
 	/*
 	 * Insert the new file into the descriptor slot.
-	 *
-	 * The memory barriers provided by lock activity in this routine
-	 * ensure that any updates to the file structure become globally
-	 * visible before the file becomes visible to other LWPs in the
-	 * current process; otherwise we would set ff->ff_file with
-	 * atomic_store_release(&ff->ff_file, fp) at the bottom.
 	 */
 	fdp = p->p_fd;
 	dt = atomic_load_consume(&fdp->fd_dt);
@@ -1164,7 +1199,7 @@ fd_affix(proc_t *p, file_t *fp, unsigned
 	KASSERT(fd >= NDFDFILE || ff == (fdfile_t *)fdp->fd_dfdfile[fd]);
 
 	/* No need to lock in order to make file initially visible. */
-	ff->ff_file = fp;
+	atomic_store_release(&ff->ff_file, fp);
 }
 
 /*
@@ -1532,10 +1567,13 @@ fd_free(void)
 	KASSERT(fdp->fd_dtbuiltin.dt_link == NULL);
 
 #ifndef __HAVE_ATOMIC_AS_MEMBAR
-	membar_exit();
+	membar_release();
 #endif
 	if (atomic_dec_uint_nv(&fdp->fd_refcnt) > 0)
 		return;
+#ifndef __HAVE_ATOMIC_AS_MEMBAR
+	membar_acquire();
+#endif
 
 	/*
 	 * Close any files that the process holds open.

Reply via email to