diff options
author | WANG Cong <xiyou.wangcong@gmail.com> | 2017-05-04 17:54:17 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2017-05-08 14:35:03 -0400 |
commit | 82486aa6f1b9bc8145e6d0fa2bc0b44307f3b875 (patch) | |
tree | 97674aade6f2ac755baebde5255a916028c50125 | |
parent | 3013c4983eb15f4ce8958e81922cdfd80f771d3e (diff) |
ipv4: restore rt->fi for reference counting
IPv4 dst could use fi->fib_metrics to store metrics but fib_info
itself is refcnt'ed, so without taking a refcnt fi and
fi->fib_metrics could be freed while dst metrics still points to
it. This triggers use-after-free as reported by Andrey twice.
This patch reverts commit 2860583fe840 ("ipv4: Kill rt->fi") to
restore this reference counting. It is a quick fix for -net and
-stable, for -net-next, as Eric suggested, we can consider doing
reference counting for metrics itself instead of relying on fib_info.
IPv6 is very different, it copies or steals the metrics from mx6_config
in fib6_commit_metrics() so probably doesn't need a refcnt.
Decnet has already done the refcnt'ing, see dn_fib_semantic_match().
Fixes: 2860583fe840 ("ipv4: Kill rt->fi")
Reported-by: Andrey Konovalov <andreyknvl@google.com>
Tested-by: Andrey Konovalov <andreyknvl@google.com>
Signed-off-by: Cong Wang <xiyou.wangcong@gmail.com>
Acked-by: Eric Dumazet <edumazet@google.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r-- | include/net/route.h | 1 | ||||
-rw-r--r-- | net/ipv4/route.c | 18 |
2 files changed, 18 insertions, 1 deletions
diff --git a/include/net/route.h b/include/net/route.h index 2cc0e14c6359..4335eb72a04c 100644 --- a/include/net/route.h +++ b/include/net/route.h | |||
@@ -69,6 +69,7 @@ struct rtable { | |||
69 | 69 | ||
70 | struct list_head rt_uncached; | 70 | struct list_head rt_uncached; |
71 | struct uncached_list *rt_uncached_list; | 71 | struct uncached_list *rt_uncached_list; |
72 | struct fib_info *fi; /* for refcnt to shared metrics */ | ||
72 | }; | 73 | }; |
73 | 74 | ||
74 | static inline bool rt_is_input_route(const struct rtable *rt) | 75 | static inline bool rt_is_input_route(const struct rtable *rt) |
diff --git a/net/ipv4/route.c b/net/ipv4/route.c index 655d9eebe43e..f647310f8e4d 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c | |||
@@ -1387,6 +1387,11 @@ static void ipv4_dst_destroy(struct dst_entry *dst) | |||
1387 | { | 1387 | { |
1388 | struct rtable *rt = (struct rtable *) dst; | 1388 | struct rtable *rt = (struct rtable *) dst; |
1389 | 1389 | ||
1390 | if (rt->fi) { | ||
1391 | fib_info_put(rt->fi); | ||
1392 | rt->fi = NULL; | ||
1393 | } | ||
1394 | |||
1390 | if (!list_empty(&rt->rt_uncached)) { | 1395 | if (!list_empty(&rt->rt_uncached)) { |
1391 | struct uncached_list *ul = rt->rt_uncached_list; | 1396 | struct uncached_list *ul = rt->rt_uncached_list; |
1392 | 1397 | ||
@@ -1424,6 +1429,16 @@ static bool rt_cache_valid(const struct rtable *rt) | |||
1424 | !rt_is_expired(rt); | 1429 | !rt_is_expired(rt); |
1425 | } | 1430 | } |
1426 | 1431 | ||
1432 | static void rt_init_metrics(struct rtable *rt, struct fib_info *fi) | ||
1433 | { | ||
1434 | if (fi->fib_metrics != (u32 *)dst_default_metrics) { | ||
1435 | fib_info_hold(fi); | ||
1436 | rt->fi = fi; | ||
1437 | } | ||
1438 | |||
1439 | dst_init_metrics(&rt->dst, fi->fib_metrics, true); | ||
1440 | } | ||
1441 | |||
1427 | static void rt_set_nexthop(struct rtable *rt, __be32 daddr, | 1442 | static void rt_set_nexthop(struct rtable *rt, __be32 daddr, |
1428 | const struct fib_result *res, | 1443 | const struct fib_result *res, |
1429 | struct fib_nh_exception *fnhe, | 1444 | struct fib_nh_exception *fnhe, |
@@ -1438,7 +1453,7 @@ static void rt_set_nexthop(struct rtable *rt, __be32 daddr, | |||
1438 | rt->rt_gateway = nh->nh_gw; | 1453 | rt->rt_gateway = nh->nh_gw; |
1439 | rt->rt_uses_gateway = 1; | 1454 | rt->rt_uses_gateway = 1; |
1440 | } | 1455 | } |
1441 | dst_init_metrics(&rt->dst, fi->fib_metrics, true); | 1456 | rt_init_metrics(rt, fi); |
1442 | #ifdef CONFIG_IP_ROUTE_CLASSID | 1457 | #ifdef CONFIG_IP_ROUTE_CLASSID |
1443 | rt->dst.tclassid = nh->nh_tclassid; | 1458 | rt->dst.tclassid = nh->nh_tclassid; |
1444 | #endif | 1459 | #endif |
@@ -1490,6 +1505,7 @@ struct rtable *rt_dst_alloc(struct net_device *dev, | |||
1490 | rt->rt_gateway = 0; | 1505 | rt->rt_gateway = 0; |
1491 | rt->rt_uses_gateway = 0; | 1506 | rt->rt_uses_gateway = 0; |
1492 | rt->rt_table_id = 0; | 1507 | rt->rt_table_id = 0; |
1508 | rt->fi = NULL; | ||
1493 | INIT_LIST_HEAD(&rt->rt_uncached); | 1509 | INIT_LIST_HEAD(&rt->rt_uncached); |
1494 | 1510 | ||
1495 | rt->dst.output = ip_output; | 1511 | rt->dst.output = ip_output; |