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

ipv6: make ip6_rt_gc_expire an atomic_t

Reads and Writes to ip6_rt_gc_expire always have been racy,
as syzbot reported lately [1]

There is a possible risk of under-flow, leading
to unexpected high value passed to fib6_run_gc(),
although I have not observed this in the field.

Hosts hitting ip6_dst_gc() very hard are under pretty bad
state anyway.

[1]
BUG: KCSAN: data-race in ip6_dst_gc / ip6_dst_gc

read-write to 0xffff888102110744 of 4 bytes by task 13165 on cpu 1:
ip6_dst_gc+0x1f3/0x220 net/ipv6/route.c:3311
dst_alloc+0x9b/0x160 net/core/dst.c:86
ip6_dst_alloc net/ipv6/route.c:344 [inline]
icmp6_dst_alloc+0xb2/0x360 net/ipv6/route.c:3261
mld_sendpack+0x2b9/0x580 net/ipv6/mcast.c:1807
mld_send_cr net/ipv6/mcast.c:2119 [inline]
mld_ifc_work+0x576/0x800 net/ipv6/mcast.c:2651
process_one_work+0x3d3/0x720 kernel/workqueue.c:2289
worker_thread+0x618/0xa70 kernel/workqueue.c:2436
kthread+0x1a9/0x1e0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30

read-write to 0xffff888102110744 of 4 bytes by task 11607 on cpu 0:
ip6_dst_gc+0x1f3/0x220 net/ipv6/route.c:3311
dst_alloc+0x9b/0x160 net/core/dst.c:86
ip6_dst_alloc net/ipv6/route.c:344 [inline]
icmp6_dst_alloc+0xb2/0x360 net/ipv6/route.c:3261
mld_sendpack+0x2b9/0x580 net/ipv6/mcast.c:1807
mld_send_cr net/ipv6/mcast.c:2119 [inline]
mld_ifc_work+0x576/0x800 net/ipv6/mcast.c:2651
process_one_work+0x3d3/0x720 kernel/workqueue.c:2289
worker_thread+0x618/0xa70 kernel/workqueue.c:2436
kthread+0x1a9/0x1e0 kernel/kthread.c:376
ret_from_fork+0x1f/0x30

value changed: 0x00000bb3 -> 0x00000ba9

Reported by Kernel Concurrency Sanitizer on:
CPU: 0 PID: 11607 Comm: kworker/0:21 Not tainted 5.18.0-rc1-syzkaller-00037-g42e7a03d3bad-dirty #0
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011
Workqueue: mld mld_ifc_work

Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reported-by: syzbot <syzkaller@googlegroups.com>
Reviewed-by: David Ahern <dsahern@kernel.org>
Link: https://lore.kernel.org/r/20220413181333.649424-1-eric.dumazet@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>

authored by

Eric Dumazet and committed by
Jakub Kicinski
9cb7c013 268b41b3

+8 -7
+2 -2
include/net/netns/ipv6.h
··· 75 75 struct list_head fib6_walkers; 76 76 rwlock_t fib6_walker_lock; 77 77 spinlock_t fib6_gc_lock; 78 - unsigned int ip6_rt_gc_expire; 79 - unsigned long ip6_rt_last_gc; 78 + atomic_t ip6_rt_gc_expire; 79 + unsigned long ip6_rt_last_gc; 80 80 unsigned char flowlabel_has_excl; 81 81 #ifdef CONFIG_IPV6_MULTIPLE_TABLES 82 82 bool fib6_has_custom_rules;
+6 -5
net/ipv6/route.c
··· 3292 3292 int rt_elasticity = net->ipv6.sysctl.ip6_rt_gc_elasticity; 3293 3293 int rt_gc_timeout = net->ipv6.sysctl.ip6_rt_gc_timeout; 3294 3294 unsigned long rt_last_gc = net->ipv6.ip6_rt_last_gc; 3295 + unsigned int val; 3295 3296 int entries; 3296 3297 3297 3298 entries = dst_entries_get_fast(ops); ··· 3303 3302 entries <= rt_max_size) 3304 3303 goto out; 3305 3304 3306 - net->ipv6.ip6_rt_gc_expire++; 3307 - fib6_run_gc(net->ipv6.ip6_rt_gc_expire, net, true); 3305 + fib6_run_gc(atomic_inc_return(&net->ipv6.ip6_rt_gc_expire), net, true); 3308 3306 entries = dst_entries_get_slow(ops); 3309 3307 if (entries < ops->gc_thresh) 3310 - net->ipv6.ip6_rt_gc_expire = rt_gc_timeout>>1; 3308 + atomic_set(&net->ipv6.ip6_rt_gc_expire, rt_gc_timeout >> 1); 3311 3309 out: 3312 - net->ipv6.ip6_rt_gc_expire -= net->ipv6.ip6_rt_gc_expire>>rt_elasticity; 3310 + val = atomic_read(&net->ipv6.ip6_rt_gc_expire); 3311 + atomic_set(&net->ipv6.ip6_rt_gc_expire, val - (val >> rt_elasticity)); 3313 3312 return entries > rt_max_size; 3314 3313 } 3315 3314 ··· 6510 6509 net->ipv6.sysctl.ip6_rt_min_advmss = IPV6_MIN_MTU - 20 - 40; 6511 6510 net->ipv6.sysctl.skip_notify_on_dev_down = 0; 6512 6511 6513 - net->ipv6.ip6_rt_gc_expire = 30*HZ; 6512 + atomic_set(&net->ipv6.ip6_rt_gc_expire, 30*HZ); 6514 6513 6515 6514 ret = 0; 6516 6515 out: