Commit 44e524cf authored by David S. Miller's avatar David S. Miller

Merge branch 'l2tp-fix-API-races-discovered-by-syzbot'

James Chapman says:

====================
l2tp: fix API races discovered by syzbot

This patch series addresses several races with L2TP APIs discovered by
syzbot. There are no functional changes.

The set of patches 1-5 in combination fix the following syzbot reports.

19c09769f WARNING in debug_print_object
347bd5acd KASAN: use-after-free Read in inet_shutdown
6e6a5ec8d general protection fault in pppol2tp_connect
9df43faf0 KASAN: use-after-free Read in pppol2tp_connect

My first attempts to fix these issues were as net-next patches but
the series included other refactoring and cleanup work. I was asked to
separate out the bugfixes and redo for the net tree, which is what
these patches are.

The changes are:

 1. Fix inet_shutdown races when L2TP tunnels and sessions close. (patches 1-2)
 2. Fix races with tunnel and its socket. (patch 3)
 3. Fix race in pppol2tp_release with session and its socket. (patch 4)
 4. Fix tunnel lookup use-after-free. (patch 5)

All of the syzbot reproducers hit races in the tunnel and pppol2tp
session create and destroy paths. These tests create and destroy
pppol2tp tunnels and sessions rapidly using multiple threads,
provoking races in several tunnel/session create/destroy paths. The
key problem was that each tunnel/session socket could be destroyed
while its associated tunnel/session object still existed (patches 3,
4). Patch 5 addresses a problem with the way tunnels are removed from
the tunnel list. Patch 5 is tagged that it addresses all four syzbot
issues, though all 5 patches are needed.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents 9cb9c07d 28f5bfb8
...@@ -136,51 +136,6 @@ l2tp_session_id_hash_2(struct l2tp_net *pn, u32 session_id) ...@@ -136,51 +136,6 @@ l2tp_session_id_hash_2(struct l2tp_net *pn, u32 session_id)
} }
/* Lookup the tunnel socket, possibly involving the fs code if the socket is
* owned by userspace. A struct sock returned from this function must be
* released using l2tp_tunnel_sock_put once you're done with it.
*/
static struct sock *l2tp_tunnel_sock_lookup(struct l2tp_tunnel *tunnel)
{
int err = 0;
struct socket *sock = NULL;
struct sock *sk = NULL;
if (!tunnel)
goto out;
if (tunnel->fd >= 0) {
/* Socket is owned by userspace, who might be in the process
* of closing it. Look the socket up using the fd to ensure
* consistency.
*/
sock = sockfd_lookup(tunnel->fd, &err);
if (sock)
sk = sock->sk;
} else {
/* Socket is owned by kernelspace */
sk = tunnel->sock;
sock_hold(sk);
}
out:
return sk;
}
/* Drop a reference to a tunnel socket obtained via. l2tp_tunnel_sock_put */
static void l2tp_tunnel_sock_put(struct sock *sk)
{
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk);
if (tunnel) {
if (tunnel->fd >= 0) {
/* Socket is owned by userspace */
sockfd_put(sk->sk_socket);
}
sock_put(sk);
}
sock_put(sk);
}
/* Session hash list. /* Session hash list.
* The session_id SHOULD be random according to RFC2661, but several * The session_id SHOULD be random according to RFC2661, but several
* L2TP implementations (Cisco and Microsoft) use incrementing * L2TP implementations (Cisco and Microsoft) use incrementing
...@@ -193,6 +148,13 @@ l2tp_session_id_hash(struct l2tp_tunnel *tunnel, u32 session_id) ...@@ -193,6 +148,13 @@ l2tp_session_id_hash(struct l2tp_tunnel *tunnel, u32 session_id)
return &tunnel->session_hlist[hash_32(session_id, L2TP_HASH_BITS)]; return &tunnel->session_hlist[hash_32(session_id, L2TP_HASH_BITS)];
} }
void l2tp_tunnel_free(struct l2tp_tunnel *tunnel)
{
sock_put(tunnel->sock);
/* the tunnel is freed in the socket destructor */
}
EXPORT_SYMBOL(l2tp_tunnel_free);
/* Lookup a tunnel. A new reference is held on the returned tunnel. */ /* Lookup a tunnel. A new reference is held on the returned tunnel. */
struct l2tp_tunnel *l2tp_tunnel_get(const struct net *net, u32 tunnel_id) struct l2tp_tunnel *l2tp_tunnel_get(const struct net *net, u32 tunnel_id)
{ {
...@@ -345,13 +307,11 @@ int l2tp_session_register(struct l2tp_session *session, ...@@ -345,13 +307,11 @@ int l2tp_session_register(struct l2tp_session *session,
} }
l2tp_tunnel_inc_refcount(tunnel); l2tp_tunnel_inc_refcount(tunnel);
sock_hold(tunnel->sock);
hlist_add_head_rcu(&session->global_hlist, g_head); hlist_add_head_rcu(&session->global_hlist, g_head);
spin_unlock_bh(&pn->l2tp_session_hlist_lock); spin_unlock_bh(&pn->l2tp_session_hlist_lock);
} else { } else {
l2tp_tunnel_inc_refcount(tunnel); l2tp_tunnel_inc_refcount(tunnel);
sock_hold(tunnel->sock);
} }
hlist_add_head(&session->hlist, head); hlist_add_head(&session->hlist, head);
...@@ -969,7 +929,7 @@ int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb) ...@@ -969,7 +929,7 @@ int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
{ {
struct l2tp_tunnel *tunnel; struct l2tp_tunnel *tunnel;
tunnel = l2tp_sock_to_tunnel(sk); tunnel = l2tp_tunnel(sk);
if (tunnel == NULL) if (tunnel == NULL)
goto pass_up; goto pass_up;
...@@ -977,13 +937,10 @@ int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb) ...@@ -977,13 +937,10 @@ int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb)
tunnel->name, skb->len); tunnel->name, skb->len);
if (l2tp_udp_recv_core(tunnel, skb, tunnel->recv_payload_hook)) if (l2tp_udp_recv_core(tunnel, skb, tunnel->recv_payload_hook))
goto pass_up_put; goto pass_up;
sock_put(sk);
return 0; return 0;
pass_up_put:
sock_put(sk);
pass_up: pass_up:
return 1; return 1;
} }
...@@ -1207,14 +1164,12 @@ EXPORT_SYMBOL_GPL(l2tp_xmit_skb); ...@@ -1207,14 +1164,12 @@ EXPORT_SYMBOL_GPL(l2tp_xmit_skb);
static void l2tp_tunnel_destruct(struct sock *sk) static void l2tp_tunnel_destruct(struct sock *sk)
{ {
struct l2tp_tunnel *tunnel = l2tp_tunnel(sk); struct l2tp_tunnel *tunnel = l2tp_tunnel(sk);
struct l2tp_net *pn;
if (tunnel == NULL) if (tunnel == NULL)
goto end; goto end;
l2tp_info(tunnel, L2TP_MSG_CONTROL, "%s: closing...\n", tunnel->name); l2tp_info(tunnel, L2TP_MSG_CONTROL, "%s: closing...\n", tunnel->name);
/* Disable udp encapsulation */ /* Disable udp encapsulation */
switch (tunnel->encap) { switch (tunnel->encap) {
case L2TP_ENCAPTYPE_UDP: case L2TP_ENCAPTYPE_UDP:
...@@ -1231,18 +1186,11 @@ static void l2tp_tunnel_destruct(struct sock *sk) ...@@ -1231,18 +1186,11 @@ static void l2tp_tunnel_destruct(struct sock *sk)
sk->sk_destruct = tunnel->old_sk_destruct; sk->sk_destruct = tunnel->old_sk_destruct;
sk->sk_user_data = NULL; sk->sk_user_data = NULL;
/* Remove the tunnel struct from the tunnel list */
pn = l2tp_pernet(tunnel->l2tp_net);
spin_lock_bh(&pn->l2tp_tunnel_list_lock);
list_del_rcu(&tunnel->list);
spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
tunnel->sock = NULL;
l2tp_tunnel_dec_refcount(tunnel);
/* Call the original destructor */ /* Call the original destructor */
if (sk->sk_destruct) if (sk->sk_destruct)
(*sk->sk_destruct)(sk); (*sk->sk_destruct)(sk);
kfree_rcu(tunnel, rcu);
end: end:
return; return;
} }
...@@ -1303,49 +1251,43 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_closeall); ...@@ -1303,49 +1251,43 @@ EXPORT_SYMBOL_GPL(l2tp_tunnel_closeall);
/* Tunnel socket destroy hook for UDP encapsulation */ /* Tunnel socket destroy hook for UDP encapsulation */
static void l2tp_udp_encap_destroy(struct sock *sk) static void l2tp_udp_encap_destroy(struct sock *sk)
{ {
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk); struct l2tp_tunnel *tunnel = l2tp_tunnel(sk);
if (tunnel) {
l2tp_tunnel_closeall(tunnel); if (tunnel)
sock_put(sk); l2tp_tunnel_delete(tunnel);
}
} }
/* Workqueue tunnel deletion function */ /* Workqueue tunnel deletion function */
static void l2tp_tunnel_del_work(struct work_struct *work) static void l2tp_tunnel_del_work(struct work_struct *work)
{ {
struct l2tp_tunnel *tunnel = NULL; struct l2tp_tunnel *tunnel = container_of(work, struct l2tp_tunnel,
struct socket *sock = NULL; del_work);
struct sock *sk = NULL; struct sock *sk = tunnel->sock;
struct socket *sock = sk->sk_socket;
tunnel = container_of(work, struct l2tp_tunnel, del_work); struct l2tp_net *pn;
l2tp_tunnel_closeall(tunnel); l2tp_tunnel_closeall(tunnel);
sk = l2tp_tunnel_sock_lookup(tunnel); /* If the tunnel socket was created within the kernel, use
if (!sk)
goto out;
sock = sk->sk_socket;
/* If the tunnel socket was created by userspace, then go through the
* inet layer to shut the socket down, and let userspace close it.
* Otherwise, if we created the socket directly within the kernel, use
* the sk API to release it here. * the sk API to release it here.
* In either case the tunnel resources are freed in the socket
* destructor when the tunnel socket goes away.
*/ */
if (tunnel->fd >= 0) { if (tunnel->fd < 0) {
if (sock)
inet_shutdown(sock, 2);
} else {
if (sock) { if (sock) {
kernel_sock_shutdown(sock, SHUT_RDWR); kernel_sock_shutdown(sock, SHUT_RDWR);
sock_release(sock); sock_release(sock);
} }
} }
l2tp_tunnel_sock_put(sk); /* Remove the tunnel struct from the tunnel list */
out: pn = l2tp_pernet(tunnel->l2tp_net);
spin_lock_bh(&pn->l2tp_tunnel_list_lock);
list_del_rcu(&tunnel->list);
spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
/* drop initial ref */
l2tp_tunnel_dec_refcount(tunnel);
/* drop workqueue ref */
l2tp_tunnel_dec_refcount(tunnel); l2tp_tunnel_dec_refcount(tunnel);
} }
...@@ -1598,13 +1540,22 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 ...@@ -1598,13 +1540,22 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
sk->sk_user_data = tunnel; 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 /* Hook on the tunnel socket destructor so that we can cleanup
* if the tunnel socket goes away. * if the tunnel socket goes away.
*/ */
tunnel->old_sk_destruct = sk->sk_destruct; tunnel->old_sk_destruct = sk->sk_destruct;
sk->sk_destruct = &l2tp_tunnel_destruct; sk->sk_destruct = &l2tp_tunnel_destruct;
tunnel->sock = sk;
tunnel->fd = fd;
lockdep_set_class_and_name(&sk->sk_lock.slock, &l2tp_socket_class, "l2tp_sock"); lockdep_set_class_and_name(&sk->sk_lock.slock, &l2tp_socket_class, "l2tp_sock");
sk->sk_allocation = GFP_ATOMIC; sk->sk_allocation = GFP_ATOMIC;
...@@ -1614,11 +1565,6 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32 ...@@ -1614,11 +1565,6 @@ int l2tp_tunnel_create(struct net *net, int fd, int version, u32 tunnel_id, u32
/* Add tunnel to our list */ /* Add tunnel to our list */
INIT_LIST_HEAD(&tunnel->list); INIT_LIST_HEAD(&tunnel->list);
/* Bump the reference count. The tunnel context is deleted
* only when this drops to zero. Must be done before list insertion
*/
refcount_set(&tunnel->ref_count, 1);
spin_lock_bh(&pn->l2tp_tunnel_list_lock); spin_lock_bh(&pn->l2tp_tunnel_list_lock);
list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list); list_add_rcu(&tunnel->list, &pn->l2tp_tunnel_list);
spin_unlock_bh(&pn->l2tp_tunnel_list_lock); spin_unlock_bh(&pn->l2tp_tunnel_list_lock);
...@@ -1659,8 +1605,6 @@ void l2tp_session_free(struct l2tp_session *session) ...@@ -1659,8 +1605,6 @@ void l2tp_session_free(struct l2tp_session *session)
if (tunnel) { if (tunnel) {
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC); BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
sock_put(tunnel->sock);
session->tunnel = NULL;
l2tp_tunnel_dec_refcount(tunnel); l2tp_tunnel_dec_refcount(tunnel);
} }
......
...@@ -214,27 +214,8 @@ static inline void *l2tp_session_priv(struct l2tp_session *session) ...@@ -214,27 +214,8 @@ static inline void *l2tp_session_priv(struct l2tp_session *session)
return &session->priv[0]; return &session->priv[0];
} }
static inline struct l2tp_tunnel *l2tp_sock_to_tunnel(struct sock *sk)
{
struct l2tp_tunnel *tunnel;
if (sk == NULL)
return NULL;
sock_hold(sk);
tunnel = (struct l2tp_tunnel *)(sk->sk_user_data);
if (tunnel == NULL) {
sock_put(sk);
goto out;
}
BUG_ON(tunnel->magic != L2TP_TUNNEL_MAGIC);
out:
return tunnel;
}
struct l2tp_tunnel *l2tp_tunnel_get(const struct net *net, u32 tunnel_id); struct l2tp_tunnel *l2tp_tunnel_get(const struct net *net, u32 tunnel_id);
void l2tp_tunnel_free(struct l2tp_tunnel *tunnel);
struct l2tp_session *l2tp_session_get(const struct net *net, struct l2tp_session *l2tp_session_get(const struct net *net,
struct l2tp_tunnel *tunnel, struct l2tp_tunnel *tunnel,
...@@ -283,7 +264,7 @@ static inline void l2tp_tunnel_inc_refcount(struct l2tp_tunnel *tunnel) ...@@ -283,7 +264,7 @@ static inline void l2tp_tunnel_inc_refcount(struct l2tp_tunnel *tunnel)
static inline void l2tp_tunnel_dec_refcount(struct l2tp_tunnel *tunnel) static inline void l2tp_tunnel_dec_refcount(struct l2tp_tunnel *tunnel)
{ {
if (refcount_dec_and_test(&tunnel->ref_count)) if (refcount_dec_and_test(&tunnel->ref_count))
kfree_rcu(tunnel, rcu); l2tp_tunnel_free(tunnel);
} }
/* Session reference counts. Incremented when code obtains a reference /* Session reference counts. Incremented when code obtains a reference
......
...@@ -234,17 +234,13 @@ static void l2tp_ip_close(struct sock *sk, long timeout) ...@@ -234,17 +234,13 @@ static void l2tp_ip_close(struct sock *sk, long timeout)
static void l2tp_ip_destroy_sock(struct sock *sk) static void l2tp_ip_destroy_sock(struct sock *sk)
{ {
struct sk_buff *skb; struct sk_buff *skb;
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk); struct l2tp_tunnel *tunnel = sk->sk_user_data;
while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL) while ((skb = __skb_dequeue_tail(&sk->sk_write_queue)) != NULL)
kfree_skb(skb); kfree_skb(skb);
if (tunnel) { if (tunnel)
l2tp_tunnel_closeall(tunnel); l2tp_tunnel_delete(tunnel);
sock_put(sk);
}
sk_refcnt_debug_dec(sk);
} }
static int l2tp_ip_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len) static int l2tp_ip_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)
......
...@@ -248,16 +248,14 @@ static void l2tp_ip6_close(struct sock *sk, long timeout) ...@@ -248,16 +248,14 @@ static void l2tp_ip6_close(struct sock *sk, long timeout)
static void l2tp_ip6_destroy_sock(struct sock *sk) static void l2tp_ip6_destroy_sock(struct sock *sk)
{ {
struct l2tp_tunnel *tunnel = l2tp_sock_to_tunnel(sk); struct l2tp_tunnel *tunnel = sk->sk_user_data;
lock_sock(sk); lock_sock(sk);
ip6_flush_pending_frames(sk); ip6_flush_pending_frames(sk);
release_sock(sk); release_sock(sk);
if (tunnel) { if (tunnel)
l2tp_tunnel_closeall(tunnel); l2tp_tunnel_delete(tunnel);
sock_put(sk);
}
inet6_destroy_sock(sk); inet6_destroy_sock(sk);
} }
......
...@@ -416,20 +416,28 @@ static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb) ...@@ -416,20 +416,28 @@ static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb)
* Session (and tunnel control) socket create/destroy. * Session (and tunnel control) socket create/destroy.
*****************************************************************************/ *****************************************************************************/
static void pppol2tp_put_sk(struct rcu_head *head)
{
struct pppol2tp_session *ps;
ps = container_of(head, typeof(*ps), rcu);
sock_put(ps->__sk);
}
/* Called by l2tp_core when a session socket is being closed. /* Called by l2tp_core when a session socket is being closed.
*/ */
static void pppol2tp_session_close(struct l2tp_session *session) static void pppol2tp_session_close(struct l2tp_session *session)
{ {
struct sock *sk; struct pppol2tp_session *ps;
BUG_ON(session->magic != L2TP_SESSION_MAGIC);
sk = pppol2tp_session_get_sock(session); ps = l2tp_session_priv(session);
if (sk) { mutex_lock(&ps->sk_lock);
if (sk->sk_socket) ps->__sk = rcu_dereference_protected(ps->sk,
inet_shutdown(sk->sk_socket, SEND_SHUTDOWN); lockdep_is_held(&ps->sk_lock));
sock_put(sk); RCU_INIT_POINTER(ps->sk, NULL);
} if (ps->__sk)
call_rcu(&ps->rcu, pppol2tp_put_sk);
mutex_unlock(&ps->sk_lock);
} }
/* Really kill the session socket. (Called from sock_put() if /* Really kill the session socket. (Called from sock_put() if
...@@ -449,14 +457,6 @@ static void pppol2tp_session_destruct(struct sock *sk) ...@@ -449,14 +457,6 @@ static void pppol2tp_session_destruct(struct sock *sk)
} }
} }
static void pppol2tp_put_sk(struct rcu_head *head)
{
struct pppol2tp_session *ps;
ps = container_of(head, typeof(*ps), rcu);
sock_put(ps->__sk);
}
/* Called when the PPPoX socket (session) is closed. /* Called when the PPPoX socket (session) is closed.
*/ */
static int pppol2tp_release(struct socket *sock) static int pppol2tp_release(struct socket *sock)
...@@ -480,26 +480,17 @@ static int pppol2tp_release(struct socket *sock) ...@@ -480,26 +480,17 @@ static int pppol2tp_release(struct socket *sock)
sock_orphan(sk); sock_orphan(sk);
sock->sk = NULL; sock->sk = NULL;
/* If the socket is associated with a session,
* l2tp_session_delete will call pppol2tp_session_close which
* will drop the session's ref on the socket.
*/
session = pppol2tp_sock_to_session(sk); session = pppol2tp_sock_to_session(sk);
if (session) {
if (session != NULL) {
struct pppol2tp_session *ps;
l2tp_session_delete(session); l2tp_session_delete(session);
/* drop the ref obtained by pppol2tp_sock_to_session */
ps = l2tp_session_priv(session); sock_put(sk);
mutex_lock(&ps->sk_lock);
ps->__sk = rcu_dereference_protected(ps->sk,
lockdep_is_held(&ps->sk_lock));
RCU_INIT_POINTER(ps->sk, NULL);
mutex_unlock(&ps->sk_lock);
call_rcu(&ps->rcu, pppol2tp_put_sk);
/* Rely on the sock_put() call at the end of the function for
* dropping the reference held by pppol2tp_sock_to_session().
* The last reference will be dropped by pppol2tp_put_sk().
*/
} }
release_sock(sk); release_sock(sk);
/* This will delete the session context via /* This will delete the session context via
...@@ -796,6 +787,7 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr, ...@@ -796,6 +787,7 @@ static int pppol2tp_connect(struct socket *sock, struct sockaddr *uservaddr,
out_no_ppp: out_no_ppp:
/* This is how we get the session context from the socket. */ /* This is how we get the session context from the socket. */
sock_hold(sk);
sk->sk_user_data = session; sk->sk_user_data = session;
rcu_assign_pointer(ps->sk, sk); rcu_assign_pointer(ps->sk, sk);
mutex_unlock(&ps->sk_lock); mutex_unlock(&ps->sk_lock);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment