⬢ DragonFlyBSD Kernel Audit
DF-3316 / fix.diff
← back to finding ↓ download raw
--- tcp_subr.c.orig
+++ tcp_subr.c
@@ -1273,23 +1273,47 @@
 			 * process a snapshot of pcbs, ignoring placemarkers
 			 * and using our own to allow SYSCTL_OUT to block.
 			 */
+			int skip = 0;
+
+			/*
+			 * The marker advance (read next, unlink marker,
+			 * relink marker after next) is a non-atomic two-step
+			 * sequence.  Nothing serializes this sysctl user
+			 * thread against a same-cpu netisr tearing down the
+			 * inpcb we just stepped to (tcp_close ->
+			 * in_pcbdetach -> LIST_REMOVE + kfree): relinking the
+			 * marker after an unlinked (and possibly freed and
+			 * recycled) inpcb corrupts the list and writes into
+			 * freed memory.  The critical section makes the
+			 * advance + snapshot atomic against same-cpu
+			 * preemption; the marker itself keeps the walk
+			 * coherent across the (sleeping) SYSCTL_OUT below.
+			 */
+			crit_enter();
 			LIST_REMOVE(marker, inp_list);
 			LIST_INSERT_AFTER(inp, marker, inp_list);
 
-			if (inp->inp_flags & INP_PLACEMARKER)
-				continue;
-			if (prison_xinpcb(req->td, inp))
-				continue;
+			if (inp->inp_flags & INP_PLACEMARKER) {
+				skip = 1;
+			} else if (prison_xinpcb(req->td, inp)) {
+				skip = 1;
+			} else {
+				xt.xt_len = sizeof xt;
+				bcopy(inp, &xt.xt_inp, sizeof *inp);
+				inp_ppcb = inp->inp_ppcb;
+				if (inp_ppcb != NULL)
+					bcopy(inp_ppcb, &xt.xt_tp,
+					    sizeof xt.xt_tp);
+				else
+					bzero(&xt.xt_tp, sizeof xt.xt_tp);
+				if (inp->inp_socket)
+					sotoxsocket(inp->inp_socket,
+					    &xt.xt_socket);
+			}
+			crit_exit();
 
-			xt.xt_len = sizeof xt;
-			bcopy(inp, &xt.xt_inp, sizeof *inp);
-			inp_ppcb = inp->inp_ppcb;
-			if (inp_ppcb != NULL)
-				bcopy(inp_ppcb, &xt.xt_tp, sizeof xt.xt_tp);
-			else
-				bzero(&xt.xt_tp, sizeof xt.xt_tp);
-			if (inp->inp_socket)
-				sotoxsocket(inp->inp_socket, &xt.xt_socket);
+			if (skip)
+				continue;
 			if ((error = SYSCTL_OUT(req, &xt, sizeof xt)) != 0)
 				break;
 			++i;