l2tp: fix duplicate session creation

l2tp_session_create() relies on its caller for checking for duplicate
sessions. This is racy since a session can be concurrently inserted
after the caller's verification.

Fix this by letting l2tp_session_create() verify sessions uniqueness
upon insertion. Callers need to be adapted to check for
l2tp_session_create()'s return code instead of calling
l2tp_session_find().

pppol2tp_connect() is a bit special because it has to work on existing
sessions (if they're not connected) or to create a new session if none
is found. When acting on a preexisting session, a reference must be
held or it could go away on us. So we have to use l2tp_session_get()
instead of l2tp_session_find() and drop the reference before exiting.

Fixes: d9e31d17ce ("l2tp: Add L2TP ethernet pseudowire support")
Fixes: fd558d186d ("l2tp: Split pppol2tp patch into separate l2tp and ppp parts")
Signed-off-by: Guillaume Nault <g.nault@alphalink.fr>
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
Guillaume Nault
2017-03-31 13:02:27 +02:00
committed by David S. Miller
parent 57377d6354
commit dbdbc73b44
3 changed files with 85 additions and 57 deletions

View File

@@ -583,6 +583,7 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
int error = 0;
u32 tunnel_id, peer_tunnel_id;
u32 session_id, peer_session_id;
bool drop_refcnt = false;
int ver = 2;
int fd;
@@ -684,36 +685,36 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
if (tunnel->peer_tunnel_id == 0)
tunnel->peer_tunnel_id = peer_tunnel_id;
/* Create session if it doesn't already exist. We handle the
* case where a session was previously created by the netlink
* interface by checking that the session doesn't already have
* a socket and its tunnel socket are what we expect. If any
* of those checks fail, return EEXIST to the caller.
*/
session = l2tp_session_find(sock_net(sk), tunnel, session_id);
if (session == NULL) {
/* Default MTU must allow space for UDP/L2TP/PPP
* headers.
*/
cfg.mtu = cfg.mru = 1500 - PPPOL2TP_HEADER_OVERHEAD;
session = l2tp_session_get(sock_net(sk), tunnel, session_id, false);
if (session) {
drop_refcnt = true;
ps = l2tp_session_priv(session);
/* Allocate and initialize a new session context. */
session = l2tp_session_create(sizeof(struct pppol2tp_session),
tunnel, session_id,
peer_session_id, &cfg);
if (session == NULL) {
error = -ENOMEM;
/* Using a pre-existing session is fine as long as it hasn't
* been connected yet.
*/
if (ps->sock) {
error = -EEXIST;
goto end;
}
/* consistency checks */
if (ps->tunnel_sock != tunnel->sock) {
error = -EEXIST;
goto end;
}
} else {
ps = l2tp_session_priv(session);
error = -EEXIST;
if (ps->sock != NULL)
goto end;
/* Default MTU must allow space for UDP/L2TP/PPP headers */
cfg.mtu = 1500 - PPPOL2TP_HEADER_OVERHEAD;
cfg.mru = cfg.mtu;
/* consistency checks */
if (ps->tunnel_sock != tunnel->sock)
session = l2tp_session_create(sizeof(struct pppol2tp_session),
tunnel, session_id,
peer_session_id, &cfg);
if (IS_ERR(session)) {
error = PTR_ERR(session);
goto end;
}
}
/* Associate session with its PPPoL2TP socket */
@@ -778,6 +779,8 @@ out_no_ppp:
session->name);
end:
if (drop_refcnt)
l2tp_session_dec_refcount(session);
release_sock(sk);
return error;
@@ -805,12 +808,6 @@ static int pppol2tp_session_create(struct net *net, u32 tunnel_id, u32 session_i
if (tunnel->sock == NULL)
goto out;
/* Check that this session doesn't already exist */
error = -EEXIST;
session = l2tp_session_find(net, tunnel, session_id);
if (session != NULL)
goto out;
/* Default MTU values. */
if (cfg->mtu == 0)
cfg->mtu = 1500 - PPPOL2TP_HEADER_OVERHEAD;
@@ -818,12 +815,13 @@ static int pppol2tp_session_create(struct net *net, u32 tunnel_id, u32 session_i
cfg->mru = cfg->mtu;
/* Allocate and initialize a new session context. */
error = -ENOMEM;
session = l2tp_session_create(sizeof(struct pppol2tp_session),
tunnel, session_id,
peer_session_id, cfg);
if (session == NULL)
if (IS_ERR(session)) {
error = PTR_ERR(session);
goto out;
}
ps = l2tp_session_priv(session);
ps->tunnel_sock = tunnel->sock;