⬢ DragonFlyBSD Kernel Audit
DF-3307 / fix.diff
← back to finding ↓ download raw
--- a/sys/netinet6/ip6_output.c	2026-09-10 12:17:50.534325915 +0000
+++ b/sys/netinet6/ip6_output.c	2026-09-10 12:18:58.537454293 +0000
@@ -123,6 +123,7 @@
 static int ip6_insert_jumboopt (struct ip6_exthdrs *, u_int32_t);
 static struct mbuf *ip6_splithdr (struct mbuf *);
 static int copypktopts(struct ip6_pktopts *, struct ip6_pktopts *, int);
+static u_char *ip6_lastunfignxt(struct mbuf *, int);
 
 /*
  * IP6 output. The packet in mbuf chain m contains a skeletal IP6
@@ -775,19 +776,20 @@
 		 * Change the next header field of the last header in the
 		 * unfragmentable part.
 		 */
-		if (exthdrs.ip6e_rthdr) {
-			nextproto = *mtod(exthdrs.ip6e_rthdr, u_char *);
-			*mtod(exthdrs.ip6e_rthdr, u_char *) = IPPROTO_FRAGMENT;
-		} else if (exthdrs.ip6e_dest1) {
-			nextproto = *mtod(exthdrs.ip6e_dest1, u_char *);
-			*mtod(exthdrs.ip6e_dest1, u_char *) = IPPROTO_FRAGMENT;
-		} else if (exthdrs.ip6e_hbh) {
-			nextproto = *mtod(exthdrs.ip6e_hbh, u_char *);
-			*mtod(exthdrs.ip6e_hbh, u_char *) = IPPROTO_FRAGMENT;
-		} else {
-			nextproto = ip6->ip6_nxt;
-			ip6->ip6_nxt = IPPROTO_FRAGMENT;
+		/*
+		 * DF-3307: pfil hooks may have replaced the mbuf chain;
+		 * re-locate the next-header field of the last header in
+		 * the unfragmentable part in the current chain rather
+		 * than dereferencing the stale exthdrs[] pointers.
+		 */
+		nexthdrp = ip6_lastunfignxt(m, unfragpartlen);
+		if (nexthdrp == NULL) {
+			error = EINVAL;
+			ip6stat.ip6s_odropped++;
+			goto sendorfree;
 		}
+		nextproto = *nexthdrp;
+		*nexthdrp = IPPROTO_FRAGMENT;
 
 		/*
 		 * Loop through length of segment after first fragment,
@@ -1042,6 +1044,45 @@
 	return (0);
 }
 
+/*
+ * DF-3307: Locate the next-header byte of the last extension header
+ * within the unfragmentable part of the mbuf chain m.
+ *
+ * pfil_run_hooks() may legally replace (free + copy) the packet chain
+ * between the time ip6_output() filled in its exthdrs[] pointers and
+ * the fragmentation code that rewrites the next-header byte, so the
+ * cached pointers must not be used; walk the current chain instead.
+ *
+ * Returns the address of the next-header byte, or NULL if the
+ * extension headers are malformed (fail closed: drop the packet).
+ */
+static u_char *
+ip6_lastunfignxt(struct mbuf *m, int unfragpartlen)
+{
+	struct mbuf *n;
+	u_char *p;
+	int off, hlen, loff;
+
+	if (unfragpartlen < (int)sizeof(struct ip6_hdr))
+		return (NULL);
+	if (unfragpartlen == (int)sizeof(struct ip6_hdr))
+		return (&mtod(m, struct ip6_hdr *)->ip6_nxt);
+
+	off = sizeof(struct ip6_hdr);
+	for (;;) {
+		n = m_getptr(m, off, &loff);
+		if (n == NULL || n->m_len - loff < 2)
+			return (NULL);
+		p = mtod(n, u_char *) + loff;
+		hlen = (p[1] + 1) << 3;
+		if (off + hlen > unfragpartlen)
+			return (NULL);
+		if (off + hlen == unfragpartlen)
+			return (p);
+		off += hlen;
+	}
+}
+
 static int
 ip6_getpmtu(struct route_in6 *ro_pmtu, struct route_in6 *ro,
     struct ifnet *ifp, struct in6_addr *dst, u_long *mtup,