[Bug 296598] pf: IPsec tunnel mode over if_wg, inner header not visible to pf

Krämer, Lars <[email protected]> Tue, 28 Jul 2026 08:18:57 +0000
Newsgroups gmane.os.freebsd.devel.net
Message-ID <[email protected]>
Hi,

I've tracked down a bug where TCP connections over IPsec die after a short =
time when IPsec runs over if_wg.
I would appreciate if someone could take a look at it.
Lots of diagnostic data is in the bugzilla report: https://bugs.freebsd.org=
/bugzilla/show_bug.cgi?id=3D296598

In a few words, ipsec4_common_input_cb tries stripping the outer IP-IP head=
er from the packet before passing it to pf hooks.
When a packet arrives via if_wg, the head of the mbuf is just the outer IP =
header and m_striphdr leaves behind a zero-length head.
This breaks when pf_test tries to read the IP header via mtod(), because th=
is condition is never checked, and pf reads the outer header instead.
The patch below fixes this by performing a pullup on the mbuf, though that =
might not necessarily be the correct way of handling this.

--- sys/netipsec/ipsec_input.c.orig
+++ sys/netipsec/ipsec_input.c
@@ -409,7 +409,10 @@
                        goto bad;
                }
                /* enc0: strip outer IPv4 header */
-               m_striphdr(m, 0, ip->ip_hl << 2);
+               m_striphdr(m, 0, ip->ip_hl << 2);
+               m =3D m_pullup(m, sizeof(struct ip));
+               if (m =3D=3D NULL) { error =3D ENOBUFS; goto bad; }
        }
 #ifdef INET6
        /* IPv6-in-IP encapsulation. */
@@ -421,7 +424,10 @@
                        goto bad;
                }
                /* enc0: strip IPv4 header, keep IPv6 header only */
-               m_striphdr(m, 0, ip->ip_hl << 2);
+               m_striphdr(m, 0, ip->ip_hl << 2);
+               m =3D m_pullup(m, sizeof(struct ip6_hdr));
+               if (m =3D=3D NULL) { error =3D ENOBUFS; goto bad; }
        }
 #endif /* INET6 */

Reproduced on 13.2, 14.3 (OPNSense) and vanilla 15.1.
Thanks!
Lars Kr=E4mer