Skip to content

Commit

Permalink
l2tp: unhash l2tp sessions on delete, not on free
Browse files Browse the repository at this point in the history
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: Tom Parkin <tparkin@katalix.com>
Signed-off-by: James Chapman <jchapman@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
  • Loading branch information
Tom Parkin authored and David S. Miller committed Mar 20, 2013
1 parent 7b7c071 commit f6e16b2
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 50 deletions.
75 changes: 34 additions & 41 deletions net/l2tp/l2tp_core.c
Original file line number Diff line number Diff line change
Expand Up @@ -1316,26 +1316,12 @@ void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)

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)
(*session->ref)(session);

write_unlock_bh(&tunnel->hlist_lock);

if (tunnel->version != L2TP_HDR_VER_2) {
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_unhash(session);
l2tp_session_queue_purge(session);

if (session->session_close != NULL)
Expand Down Expand Up @@ -1732,64 +1718,71 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_delete);
*/
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);

tunnel = session->tunnel;
if (tunnel != NULL) {
if (tunnel) {
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);
hlist_del_init(&session->hlist);
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) {
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();
}

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
pseudowire modules.
*/
int l2tp_session_delete(struct l2tp_session *session)
{
if (session->ref)
(*session->ref)(session);
__l2tp_session_unhash(session);
l2tp_session_queue_purge(session);

if (session->session_close != NULL)
(*session->session_close)(session);

if (session->deref)
(*session->ref)(session);
l2tp_session_dec_refcount(session);

return 0;
}
EXPORT_SYMBOL_GPL(l2tp_session_delete);


/* We come here whenever a session's send_seq, cookie_len or
* l2specific_len parameters are set.
*/
Expand Down
1 change: 1 addition & 0 deletions net/l2tp/l2tp_core.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 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 void __l2tp_session_unhash(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_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));
Expand Down
12 changes: 3 additions & 9 deletions net/l2tp/l2tp_ppp.c
Original file line number Diff line number Diff line change
Expand Up @@ -466,19 +466,12 @@ static void pppol2tp_session_close(struct l2tp_session *session)
*/
static void pppol2tp_session_destruct(struct sock *sk)
{
struct l2tp_session *session;

if (sk->sk_user_data != NULL) {
session = sk->sk_user_data;
if (session == NULL)
goto out;

struct l2tp_session *session = sk->sk_user_data;
if (session) {
sk->sk_user_data = NULL;
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
l2tp_session_dec_refcount(session);
}

out:
return;
}

Expand Down Expand Up @@ -509,6 +502,7 @@ static int pppol2tp_release(struct socket *sock)

/* Purge any queued data */
if (session != NULL) {
__l2tp_session_unhash(session);
l2tp_session_queue_purge(session);
sock_put(sk);
}
Expand Down

0 comments on commit f6e16b2

Please sign in to comment.