Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

net: dst: fix missing initialization of rt_uncached

xfrm_alloc_dst() followed by xfrm4_dst_destroy(), without a
xfrm4_fill_dst() call in between, causes the following BUG:

BUG: spinlock bad magic on CPU#0, fbxhostapd/732
lock: 0x890b7668, .magic: 890b7668, .owner: <none>/-1, .owner_cpu: 0
CPU: 0 PID: 732 Comm: fbxhostapd Not tainted 6.3.0-rc6-next-20230414-00613-ge8de66369925-dirty #9
Hardware name: Marvell Kirkwood (Flattened Device Tree)
unwind_backtrace from show_stack+0x10/0x14
show_stack from dump_stack_lvl+0x28/0x30
dump_stack_lvl from do_raw_spin_lock+0x20/0x80
do_raw_spin_lock from rt_del_uncached_list+0x30/0x64
rt_del_uncached_list from xfrm4_dst_destroy+0x3c/0xbc
xfrm4_dst_destroy from dst_destroy+0x5c/0xb0
dst_destroy from rcu_process_callbacks+0xc4/0xec
rcu_process_callbacks from __do_softirq+0xb4/0x22c
__do_softirq from call_with_stack+0x1c/0x24
call_with_stack from do_softirq+0x60/0x6c
do_softirq from __local_bh_enable_ip+0xa0/0xcc

Patch "net: dst: Prevent false sharing vs. dst_entry:: __refcnt" moved
rt_uncached and rt_uncached_list fields from rtable struct to dst
struct, so they are more zeroed by memset_after(xdst, 0, u.dst) in
xfrm_alloc_dst().

Note that rt_uncached (list_head) was never properly initialized at
alloc time, but xfrm[46]_dst_destroy() is written in such a way that
it was not an issue thanks to the memset:

if (xdst->u.rt.dst.rt_uncached_list)
rt_del_uncached_list(&xdst->u.rt);

The route code does it the other way around: rt_uncached_list is
assumed to be valid IIF rt_uncached list_head is not empty:

void rt_del_uncached_list(struct rtable *rt)
{
if (!list_empty(&rt->dst.rt_uncached)) {
struct uncached_list *ul = rt->dst.rt_uncached_list;

spin_lock_bh(&ul->lock);
list_del_init(&rt->dst.rt_uncached);
spin_unlock_bh(&ul->lock);
}
}

This patch adds mandatory rt_uncached list_head initialization in
generic dst_init(), and adapt xfrm[46]_dst_destroy logic to match the
rest of the code.

Fixes: d288a162dd1c ("net: dst: Prevent false sharing vs. dst_entry:: __refcnt")
Reported-by: kernel test robot <oliver.sang@intel.com>
Link: https://lore.kernel.org/oe-lkp/202304162125.18b7bcdd-oliver.sang@intel.com
Reviewed-by: David Ahern <dsahern@kernel.org>
Reviewed-by: Eric Dumazet <edumazet@google.com>
CC: Leon Romanovsky <leon@kernel.org>
Signed-off-by: Maxime Bizon <mbizon@freebox.fr>
Link: https://lore.kernel.org/r/20230420182508.2417582-1-mbizon@freebox.fr
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Maxime Bizon and committed by
Jakub Kicinski
418a7307 33c1af8e

+3 -11
+1
net/core/dst.c
··· 67 67 #endif 68 68 dst->lwtstate = NULL; 69 69 rcuref_init(&dst->__rcuref, initial_ref); 70 + INIT_LIST_HEAD(&dst->rt_uncached); 70 71 dst->__use = 0; 71 72 dst->lastuse = jiffies; 72 73 dst->flags = flags;
-4
net/ipv4/route.c
··· 1644 1644 rt->rt_uses_gateway = 0; 1645 1645 rt->rt_gw_family = 0; 1646 1646 rt->rt_gw4 = 0; 1647 - INIT_LIST_HEAD(&rt->dst.rt_uncached); 1648 1647 1649 1648 rt->dst.output = ip_output; 1650 1649 if (flags & RTCF_LOCAL) ··· 1674 1675 new_rt->rt_gw4 = rt->rt_gw4; 1675 1676 else if (rt->rt_gw_family == AF_INET6) 1676 1677 new_rt->rt_gw6 = rt->rt_gw6; 1677 - INIT_LIST_HEAD(&new_rt->dst.rt_uncached); 1678 1678 1679 1679 new_rt->dst.input = rt->dst.input; 1680 1680 new_rt->dst.output = rt->dst.output; ··· 2856 2858 rt->rt_gw4 = ort->rt_gw4; 2857 2859 else if (rt->rt_gw_family == AF_INET6) 2858 2860 rt->rt_gw6 = ort->rt_gw6; 2859 - 2860 - INIT_LIST_HEAD(&rt->dst.rt_uncached); 2861 2861 } 2862 2862 2863 2863 dst_release(dst_orig);
+1 -3
net/ipv4/xfrm4_policy.c
··· 91 91 xdst->u.rt.rt_gw6 = rt->rt_gw6; 92 92 xdst->u.rt.rt_pmtu = rt->rt_pmtu; 93 93 xdst->u.rt.rt_mtu_locked = rt->rt_mtu_locked; 94 - INIT_LIST_HEAD(&xdst->u.rt.dst.rt_uncached); 95 94 rt_add_uncached_list(&xdst->u.rt); 96 95 97 96 return 0; ··· 120 121 struct xfrm_dst *xdst = (struct xfrm_dst *)dst; 121 122 122 123 dst_destroy_metrics_generic(dst); 123 - if (xdst->u.rt.dst.rt_uncached_list) 124 - rt_del_uncached_list(&xdst->u.rt); 124 + rt_del_uncached_list(&xdst->u.rt); 125 125 xfrm_dst_destroy(xdst); 126 126 } 127 127
-1
net/ipv6/route.c
··· 334 334 static void rt6_info_init(struct rt6_info *rt) 335 335 { 336 336 memset_after(rt, 0, dst); 337 - INIT_LIST_HEAD(&rt->dst.rt_uncached); 338 337 } 339 338 340 339 /* allocate dst with ip6_dst_ops */
+1 -3
net/ipv6/xfrm6_policy.c
··· 89 89 xdst->u.rt6.rt6i_gateway = rt->rt6i_gateway; 90 90 xdst->u.rt6.rt6i_dst = rt->rt6i_dst; 91 91 xdst->u.rt6.rt6i_src = rt->rt6i_src; 92 - INIT_LIST_HEAD(&xdst->u.rt6.dst.rt_uncached); 93 92 rt6_uncached_list_add(&xdst->u.rt6); 94 93 95 94 return 0; ··· 120 121 if (likely(xdst->u.rt6.rt6i_idev)) 121 122 in6_dev_put(xdst->u.rt6.rt6i_idev); 122 123 dst_destroy_metrics_generic(dst); 123 - if (xdst->u.rt6.dst.rt_uncached_list) 124 - rt6_uncached_list_del(&xdst->u.rt6); 124 + rt6_uncached_list_del(&xdst->u.rt6); 125 125 xfrm_dst_destroy(xdst); 126 126 } 127 127