ipv6: Fix 'inet6_rtm_getroute' to release 'rt->dst' in case of 'alloc_skb' failure
[cascardo/linux.git] / net / ipv6 / route.c
index 24c456e..8c5df6f 100644 (file)
@@ -881,6 +881,16 @@ static struct rt6_info *ip6_pol_route_input(struct net *net, struct fib6_table *
        return ip6_pol_route(net, table, fl6->flowi6_iif, fl6, flags);
 }
 
+static struct dst_entry *ip6_route_input_lookup(struct net *net,
+                                               struct net_device *dev,
+                                               struct flowi6 *fl6, int flags)
+{
+       if (rt6_need_strict(&fl6->daddr) && dev->type != ARPHRD_PIMREG)
+               flags |= RT6_LOOKUP_F_IFACE;
+
+       return fib6_rule_lookup(net, fl6, flags, ip6_pol_route_input);
+}
+
 void ip6_route_input(struct sk_buff *skb)
 {
        const struct ipv6hdr *iph = ipv6_hdr(skb);
@@ -895,10 +905,7 @@ void ip6_route_input(struct sk_buff *skb)
                .flowi6_proto = iph->nexthdr,
        };
 
-       if (rt6_need_strict(&iph->daddr) && skb->dev->type != ARPHRD_PIMREG)
-               flags |= RT6_LOOKUP_F_IFACE;
-
-       skb_dst_set(skb, fib6_rule_lookup(net, &fl6, flags, ip6_pol_route_input));
+       skb_dst_set(skb, ip6_route_input_lookup(net, skb->dev, &fl6, flags));
 }
 
 static struct rt6_info *ip6_pol_route_output(struct net *net, struct fib6_table *table,
@@ -2406,7 +2413,8 @@ static int rt6_fill_node(struct net *net,
        else
                table = RT6_TABLE_UNSPEC;
        rtm->rtm_table = table;
-       NLA_PUT_U32(skb, RTA_TABLE, table);
+       if (nla_put_u32(skb, RTA_TABLE, table))
+               goto nla_put_failure;
        if (rt->rt6i_flags & RTF_REJECT)
                rtm->rtm_type = RTN_UNREACHABLE;
        else if (rt->rt6i_flags & RTF_LOCAL)
@@ -2429,16 +2437,20 @@ static int rt6_fill_node(struct net *net,
                rtm->rtm_flags |= RTM_F_CLONED;
 
        if (dst) {
-               NLA_PUT(skb, RTA_DST, 16, dst);
+               if (nla_put(skb, RTA_DST, 16, dst))
+                       goto nla_put_failure;
                rtm->rtm_dst_len = 128;
        } else if (rtm->rtm_dst_len)
-               NLA_PUT(skb, RTA_DST, 16, &rt->rt6i_dst.addr);
+               if (nla_put(skb, RTA_DST, 16, &rt->rt6i_dst.addr))
+                       goto nla_put_failure;
 #ifdef CONFIG_IPV6_SUBTREES
        if (src) {
-               NLA_PUT(skb, RTA_SRC, 16, src);
+               if (nla_put(skb, RTA_SRC, 16, src))
+                       goto nla_put_failure;
                rtm->rtm_src_len = 128;
-       } else if (rtm->rtm_src_len)
-               NLA_PUT(skb, RTA_SRC, 16, &rt->rt6i_src.addr);
+       } else if (rtm->rtm_src_len &&
+                  nla_put(skb, RTA_SRC, 16, &rt->rt6i_src.addr))
+               goto nla_put_failure;
 #endif
        if (iif) {
 #ifdef CONFIG_IPV6_MROUTE
@@ -2456,17 +2468,20 @@ static int rt6_fill_node(struct net *net,
                        }
                } else
 #endif
-                       NLA_PUT_U32(skb, RTA_IIF, iif);
+                       if (nla_put_u32(skb, RTA_IIF, iif))
+                               goto nla_put_failure;
        } else if (dst) {
                struct in6_addr saddr_buf;
-               if (ip6_route_get_saddr(net, rt, dst, 0, &saddr_buf) == 0)
-                       NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
+               if (ip6_route_get_saddr(net, rt, dst, 0, &saddr_buf) == 0 &&
+                   nla_put(skb, RTA_PREFSRC, 16, &saddr_buf))
+                       goto nla_put_failure;
        }
 
        if (rt->rt6i_prefsrc.plen) {
                struct in6_addr saddr_buf;
                saddr_buf = rt->rt6i_prefsrc.addr;
-               NLA_PUT(skb, RTA_PREFSRC, 16, &saddr_buf);
+               if (nla_put(skb, RTA_PREFSRC, 16, &saddr_buf))
+                       goto nla_put_failure;
        }
 
        if (rtnetlink_put_metrics(skb, dst_metrics_ptr(&rt->dst)) < 0)
@@ -2474,15 +2489,19 @@ static int rt6_fill_node(struct net *net,
 
        rcu_read_lock();
        n = dst_get_neighbour_noref(&rt->dst);
-       if (n)
-               NLA_PUT(skb, RTA_GATEWAY, 16, &n->primary_key);
+       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->dst.dev->ifindex);
-
-       NLA_PUT_U32(skb, RTA_PRIORITY, rt->rt6i_metric);
-
+       if (rt->dst.dev &&
+           nla_put_u32(skb, RTA_OIF, rt->dst.dev->ifindex))
+               goto nla_put_failure;
+       if (nla_put_u32(skb, RTA_PRIORITY, rt->rt6i_metric))
+               goto nla_put_failure;
        if (!(rt->rt6i_flags & RTF_EXPIRES))
                expires = 0;
        else if (rt->dst.expires - jiffies < INT_MAX)
@@ -2533,7 +2552,7 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void
        struct sk_buff *skb;
        struct rtmsg *rtm;
        struct flowi6 fl6;
-       int err, iif = 0;
+       int err, iif = 0, oif = 0;
 
        err = nlmsg_parse(nlh, sizeof(*rtm), tb, RTA_MAX, rtm_ipv6_policy);
        if (err < 0)
@@ -2560,19 +2579,34 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void
                iif = nla_get_u32(tb[RTA_IIF]);
 
        if (tb[RTA_OIF])
-               fl6.flowi6_oif = nla_get_u32(tb[RTA_OIF]);
+               oif = nla_get_u32(tb[RTA_OIF]);
 
        if (iif) {
                struct net_device *dev;
+               int flags = 0;
+
                dev = __dev_get_by_index(net, iif);
                if (!dev) {
                        err = -ENODEV;
                        goto errout;
                }
+
+               fl6.flowi6_iif = iif;
+
+               if (!ipv6_addr_any(&fl6.saddr))
+                       flags |= RT6_LOOKUP_F_HAS_SADDR;
+
+               rt = (struct rt6_info *)ip6_route_input_lookup(net, dev, &fl6,
+                                                              flags);
+       } else {
+               fl6.flowi6_oif = oif;
+
+               rt = (struct rt6_info *)ip6_route_output(net, NULL, &fl6);
        }
 
        skb = alloc_skb(NLMSG_GOODSIZE, GFP_KERNEL);
        if (!skb) {
+               dst_release(&rt->dst);
                err = -ENOBUFS;
                goto errout;
        }
@@ -2583,7 +2617,6 @@ static int inet6_rtm_getroute(struct sk_buff *in_skb, struct nlmsghdr* nlh, void
        skb_reset_mac_header(skb);
        skb_reserve(skb, MAX_HEADER + sizeof(struct ipv6hdr));
 
-       rt = (struct rt6_info*) ip6_route_output(net, NULL, &fl6);
        skb_dst_set(skb, &rt->dst);
 
        err = rt6_fill_node(net, skb, rt, &fl6.daddr, &fl6.saddr, iif,