]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - net/ipv6/route.c
net: fix a potential rcu_read_lock() imbalance in rt6_fill_node()
[karo-tx-linux.git] / net / ipv6 / route.c
index de2b1decd78668d832937cf525115152411bb075..8e600f827fe7c9b9e43c3cccd6332c4ce5d964d1 100644 (file)
@@ -228,9 +228,10 @@ static struct rt6_info ip6_blk_hole_entry_template = {
 
 /* allocate dst with ip6_dst_ops */
 static inline struct rt6_info *ip6_dst_alloc(struct dst_ops *ops,
-                                            struct net_device *dev)
+                                            struct net_device *dev,
+                                            int flags)
 {
-       struct rt6_info *rt = dst_alloc(ops, dev, 0, 0, 0);
+       struct rt6_info *rt = dst_alloc(ops, dev, 0, 0, flags);
 
        memset(&rt->rt6i_table, 0, sizeof(*rt) - sizeof(struct dst_entry));
 
@@ -355,7 +356,7 @@ out:
 #ifdef CONFIG_IPV6_ROUTER_PREF
 static void rt6_probe(struct rt6_info *rt)
 {
-       struct neighbour *neigh = rt ? rt->rt6i_nexthop : NULL;
+       struct neighbour *neigh;
        /*
         * Okay, this does not seem to be appropriate
         * for now, however, we need to check if it
@@ -364,8 +365,10 @@ static void rt6_probe(struct rt6_info *rt)
         * Router Reachability Probe MUST be rate-limited
         * to no more than one per minute.
         */
+       rcu_read_lock();
+       neigh = rt ? dst_get_neighbour(&rt->dst) : NULL;
        if (!neigh || (neigh->nud_state & NUD_VALID))
-               return;
+               goto out;
        read_lock_bh(&neigh->lock);
        if (!(neigh->nud_state & NUD_VALID) &&
            time_after(jiffies, neigh->updated + rt->rt6i_idev->cnf.rtr_probe_interval)) {
@@ -378,8 +381,11 @@ static void rt6_probe(struct rt6_info *rt)
                target = (struct in6_addr *)&neigh->primary_key;
                addrconf_addr_solict_mult(target, &mcaddr);
                ndisc_send_ns(rt->rt6i_dev, NULL, target, &mcaddr, NULL);
-       } else
+       } else {
                read_unlock_bh(&neigh->lock);
+       }
+out:
+       rcu_read_unlock();
 }
 #else
 static inline void rt6_probe(struct rt6_info *rt)
@@ -403,8 +409,11 @@ static inline int rt6_check_dev(struct rt6_info *rt, int oif)
 
 static inline int rt6_check_neigh(struct rt6_info *rt)
 {
-       struct neighbour *neigh = rt->rt6i_nexthop;
+       struct neighbour *neigh;
        int m;
+
+       rcu_read_lock();
+       neigh = dst_get_neighbour(&rt->dst);
        if (rt->rt6i_flags & RTF_NONEXTHOP ||
            !(rt->rt6i_flags & RTF_GATEWAY))
                m = 1;
@@ -421,6 +430,7 @@ static inline int rt6_check_neigh(struct rt6_info *rt)
                read_unlock_bh(&neigh->lock);
        } else
                m = 0;
+       rcu_read_unlock();
        return m;
 }
 
@@ -744,8 +754,7 @@ static struct rt6_info *rt6_alloc_cow(struct rt6_info *ort, const struct in6_add
                        dst_free(&rt->dst);
                        return NULL;
                }
-               rt->rt6i_nexthop = neigh;
-
+               dst_set_neighbour(&rt->dst, neigh);
        }
 
        return rt;
@@ -759,7 +768,7 @@ static struct rt6_info *rt6_alloc_clone(struct rt6_info *ort, const struct in6_a
                rt->rt6i_dst.plen = 128;
                rt->rt6i_flags |= RTF_CACHE;
                rt->dst.flags |= DST_HOST;
-               rt->rt6i_nexthop = neigh_clone(ort->rt6i_nexthop);
+               dst_set_neighbour(&rt->dst, neigh_clone(dst_get_neighbour_raw(&ort->dst)));
        }
        return rt;
 }
@@ -793,7 +802,7 @@ restart:
        dst_hold(&rt->dst);
        read_unlock_bh(&table->tb6_lock);
 
