netinet6: Don't return non-IPv6 enabled interfaces from in6_getlinkifnet()

There are scenarios where we can end up looking up an interface by its scope and
turn up an interface that doesn't have IPv6 enabled on it. If that happens we
could end up dereferencing a NULL pointer accessing ifp->if_afdata[AF_INET6].
Check for this.

One such scenario is if a firewall rewrites a destination address to a
link-local address, with an embedded scope for such an interface. Attach a test
case which provokes this.

PR:		288263
Reported by:	Robert Morris <rtm@lcs.mit.edu>
Reviewed by:	zlei
Sponsored by:	Rubicon Communications, LLC ("Netgate")
Differential Revision:	https://reviews.freebsd.org/D51500
This commit is contained in:
Kristof Provost
2025-07-24 16:31:41 +02:00
parent da50f49977
commit e9ca883b12
2 changed files with 56 additions and 2 deletions
+16 -1
View File
@@ -505,8 +505,23 @@ in6_set_unicast_scopeid(struct in6_addr *in6, uint32_t scopeid)
struct ifnet*
in6_getlinkifnet(uint32_t zoneid)
{
struct ifnet *ifp;
return (ifnet_byindex((u_short)zoneid));
ifp = ifnet_byindex((u_short)zoneid);
if (ifp == NULL)
return (NULL);
/* An interface might not be IPv6 capable. */
if (ifp->if_afdata[AF_INET6] == NULL) {
log(LOG_NOTICE,
"%s: embedded scope points to an interface without "
"IPv6: %s%%%d.\n", __func__,
if_name(ifp), zoneid);
return (NULL);
}
return (ifp);
}
/*
+40 -1
View File
@@ -33,7 +33,7 @@
from atf_python.sys.net.vnet import VnetTestTemplate
class TestNAT64(VnetTestTemplate):
REQUIRED_MODULES = [ "pf" ]
REQUIRED_MODULES = [ "pf", "pflog" ]
TOPOLOGY = {
"vnet1": {"ifaces": ["if1"]},
"vnet2": {"ifaces": ["if1", "if2"]},
@@ -92,12 +92,15 @@ def vnet3_handler(self, vnet):
def vnet2_handler(self, vnet):
ifname = vnet.iface_alias_map["if1"].name
ToolsHelper.print_output("/sbin/sysctl net.inet6.ip6.forwarding=1")
ToolsHelper.print_output("/sbin/route add default 192.0.2.2")
ToolsHelper.print_output("/sbin/pfctl -e")
ToolsHelper.pf_rules([
"pass inet6 proto icmp6",
"pass in on %s inet6 af-to inet from 192.0.2.1" % ifname])
vnet.pipe.send(socket.if_nametoindex("pflog0"))
@pytest.mark.require_user("root")
@pytest.mark.require_progs(["scapy"])
def test_tcp_rst(self):
@@ -287,3 +290,39 @@ def test_bad_len(self):
reply = sp.sr1(packet, timeout=3)
# We don't expect a reply to a corrupted packet
assert not reply
@pytest.mark.require_user("root")
@pytest.mark.require_progs(["scapy"])
def test_noip6(self):
"""
PR 288263: link-local target address in icmp6 ADVERT can cause NULL deref
"""
ifname = self.vnet.iface_alias_map["if1"].name
gw_mac = self.vnet.iface_alias_map["if1"].epairb.ether
scopeid = self.wait_object(self.vnet_map["vnet2"].pipe)
ToolsHelper.print_output("/sbin/route -6 add default 2001:db8::1")
import scapy.all as sp
pkt = sp.Ether(dst=gw_mac) \
/ sp.IPv6(dst="64:ff9b::203.0.113.2") \
/ sp.ICMPv6ND_NA(tgt="FFA2:%x:2821:125F:1D27:B3B2:3F6F:C43C" % scopeid)
pkt.show()
sp.hexdump(pkt)
s = DelayedSend(pkt, sendif=ifname)
packets = sp.sniff(iface=ifname, timeout=5)
for r in packets:
r.show()
# Try scope id that likely doesn't have an interface at all
pkt = sp.Ether(dst=gw_mac) \
/ sp.IPv6(dst="64:ff9b::203.0.113.2") \
/ sp.ICMPv6ND_NA(tgt="FFA2:%x:2821:125F:1D27:B3B2:3F6F:C43C" % 255)
pkt.show()
sp.hexdump(pkt)
s = DelayedSend(pkt, sendif=ifname)
packets = sp.sniff(iface=ifname, timeout=5)
for r in packets:
r.show()