diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c --- a/sys/netpfil/pf/pf.c +++ b/sys/netpfil/pf/pf.c @@ -7709,7 +7709,7 @@ if (__predict_false(ip_divert_ptr != NULL) && ((ipfwtag = m_tag_locate(m, MTAG_IPFW_RULE, 0, NULL)) != NULL)) { struct ipfw_rule_ref *rr = (struct ipfw_rule_ref *)(ipfwtag+1); - if (rr->info & IPFW_IS_DIVERT && rr->rulenum == 0) { + if (rr->info & IPFW_IS_DIVERT && ((rr->rulenum - 1) == dir)) { if (pd.pf_mtag == NULL && ((pd.pf_mtag = pf_get_mtag(m)) == NULL)) { action = PF_DROP; @@ -8040,6 +8040,9 @@ DPFPRINTF(PF_DEBUG_MISC, ("pf: failed to allocate divert tag\n")); } + } else if (PACKET_LOOPED(&pd)) { + /* this flag will be outdated if the pkt is forwarded */ + pd.pf_mtag->flags &= ~PF_MTAG_FLAG_PACKET_LOOPED; } if (pd.act.log) { diff --git a/tests/sys/netpfil/pf/Makefile b/tests/sys/netpfil/pf/Makefile --- a/tests/sys/netpfil/pf/Makefile +++ b/tests/sys/netpfil/pf/Makefile @@ -2,10 +2,12 @@ PACKAGE= tests TESTSDIR= ${TESTSBASE}/sys/netpfil/pf +BINDIR= ${TESTSDIR} TESTS_SUBDIRS+= ioctl ATF_TESTS_SH+= altq \ anchor \ + divert-to \ dup \ ether \ forward \ @@ -45,6 +47,8 @@ # Tests reuse jail names and so cannot run in parallel. TEST_METADATA+= is_exclusive=true +PROGS= divapp + ${PACKAGE}FILES+= CVE-2019-5597.py \ CVE-2019-5598.py \ daytime_inetd.conf \ diff --git a/tests/sys/netpfil/pf/divapp.c b/tests/sys/netpfil/pf/divapp.c new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/divapp.c @@ -0,0 +1,142 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Igor Ostapenko + * + * 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 PROJECT 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 PROJECT 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. + */ + +/* Used by tests like divert-to.sh */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +struct context { + unsigned short divert_port; + bool divert_back; + + int fd; + struct sockaddr_in sin; + socklen_t sin_len; + char pkt[IP_MAXPACKET]; + ssize_t pkt_n; +}; + +static void +init(struct context *c) +{ + c->fd = socket(PF_DIVERT, SOCK_RAW, 0); + if (c->fd == -1) + errx(EX_OSERR, "Cannot create divert socket."); + + memset(&c->sin, 0, sizeof(c->sin)); + c->sin.sin_family = AF_INET; // TODO: AF_DIVERT does not work, a bug? + c->sin.sin_port = htons(c->divert_port); + c->sin.sin_addr.s_addr = INADDR_ANY; + c->sin_len = sizeof(struct sockaddr_in); + + if (bind(c->fd, (struct sockaddr *) &c->sin, c->sin_len) != 0) + errx(EX_OSERR, "Cannot bind divert socket."); +} + +static ssize_t +recv_pkt(struct context *c) +{ + fd_set readfds; + struct timeval timeout; + int s; + + FD_ZERO(&readfds); + FD_SET(c->fd, &readfds); + timeout.tv_sec = 3; + timeout.tv_usec = 0; + + s = select(c->fd + 1, &readfds, 0, 0, &timeout); + if (s == -1) + errx(EX_IOERR, "recv_pkt: select() errors."); + if (s != 1) // timeout + return -1; + + c->pkt_n = recvfrom(c->fd, c->pkt, sizeof(c->pkt), 0, + (struct sockaddr *) &c->sin, &c->sin_len); + if (c->pkt_n == -1) + errx(EX_IOERR, "recv_pkt: recvfrom() errors."); + + return (c->pkt_n); +} + +static void +send_pkt(struct context *c) +{ + ssize_t n; + + n = sendto(c->fd, c->pkt, c->pkt_n, 0, + (struct sockaddr *) &c->sin, c->sin_len); + if (n != c->pkt_n) + errx(EX_IOERR, "send_pkt: sendto() errors."); +} + +int +main(int argc, char *argv[]) +{ + struct context c; + int npkt; + + if (argc < 2) + errx(EX_USAGE, + "Usage: %s [divert-back]", argv[0]); + + memset(&c, 0, sizeof(struct context)); + + c.divert_port = (unsigned short) strtol(argv[1], NULL, 10); + if (c.divert_port == 0) + errx(EX_USAGE, "divert port is not defined."); + + if (argc >= 3 && strcmp(argv[2], "divert-back") == 0) + c.divert_back = true; + + + init(&c); + + npkt = 0; + while (recv_pkt(&c) > 0) { + if (c.divert_back) + send_pkt(&c); + npkt++; + if (npkt >= 10) + break; + } + + if (npkt != 1) + errx(EXIT_FAILURE, "%d: npkt=%d.", c.divert_port, npkt); + + return EXIT_SUCCESS; +} diff --git a/tests/sys/netpfil/pf/divert-to.sh b/tests/sys/netpfil/pf/divert-to.sh new file mode 100644 --- /dev/null +++ b/tests/sys/netpfil/pf/divert-to.sh @@ -0,0 +1,292 @@ +# +# SPDX-License-Identifier: BSD-2-Clause +# +# Copyright (c) 2023 Igor Ostapenko +# +# 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. + +# +# pf divert-to action test cases +# +# -----------| |-- |----| ----| |----------- +# ( ) inbound |pf_check_in| ) -> |host| -> ( ) |pf_check_out| outbound ) +# -----------| | |-- |----| ----| | |----------- +# | | +# \|/ \|/ +# |------| |------| +# |divapp| |divapp| +# |------| |------| +# +# The basic cases: +# - inbound > diverted | divapp terminated +# - inbound > diverted > inbound | host terminated +# - inbound > diverted > outbound | network terminated +# - outbound > diverted | divapp terminated +# - outbound > diverted > outbound | network terminated +# - outbound > diverted > inbound | e.g. host terminated +# +# When a packet is diverted, forwarded, and possibly diverted again: +# - inbound > diverted > inbound > forwarded +# > outbound | network terminated +# - inbound > diverted > inbound > forwarded +# > outbound > diverted > outbound | network terminated +# +# Test case naming legend: +# in - inbound +# div - diverted +# out - outbound +# fwd - forwarded +# + +. $(atf_get_srcdir)/utils.subr + +divert_init() +{ + if ! kldstat -q -m ipdivert; then + atf_skip "This test requires ipdivert" + fi +} + +atf_test_case "in_div" "cleanup" +in_div_head() +{ + atf_set descr 'Test inbound > diverted | divapp terminated' + atf_set require.user root +} +in_div_body() +{ + pft_init + divert_init + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000" + + jexec div $(atf_get_srcdir)/divapp 2000 & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is expected to "eat" the packet + atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +in_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "in_div_in" "cleanup" +in_div_in_head() +{ + atf_set descr 'Test inbound > diverted > inbound | host terminated' + atf_set require.user root +} +in_div_in_body() +{ + pft_init + divert_init + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 divert-back & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +in_div_in_cleanup() +{ + pft_cleanup +} + +atf_test_case "out_div" "cleanup" +out_div_head() +{ + atf_set descr 'Test outbound > diverted | divapp terminated' + atf_set require.user root +} +out_div_body() +{ + pft_init + divert_init + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq no state" \ + "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is expected to "eat" the packet + atf_check -s not-exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +out_div_cleanup() +{ + pft_cleanup +} + +atf_test_case "out_div_out" "cleanup" +out_div_out_head() +{ + atf_set descr 'Test outbound > diverted > outbound | network terminated' + atf_set require.user root +} +out_div_out_body() +{ + pft_init + divert_init + + epair=$(vnet_mkepair) + vnet_mkjail div ${epair}b + ifconfig ${epair}a 192.0.2.1/24 up + jexec div ifconfig ${epair}b 192.0.2.2/24 up + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + jexec div pfctl -e + pft_set_rules div \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq no state" \ + "pass out inet proto icmp icmp-type echorep divert-to 127.0.0.1 port 2000 no state" + + jexec div $(atf_get_srcdir)/divapp 2000 divert-back & + divapp_pid=$! + # Wait for the divapp to be ready + sleep 1 + + # divapp is NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 192.0.2.2 + + wait $divapp_pid +} +out_div_out_cleanup() +{ + pft_cleanup +} + +atf_test_case "in_div_in_fwd_out_div_out" "cleanup" +in_div_in_fwd_out_div_out_head() +{ + atf_set descr 'Test inbound > diverted > inbound > forwarded > outbound > diverted > outbound | network terminated' + atf_set require.user root +} +in_div_in_fwd_out_div_out_body() +{ + pft_init + divert_init + + # host router site + epair0=$(vnet_mkepair) + epair1=$(vnet_mkepair) + + vnet_mkjail router ${epair0}b ${epair1}a + ifconfig ${epair0}a 192.0.2.1/24 up + jexec router sysctl net.inet.ip.forwarding=1 + jexec router ifconfig ${epair0}b 192.0.2.2/24 up + jexec router ifconfig ${epair1}a 198.51.100.1/24 up + + vnet_mkjail site ${epair1}b + jexec site ifconfig ${epair1}b 198.51.100.2/24 up + jexec site route add default 198.51.100.1 + + route add -net 198.51.100.0/24 192.0.2.2 + + # Sanity check + atf_check -s exit:0 -o ignore ping -c3 192.0.2.2 + + # Should be routed without pf + atf_check -s exit:0 -o ignore ping -c3 198.51.100.2 + + jexec router pfctl -e + pft_set_rules router \ + "pass all" \ + "pass in inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2001 no state" \ + "pass out inet proto icmp icmp-type echoreq divert-to 127.0.0.1 port 2002 no state" + + jexec router $(atf_get_srcdir)/divapp 2001 divert-back & + indivapp_pid=$! + jexec router $(atf_get_srcdir)/divapp 2002 divert-back & + outdivapp_pid=$! + # Wait for the divappS to be ready + sleep 1 + + # Both divappS are NOT expected to "eat" the packet + atf_check -s exit:0 -o ignore ping -c1 198.51.100.2 + + wait $indivapp_pid && wait $outdivapp_pid +} +in_div_in_fwd_out_div_out_cleanup() +{ + pft_cleanup +} + +atf_init_test_cases() +{ + atf_add_test_case "in_div" + atf_add_test_case "in_div_in" + + atf_add_test_case "out_div" + atf_add_test_case "out_div_out" + + atf_add_test_case "in_div_in_fwd_out_div_out" +}