Merge branch 'l2tp-tunnel-creation-fixes'

Guillaume Nault says:

====================
l2tp: tunnel creation fixes

L2TP tunnel creation is racy. We need to make sure that the tunnel
returned by l2tp_tunnel_create() isn't going to be freed while the
caller is using it. This is done in patch #1, by separating tunnel
creation from tunnel registration.

With the tunnel registration code in place, we can now check for
duplicate tunnels in a race-free way. This is done in patch #2, which
incidentally removes the last use of l2tp_tunnel_find().
====================

Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David S. Miller 2018-04-11 17:41:28 -04:00
commit 0c84cee8f1
4 changed files with 123 additions and 137 deletions

View File

@ -335,26 +335,6 @@ err_tlock:
}
EXPORT_SYMBOL_GPL(l2tp_session_register);
/* Lookup a tunnel by id
*/
struct l2tp_tunnel *l2tp_tunnel_find(const struct net *net, u32 tunnel_id)
{
struct l2tp_tunnel *tunnel;
struct l2tp_net *pn = l2tp_pernet(net);
rcu_read_lock_bh();
list_for_each_entry_rcu(tunnel, &pn->l2tp_tunnel_list, list) {
if (tunnel->tunnel_id == tunnel_id) {
rcu_read_unlock_bh();
return tunnel;
}
}
rcu_read_unlock_bh();
return NULL;
}
EXPORT_SYMBOL_GPL(l2tp_tunnel_find);
struct l2tp_tunnel *l2tp_tunnel_find_nth(const struct net *net, int nth)
{
struct l2tp_net *pn = l2tp_pernet(net);
@ -1436,74 +1416,11 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
{
struct l2tp_tunnel *tunnel = NULL;
int err;
struct socket *sock = NULL;
struct sock *sk = NULL;
struct l2tp_net *pn;
enum l2tp_encap_type encap = L2TP_ENCAPTYPE_UDP;
/* Get the tunnel socket from the fd, which was opened by
* the userspace L2TP daemon. If not specified, create a
* kernel socket.
*/
if (fd < 0) {
err = l2tp_tunnel_sock_create(net, tunnel_id, peer_tunnel_id,
cfg, &sock);
if (err < 0)
goto err;
} else {
sock = sockfd_lookup(fd, &err);
if (!sock) {
pr_err("tunl %u: sockfd_lookup(fd=%d) returned %d\n",
tunnel_id, fd, err);
err = -EBADF;
goto err;
}
/* Reject namespace mismatches */
if (!net_eq(sock_net(sock->sk), net)) {
pr_err("tunl %u: netns mismatch\n", tunnel_id);
err = -EINVAL;
goto err;
}
}
sk = sock->sk;
if (cfg != NULL)
encap = cfg->encap;
/* Quick sanity checks */
err = -EPROTONOSUPPORT;
if (sk->sk_type != SOCK_DGRAM) {
pr_debug("tunl %hu: fd %d wrong socket type\n",
tunnel_id, fd);
goto err;
}
switch (encap) {
case L2TP_ENCAPTYPE_UDP:
if (sk->sk_protocol != IPPROTO_UDP) {
pr_err("tunl %hu: fd %d wrong protocol, got %d, expected %d\n",
tunnel_id, fd, sk->sk_protocol, IPPROTO_UDP);
goto err;
}
break;
case L2TP_ENCAPTYPE_IP:
if (sk->sk_protocol != IPPROTO_L2TP) {
pr_err("tunl %hu: fd %d wrong protocol, got %d, expected %d\n",
tunnel_id, fd, sk->sk_protocol, IPPROTO_L2TP);
goto err;
}
break;
}
/* Check if this socket has already been prepped */
tunnel = l2tp_tunnel(sk);
if (tunnel != NULL) {
/* This socket has already been prepped */
err = -EBUSY;
goto err;
}
tunnel = kzalloc(sizeof(struct l2tp_tunnel), GFP_KERNEL);
if (tunnel == NULL) {
err = -ENOMEM;
@ -1520,72 +1437,126 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
rwlock_init(&tunnel->hlist_lock);
tunnel->acpt_newsess = true;
/* The net we belong to */
tunnel->l2tp_net = net;
pn = l2tp_pernet(net);
if (cfg != NULL)
tunnel->debug = cfg->debug;
/* Mark socket as an encapsulation socket. See net/ipv4/udp.c */
tunnel->encap = encap;
if (encap == L2TP_ENCAPTYPE_UDP) {
struct udp_tunnel_sock_cfg udp_cfg = { };
udp_cfg.sk_user_data = tunnel;
udp_cfg.encap_type = UDP_ENCAP_L2TPINUDP;
udp_cfg.encap_rcv = l2tp_udp_encap_recv;
udp_cfg.encap_destroy = l2tp_udp_encap_destroy;
setup_udp_tunnel_sock(net, sock, &udp_cfg);
} else {
sk->sk_user_data = tunnel;
}
/* Bump the reference count. The tunnel context is deleted
* only when this drops to zero. A reference is also held on
* the tunnel socket to ensure that it is not released while
* the tunnel is extant. Must be done before sk_destruct is
* set.
*/
refcount_set(&tunnel->ref_count, 1);
sock_hold(sk);
tunnel->sock = sk;
tunnel->fd = fd;
/* Hook on the tunnel socket destructor so that we can cleanup
* if the tunnel socket goes away.
*/
tunnel->old_sk_destruct = sk->sk_destruct;
sk->sk_destruct = &l2tp_tunnel_destruct;
lockdep_set_class_and_name(&sk->sk_lock.slock, &l2tp_socket_class, "l2tp_sock");
sk->sk_allocation = GFP_ATOMIC;
/* Init delete workqueue struct */
INIT_WORK(&tunnel->del_work, l2tp_tunnel_del_work);
/* Add tunnel to our list */
INIT_LIST_HEAD(&tunnel->list);
spin_lock_bh(&pn->l2tp_tunnel_list_lock);
list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list);
spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
err = 0;
err:
if (tunnelp)
*tunnelp = tunnel;
/* If tunnel's socket was created by the kernel, it doesn't
* have a file.
*/
if (sock && sock->file)
sockfd_put(sock);
return err;
}
EXPORT_SYMBOL_GPL(l2tp_tunnel_create);
static int l2tp_validate_socket(const struct sock *sk, const struct net *net,
enum l2tp_encap_type encap)
{
if (!net_eq(sock_net(sk), net))
return -EINVAL;
if (sk->sk_type != SOCK_DGRAM)
return -EPROTONOSUPPORT;
if ((encap == L2TP_ENCAPTYPE_UDP && sk->sk_protocol != IPPROTO_UDP) ||
(encap == L2TP_ENCAPTYPE_IP && sk->sk_protocol != IPPROTO_L2TP))
return -EPROTONOSUPPORT;
if (sk->sk_user_data)
return -EBUSY;
return 0;
}
int l2tp_tunnel_register(struct l2tp_tunnel *tunnel, struct net *net,
struct l2tp_tunnel_cfg *cfg)
{
struct l2tp_tunnel *tunnel_walk;
struct l2tp_net *pn;
struct socket *sock;
struct sock *sk;
int ret;
if (tunnel->fd < 0) {
ret = l2tp_tunnel_sock_create(net, tunnel->tunnel_id,
tunnel->peer_tunnel_id, cfg,
&sock);
if (ret < 0)
goto err;
} else {
sock = sockfd_lookup(tunnel->fd, &ret);
if (!sock)
goto err;
ret = l2tp_validate_socket(sock->sk, net, tunnel->encap);
if (ret < 0)
goto err_sock;
}
sk = sock->sk;
sock_hold(sk);
tunnel->sock = sk;
tunnel->l2tp_net = net;
pn = l2tp_pernet(net);
spin_lock_bh(&pn->l2tp_tunnel_list_lock);
list_for_each_entry(tunnel_walk, &pn->l2tp_tunnel_list, list) {
if (tunnel_walk->tunnel_id == tunnel->tunnel_id) {
spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
ret = -EEXIST;
goto err_sock;
}
}
list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list);
spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
if (tunnel->encap == L2TP_ENCAPTYPE_UDP) {
struct udp_tunnel_sock_cfg udp_cfg = {
.sk_user_data = tunnel,
.encap_type = UDP_ENCAP_L2TPINUDP,
.encap_rcv = l2tp_udp_encap_recv,
.encap_destroy = l2tp_udp_encap_destroy,
};
setup_udp_tunnel_sock(net, sock, &udp_cfg);
} else {
sk->sk_user_data = tunnel;
}
tunnel->old_sk_destruct = sk->sk_destruct;
sk->sk_destruct = &l2tp_tunnel_destruct;
lockdep_set_class_and_name(&sk->sk_lock.slock, &l2tp_socket_class,
"l2tp_sock");
sk->sk_allocation = GFP_ATOMIC;
if (tunnel->fd >= 0)
sockfd_put(sock);
return 0;
err_sock:
if (tunnel->fd < 0)
sock_release(sock);
else
sockfd_put(sock);
err:
return ret;
}
EXPORT_SYMBOL_GPL(l2tp_tunnel_register);
/* This function is used by the netlink TUNNEL_DELETE command.
*/
void l2tp_tunnel_delete(struct l2tp_tunnel *tunnel)

