ip: Defer checks for an unspecified dstaddr until after pfil hooks

To comply with Common Criteria certification requirements, it may be
necessary to ensure that packets to 0.0.0.0/::0 are dropped and logged
by the system firewall.  Currently, such packets are dropped by
ip_input() and ip6_input() before reaching pfil hooks; let's defer the
checks slightly to give firewalls a chance to drop the packets
themselves, as this gives better observability.  Add some regression
tests for this with pf+pflog.

Note that prior to commit 713264f6b8, v4 packets to the unspecified
address were not dropped by the IP stack at all.

Note that ip_forward() and ip6_forward() ensure that such packets are
not forwarded; they are passed back unmodified.

Add a regression test which ensures that such packets are visible to
pflog.

Reviewed by:	glebius
MFC after:	3 weeks
Sponsored by:	Klara, Inc.
Sponsored by:	OPNsense
Differential Revision:	https://reviews.freebsd.org/D48163
This commit is contained in:
Mark Johnston 2025-01-16 15:46:37 +00:00
parent 886396f1b1
commit 40faf87894
4 changed files with 97 additions and 8 deletions

View file

@ -521,11 +521,6 @@ ip_input(struct mbuf *m)
goto bad;
}
}
/* The unspecified address can appear only as a src address - RFC1122 */
if (__predict_false(ntohl(ip->ip_dst.s_addr) == INADDR_ANY)) {
IPSTAT_INC(ips_badaddr);
goto bad;
}
if (m->m_pkthdr.csum_flags & CSUM_IP_CHECKED) {
sum = !(m->m_pkthdr.csum_flags & CSUM_IP_VALID);
@ -641,6 +636,17 @@ tooshort:
}
}
passin:
/*
* The unspecified address can appear only as a src address - RFC1122.
*
* The check is deferred to here to give firewalls a chance to block
* (and log) such packets. ip_tryforward() will not process such
* packets.
*/
if (__predict_false(ntohl(ip->ip_dst.s_addr) == INADDR_ANY)) {
IPSTAT_INC(ips_badaddr);
goto bad;
}
/*
* Process options and, if not destined for us,

View file

@ -107,6 +107,7 @@ ip6_tryforward(struct mbuf *m)
IN6_IS_ADDR_MULTICAST(&ip6->ip6_dst) ||
IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_dst) ||
IN6_IS_ADDR_LINKLOCAL(&ip6->ip6_src) ||
IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst) ||
IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_src) ||
in6_localip(&ip6->ip6_dst))
return (m);

View file

@ -621,10 +621,10 @@ ip6_input(struct mbuf *m)
IP_PROBE(receive, NULL, NULL, ip6, rcvif, NULL, ip6);
/*
* Check against address spoofing/corruption.
* Check against address spoofing/corruption. The unspecified address
* is checked further below.
*/
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src) ||
IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst)) {
if (IN6_IS_ADDR_MULTICAST(&ip6->ip6_src)) {
/*
* XXX: "badscope" is not very suitable for a multicast source.
*/
@ -749,6 +749,17 @@ ip6_input(struct mbuf *m)
}
passin:
/*
* The check is deferred to here to give firewalls a chance to block
* (and log) such packets. ip6_tryforward() will not process such
* packets.
*/
if (__predict_false(IN6_IS_ADDR_UNSPECIFIED(&ip6->ip6_dst))) {
IP6STAT_INC(ip6s_badscope);
in6_ifstat_inc(rcvif, ifs6_in_addrerr);
goto bad;
}
/*
* Disambiguate address scope zones (if there is ambiguity).
* We first make sure that the original source or destination address

View file

@ -195,9 +195,80 @@ state_max_cleanup()
pft_cleanup
}
atf_test_case "unspecified_v4" "cleanup"
unspecified_v4_head()
{
atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
atf_set require.user root
}
unspecified_v4_body()
{
pflog_init
vnet_mkjail alcatraz
jexec alcatraz ifconfig lo0 inet 127.0.0.1
jexec alcatraz route add default 127.0.0.1
jexec alcatraz pfctl -e
jexec alcatraz ifconfig pflog0 up
pft_set_rules alcatraz "block log on lo0 to 0.0.0.0"
jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
sleep 1 # Wait for tcpdump to start
atf_check -s not-exit:0 -o ignore -e ignore \
jexec alcatraz ping -S 127.0.0.1 -c 1 0.0.0.0
atf_check -o match:".*: block out on lo0: 127.0.0.1 > 0.0.0.0: ICMP echo request,.*" \
cat pflog.txt
}
unspecified_v4_cleanup()
{
pft_cleanup
}
atf_test_case "unspecified_v6" "cleanup"
unspecified_v6_head()
{
atf_set descr 'Ensure that packets to the unspecified address are visible to pfil hooks'
atf_set require.user root
}
unspecified_v6_body()
{
pflog_init
vnet_mkjail alcatraz
jexec alcatraz ifconfig lo0 up
jexec alcatraz route -6 add ::0 ::1
jexec alcatraz pfctl -e
jexec alcatraz ifconfig pflog0 up
pft_set_rules alcatraz "block log on lo0 to ::0"
jexec alcatraz tcpdump -n -e -ttt --immediate-mode -l -U -i pflog0 >> pflog.txt &
sleep 1 # Wait for tcpdump to start
atf_check -s not-exit:0 -o ignore -e ignore \
jexec alcatraz ping -6 -S ::1 -c 1 ::0
cat pflog.txt
atf_check -o match:".*: block out on lo0: ::1 > ::: ICMP6, echo request,.*" \
cat pflog.txt
}
unspecified_v6_cleanup()
{
pft_cleanup
}
atf_init_test_cases()
{
atf_add_test_case "malformed"
atf_add_test_case "matches"
atf_add_test_case "state_max"
atf_add_test_case "unspecified_v4"
atf_add_test_case "unspecified_v6"
}