diff options
| author | Ying Xue <ying.xue@windriver.com> | 2013-12-26 21:18:28 -0500 |
|---|---|---|
| committer | David S. Miller <davem@davemloft.net> | 2013-12-29 22:24:07 -0500 |
| commit | 84602761ca4495dd409be936dfa93ed20c946684 (patch) | |
| tree | 5936e501cc24cc66cf298a8e6713cbb04b4427b0 /net | |
| parent | 8eb9bff0edefcce50116ec50397a60dd626022d6 (diff) | |
tipc: fix deadlock during socket release
A deadlock might occur if name table is withdrawn in socket release
routine, and while packets are still being received from bearer.
CPU0 CPU1
T0: recv_msg() release()
T1: tipc_recv_msg() tipc_withdraw()
T2: [grab node lock] [grab port lock]
T3: tipc_link_wakeup_ports() tipc_nametbl_withdraw()
T4: [grab port lock]* named_cluster_distribute()
T5: wakeupdispatch() tipc_link_send()
T6: [grab node lock]*
The opposite order of holding port lock and node lock on above two
different paths may result in a deadlock. If socket lock instead of
port lock is used to protect port instance in tipc_withdraw(), the
reverse order of holding port lock and node lock will be eliminated,
as a result, the deadlock is killed as well.
Reported-by: Lars Everbrand <lars.everbrand@ericsson.com>
Reviewed-by: Erik Hugne <erik.hugne@ericsson.com>
Signed-off-by: Ying Xue <ying.xue@windriver.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'net')
| -rw-r--r-- | net/tipc/port.c | 45 | ||||
| -rw-r--r-- | net/tipc/port.h | 6 | ||||
| -rw-r--r-- | net/tipc/socket.c | 46 |
3 files changed, 49 insertions, 48 deletions
diff --git a/net/tipc/port.c b/net/tipc/port.c index c081a7632302..d43f3182b1d4 100644 --- a/net/tipc/port.c +++ b/net/tipc/port.c | |||
| @@ -251,18 +251,15 @@ struct tipc_port *tipc_createport(struct sock *sk, | |||
| 251 | return p_ptr; | 251 | return p_ptr; |
| 252 | } | 252 | } |
| 253 | 253 | ||
| 254 | int tipc_deleteport(u32 ref) | 254 | int tipc_deleteport(struct tipc_port *p_ptr) |
| 255 | { | 255 | { |
| 256 | struct tipc_port *p_ptr; | ||
| 257 | struct sk_buff *buf = NULL; | 256 | struct sk_buff *buf = NULL; |
| 258 | 257 | ||
| 259 | tipc_withdraw(ref, 0, NULL); | 258 | tipc_withdraw(p_ptr, 0, NULL); |
| 260 | p_ptr = tipc_port_lock(ref); | ||
| 261 | if (!p_ptr) | ||
| 262 | return -EINVAL; | ||
| 263 | 259 | ||
| 264 | tipc_ref_discard(ref); | 260 | spin_lock_bh(p_ptr->lock); |
| 265 | tipc_port_unlock(p_ptr); | 261 | tipc_ref_discard(p_ptr->ref); |
| 262 | spin_unlock_bh(p_ptr->lock); | ||
| 266 | 263 | ||
| 267 | k_cancel_timer(&p_ptr->timer); | 264 | k_cancel_timer(&p_ptr->timer); |
| 268 | if (p_ptr->connected) { | 265 | if (p_ptr->connected) { |
| @@ -704,47 +701,36 @@ int tipc_set_portimportance(u32 ref, unsigned int imp) | |||
| 704 | } | 701 | } |
| 705 | 702 | ||
| 706 | 703 | ||
| 707 | int tipc_publish(u32 ref, unsigned int scope, struct tipc_name_seq const *seq) | 704 | int tipc_publish(struct tipc_port *p_ptr, unsigned int scope, |
| 705 | struct tipc_name_seq const *seq) | ||
| 708 | { | 706 | { |
| 709 | struct tipc_port *p_ptr; | ||
| 710 | struct publication *publ; | 707 | struct publication *publ; |
| 711 | u32 key; | 708 | u32 key; |
| 712 | int res = -EINVAL; | ||
| 713 | 709 | ||
| 714 | p_ptr = tipc_port_lock(ref); | 710 | if (p_ptr->connected) |
| 715 | if (!p_ptr) | ||
| 716 | return -EINVAL; | 711 | return -EINVAL; |
| 712 | key = p_ptr->ref + p_ptr->pub_count + 1; | ||
| 713 | if (key == p_ptr->ref) | ||
| 714 | return -EADDRINUSE; | ||
| 717 | 715 | ||
| 718 | if (p_ptr->connected) | ||
| 719 | goto exit; | ||
| 720 | key = ref + p_ptr->pub_count + 1; | ||
| 721 | if (key == ref) { | ||
| 722 | res = -EADDRINUSE; | ||
| 723 | goto exit; | ||
| 724 | } | ||
| 725 | publ = tipc_nametbl_publish(seq->type, seq->lower, seq->upper, | 716 | publ = tipc_nametbl_publish(seq->type, seq->lower, seq->upper, |
| 726 | scope, p_ptr->ref, key); | 717 | scope, p_ptr->ref, key); |
| 727 | if (publ) { | 718 | if (publ) { |
| 728 | list_add(&publ->pport_list, &p_ptr->publications); | 719 | list_add(&publ->pport_list, &p_ptr->publications); |
| 729 | p_ptr->pub_count++; | 720 | p_ptr->pub_count++; |
| 730 | p_ptr->published = 1; | 721 | p_ptr->published = 1; |
| 731 | res = 0; | 722 | return 0; |
| 732 | } | 723 | } |
| 733 | exit: | 724 | return -EINVAL; |
| 734 | tipc_port_unlock(p_ptr); | ||
| 735 | return res; | ||
| 736 | } | 725 | } |
| 737 | 726 | ||
| 738 | int tipc_withdraw(u32 ref, unsigned int scope, struct tipc_name_seq const *seq) | 727 | int tipc_withdraw(struct tipc_port *p_ptr, unsigned int scope, |
| 728 | struct tipc_name_seq const *seq) | ||
| 739 | { | 729 | { |
| 740 | struct tipc_port *p_ptr; | ||
| 741 | struct publication *publ; | 730 | struct publication *publ; |
| 742 | struct publication *tpubl; | 731 | struct publication *tpubl; |
| 743 | int res = -EINVAL; | 732 | int res = -EINVAL; |
| 744 | 733 | ||
| 745 | p_ptr = tipc_port_lock(ref); | ||
| 746 | if (!p_ptr) | ||
| 747 | return -EINVAL; | ||
| 748 | if (!seq) { | 734 | if (!seq) { |
| 749 | list_for_each_entry_safe(publ, tpubl, | 735 | list_for_each_entry_safe(publ, tpubl, |
| 750 | &p_ptr->publications, pport_list) { | 736 | &p_ptr->publications, pport_list) { |
| @@ -771,7 +757,6 @@ int tipc_withdraw(u32 ref, unsigned int scope, struct tipc_name_seq const *seq) | |||
| 771 | } | 757 | } |
| 772 | if (list_empty(&p_ptr->publications)) | 758 | if (list_empty(&p_ptr->publications)) |
| 773 | p_ptr->published = 0; | 759 | p_ptr->published = 0; |
| 774 | tipc_port_unlock(p_ptr); | ||
| 775 | return res; | 760 | return res; |
| 776 | } | 761 | } |
| 777 | 762 | ||
diff --git a/net/tipc/port.h b/net/tipc/port.h index 912253597343..34f12bd4074e 100644 --- a/net/tipc/port.h +++ b/net/tipc/port.h | |||
| @@ -116,7 +116,7 @@ int tipc_reject_msg(struct sk_buff *buf, u32 err); | |||
| 116 | 116 | ||
| 117 | void tipc_acknowledge(u32 port_ref, u32 ack); | 117 | void tipc_acknowledge(u32 port_ref, u32 ack); |
| 118 | 118 | ||
| 119 | int tipc_deleteport(u32 portref); | 119 | int tipc_deleteport(struct tipc_port *p_ptr); |
| 120 | 120 | ||
| 121 | int tipc_portimportance(u32 portref, unsigned int *importance); | 121 | int tipc_portimportance(u32 portref, unsigned int *importance); |
| 122 | int tipc_set_portimportance(u32 portref, unsigned int importance); | 122 | int tipc_set_portimportance(u32 portref, unsigned int importance); |
| @@ -127,9 +127,9 @@ int tipc_set_portunreliable(u32 portref, unsigned int isunreliable); | |||
| 127 | int tipc_portunreturnable(u32 portref, unsigned int *isunreturnable); | 127 | int tipc_portunreturnable(u32 portref, unsigned int *isunreturnable); |
| 128 | int tipc_set_portunreturnable(u32 portref, unsigned int isunreturnable); | 128 | int tipc_set_portunreturnable(u32 portref, unsigned int isunreturnable); |
| 129 | 129 | ||
| 130 | int tipc_publish(u32 portref, unsigned int scope, | 130 | int tipc_publish(struct tipc_port *p_ptr, unsigned int scope, |
| 131 | struct tipc_name_seq const *name_seq); | 131 | struct tipc_name_seq const *name_seq); |
| 132 | int tipc_withdraw(u32 portref, unsigned int scope, | 132 | int tipc_withdraw(struct tipc_port *p_ptr, unsigned int scope, |
| 133 | struct tipc_name_seq const *name_seq); | 133 | struct tipc_name_seq const *name_seq); |
| 134 | 134 | ||
| 135 | int tipc_connect(u32 portref, struct tipc_portid const *port); | 135 | int tipc_connect(u32 portref, struct tipc_portid const *port); |
diff --git a/net/tipc/socket.c b/net/tipc/socket.c index 3b61851bb927..e741416d1d24 100644 --- a/net/tipc/socket.c +++ b/net/tipc/socket.c | |||
| @@ -354,7 +354,7 @@ static int release(struct socket *sock) | |||
| 354 | * Delete TIPC port; this ensures no more messages are queued | 354 | * Delete TIPC port; this ensures no more messages are queued |
| 355 | * (also disconnects an active connection & sends a 'FIN-' to peer) | 355 | * (also disconnects an active connection & sends a 'FIN-' to peer) |
| 356 | */ | 356 | */ |
| 357 | res = tipc_deleteport(tport->ref); | 357 | res = tipc_deleteport(tport); |
| 358 | 358 | ||
| 359 | /* Discard any remaining (connection-based) messages in receive queue */ | 359 | /* Discard any remaining (connection-based) messages in receive queue */ |
| 360 | __skb_queue_purge(&sk->sk_receive_queue); | 360 | __skb_queue_purge(&sk->sk_receive_queue); |
| @@ -386,30 +386,46 @@ static int release(struct socket *sock) | |||
| 386 | */ | 386 | */ |
| 387 | static int bind(struct socket *sock, struct sockaddr *uaddr, int uaddr_len) | 387 | static int bind(struct socket *sock, struct sockaddr *uaddr, int uaddr_len) |
| 388 | { | 388 | { |
| 389 | struct sock *sk = sock->sk; | ||
| 389 | struct sockaddr_tipc *addr = (struct sockaddr_tipc *)uaddr; | 390 | struct sockaddr_tipc *addr = (struct sockaddr_tipc *)uaddr; |
| 390 | u32 portref = tipc_sk_port(sock->sk)->ref; | 391 | struct tipc_port *tport = tipc_sk_port(sock->sk); |
| 392 | int res = -EINVAL; | ||
| 391 | 393 | ||
| 392 | if (unlikely(!uaddr_len)) | 394 | lock_sock(sk); |
| 393 | return tipc_withdraw(portref, 0, NULL); | 395 | if (unlikely(!uaddr_len)) { |
| 396 | res = tipc_withdraw(tport, 0, NULL); | ||
| 397 | goto exit; | ||
| 398 | } | ||
| 394 | 399 | ||
| 395 | if (uaddr_len < sizeof(struct sockaddr_tipc)) | 400 | if (uaddr_len < sizeof(struct sockaddr_tipc)) { |
| 396 | return -EINVAL; | 401 | res = -EINVAL; |
| 397 | if (addr->family != AF_TIPC) | 402 | goto exit; |
| 398 | return -EAFNOSUPPORT; | 403 | } |
| 404 | if (addr->family != AF_TIPC) { | ||
| 405 | res = -EAFNOSUPPORT; | ||
| 406 | goto exit; | ||
| 407 | } | ||
| 399 | 408 | ||
| 400 | if (addr->addrtype == TIPC_ADDR_NAME) | 409 | if (addr->addrtype == TIPC_ADDR_NAME) |
| 401 | addr->addr.nameseq.upper = addr->addr.nameseq.lower; | 410 | addr->addr.nameseq.upper = addr->addr.nameseq.lower; |
| 402 | else if (addr->addrtype != TIPC_ADDR_NAMESEQ) | 411 | else if (addr->addrtype != TIPC_ADDR_NAMESEQ) { |
| 403 | return -EAFNOSUPPORT; | 412 | res = -EAFNOSUPPORT; |
| 413 | goto exit; | ||
| 414 | } | ||
| 404 | 415 | ||
| 405 | if ((addr->addr.nameseq.type < TIPC_RESERVED_TYPES) && | 416 | if ((addr->addr.nameseq.type < TIPC_RESERVED_TYPES) && |
| 406 | (addr->addr.nameseq.type != TIPC_TOP_SRV) && | 417 | (addr->addr.nameseq.type != TIPC_TOP_SRV) && |
| 407 | (addr->addr.nameseq.type != TIPC_CFG_SRV)) | 418 | (addr->addr.nameseq.type != TIPC_CFG_SRV)) { |
| 408 | return -EACCES; | 419 | res = -EACCES; |
| 420 | goto exit; | ||
| 421 | } | ||
| 409 | 422 | ||
| 410 | return (addr->scope > 0) ? | 423 | res = (addr->scope > 0) ? |
| 411 | tipc_publish(portref, addr->scope, &addr->addr.nameseq) : | 424 | tipc_publish(tport, addr->scope, &addr->addr.nameseq) : |
| 412 | tipc_withdraw(portref, -addr->scope, &addr->addr.nameseq); | 425 | tipc_withdraw(tport, -addr->scope, &addr->addr.nameseq); |
| 426 | exit: | ||
| 427 | release_sock(sk); | ||
| 428 | return res; | ||
| 413 | } | 429 | } |
| 414 | 430 | ||
| 415 | /** | 431 | /** |
