If an RTM_ADD command on a routing socket includes an RTA_IFA sockaddr,
and that sockaddr is an exact match for one of the interfaces in the
relevant routing domain, any RTA_IFP sockaddr in the same message is
ignored.  If there are multiple interfaces with the same IP address,
this can cause packets to be sent out the wrong interface.  Fix this
by skipping the exact-match check on RTA_IFA if an RTA_IFP sockaddr
was sent.

The RTA_IFP sockaddr was also being ignored if there was no interface
with the requested index.  Return ENXIO to userspace instead.

The bug can easily be seen by running the following commands as root
on a machine where vether0 and vether1 do not exist, and on which
the subnet 192.0.2.0/24 (reserved for documentation) is not in use:

# ifconfig vether0 destroy 2>/dev/null
# ifconfig vether1 destroy 2>/dev/null
# dummy_mac=fe:ff:ff:ff:ff:ff dummy_ip=192.0.2.5
# ifconfig vether0 create lladdr "$dummy_mac"
# ifconfig vether1 create lladdr "$dummy_mac"
# ifconfig vether0 inet "$dummy_ip" prefixlen 32
# route -n delete "$dummy_ip/32" "$dummy_ip"
# ifconfig vether1 inet "$dummy_ip" prefixlen 32
# route -n delete "$dummy_ip/32" "$dummy_ip"
# route -n add -inet 192.0.2.6 -static -iface -llinfo -link vether1 -ifp 
vether1 -inet -ifa "$dummy_ip"
# route -n show -inet

On a system with an unpatched kernel, the final command will show
that the route to 192.0.2.6 is via vether0.  If this patch has been
applied, the route to 192.0.2.6 will be (correctly) via vether1.

---
 sys/net/rtsock.c | 26 ++++++++++++++++++++------
 1 file changed, 20 insertions(+), 6 deletions(-)

diff --git sys/net/rtsock.c sys/net/rtsock.c
index fa84ddc25e5..dc8446bd78f 100644
--- sys/net/rtsock.c
+++ sys/net/rtsock.c
@@ -1235,7 +1235,8 @@ rtm_getifa(struct rt_addrinfo *info, unsigned int rtid)
                struct sockaddr_dl *sdl;
 
                sdl = satosdl(info->rti_info[RTAX_IFP]);
-               ifp = if_get(sdl->sdl_index);
+               if ((ifp = if_get(sdl->sdl_index)) == NULL)
+                       return (ENXIO);
        }
 
 #ifdef IPSEC
@@ -1246,11 +1247,19 @@ rtm_getifa(struct rt_addrinfo *info, unsigned int rtid)
         * enc0.
         */
        if (info->rti_info[RTAX_DST] &&
-           info->rti_info[RTAX_DST]->sa_family == PF_KEY)
+           info->rti_info[RTAX_DST]->sa_family == PF_KEY) {
                info->rti_ifa = enc_getifa(rtid, 0);
+
+               if (info->rti_ifa != NULL && ifp != NULL &&
+                   ifp != info->rti_ifa->ifa_ifp) {
+                       if_put(ifp);
+                       return (EADDRNOTAVAIL);
+               }
+       }
 #endif
 
-       if (info->rti_ifa == NULL && info->rti_info[RTAX_IFA] != NULL)
+       if (info->rti_ifa == NULL && ifp == NULL &&
+           info->rti_info[RTAX_IFA] != NULL)
                info->rti_ifa = ifa_ifwithaddr(info->rti_info[RTAX_IFA], rtid);
 
        if (info->rti_ifa == NULL) {
@@ -1273,10 +1282,15 @@ rtm_getifa(struct rt_addrinfo *info, unsigned int rtid)
                            sa, sa, rtid);
        }
 
-       if_put(ifp);
-
-       if (info->rti_ifa == NULL)
+       if (info->rti_ifa == NULL) {
+               if_put(ifp);
                return (ENETUNREACH);
+       }
+
+       if (ifp != NULL && ifp != info->rti_ifa->ifa_ifp)
+               panic("rtm_getifa: returned route to wrong interface");
+
+       if_put(ifp);
 
        return (0);
 }

Attachment: signature.asc
Description: OpenPGP digital signature

Reply via email to