-       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
+       if (!dst_get_neighbour_raw(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
                nrt = rt6_alloc_cow(rt, &fl6->daddr, &fl6->saddr);
        else if (!(rt->dst.flags & DST_HOST))
                nrt = rt6_alloc_clone(rt, &fl6->daddr);
@@ -1042,7 +1051,7 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
        if (unlikely(idev == NULL))
                return NULL;
 
-       rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops, dev);
+       rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops, dev, 0);
        if (unlikely(rt == NULL)) {
                in6_dev_put(idev);
                goto out;
@@ -1057,19 +1066,11 @@ struct dst_entry *icmp6_dst_alloc(struct net_device *dev,
        }
 
        rt->rt6i_idev     = idev;
-       rt->rt6i_nexthop  = neigh;
+       dst_set_neighbour(&rt->dst, neigh);
        atomic_set(&rt->dst.__refcnt, 1);
        dst_metric_set(&rt->dst, RTAX_HOPLIMIT, 255);
        rt->dst.output  = ip6_output;
 
-#if 0  /* there's no chance to use these for ndisc */
-       rt->dst.flags   = ipv6_addr_type(addr) & IPV6_ADDR_UNICAST
-                               ? DST_HOST
-                               : 0;
-       ipv6_addr_copy(&rt->rt6i_dst.addr, addr);
-       rt->rt6i_dst.plen = 128;
-#endif
-
        spin_lock_bh(&icmp6_dst_lock);
        rt->dst.next = icmp6_dst_gc_list;
        icmp6_dst_gc_list = &rt->dst;
@@ -1214,7 +1215,7 @@ int ip6_route_add(struct fib6_config *cfg)
                goto out;
        }
 
