aboutsummaryrefslogtreecommitdiffstats
path: root/drivers
diff options
context:
space:
mode:
authorJames Chapman <jchapman@katalix.com>2010-03-16 02:29:20 -0400
committerDavid S. Miller <davem@davemloft.net>2010-03-16 17:15:44 -0400
commitc3259c8a7060d480e8eb2166da0a99d6879146b4 (patch)
tree426adf20cdf728f9f283fde9b20ea1e61e7f7c1e /drivers
parentdb443c441e204cecc1bcec490d40997db988ce3a (diff)
l2tp: Fix UDP socket reference count bugs in the pppol2tp driver
This patch fixes UDP socket refcnt bugs in the pppol2tp driver. A bug can cause a kernel stack trace when a tunnel socket is closed. A way to reproduce the issue is to prepare the UDP socket for L2TP (by opening a tunnel pppol2tp socket) and then close it before any L2TP sessions are added to it. The sequence is Create UDP socket Create tunnel pppol2tp socket to prepare UDP socket for L2TP pppol2tp_connect: session_id=0, peer_session_id=0 L2TP SCCRP control frame received (tunnel_id==0) pppol2tp_recv_core: sock_hold() pppol2tp_recv_core: sock_put L2TP ZLB control frame received (tunnel_id=nnn) pppol2tp_recv_core: sock_hold() pppol2tp_recv_core: sock_put Close tunnel management socket pppol2tp_release: session_id=0, peer_session_id=0 Close UDP socket udp_lib_close: BUG The addition of sock_hold() in pppol2tp_connect() solves the problem. For data frames, two sock_put() calls were added to plug a refcnt leak per received data frame. The ref that is grabbed at the top of pppol2tp_recv_core() must always be released, but this wasn't done for accepted data frames or data frames discarded because of bad UDP checksums. This leak meant that any UDP socket that had passed L2TP data traffic (i.e. L2TP data frames, not just L2TP control frames) using pppol2tp would not be released by the kernel. WARNING: at include/net/sock.h:435 udp_lib_unhash+0x117/0x120() Pid: 1086, comm: openl2tpd Not tainted 2.6.33-rc1 #8 Call Trace: [<c119e9b7>] ? udp_lib_unhash+0x117/0x120 [<c101b871>] ? warn_slowpath_common+0x71/0xd0 [<c119e9b7>] ? udp_lib_unhash+0x117/0x120 [<c101b8e3>] ? warn_slowpath_null+0x13/0x20 [<c119e9b7>] ? udp_lib_unhash+0x117/0x120 [<c11598a7>] ? sk_common_release+0x17/0x90 [<c11a5e33>] ? inet_release+0x33/0x60 [<c11577b0>] ? sock_release+0x10/0x60 [<c115780f>] ? sock_close+0xf/0x30 [<c106e542>] ? __fput+0x52/0x150 [<c106b68e>] ? filp_close+0x3e/0x70 [<c101d2e2>] ? put_files_struct+0x62/0xb0 [<c101eaf7>] ? do_exit+0x5e7/0x650 [<c1081623>] ? mntput_no_expire+0x13/0x70 [<c106b68e>] ? filp_close+0x3e/0x70 [<c101eb8a>] ? do_group_exit+0x2a/0x70 [<c101ebe1>] ? sys_exit_group+0x11/0x20 [<c10029b0>] ? sysenter_do_call+0x12/0x26 Signed-off-by: James Chapman <jchapman@katalix.com> Signed-off-by: David S. Miller <davem@davemloft.net>
Diffstat (limited to 'drivers')
-rw-r--r--drivers/net/pppol2tp.c3
1 files changed, 3 insertions, 0 deletions
diff --git a/drivers/net/pppol2tp.c b/drivers/net/pppol2tp.c
index 5861ee9599a2..449a9825200d 100644
--- a/drivers/net/pppol2tp.c
+++ b/drivers/net/pppol2tp.c
@@ -756,6 +756,7 @@ static int pppol2tp_recv_core(struct sock *sock, struct sk_buff *skb)
756 756
757 /* Try to dequeue as many skbs from reorder_q as we can. */ 757 /* Try to dequeue as many skbs from reorder_q as we can. */
758 pppol2tp_recv_dequeue(session); 758 pppol2tp_recv_dequeue(session);
759 sock_put(sock);
759 760
760 return 0; 761 return 0;
761 762
@@ -772,6 +773,7 @@ discard_bad_csum:
772 UDP_INC_STATS_USER(&init_net, UDP_MIB_INERRORS, 0); 773 UDP_INC_STATS_USER(&init_net, UDP_MIB_INERRORS, 0);
773 tunnel->stats.rx_errors++; 774 tunnel->stats.rx_errors++;
774 kfree_skb(skb); 775 kfree_skb(skb);
776 sock_put(sock);
775 777
776 return 0; 778 return 0;
777 779
@@ -1662,6 +1664,7 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
1662 if (tunnel_sock == NULL) 1664 if (tunnel_sock == NULL)
1663 goto end; 1665 goto end;
1664 1666
1667 sock_hold(tunnel_sock);
1665 tunnel = tunnel_sock->sk_user_data; 1668 tunnel = tunnel_sock->sk_user_data;
1666 } else { 1669 } else {
1667 tunnel = pppol2tp_tunnel_find(sock_net(sk), sp->pppol2tp.s_tunnel); 1670 tunnel = pppol2tp_tunnel_find(sock_net(sk), sp->pppol2tp.s_tunnel);