diff options
author | Wei Wang <weiwan@google.com> | 2018-07-21 23:56:32 -0400 |
---|---|---|
committer | David S. Miller <davem@davemloft.net> | 2018-07-23 14:19:02 -0400 |
commit | e873e4b9cc7e8ce79e5c5627b32b107035bb3f5d (patch) | |
tree | caf1e5fb8429bcbf3c40350b261dfce695e4dc8a | |
parent | 5302a84e378107075c8a18c8af27cc4be258b682 (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.h | 5 | ||||
-rw-r--r-- | net/ipv6/addrconf.c | 3 | ||||
-rw-r--r-- | net/ipv6/route.c | 41 |
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 | ||
284 | static inline bool fib6_info_hold_safe(struct fib6_info *f6i) | ||
285 | { | ||
286 | return atomic_inc_not_zero(&f6i->fib6_ref); | ||
287 | } | ||
288 | |||
284 | static inline void fib6_info_release(struct fib6_info *f6i) | 289 | static 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 | } |
2380 | out: | 2381 | out: |
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 */ | ||
975 | static void rt6_set_from(struct rt6_info *rt, struct fib6_info *from) | 976 | static 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 */ | ||
987 | static void ip6_rt_copy_init(struct rt6_info *rt, struct fib6_info *ort) | 988 | static 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 | ||
2487 | out: | 2503 | out: |
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 | } |
3476 | out: | 3497 | out: |
@@ -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; |