-       rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops, NULL);
+       rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops, NULL, DST_NOCOUNT);
 
        if (rt == NULL) {
                err = -ENOMEM;
@@ -1244,7 +1245,7 @@ int ip6_route_add(struct fib6_config *cfg)
        ipv6_addr_prefix(&rt->rt6i_dst.addr, &cfg->fc_dst, cfg->fc_dst_len);
        rt->rt6i_dst.plen = cfg->fc_dst_len;
        if (rt->rt6i_dst.plen == 128)
-              rt->dst.flags = DST_HOST;
+              rt->dst.flags |= DST_HOST;
 
 #ifdef CONFIG_IPV6_SUBTREES
        ipv6_addr_prefix(&rt->rt6i_src.addr, &cfg->fc_src, cfg->fc_src_len);
@@ -1345,12 +1346,12 @@ int ip6_route_add(struct fib6_config *cfg)
                rt->rt6i_prefsrc.plen = 0;
 
        if (cfg->fc_flags & (RTF_GATEWAY | RTF_NONEXTHOP)) {
-               rt->rt6i_nexthop = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev);
-               if (IS_ERR(rt->rt6i_nexthop)) {
-                       err = PTR_ERR(rt->rt6i_nexthop);
-                       rt->rt6i_nexthop = NULL;
+               struct neighbour *neigh = __neigh_lookup_errno(&nd_tbl, &rt->rt6i_gateway, dev);
+               if (IS_ERR(neigh)) {
+                       err = PTR_ERR(neigh);
                        goto out;
                }
+               dst_set_neighbour(&rt->dst, neigh);
        }
 
        rt->rt6i_flags = cfg->fc_flags;
@@ -1581,7 +1582,7 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src,
        dst_confirm(&rt->dst);
 
        /* Duplicate redirect: silently ignore. */
-       if (neigh == rt->dst.neighbour)
+       if (neigh == dst_get_neighbour_raw(&rt->dst))
                goto out;
 
        nrt = ip6_rt_copy(rt);
@@ -1597,7 +1598,7 @@ void rt6_redirect(const struct in6_addr *dest, const struct in6_addr *src,
        nrt->dst.flags |= DST_HOST;
 
        ipv6_addr_copy(&nrt->rt6i_gateway, (struct in6_addr*)neigh->primary_key);
-       nrt->rt6i_nexthop = neigh_clone(neigh);
+       dst_set_neighbour(&nrt->dst, neigh_clone(neigh));
 
        if (ip6_ins_rt(nrt))
                goto out;
@@ -1677,7 +1678,7 @@ again:
           1. It is connected route. Action: COW
           2. It is gatewayed route or NONEXTHOP route. Action: clone it.
         */
-       if (!rt->rt6i_nexthop && !(rt->rt6i_flags & RTF_NONEXTHOP))
+       if (!dst_get_neighbour_raw(&rt->dst) && !(rt->rt6i_flags & RTF_NONEXTHOP))
                nrt = rt6_alloc_cow(rt, daddr, saddr);
        else
                nrt = rt6_alloc_clone(rt, daddr);
@@ -1734,7 +1735,7 @@ static struct rt6_info * ip6_rt_copy(struct rt6_info *ort)
 {
        struct net *net = dev_net(ort->rt6i_dev);
        struct rt6_info *rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops,
-                                           ort->dst.dev);
+                                           ort->dst.dev, 0);
 
        if (rt) {
                rt->dst.input = ort->dst.input;
@@ -2013,7 +2014,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
 {
        struct net *net = dev_net(idev->dev);
        struct rt6_info *rt = ip6_dst_alloc(&net->ipv6.ip6_dst_ops,
-                                           net->loopback_dev);
+                                           net->loopback_dev, 0);
        struct neighbour *neigh;
 
        if (rt == NULL) {
@@ -2025,7 +2026,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
 
        in6_dev_hold(idev);
 
-       rt->dst.flags = DST_HOST;
+       rt->dst.flags |= DST_HOST;
        rt->dst.input = ip6_input;
        rt->dst.output = ip6_output;
        rt->rt6i_idev = idev;
@@ -2042,7 +2043,7 @@ struct rt6_info *addrconf_dst_alloc(struct inet6_dev *idev,
 
                return ERR_CAST(neigh);
        }
-       rt->rt6i_nexthop = neigh;
+       dst_set_neighbour(&rt->dst, neigh);
 
        ipv6_addr_copy(&rt->rt6i_dst.addr, addr);
        rt->rt6i_dst.plen = 128;
@@ -2319,6 +2320,7 @@ static int rt6_fill_node(struct net *net,
        struct nlmsghdr *nlh;
        long expires;
        u32 table;
+       struct neighbour *n;
 
        if (prefix) {   /* user wants prefix routes only */
                if (!(rt->rt6i_flags & RTF_PREFIX_RT)) {
@@ -2407,8 +2409,15 @@ static int rt6_fill_node(struct net *net,
        if (rtnetlink_put_metrics(skb, dst_metrics_ptr(&rt->dst)) < 0)
                goto nla_put_failure;
 
-       if (rt->dst.neighbour)
-               NLA_PUT(skb, RTA_GATEWAY, 16, &rt->dst.neighbour->primary_key);
+       rcu_read_lock();
+       n = dst_get_neighbour(&rt->dst);
+       if (n) {
+               if (nla_put(skb, RTA_GATEWAY, 16, &n->primary_key) < 0) {
+                       rcu_read_unlock();
+                       goto nla_put_failure;
+               }
+       }
+       rcu_read_unlock();
 
        if (rt->dst.dev)
                NLA_PUT_U32(skb, RTA_OIF, rt->rt6i_dev->ifindex);
@@ -2592,6 +2601,7 @@ struct rt6_proc_arg
 static int rt6_info_route(struct rt6_info *rt, void *p_arg)
 {
        struct seq_file *m = p_arg;
+       struct neighbour *n;
 
        seq_printf(m, "%pi6 %02x ", &rt->rt6i_dst.addr, rt->rt6i_dst.plen);
 
@@ -2600,12 +2610,14 @@ static int rt6_info_route(struct rt6_info *rt, void *p_arg)
 #else
        seq_puts(m, "00000000000000000000000000000000 00 ");
 #endif
-
-       if (rt->rt6i_nexthop) {
-               seq_printf(m, "%pi6", rt->rt6i_nexthop->primary_key);
+       rcu_read_lock();
+       n = dst_get_neighbour(&rt->dst);
+       if (n) {
+               seq_printf(m, "%pi6", n->primary_key);
        } else {
                seq_puts(m, "00000000000000000000000000000000");
        }
+       rcu_read_unlock();
        seq_printf(m, " %08x %08x %08x %08x %8s\n",
                   rt->rt6i_metric, atomic_read(&rt->dst.__refcnt),
                   rt->dst.__use, rt->rt6i_flags,