Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0c84cee8 authored by David S. Miller's avatar David S. Miller
Browse files

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: default avatarDavid S. Miller <davem@davemloft.net>
parents 83c1f36f f6cd651b
Loading
Loading
Loading
Loading
+98 −127
Original line number Diff line number Diff line
@@ -335,26 +335,6 @@ int l2tp_session_register(struct l2tp_session *session,
}
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)
+3 −1
Original line number Diff line number Diff line
@@ -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,
+13 −9
Original line number Diff line number Diff line
@@ -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;
}
+9 −0
Original line number Diff line number Diff line
@@ -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 */