l2tp: fix some races in session deletion
Subsystems (3)
cover l2tp: fix some races in session deletion
L2TP provides several interfaces for deleting sessions. Using two of them concurrently can lead to use-after-free bugs.
Patch #2 uses a flag to prevent double removal of L2TP sessions. Patch #1 fixes a bug found in the way. Fixing this bug is also necessary for patch #2 to handle all cases.
This issue is similar to the tunnel deletion bug being worked on by
Sabrina: https://patchwork.ozlabs.org/patch/814173/
Guillaume Nault (2):
l2tp: ensure sessions are freed after their PPPOL2TP socket
l2tp: fix race between l2tp_session_delete() and
l2tp_tunnel_closeall()
net/l2tp/l2tp_core.c | 6 ++++++ net/l2tp/l2tp_core.h | 1 + net/l2tp/l2tp_ppp.c | 8 ++++---- 3 files changed, 11 insertions(+), 4 deletions(-)
Signature · 1 line
2.14.1
PATCH 1/2 l2tp: ensure sessions are freed after their PPPOL2TP socket
If l2tp_tunnel_delete() or l2tp_tunnel_closeall() deletes a session right after pppol2tp_release() orphaned its socket, then the 'sock' variable of the pppol2tp_session_close() callback is NULL. Yet the session is still used by pppol2tp_release().
Therefore we need to take an extra reference in any case, to prevent l2tp_tunnel_delete() or l2tp_tunnel_closeall() from freeing the session.
Since the pppol2tp_session_close() callback is only set if the session is associated to a PPPOL2TP socket and that both l2tp_tunnel_delete() and l2tp_tunnel_closeall() hold the PPPOL2TP socket before calling pppol2tp_session_close(), we're sure that pppol2tp_session_close() and pppol2tp_session_destruct() are paired and called in the right order. So the reference taken by the former will be released by the later.
--- net/l2tp/l2tp_ppp.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-)
net/l2tp/l2tp_ppp.c +4 -4
@@ -437,11 +437,11 @@ static void pppol2tp_session_close(struct l2tp_session *session)BUG_ON (session->magic !=L2TP_SESSION_MAGIC ); -if (sock) { +if (sock)inet_shutdown (sock,SEND_SHUTDOWN ); -/* Don't let the session go away before our socket does */ -l2tp_session_inc_refcount (session); - } + +/* Don't let the session go away before our socket does */ +l2tp_session_inc_refcount (session); }/* Really kill the session socket. (Called from sock_put() if
Signature · 1 line
2.14.1
PATCH 2/2 l2tp: fix race between l2tp_session_delete() and l2tp_tunnel_closeall()
In reply to Guillaume NaultThere are several ways to remove L2TP sessions:
* deleting a session explicitly using the netlink interface (with L2TP_CMD_SESSION_DELETE),
* deleting the session's parent tunnel (either by closing the tunnel's file descriptor or using the netlink interface),
* closing the PPPOL2TP file descriptor of a PPP pseudo-wire.
In some cases, when these methods are used concurrently on the same session, the session can be removed twice, leading to use-after-free bugs.
This patch adds a 'dead' flag, used by l2tp_session_delete() and l2tp_tunnel_closeall() to prevent them from stepping on each other's toes.
The session deletion path used when closing a PPPOL2TP file descriptor doesn't need to be adapted. It already has to ensure that a session remains valid for the lifetime of its PPPOL2TP file descriptor. So it takes an extra reference on the session in the ->session_close() callback (pppol2tp_session_close()), which is eventually dropped in the ->sk_destruct() callback of the PPPOL2TP socket (pppol2tp_session_destruct()).
Still, __l2tp_session_unhash() and l2tp_session_queue_purge() can be called twice and even concurrently for a given session, but thanks to proper locking and re-initialisation of list fields, this is not an issue.
--- net/l2tp/l2tp_core.c | 6 ++++++ net/l2tp/l2tp_core.h | 1 + 2 files changed, 7 insertions(+)
net/l2tp/l2tp_core.c +6 -0
@@ -1314,6 +1314,9 @@ void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel)hlist_del_init (&session->hlist); +if (test_and_set_bit (0 , &session->dead)) + goto again; +if (session->ref !=NULL ) (*session->ref)(session);
@@ -1750,6 +1753,9 @@ EXPORT_SYMBOL_GPL(__l2tp_session_unhash);*/ int l2tp_session_delete (struct l2tp_session *session) { +if (test_and_set_bit (0 , &session->dead)) +return 0 ; +if (session->ref) (*session->ref)(session);__l2tp_session_unhash (session);
net/l2tp/l2tp_core.h +1 -0
@@ -76,6 +76,7 @@ struct l2tp_session_cfg {struct l2tp_session {int magic;/* should be * L2TP_SESSION_MAGIC */ +long dead;struct l2tp_tunnel *tunnel;/* back pointer to tunnel * context */
Signature · 1 line
2.14.1
David Miller
maintainerFrom: Guillaume Nault <[email protected]>
Date: Fri, 22 Sep 2017 15:39:22 +0200
Quoted 9 lines
L2TP provides several interfaces for deleting sessions. Using two of them concurrently can lead to use-after-free bugs.
Patch #2 uses a flag to prevent double removal of L2TP sessions. Patch #1 fixes a bug found in the way. Fixing this bug is also necessary for patch #2 to handle all cases.
This issue is similar to the tunnel deletion bug being worked on by
Sabrina: https://patchwork.ozlabs.org/patch/814173/
Series applied and queued up for -stable, thanks.