View File

@ -220,12 +220,14 @@ struct l2tp_session *l2tp_session_get(const struct net *net,
struct l2tp_session *l2tp_session_get_nth(struct l2tp_tunnel *tunnel, int nth);
struct l2tp_session *l2tp_session_get_by_ifname(const struct net *net,
const char *ifname);
struct l2tp_tunnel *l2tp_tunnel_find(const struct net *net, u32 tunnel_id);
struct l2tp_tunnel *l2tp_tunnel_find_nth(const struct net *net, int nth);
int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id,
u32 peer_tunnel_id, struct l2tp_tunnel_cfg *cfg,
struct l2tp_tunnel **tunnelp);
int l2tp_tunnel_register(struct l2tp_tunnel *tunnel, struct net *net,
struct l2tp_tunnel_cfg *cfg);
void l2tp_tunnel_closeall(struct l2tp_tunnel *tunnel);
void l2tp_tunnel_delete(struct l2tp_tunnel *tunnel);
struct l2tp_session *l2tp_session_create(int priv_size,

View File

@ -236,12 +236,6 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info
if (info->attrs[L2TP_ATTR_DEBUG])
cfg.debug = nla_get_u32(info->attrs[L2TP_ATTR_DEBUG]);
tunnel = l2tp_tunnel_find(net, tunnel_id);
if (tunnel != NULL) {
ret = -EEXIST;
goto out;
}
ret = -EINVAL;
switch (cfg.encap) {
case L2TP_ENCAPTYPE_UDP:
@ -251,9 +245,19 @@ static int l2tp_nl_cmd_tunnel_create(struct sk_buff *skb, struct genl_info *info
break;
}
if (ret >= 0)
ret = l2tp_tunnel_notify(&l2tp_nl_family, info,
tunnel, L2TP_CMD_TUNNEL_CREATE);
if (ret < 0)
goto out;
l2tp_tunnel_inc_refcount(tunnel);
ret = l2tp_tunnel_register(tunnel, net, &cfg);
if (ret < 0) {
kfree(tunnel);
goto out;
}
ret = l2tp_tunnel_notify(&l2tp_nl_family, info, tunnel,
L2TP_CMD_TUNNEL_CREATE);
l2tp_tunnel_dec_refcount(tunnel);
out:
return ret;
}

View File

@ -698,6 +698,15 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
error = l2tp_tunnel_create(sock_net(sk), fd, ver, tunnel_id, peer_tunnel_id, &tcfg, &tunnel);
if (error < 0)
goto end;
l2tp_tunnel_inc_refcount(tunnel);
error = l2tp_tunnel_register(tunnel, sock_net(sk),
&tcfg);
if (error < 0) {
kfree(tunnel);
goto end;
}
drop_tunnel = true;
}
} else {
/* Error if we can't find the tunnel */