3.16.47-rc1 review patch. If anyone has any objections, please let me know. ------------------ From: WANG Cong <xiyou.wangcong@xxxxxxxxx> commit 82486aa6f1b9bc8145e6d0fa2bc0b44307f3b875 upstream. 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@xxxxxxxxxx> Tested-by: Andrey Konovalov <andreyknvl@xxxxxxxxxx> Signed-off-by: Cong Wang <xiyou.wangcong@xxxxxxxxx> Acked-by: Eric Dumazet <edumazet@xxxxxxxxxx> Signed-off-by: David S. Miller <davem@xxxxxxxxxxxxx> [bwh: Backported to 3.16: - Update all 5 places where rtable is initialised - Open-code fib_info_hold() - Adjust context] Signed-off-by: Ben Hutchings <ben@xxxxxxxxxxxxxxx> --- --- a/include/net/route.h +++ b/include/net/route.h @@ -64,6 +64,7 @@ struct rtable { u32 rt_pmtu; struct list_head rt_uncached; + struct fib_info *fi; /* for refcnt to shared metrics */ }; static inline bool rt_is_input_route(const struct rtable *rt) --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1346,6 +1346,11 @@ static void ipv4_dst_destroy(struct dst_ { struct rtable *rt = (struct rtable *) dst; + if (rt->fi) { + fib_info_put(rt->fi); + rt->fi = NULL; + } + if (!list_empty(&rt->rt_uncached)) { spin_lock_bh(&rt_uncached_lock); list_del(&rt->rt_uncached); @@ -1378,6 +1383,16 @@ static bool rt_cache_valid(const struct !rt_is_expired(rt); } +static void rt_init_metrics(struct rtable *rt, struct fib_info *fi) +{ + if (fi->fib_metrics != (u32 *)dst_default_metrics) { + atomic_inc(&fi->fib_clntref); + rt->fi = fi; + } + + dst_init_metrics(&rt->dst, fi->fib_metrics, true); +} + static void rt_set_nexthop(struct rtable *rt, __be32 daddr, const struct fib_result *res, struct fib_nh_exception *fnhe, @@ -1392,7 +1407,7 @@ static void rt_set_nexthop(struct rtable rt->rt_gateway = nh->nh_gw; rt->rt_uses_gateway = 1; } - dst_init_metrics(&rt->dst, fi->fib_metrics, true); + rt_init_metrics(rt, fi); #ifdef CONFIG_IP_ROUTE_CLASSID rt->dst.tclassid = nh->nh_tclassid; #endif @@ -1480,6 +1495,7 @@ static int ip_route_input_mc(struct sk_b rth->rt_pmtu = 0; rth->rt_gateway = 0; rth->rt_uses_gateway = 0; + rth->fi = NULL; INIT_LIST_HEAD(&rth->rt_uncached); if (our) { rth->dst.input= ip_local_deliver; @@ -1649,6 +1665,7 @@ rt_cache: rth->rt_pmtu = 0; rth->rt_gateway = 0; rth->rt_uses_gateway = 0; + rth->fi = NULL; INIT_LIST_HEAD(&rth->rt_uncached); RT_CACHE_STAT_INC(in_slow_tot); @@ -1823,6 +1840,7 @@ local_input: rth->rt_pmtu = 0; rth->rt_gateway = 0; rth->rt_uses_gateway = 0; + rth->fi = NULL; INIT_LIST_HEAD(&rth->rt_uncached); RT_CACHE_STAT_INC(in_slow_tot); if (res.type == RTN_UNREACHABLE) { @@ -2037,6 +2055,7 @@ add: rth->rt_pmtu = 0; rth->rt_gateway = 0; rth->rt_uses_gateway = 0; + rth->fi = NULL; INIT_LIST_HEAD(&rth->rt_uncached); RT_CACHE_STAT_INC(out_slow_tot); @@ -2316,6 +2335,9 @@ struct dst_entry *ipv4_blackhole_route(s rt->rt_type = ort->rt_type; rt->rt_gateway = ort->rt_gateway; rt->rt_uses_gateway = ort->rt_uses_gateway; + rt->fi = ort->fi; + if (rt->fi) + atomic_inc(&rt->fi->fib_clntref); INIT_LIST_HEAD(&rt->rt_uncached);