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: Tom Parkin <tparkin@katalix.com>
Signed-off-by: James Chapman <jchapman@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Tom Parkin 2013-03-19 06:11:23 +00:00 committed by David S. Miller
parent 7b7c0719cd
commit f6e16b299b
3 changed files with 40 additions and 52 deletions

View File

@ -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)
@ -1732,37 +1718,15 @@ 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);
/* Delete the session from the 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 */
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);
}
@ -1773,23 +1737,52 @@ void l2tp_session_free(struct l2tp_session *session)
}
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;
/* 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);
/* 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();
}
}
}
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.
*/

View File

@ -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));

View File

@ -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;
}
@ -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);
}