Commit f6e16b29 authored by Tom Parkin's avatar Tom Parkin Committed by David S. Miller

l2tp: unhash l2tp sessions on delete, not on free

If we postpone unhashing of l2tp sessions until the structure is freed, we
risk:

 1. further packets arriving and getting queued while the pseudowire is being
    closed down
 2. the recv path hitting "scheduling while atomic" errors in the case that
    recv drops the last reference to a session and calls l2tp_session_free
    while in atomic context

As such, l2tp sessions should be unhashed from l2tp_core data structures early
in the teardown process prior to calling pseudowire close.  For pseudowires
like l2tp_ppp which have multiple shutdown codepaths, provide an unhash hook.
Signed-off-by: default avatarTom Parkin <tparkin@katalix.com>
Signed-off-by: default avatarJames Chapman <jchapman@katalix.com>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 7b7c0719
...@@ -1316,26 +1316,12 @@ void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel) ...@@ -1316,26 +1316,12 @@ void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)
hlist_del_init(&session->hlist); hlist_del_init(&session->hlist);
/* Since we should hold the sock lock while
* doing any unbinding, we need to release the
* lock we're holding before taking that lock.
* Hold a reference to the sock so it doesn't
* disappear as we're jumping between locks.
*/
if (session->ref != NULL) if (session->ref != NULL)
(*session->ref)(session); (*session->ref)(session);
write_unlock_bh(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
if (tunnel->version != L2TP_HDR_VER_2) { __l2tp_session_unhash(session);
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
spin_lock_bh(&pn->l2tp_session_hlist_lock);
hlist_del_init_rcu(&session->global_hlist);
spin_unlock_bh(&pn->l2tp_session_hlist_lock);
synchronize_rcu();
}
l2tp_session_queue_purge(session); l2tp_session_queue_purge(session);
if (session->session_close != NULL) if (session->session_close != NULL)
...@@ -1732,64 +1718,71 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_delete); ...@@ -1732,64 +1718,71 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_delete);
*/ */
void l2tp_session_free(struct l2tp_session *session) void l2tp_session_free(struct l2tp_session *session)
{ {
struct l2tp_tunnel *tunnel; struct l2tp_tunnel *tunnel = session->tunnel;
BUG_ON(atomic_read(&session->ref_count) != 0); BUG_ON(atomic_read(&session->ref_count) != 0);
tunnel = session->tunnel; if (tunnel) {
if (tunnel != NULL) {
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC); BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
if (session->session_id != 0)
atomic_dec(&l2tp_session_count);
sock_put(tunnel->sock);
session->tunnel = NULL;
l2tp_tunnel_dec_refcount(tunnel);
}
kfree(session);
return;
}
EXPORT_SYMBOL_GPL(l2tp_session_free);
/* Remove an l2tp session from l2tp_core's hash lists.
* Provides a tidyup interface for pseudowire code which can't just route all
* shutdown via. l2tp_session_delete and a pseudowire-specific session_close
* callback.
*/
void __l2tp_session_unhash(struct l2tp_session *session)
{
struct l2tp_tunnel *tunnel = session->tunnel;
/* Delete the session from the hash */ /* Remove the session from core hashes */
if (tunnel) {
/* Remove from the per-tunnel hash */
write_lock_bh(&tunnel->hlist_lock); write_lock_bh(&tunnel->hlist_lock);
hlist_del_init(&session->hlist); hlist_del_init(&session->hlist);
write_unlock_bh(&tunnel->hlist_lock); write_unlock_bh(&tunnel->hlist_lock);
/* Unlink from the global hash if not L2TPv2 */ /* For L2TPv3 we have a per-net hash: remove from there, too */
if (tunnel->version != L2TP_HDR_VER_2) { if (tunnel->version != L2TP_HDR_VER_2) {
struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net); struct l2tp_net *pn = l2tp_pernet(tunnel->l2tp_net);
spin_lock_bh(&pn->l2tp_session_hlist_lock); spin_lock_bh(&pn->l2tp_session_hlist_lock);
hlist_del_init_rcu(&session->global_hlist); hlist_del_init_rcu(&session->global_hlist);
spin_unlock_bh(&pn->l2tp_session_hlist_lock); spin_unlock_bh(&pn->l2tp_session_hlist_lock);
synchronize_rcu(); synchronize_rcu();
} }
if (session->session_id != 0)
atomic_dec(&l2tp_session_count);
sock_put(tunnel->sock);
/* This will delete the tunnel context if this
* is the last session on the tunnel.
*/
session->tunnel = NULL;
l2tp_tunnel_dec_refcount(tunnel);
} }
kfree(session);
return;
} }
EXPORT_SYMBOL_GPL(l2tp_session_free); EXPORT_SYMBOL_GPL(__l2tp_session_unhash);
/* This function is used by the netlink SESSION_DELETE command and by /* This function is used by the netlink SESSION_DELETE command and by
pseudowire modules. pseudowire modules.
*/ */
int l2tp_session_delete(struct l2tp_session *session) int l2tp_session_delete(struct l2tp_session *session)
{ {
if (session->ref)
(*session->ref)(session);
__l2tp_session_unhash(session);
l2tp_session_queue_purge(session); l2tp_session_queue_purge(session);
if (session->session_close != NULL) if (session->session_close != NULL)
(*session->session_close)(session); (*session->session_close)(session);
if (session->deref)
(*session->ref)(session);
l2tp_session_dec_refcount(session); l2tp_session_dec_refcount(session);
return 0; return 0;
} }
EXPORT_SYMBOL_GPL(l2tp_session_delete); EXPORT_SYMBOL_GPL(l2tp_session_delete);
/* We come here whenever a session's send_seq, cookie_len or /* We come here whenever a session's send_seq, cookie_len or
* l2specific_len parameters are set. * l2specific_len parameters are set.
*/ */
......
...@@ -242,6 +242,7 @@ extern int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_i ...@@ -242,6 +242,7 @@ extern int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_i
extern void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel); extern void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel);
extern int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel); extern int l2tp_tunnel_delete(struct l2tp_tunnel *tunnel);
extern struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnel, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg); extern struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnel, u32 session_id, u32 peer_session_id, struct l2tp_session_cfg *cfg);
extern void __l2tp_session_unhash(struct l2tp_session *session);
extern int l2tp_session_delete(struct l2tp_session *session); extern int l2tp_session_delete(struct l2tp_session *session);
extern void l2tp_session_free(struct l2tp_session *session); extern void l2tp_session_free(struct l2tp_session *session);
extern void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, unsigned char *ptr, unsigned char *optr, u16 hdrflags, int length, int (*payload_hook)(struct sk_buff *skb)); extern void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, unsigned char *ptr, unsigned char *optr, u16 hdrflags, int length, int (*payload_hook)(struct sk_buff *skb));
......
...@@ -466,19 +466,12 @@ static void pppol2tp_session_close(struct l2tp_session *session) ...@@ -466,19 +466,12 @@ static void pppol2tp_session_close(struct l2tp_session *session)
*/ */
static void pppol2tp_session_destruct(struct sock *sk) static void pppol2tp_session_destruct(struct sock *sk)
{ {
struct l2tp_session *session; struct l2tp_session *session = sk->sk_user_data;
if (session) {
if (sk->sk_user_data != NULL) {
session = sk->sk_user_data;
if (session == NULL)
goto out;
sk->sk_user_data = NULL; sk->sk_user_data = NULL;
BUG_ON(session->magic != L2TP_SESSION_MAGIC); BUG_ON(session->magic != L2TP_SESSION_MAGIC);
l2tp_session_dec_refcount(session); l2tp_session_dec_refcount(session);
} }
out:
return; return;
} }
...@@ -509,6 +502,7 @@ static int pppol2tp_release(struct socket *sock) ...@@ -509,6 +502,7 @@ static int pppol2tp_release(struct socket *sock)
/* Purge any queued data */ /* Purge any queued data */
if (session != NULL) { if (session != NULL) {
__l2tp_session_unhash(session);
l2tp_session_queue_purge(session); l2tp_session_queue_purge(session);
sock_put(sk); sock_put(sk);
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment