Some problems have been experienced in the field which cause an oops
in the pppol2tp driver if L2TP tunnels fail while passing data.
The pppol2tp driver uses private data that is referenced via the
sk->sk_user_data of its UDP and PPPoL2TP sockets. This patch makes
sure that the driver uses sock_hold() when it holds a reference to the
sk pointer. This affects its sendmsg(), recvmsg(), getname(),
[gs]etsockopt() and ioctl() handlers.
Tested by ISP where problem was seen. System has been up 10 days with
no oops since running this patch. Without the patch, an oops would
occur every 1-2 days.
Signed-off-by: James Chapman <jchapman@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
if (sk == NULL)
return NULL;
if (sk == NULL)
return NULL;
session = (struct pppol2tp_session *)(sk->sk_user_data);
session = (struct pppol2tp_session *)(sk->sk_user_data);
- if (session == NULL)
- return NULL;
+ if (session == NULL) {
+ sock_put(sk);
+ goto out;
+ }
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
if (sk == NULL)
return NULL;
if (sk == NULL)
return NULL;
tunnel = (struct pppol2tp_tunnel *)(sk->sk_user_data);
tunnel = (struct pppol2tp_tunnel *)(sk->sk_user_data);
- if (tunnel == NULL)
- return NULL;
+ if (tunnel == NULL) {
+ sock_put(sk);
+ goto out;
+ }
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
session->stats.rx_errors++;
kfree_skb(skb);
sock_put(session->sock);
session->stats.rx_errors++;
kfree_skb(skb);
sock_put(session->sock);
return 0;
error:
/* Put UDP header back */
__skb_push(skb, sizeof(struct udphdr));
return 0;
error:
/* Put UDP header back */
__skb_push(skb, sizeof(struct udphdr));
"%s: received %d bytes\n", tunnel->name, skb->len);
if (pppol2tp_recv_core(sk, skb))
"%s: received %d bytes\n", tunnel->name, skb->len);
if (pppol2tp_recv_core(sk, skb))
+pass_up_put:
+ sock_put(sk);
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
/* What header length is configured for this session? */
hdr_len = pppol2tp_l2tp_header_len(session);
/* What header length is configured for this session? */
hdr_len = pppol2tp_l2tp_header_len(session);
sizeof(ppph) + total_len,
0, GFP_KERNEL);
if (!skb)
sizeof(ppph) + total_len,
0, GFP_KERNEL);
if (!skb)
+ goto error_put_sess_tun;
/* Reserve space for headers. */
skb_reserve(skb, NET_SKB_PAD);
/* Reserve space for headers. */
skb_reserve(skb, NET_SKB_PAD);
error = memcpy_fromiovec(skb->data, m->msg_iov, total_len);
if (error < 0) {
kfree_skb(skb);
error = memcpy_fromiovec(skb->data, m->msg_iov, total_len);
if (error < 0) {
kfree_skb(skb);
+ goto error_put_sess_tun;
}
skb_put(skb, total_len);
}
skb_put(skb, total_len);
session->stats.tx_errors++;
}
session->stats.tx_errors++;
}
+ return error;
+
+error_put_sess_tun:
+ sock_put(session->tunnel_sock);
+error_put_sess:
+ sock_put(sk);
+/* Automatically called when the skb is freed.
+ */
+static void pppol2tp_sock_wfree(struct sk_buff *skb)
+{
+ sock_put(skb->sk);
+}
+
+/* For data skbs that we transmit, we associate with the tunnel socket
+ * but don't do accounting.
+ */
+static inline void pppol2tp_skb_set_owner_w(struct sk_buff *skb, struct sock *sk)
+{
+ sock_hold(sk);
+ skb->sk = sk;
+ skb->destructor = pppol2tp_sock_wfree;
+}
+
/* Transmit function called by generic PPP driver. Sends PPP frame
* over PPPoL2TP socket.
*
/* Transmit function called by generic PPP driver. Sends PPP frame
* over PPPoL2TP socket.
*
sk_tun = session->tunnel_sock;
if (sk_tun == NULL)
sk_tun = session->tunnel_sock;
if (sk_tun == NULL)
tunnel = pppol2tp_sock_to_tunnel(sk_tun);
if (tunnel == NULL)
tunnel = pppol2tp_sock_to_tunnel(sk_tun);
if (tunnel == NULL)
/* What header length is configured for this session? */
hdr_len = pppol2tp_l2tp_header_len(session);
/* What header length is configured for this session? */
hdr_len = pppol2tp_l2tp_header_len(session);
sizeof(struct udphdr) + hdr_len + sizeof(ppph);
old_headroom = skb_headroom(skb);
if (skb_cow_head(skb, headroom))
sizeof(struct udphdr) + hdr_len + sizeof(ppph);
old_headroom = skb_headroom(skb);
if (skb_cow_head(skb, headroom))
+ goto abort_put_sess_tun;
new_headroom = skb_headroom(skb);
skb_orphan(skb);
new_headroom = skb_headroom(skb);
skb_orphan(skb);
/* Get routing info from the tunnel socket */
dst_release(skb->dst);
skb->dst = dst_clone(__sk_dst_get(sk_tun));
/* Get routing info from the tunnel socket */
dst_release(skb->dst);
skb->dst = dst_clone(__sk_dst_get(sk_tun));
+ pppol2tp_skb_set_owner_w(skb, sk_tun);
/* Queue the packet to IP for output */
len = skb->len;
/* Queue the packet to IP for output */
len = skb->len;
session->stats.tx_errors++;
}
session->stats.tx_errors++;
}
+ sock_put(sk_tun);
+ sock_put(sk);
+abort_put_sess_tun:
+ sock_put(sk_tun);
+abort_put_sess:
+ sock_put(sk);
abort:
/* Free the original skb */
kfree_skb(skb);
abort:
/* Free the original skb */
kfree_skb(skb);
{
struct pppol2tp_tunnel *tunnel;
{
struct pppol2tp_tunnel *tunnel;
- tunnel = pppol2tp_sock_to_tunnel(sk);
+ tunnel = sk->sk_user_data;
if (tunnel == NULL)
goto end;
if (tunnel == NULL)
goto end;
if (sk->sk_user_data != NULL) {
struct pppol2tp_tunnel *tunnel;
if (sk->sk_user_data != NULL) {
struct pppol2tp_tunnel *tunnel;
- session = pppol2tp_sock_to_session(sk);
+ session = sk->sk_user_data;
if (session == NULL)
goto out;
if (session == NULL)
goto out;
+ BUG_ON(session->magic != L2TP_SESSION_MAGIC);
+
/* Don't use pppol2tp_sock_to_tunnel() here to
* get the tunnel context because the tunnel
* socket might have already been closed (its
/* Don't use pppol2tp_sock_to_tunnel() here to
* get the tunnel context because the tunnel
* socket might have already been closed (its
error = ppp_register_channel(&po->chan);
if (error)
error = ppp_register_channel(&po->chan);
if (error)
/* This is how we get the session context from the socket. */
sk->sk_user_data = session;
/* This is how we get the session context from the socket. */
sk->sk_user_data = session;
PRINTK(session->debug, PPPOL2TP_MSG_CONTROL, KERN_INFO,
"%s: created\n", session->name);
PRINTK(session->debug, PPPOL2TP_MSG_CONTROL, KERN_INFO,
"%s: created\n", session->name);
+end_put_tun:
+ sock_put(tunnel_sock);
*usockaddr_len = len;
error = 0;
*usockaddr_len = len;
error = 0;
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = pppol2tp_tunnel_ioctl(tunnel, cmd, arg);
err = pppol2tp_tunnel_ioctl(tunnel, cmd, arg);
+ sock_put(session->tunnel_sock);
+ goto end_put_sess;
}
err = pppol2tp_session_ioctl(session, cmd, arg);
}
err = pppol2tp_session_ioctl(session, cmd, arg);
+end_put_sess:
+ sock_put(sk);
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = pppol2tp_tunnel_setsockopt(sk, tunnel, optname, val);
err = pppol2tp_tunnel_setsockopt(sk, tunnel, optname, val);
+ sock_put(session->tunnel_sock);
} else
err = pppol2tp_session_setsockopt(sk, session, optname, val);
err = 0;
} else
err = pppol2tp_session_setsockopt(sk, session, optname, val);
err = 0;
+end_put_sess:
+ sock_put(sk);
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = -EBADF;
tunnel = pppol2tp_sock_to_tunnel(session->tunnel_sock);
if (tunnel == NULL)
err = pppol2tp_tunnel_getsockopt(sk, tunnel, optname, &val);
err = pppol2tp_tunnel_getsockopt(sk, tunnel, optname, &val);
+ sock_put(session->tunnel_sock);
} else
err = pppol2tp_session_getsockopt(sk, session, optname, &val);
err = -EFAULT;
if (put_user(len, (int __user *) optlen))
} else
err = pppol2tp_session_getsockopt(sk, session, optname, &val);
err = -EFAULT;
if (put_user(len, (int __user *) optlen))
if (copy_to_user((void __user *) optval, &val, len))
if (copy_to_user((void __user *) optval, &val, len))
+
+end_put_sess:
+ sock_put(sk);