From: Hengyu Liang <[email protected]>

ip6_route_info_create_nh() promotes routes that use the loopback device
as their nexthop device to reject routes, except for local and anycast
routes and routes to the loopback address, as true routes via the
loopback device would result in kernel looping.

Before commit 21ec92774d15 ("net: ipv6: fix panic when IPv4 route
references loopback IPv6 nexthop"), fib6_nh_init() also treated these
routes as reject routes, so it neither validated their gateway nor
checked the state of the loopback device. That commit restricted the
check in fib6_nh_init() to explicit reject routes to fix IPv6 nexthop
objects that use the loopback device. As a side effect, the nexthop of a
route via the loopback device is now validated like the nexthop of a
regular route before the route is promoted to a reject route, and adding
such a route fails in cases that used to work:

  # ip link set dev lo down
  # ip -6 route add 2001:db8::/32 dev lo
  Error: Nexthop device is not up.
  # ip link set dev lo up
  # ip -6 route add 2001:db8::/32 via 2001:db8:1::1 dev lo
  RTNETLINK answers: No route to host
  # ip -6 route add default via ::ffff:192.0.2.1 dev lo
  RTNETLINK answers: No route to host
  # sysctl -qw net.ipv6.conf.lo.disable_ipv6=1
  # ip -6 route add 2001:db8::/32 dev lo
  Error: IPv6 is disabled on nexthop device.

As the loopback device is down in a new network namespace, the first
case affects routes added before the loopback device is brought up. The
SIOCADDRT ioctl fails in the same way.

Restore the previous check in fib6_nh_init() for routes created by
ip6_route_info_create_nh(). IPv6 nexthop objects and IPv4 routes with an
IPv6 gateway are never promoted to reject routes, so they keep the
current check and the panic fixed by the above commit does not come
back.

Fixes: 21ec92774d15 ("net: ipv6: fix panic when IPv4 route references loopback 
IPv6 nexthop")
Cc: [email protected]
Signed-off-by: Hengyu Liang <[email protected]>
---
 net/ipv6/route.c | 29 +++++++++++++++++++++++------
 1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 153ce16628c1..49ff87aa3756 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -3592,13 +3592,14 @@ static bool fib6_is_reject(u32 flags, struct net_device 
*dev, int addr_type)
        return false;
 }
 
-int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
-                struct fib6_config *cfg, gfp_t gfp_flags,
-                struct netlink_ext_ack *extack)
+static int __fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
+                         struct fib6_config *cfg, bool lo_reject,
+                         gfp_t gfp_flags, struct netlink_ext_ack *extack)
 {
        netdevice_tracker *dev_tracker = &fib6_nh->fib_nh_dev_tracker;
        struct net_device *dev = NULL;
        struct inet6_dev *idev = NULL;
+       bool reject;
        int err;
 
        if (!ipv6_mod_enabled()) {
@@ -3646,9 +3647,17 @@ int fib6_nh_init(struct net *net, struct fib6_nh 
*fib6_nh,
        fib6_nh->fib_nh_weight = 1;
 
        /* Reset the nexthop device to the loopback device in case of reject
-        * routes.
+        * routes. If requested, also treat routes via the loopback device as
+        * reject routes, as ip6_route_info_create_nh() promotes them to reject
+        * routes and their nexthop does not need to be validated.
         */
-       if (cfg->fc_flags & RTF_REJECT) {
+       if (lo_reject)
+               reject = fib6_is_reject(cfg->fc_flags, dev,
+                                       ipv6_addr_type(&cfg->fc_dst));
+       else
+               reject = cfg->fc_flags & RTF_REJECT;
+
+       if (reject) {
                /* hold loopback dev/idev if we haven't done so. */
                if (dev != net->loopback_dev) {
                        if (dev) {
@@ -3725,6 +3734,13 @@ int fib6_nh_init(struct net *net, struct fib6_nh 
*fib6_nh,
        return err;
 }
 
+int fib6_nh_init(struct net *net, struct fib6_nh *fib6_nh,
+                struct fib6_config *cfg, gfp_t gfp_flags,
+                struct netlink_ext_ack *extack)
+{
+       return __fib6_nh_init(net, fib6_nh, cfg, false, gfp_flags, extack);
+}
+
 void fib6_nh_release(struct fib6_nh *fib6_nh)
 {
        struct rt6_exception_bucket *bucket;
@@ -3917,7 +3933,8 @@ static int ip6_route_info_create_nh(struct fib6_info *rt,
        } else {
                int addr_type;
 
-               err = fib6_nh_init(net, rt->fib6_nh, cfg, gfp_flags, extack);
+               err = __fib6_nh_init(net, rt->fib6_nh, cfg, true, gfp_flags,
+                                    extack);
                if (err)
                        goto out_release;
 
-- 
2.53.0


Reply via email to