--- 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,