The branch main has been updated by kp: URL: https://cgit.FreeBSD.org/src/commit/?id=8a309785c9b186c809a5d4b017fc8cf849af1ddd
commit 8a309785c9b186c809a5d4b017fc8cf849af1ddd Author: Kristof Provost <k...@freebsd.org> AuthorDate: 2025-08-04 08:29:09 +0000 Commit: Kristof Provost <k...@freebsd.org> CommitDate: 2025-08-05 22:27:16 +0000 pf: fix handling unreassembled fragments If we handle a fragment and are configured not to reassemble it the pd->proto field will show the layer 4 protocol (i.e. UDP,TCP,SCTP,...) but pd->virtual_proto will show we're a fragment. In that case we also don't have the layer 4 checksum pointer. Have code that cares about L4 (e.g. NAT) check virtual_proto so it doesn't try to dereference a NULL pcksum field. PR: 288549 Reported by: Danilo Egea Gondolfo <dan...@freebsd.org> Sponsored by: Rubicon Communications, LLC ("Netgate") Differential Revision: https://reviews.freebsd.org/D51722 --- sys/netpfil/pf/pf.c | 4 +-- tests/sys/netpfil/pf/Makefile | 1 + tests/sys/netpfil/pf/frag4.py | 72 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 2 deletions(-) diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c index 19702fde7d22..3fa7789efcfe 100644 --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -621,7 +621,7 @@ static void pf_packet_rework_nat(struct pf_pdesc *pd, int off, struct pf_state_key *nk) { - switch (pd->proto) { + switch (pd->virtual_proto) { case IPPROTO_TCP: { struct tcphdr *th = &pd->hdr.tcp; @@ -6391,7 +6391,7 @@ pf_translate_compat(struct pf_test_ctx *ctx) KASSERT(ctx->sk != NULL, ("%s: null sk", __func__)); KASSERT(ctx->nk != NULL, ("%s: null nk", __func__)); - switch (pd->proto) { + switch (pd->virtual_proto) { case IPPROTO_TCP: if (PF_ANEQ(&pd->nsaddr, &nk->addr[pd->sidx], pd->af) || nk->port[pd->sidx] != pd->nsport) { diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile index 404d5adfb07a..616ffe560b3a 100644 --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -55,6 +55,7 @@ ATF_TESTS_SH+= altq \ tcp \ tos +ATF_TESTS_PYTEST+= frag4.py ATF_TESTS_PYTEST+= frag6.py ATF_TESTS_PYTEST+= header.py ATF_TESTS_PYTEST+= icmp.py diff --git a/tests/sys/netpfil/pf/frag4.py b/tests/sys/netpfil/pf/frag4.py new file mode 100644 index 000000000000..3303d2ee7780 --- /dev/null +++ b/tests/sys/netpfil/pf/frag4.py @@ -0,0 +1,72 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2025 Rubicon Communications, LLC (Netgate) +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +import pytest +from utils import DelayedSend +from atf_python.sys.net.tools import ToolsHelper +from atf_python.sys.net.vnet import VnetTestTemplate + +class TestFrag4_NoReassemble(VnetTestTemplate): + REQUIRED_MODULES = [ "pf" ] + TOPOLOGY = { + "vnet1": {"ifaces": ["if1"]}, + "vnet2": {"ifaces": ["if1", "if2"]}, + "vnet3": {"ifaces": ["if2"]}, + "if1": {"prefixes4": [("192.0.2.1/24", "192.0.2.2/24")]}, + "if2": {"prefixes4": [("198.51.100.1/24", "198.51.100.2/24")]}, + } + + def vnet2_handler(self, vnet): + outifname = vnet.iface_alias_map["if2"].name + + ToolsHelper.print_output("/sbin/pfctl -e") + ToolsHelper.pf_rules([ + "set reassemble no", + "nat on %s from 192.0.2.0/24 to any -> (%s)" % (outifname, outifname), + "pass out" + ]) + + ToolsHelper.print_output("/sbin/sysctl net.inet.ip.forwarding=1") + + def vnet3_handler(self, vnet): + # We deliberately don't set the default gateway here, so if we get a + # reply from this we know we did NAT in vnet2 + pass + + @pytest.mark.require_user("root") + @pytest.mark.require_progs(["scapy"]) + def test_udp_frag(self): + ToolsHelper.print_output("/sbin/route add default 192.0.2.2") + ToolsHelper.print_output("/sbin/ping -c 3 198.51.100.2") + + # Import in the correct vnet, so at to not confuse Scapy + import scapy.all as sp + + pkt = sp.IP(dst="198.51.100.2", frag=123) \ + / sp.UDP(dport=12345, sport=54321) + reply = sp.sr1(pkt, timeout=3) + # We don't expect a reply + assert not reply