aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWei Wang <weiwan@google.com>2018-07-21 23:56:32 -0400
committerDavid S. Miller <davem@davemloft.net>2018-07-23 14:19:02 -0400
commite873e4b9cc7e8ce79e5c5627b32b107035bb3f5d (patch)
treecaf1e5fb8429bcbf3c40350b261dfce695e4dc8a
parent5302a84e378107075c8a18c8af27cc4be258b682 (diff)
ipv6: use fib6_info_hold_safe() when necessary
In the code path where only rcu read lock is held, e.g. in the route lookup code path, it is not safe to directly call fib6_info_hold() because the fib6_info may already have been deleted but still exists in the rcu grace period. Holding reference to it could cause double free and crash the kernel. This patch adds a new function fib6_info_hold_safe() and replace fib6_info_hold() in all necessary places. Syzbot reported 3 crash traces because of this. One of them is: 8021q: adding VLAN 0 to HW filter on device team0 IPv6: ADDRCONF(NETDEV_CHANGE): team0: link becomes ready dst_release: dst:(____ptrval____) refcnt:-1 dst_release: dst:(____ptrval____) refcnt:-2 WARNING: CPU: 1 PID: 4845 at include/net/dst.h:239 dst_hold include/net/dst.h:239 [inline] WARNING: CPU: 1 PID: 4845 at include/net/dst.h:239 ip6_setup_cork+0xd66/0x1830 net/ipv6/ip6_output.c:1204 dst_release: dst:(____ptrval____) refcnt:-1 Kernel panic - not syncing: panic_on_warn set ... CPU: 1 PID: 4845 Comm: syz-executor493 Not tainted 4.18.0-rc3+ #10 Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 01/01/2011 Call Trace: __dump_stack lib/dump_stack.c:77 [inline] dump_stack+0x1c9/0x2b4 lib/dump_stack.c:113 panic+0x238/0x4e7 kernel/panic.c:184 dst_release: dst:(____ptrval____) refcnt:-2 dst_release: dst:(____ptrval____) refcnt:-3 __warn.cold.8+0x163/0x1ba kernel/panic.c:536 dst_release: dst:(____ptrval____) refcnt:-4 report_bug+0x252/0x2d0 lib/bug.c:186 fixup_bug arch/x86/kernel/traps.c:178 [inline] do_error_trap+0x1fc/0x4d0 arch/x86/kernel/traps.c:296 dst_release: dst:(____ptrval____) refcnt:-5 do_invalid_op+0x1b/0x20 arch/x86/kernel/traps.c:316 invalid_op+0x14/0x20 arch/x86/entry/entry_64.S:992 RIP: 0010:dst_hold include/net/dst.h:239 [inline] RIP: 0010:ip6_setup_cork+0xd66/0x1830 net/ipv6/ip6_output.c:1204 Code: c1 ed 03 89 9d 18 ff ff ff 48 b8 00 00 00 00 00 fc ff df 41 c6 44 05 00 f8 e9 2d 01 00 00 4c 8b a5 c8 fe ff ff e8 1a f6 e6 fa <0f> 0b e9 6a fc ff ff e8 0e f6 e6 fa 48 8b 85 d0 fe ff ff 48 8d 78 RSP: 0018:ffff8801a8fcf178 EFLAGS: 00010293 RAX: ffff8801a8eba5c0 RBX: 0000000000000000 RCX: ffffffff869511e6 RDX: 0000000000000000 RSI: ffffffff869515b6 RDI: 0000000000000005 RBP: ffff8801a8fcf2c8 R08: ffff8801a8eba5c0 R09: ffffed0035ac8338 R10: ffffed0035ac8338 R11: ffff8801ad6419c3 R12: ffff8801a8fcf720 R13: ffff8801a8fcf6a0 R14: ffff8801ad6419c0 R15: ffff8801ad641980 ip6_make_skb+0x2c8/0x600 net/ipv6/ip6_output.c:1768 udpv6_sendmsg+0x2c90/0x35f0 net/ipv6/udp.c:1376 inet_sendmsg+0x1a1/0x690 net/ipv4/af_inet.c:798 sock_sendmsg_nosec net/socket.c:641 [inline] sock_sendmsg+0xd5/0x120 net/socket.c:651 ___sys_sendmsg+0x51d/0x930 net/socket.c:2125 __sys_sendmmsg+0x240/0x6f0 net/socket.c:2220 __do_sys_sendmmsg net/socket.c:2249 [inline] __se_sys_sendmmsg net/socket.c:2246 [inline] __x64_sys_sendmmsg+0x9d/0x100 net/socket.c:2246 do_syscall_64+0x1b9/0x820 arch/x86/entry/common.c:290 entry_SYSCALL_64_after_hwframe+0x49/0xbe RIP: 0033:0x446ba9 Code: e8 cc bb 02 00 48 83 c4 18 c3 0f 1f 80 00 00 00 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 0f 83 eb 08 fc ff c3 66 2e 0f 1f 84 00 00 00 00 RSP: 002b:00007fb39a469da8 EFLAGS: 00000246 ORIG_RAX: 0000000000000133 RAX: ffffffffffffffda RBX: 00000000006dcc54 RCX: 0000000000446ba9 RDX: 00000000000000b8 RSI: 0000000020001b00 RDI: 0000000000000003 RBP: 00000000006dcc50 R08: 00007fb39a46a700 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 45c828efc7a64843 R13: e6eeb815b9d8a477 R14: 5068caf6f713c6fc R15: 0000000000000001 Dumping ftrace buffer: (ftrace buffer empty) Kernel Offset: disabled Rebooting in 86400 seconds.. Fixes: 93531c674315 ("net/ipv6: separate handling of FIB entries from dst based routes") Reported-by: syzbot+902e2a1bcd4f7808cef5@syzkaller.appspotmail.com Reported-by: syzbot+8ae62d67f647abeeceb9@syzkaller.appspotmail.com Reported-by: syzbot+3f08feb14086930677d0@syzkaller.appspotmail.com Signed-off-by: Wei Wang <weiwan@google.com> Acked-by: Eric Dumazet <edumazet@google.com> Reviewed-by: David Ahern <dsahern@gmail.com> Signed-off-by: David S. Miller <davem@davemloft.net>
-rw-r--r--include/net/ip6_fib.h5
-rw-r--r--net/ipv6/addrconf.c3
-rw-r--r--net/ipv6/route.c41
3 files changed, 38 insertions, 11 deletions
diff --git a/include/net/ip6_fib.h b/include/net/ip6_fib.h
index 71b9043aa0e7..3d4930528db0 100644
--- a/include/net/ip6_fib.h
+++ b/include/net/ip6_fib.h
@@ -281,6 +281,11 @@ static inline void fib6_info_hold(struct fib6_info *f6i)
281 atomic_inc(&f6i->fib6_ref); 281 atomic_inc(&f6i->fib6_ref);
282} 282}
283 283
284static inline bool fib6_info_hold_safe(struct fib6_info *f6i)
285{
286 return atomic_inc_not_zero(&f6i->fib6_ref);
287}
288
284static inline void fib6_info_release(struct fib6_info *f6i) 289static inline void fib6_info_release(struct fib6_info *f6i)
285{ 290{
286 if (f6i && atomic_dec_and_test(&f6i->fib6_ref)) 291 if (f6i && atomic_dec_and_test(&f6i->fib6_ref))
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 91580c62bb86..f66a1cae3366 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -2374,7 +2374,8 @@ static struct fib6_info *addrconf_get_prefix_route(const struct in6_addr *pfx,
2374 continue; 2374 continue;
2375 if ((rt->fib6_flags & noflags) != 0) 2375 if ((rt->fib6_flags & noflags) != 0)
2376 continue; 2376 continue;
2377 fib6_info_hold(rt); 2377 if (!fib6_info_hold_safe(rt))
2378 continue;
2378 break; 2379 break;
2379 } 2380 }
2380out: 2381out:
diff --git a/net/ipv6/route.c b/net/ipv6/route.c
index 2ce0bd17de4f..ec18b3ce8b6d 100644
--- a/net/ipv6/route.c
+++ b/net/ipv6/route.c
@@ -972,10 +972,10 @@ static void ip6_rt_init_dst(struct rt6_info *rt, struct fib6_info *ort)
972 rt->dst.lastuse = jiffies; 972 rt->dst.lastuse = jiffies;
973} 973}
974 974
975/* Caller must already hold reference to @from */
975static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from) 976static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
976{ 977{
977 rt->rt6i_flags &= ~RTF_EXPIRES; 978 rt->rt6i_flags &= ~RTF_EXPIRES;
978 fib6_info_hold(from);
979 rcu_assign_pointer(rt->from, from); 979 rcu_assign_pointer(rt->from, from);
980 dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true); 980 dst_init_metrics(&rt->dst, from->fib6_metrics->metrics, true);
981 if (from->fib6_metrics != &dst_default_metrics) { 981 if (from->fib6_metrics != &dst_default_metrics) {
@@ -984,6 +984,7 @@ static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from)
984 } 984 }
985} 985}
986 986
987/* Caller must already hold reference to @ort */
987static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort) 988static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort)
988{ 989{
989 struct net_device *dev = fib6_info_nh_dev(ort); 990 struct net_device *dev = fib6_info_nh_dev(ort);
@@ -1044,9 +1045,14 @@ static struct rt6_info *ip6_create_rt_rcu(struct fib6_info *rt)
1044 struct net_device *dev = rt->fib6_nh.nh_dev; 1045 struct net_device *dev = rt->fib6_nh.nh_dev;
1045 struct rt6_info *nrt; 1046 struct rt6_info *nrt;
1046 1047
1048 if (!fib6_info_hold_safe(rt))
1049 return NULL;
1050
1047 nrt = ip6_dst_alloc(dev_net(dev), dev, flags); 1051 nrt = ip6_dst_alloc(dev_net(dev), dev, flags);
1048 if (nrt) 1052 if (nrt)
1049 ip6_rt_copy_init(nrt, rt); 1053 ip6_rt_copy_init(nrt, rt);
1054 else
1055 fib6_info_release(rt);
1050 1056
1051 return nrt; 1057 return nrt;
1052} 1058}
@@ -1178,10 +1184,15 @@ static struct rt6_info *ip6_rt_cache_alloc(struct fib6_info *ort,
1178 * Clone the route. 1184 * Clone the route.
1179 */ 1185 */
1180 1186
1187 if (!fib6_info_hold_safe(ort))
1188 return NULL;
1189
1181 dev = ip6_rt_get_dev_rcu(ort); 1190 dev = ip6_rt_get_dev_rcu(ort);
1182 rt = ip6_dst_alloc(dev_net(dev), dev, 0); 1191 rt = ip6_dst_alloc(dev_net(dev), dev, 0);
1183 if (!rt) 1192 if (!rt) {
1193 fib6_info_release(ort);
1184 return NULL; 1194 return NULL;
1195 }
1185 1196
1186 ip6_rt_copy_init(rt, ort); 1197 ip6_rt_copy_init(rt, ort);
1187 rt->rt6i_flags |= RTF_CACHE; 1198 rt->rt6i_flags |= RTF_CACHE;
@@ -1210,12 +1221,17 @@ static struct rt6_info *ip6_rt_pcpu_alloc(struct fib6_info *rt)
1210 struct net_device *dev; 1221 struct net_device *dev;
1211 struct rt6_info *pcpu_rt; 1222 struct rt6_info *pcpu_rt;
1212 1223
1224 if (!fib6_info_hold_safe(rt))
1225 return NULL;
1226
1213 rcu_read_lock(); 1227 rcu_read_lock();
1214 dev = ip6_rt_get_dev_rcu(rt); 1228 dev = ip6_rt_get_dev_rcu(rt);
1215 pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags); 1229 pcpu_rt = ip6_dst_alloc(dev_net(dev), dev, flags);
1216 rcu_read_unlock(); 1230 rcu_read_unlock();
1217 if (!pcpu_rt) 1231 if (!pcpu_rt) {
1232 fib6_info_release(rt);
1218 return NULL; 1233 return NULL;
1234 }
1219 ip6_rt_copy_init(pcpu_rt, rt); 1235 ip6_rt_copy_init(pcpu_rt, rt);
1220 pcpu_rt->rt6i_flags |= RTF_PCPU; 1236 pcpu_rt->rt6i_flags |= RTF_PCPU;
1221 return pcpu_rt; 1237 return pcpu_rt;
@@ -2486,7 +2502,7 @@ restart:
2486 2502
2487out: 2503out:
2488 if (ret) 2504 if (ret)
2489 dst_hold(&ret->dst); 2505 ip6_hold_safe(net, &ret, true);
2490 else 2506 else
2491 ret = ip6_create_rt_rcu(rt); 2507 ret = ip6_create_rt_rcu(rt);
2492 2508
@@ -3303,7 +3319,8 @@ static int ip6_route_del(struct fib6_config *cfg,
3303 continue; 3319 continue;
3304 if (cfg->fc_protocol && cfg->fc_protocol != rt->fib6_protocol) 3320 if (cfg->fc_protocol && cfg->fc_protocol != rt->fib6_protocol)
3305 continue; 3321 continue;
3306 fib6_info_hold(rt); 3322 if (!fib6_info_hold_safe(rt))
3323 continue;
3307 rcu_read_unlock(); 3324 rcu_read_unlock();
3308 3325
3309 /* if gateway was specified only delete the one hop */ 3326 /* if gateway was specified only delete the one hop */
@@ -3409,6 +3426,9 @@ static void rt6_do_redirect(struct dst_entry *dst, struct sock *sk, struct sk_bu
3409 3426
3410 rcu_read_lock(); 3427 rcu_read_lock();
3411 from = rcu_dereference(rt->from); 3428 from = rcu_dereference(rt->from);
3429 /* This fib6_info_hold() is safe here because we hold reference to rt
3430 * and rt already holds reference to fib6_info.
3431 */
3412 fib6_info_hold(from); 3432 fib6_info_hold(from);
3413 rcu_read_unlock(); 3433 rcu_read_unlock();
3414 3434
@@ -3470,7 +3490,8 @@ static struct fib6_info *rt6_get_route_info(struct net *net,
3470 continue; 3490 continue;
3471 if (!ipv6_addr_equal(&rt->fib6_nh.nh_gw, gwaddr)) 3491 if (!ipv6_addr_equal(&rt->fib6_nh.nh_gw, gwaddr))
3472 continue; 3492 continue;
3473 fib6_info_hold(rt); 3493 if (!fib6_info_hold_safe(rt))
3494 continue;
3474 break; 3495 break;
3475 } 3496 }
3476out: 3497out:
@@ -3530,8 +3551,8 @@ struct fib6_info *rt6_get_dflt_router(struct net *net,
3530 ipv6_addr_equal(&rt->fib6_nh.nh_gw, addr)) 3551 ipv6_addr_equal(&rt->fib6_nh.nh_gw, addr))
3531 break; 3552 break;
3532 } 3553 }
3533 if (rt) 3554 if (rt && !fib6_info_hold_safe(rt))
3534 fib6_info_hold(rt); 3555 rt = NULL;
3535 rcu_read_unlock(); 3556 rcu_read_unlock();
3536 return rt; 3557 return rt;
3537} 3558}
@@ -3579,8 +3600,8 @@ restart:
3579 struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL; 3600 struct inet6_dev *idev = dev ? __in6_dev_get(dev) : NULL;
3580 3601
3581 if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) && 3602 if (rt->fib6_flags & (RTF_DEFAULT | RTF_ADDRCONF) &&
3582 (!idev || idev->cnf.accept_ra != 2)) { 3603 (!idev || idev->cnf.accept_ra != 2) &&
3583 fib6_info_hold(rt); 3604 fib6_info_hold_safe(rt)) {
3584 rcu_read_unlock(); 3605 rcu_read_unlock();
3585 ip6_del_rt(net, rt); 3606 ip6_del_rt(net, rt);
3586 goto restart; 3607 goto restart;