⬢ DragonFlyBSD Kernel Audit
DF-3340 / fix.diff
← back to finding ↓ download raw
diff --git a/sys/net/pf/pf.c b/sys/net/pf/pf.c
index 239c7372..c5c2da54 100644
--- a/sys/net/pf/pf.c
+++ b/sys/net/pf/pf.c
@@ -1478,6 +1478,76 @@ pf_src_tree_remove_state(struct pf_state *s)
 	s->src_node = s->nat_src_node = NULL;
 }
 
+/*
+ * Deferred synproxy RST transmission (DF-3340).
+ *
+ * pf_unlink_state() may run on non-netisr threads (pf_purge_thread,
+ * DIOCCLRSTATES/DIOCKILLSTATES ioctls, module unload), but ip_output()
+ * asserts that it executes on a netisr thread.  When a SYN proxy state
+ * dies in the PF_TCPS_PROXY_DST state, forward the generated RST to a
+ * netisr for transmission instead of calling pf_send_tcp() directly.
+ * All parameters are copied: the state and its rule may be freed before
+ * the netmsg is dispatched.
+ *
+ * pf_synproxy_rst_pending tracks netmsgs still in flight so that the
+ * module is not torn down while a netmsg references module text.
+ */
+struct pf_synproxy_rst {
+	struct netmsg_base	nm;
+	sa_family_t		af;
+	struct pf_addr		saddr;
+	struct pf_addr		daddr;
+	u_int16_t		sport;
+	u_int16_t		dport;
+	u_int32_t		seq;
+	u_int32_t		ack;
+	u_int16_t		rtag;
+	int			rtableid;
+#ifdef ALTQ
+	u_int32_t		qid;
+	u_int8_t		ecn_af;
+#endif
+};
+
+static volatile u_int	pf_synproxy_rst_pending;
+
+static void
+pf_synproxy_rst_dispatch(netmsg_t nmsg)
+{
+	struct pf_synproxy_rst *msg = (struct pf_synproxy_rst *)nmsg;
+	struct pf_rule		r;
+
+	bzero(&r, sizeof(r));
+	r.rtableid = msg->rtableid;
+#ifdef ALTQ
+	r.qid = msg->qid;
+	r.ecn_af = msg->ecn_af;
+#endif
+
+	lwkt_gettoken(&pf_token);
+	pf_send_tcp(&r, msg->af, &msg->saddr, &msg->daddr,
+	    msg->sport, msg->dport, msg->seq, msg->ack,
+	    TH_RST | TH_ACK, 0, 0, 0, 1, msg->rtag, NULL, NULL);
+	lwkt_reltoken(&pf_token);
+
+	netisr_replymsg(&msg->nm, 0);
+	atomic_subtract_int(&pf_synproxy_rst_pending, 1);
+	wakeup(&pf_synproxy_rst_pending);
+}
+
+/*
+ * Wait (bounded) for deferred synproxy RST netmsgs to drain, so the
+ * module is not unloaded while a netmsg still references module text.
+ */
+void
+pf_synproxy_rst_drain(void)
+{
+	int i;
+
+	for (i = 0; i < 10 && pf_synproxy_rst_pending != 0; ++i)
+		tsleep(&pf_synproxy_rst_pending, 0, "pfrst", hz / 10);
+}
+
 /* callers should be at crit_enter() */
 void
 pf_unlink_state(struct pf_state *cur)
@@ -1486,13 +1556,47 @@ pf_unlink_state(struct pf_state *cur)
 
 	if (cur->src.state == PF_TCPS_PROXY_DST) {
 		/* XXX wire key the right one? */
-		pf_send_tcp(cur->rule.ptr, cur->key[PF_SK_WIRE]->af,
-		    &cur->key[PF_SK_WIRE]->addr[1],
-		    &cur->key[PF_SK_WIRE]->addr[0],
-		    cur->key[PF_SK_WIRE]->port[1],
-		    cur->key[PF_SK_WIRE]->port[0],
-		    cur->src.seqhi, cur->src.seqlo + 1,
-		    TH_RST|TH_ACK, 0, 0, 0, 1, cur->tag, NULL, NULL);
+		if (IN_NETISR_NCPUS(cpu)) {
+			/* packet path: already on a netisr thread */
+			pf_send_tcp(cur->rule.ptr,
+			    cur->key[PF_SK_WIRE]->af,
+			    &cur->key[PF_SK_WIRE]->addr[1],
+			    &cur->key[PF_SK_WIRE]->addr[0],
+			    cur->key[PF_SK_WIRE]->port[1],
+			    cur->key[PF_SK_WIRE]->port[0],
+			    cur->src.seqhi, cur->src.seqlo + 1,
+			    TH_RST|TH_ACK, 0, 0, 0, 1, cur->tag,
+			    NULL, NULL);
+		} else {
+			struct pf_synproxy_rst *msg;
+			const struct pf_state_key *sk = cur->key[PF_SK_WIRE];
+
+			msg = kmalloc(sizeof(*msg), M_LWKTMSG,
+			    M_NOWAIT | M_NULLOK);
+			if (msg != NULL) {
+				msg->af = sk->af;
+				msg->saddr = sk->addr[1];
+				msg->daddr = sk->addr[0];
+				msg->sport = sk->port[1];
+				msg->dport = sk->port[0];
+				msg->seq = cur->src.seqhi;
+				msg->ack = cur->src.seqlo + 1;
+				msg->rtag = cur->tag;
+				msg->rtableid = cur->rule.ptr != NULL ?
+				    cur->rule.ptr->rtableid : -1;
+#ifdef ALTQ
+				msg->qid = cur->rule.ptr != NULL ?
+				    cur->rule.ptr->qid : 0;
+				msg->ecn_af = sk->af;
+#endif
+				netmsg_init(&msg->nm, NULL,
+				    &netisr_afree_rport, 0,
+				    pf_synproxy_rst_dispatch);
+				atomic_add_int(&pf_synproxy_rst_pending, 1);
+				netisr_sendmsg(&msg->nm,
+				    cpu % netisr_ncpus);
+			}
+		}
 	}
 	RB_REMOVE(pf_state_tree_id, &tree_id[cpu], cur);
 	if (cur->creatorid == pf_status.hostid)
diff --git a/sys/net/pf/pf_ioctl.c b/sys/net/pf/pf_ioctl.c
index ce280fc8..cd6b9644 100644
--- a/sys/net/pf/pf_ioctl.c
+++ b/sys/net/pf/pf_ioctl.c
@@ -3391,6 +3391,8 @@ pf_unload(void)
 		wakeup_one(pf_purge_thread);
 		tsleep(pf_purge_thread, 0, "pftmo", hz / 10);
 	}
+	/* drain deferred synproxy RST netmsgs (DF-3340) */
+	pf_synproxy_rst_drain();
 	pfi_cleanup();
 	pf_osfp_flush();
 	dev_ops_remove_all(&pf_ops);
diff --git a/sys/net/pf/pfvar.h b/sys/net/pf/pfvar.h
index 30ee636d..70411d12 100644
--- a/sys/net/pf/pfvar.h
+++ b/sys/net/pf/pfvar.h
@@ -1798,6 +1798,7 @@ extern int			 pf_purge_expired_src_nodes(int);
 extern int			 pf_purge_expired_states(u_int32_t, int);
 extern void			 pf_unlink_state(struct pf_state *);
 extern void			 pf_free_state(struct pf_state *);
+extern void			 pf_synproxy_rst_drain(void);
 extern int			 pf_state_insert(struct pfi_kif *,
 				    struct pf_state_key *,
 				    struct pf_state_key *,