l2tp use-after-free fixes for 4.4 stable
Subsystems (3)
cover l2tp use-after-free fixes for 4.4 stable
Hi Greg,
Syzbot has been complaining about KASAN splats due to use-after-free issues in the l2tp code on 4.4 Android kernels (although I reproduced with latest 4.4 stable on my laptop):
https://syzkaller.appspot.com/bug?id=de316389db0fa0cd7ced6e564601ea8e56625ebc
These have been fixed upstream, but for some reason didn't get picked up for stable. This series applies to 4.4.y and I've sent patches for 4.9 separately.
Thanks,
Will
--->8
Gao Feng (1):
l2tp: Refactor the codes with existing macros instead of literal
number
Guillaume Nault (5):
l2tp: fix race in l2tp_recv_common()
l2tp: ensure session can't get removed during pppol2tp_session_ioctl()
l2tp: fix duplicate session creation
l2tp: ensure sessions are freed after their PPPOL2TP socket
l2tp: fix race between l2tp_session_delete() and
l2tp_tunnel_closeall()
Shmulik Ladkani (1):
net: l2tp: Make l2tp_ip6 namespace aware
[email protected] (1):
l2tp: Correctly return -EBADF from pppol2tp_getname.
net/l2tp/l2tp_core.c | 149 ++++++++++++++++++++++++++++++++++--------- net/l2tp/l2tp_core.h | 4 ++ net/l2tp/l2tp_eth.c | 10 +-- net/l2tp/l2tp_ip.c | 17 +++-- net/l2tp/l2tp_ip6.c | 28 +++++--- net/l2tp/l2tp_ppp.c | 110 ++++++++++++++++---------------- 6 files changed, 211 insertions(+), 107 deletions(-)
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 1/8 l2tp: Correctly return -EBADF from pppol2tp_getname.
From: "[email protected]" <[email protected]>
commit 4ac36a4adaf80013a60013d6f829f5863d5d0e05 upstream.
If 'tunnel' is NULL we should return -EBADF but the 'end_put_sess' path unconditionally sets 'error' back to zero. Rework the error path so it more closely matches pppol2tp_sendmsg.
--- net/l2tp/l2tp_ppp.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-)
net/l2tp/l2tp_ppp.c +2 -5
@@ -889,10 +889,8 @@ static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr, pls =l2tp_session_priv (session); tunnel =l2tp_sock_to_tunnel (pls->tunnel_sock); -if (tunnel ==NULL ) { - error = -EBADF ; +if (tunnel ==NULL ) goto end_put_sess; - } inet =inet_sk (tunnel->sock);if ((tunnel->version ==2 ) && (tunnel->sock->sk_family ==AF_INET )) {
@@ -970,12 +968,11 @@ static int pppol2tp_getname(struct socket *sock, struct sockaddr *uaddr, } *usockaddr_len = len; + error =0 ;sock_put (pls->tunnel_sock); end_put_sess:sock_put (sk); - error =0 ; - end:return error; }
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 2/8 net: l2tp: Make l2tp_ip6 namespace aware
In reply to Will DeaconFrom: Shmulik Ladkani <[email protected]>
commit 0e6b5259824e97a0f7e7b450421ff12865d3b0e2 upstream.
l2tp_ip6 tunnel and session lookups were still using init_net, although the l2tp core infrastructure already supports lookups keyed by 'net'.
As a result, l2tp_ip6_recv discarded packets for tunnels/sessions created in namespaces other than the init_net.
Fix, by using dev_net(skb->dev) or sock_net(sk) where appropriate.
--- net/l2tp/l2tp_ip6.c | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-)
net/l2tp/l2tp_ip6.c +7 -5
@@ -127,6 +127,7 @@ static inline struct sock *l2tp_ip6_bind_lookup(struct net *net,*/ static int l2tp_ip6_recv (struct sk_buff *skb) { +struct net *net =dev_net (skb->dev);struct sock *sk;u32 session_id;u32 tunnel_id;
@@ -153,7 +154,7 @@ static int l2tp_ip6_recv(struct sk_buff *skb) }/* Ok, this is a data packet. Lookup the session. */ - session =l2tp_session_find (&init_net,NULL , session_id); + session =l2tp_session_find (net,NULL , session_id);if (session ==NULL ) goto discard;
@@ -190,7 +191,7 @@ pass_up: goto discard; tunnel_id =ntohl (*(__be32 *) &skb->data[4 ]); - tunnel =l2tp_tunnel_find (&init_net, tunnel_id); + tunnel =l2tp_tunnel_find (net, tunnel_id);if (tunnel) { sk = tunnel->sock;sock_hold (sk);
@@ -198,7 +199,7 @@ pass_up:struct ipv6hdr *iph =ipv6_hdr (skb);read_lock_bh (&l2tp_ip6_lock); - sk =__l2tp_ip6_bind_lookup (&init_net, &iph->daddr, + sk =__l2tp_ip6_bind_lookup (net, &iph->daddr,0 , tunnel_id);if (!sk) {read_unlock_bh (&l2tp_ip6_lock);
@@ -267,6 +268,7 @@ static int l2tp_ip6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len)struct inet_sock *inet =inet_sk (sk);struct ipv6_pinfo *np =inet6_sk (sk);struct sockaddr_l2tpip6 *addr = (struct sockaddr_l2tpip6 *) uaddr; +struct net *net =sock_net (sk);__be32 v4addr =0 ;int addr_type;int err;
@@ -288,7 +290,7 @@ static int l2tp_ip6_bind(struct sock *sk, struct sockaddr *uaddr, int addr_len) err = -EADDRINUSE ;read_lock_bh (&l2tp_ip6_lock); -if (__l2tp_ip6_bind_lookup (&init_net, &addr->l2tp_addr, +if (__l2tp_ip6_bind_lookup (net, &addr->l2tp_addr, sk->sk_bound_dev_if, addr->l2tp_conn_id)) goto out_in_use;read_unlock_bh (&l2tp_ip6_lock);
@@ -461,7 +463,7 @@ static int l2tp_ip6_backlog_recv(struct sock *sk, struct sk_buff *skb)return 0 ; drop: -IP_INC_STATS (&init_net,IPSTATS_MIB_INDISCARDS ); +IP_INC_STATS (sock_net (sk),IPSTATS_MIB_INDISCARDS );kfree_skb (skb);return -1 ; }
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 3/8 l2tp: fix race in l2tp_recv_common()
In reply to Will DeaconFrom: Guillaume Nault <[email protected]>
commit 61b9a047729bb230978178bca6729689d0c50ca2 upstream.
Taking a reference on sessions in l2tp_recv_common() is racy; this has to be done by the callers.
To this end, a new function is required (l2tp_session_get()) to atomically lookup a session and take a reference on it. Callers then have to manually drop this reference.
--- net/l2tp/l2tp_core.c | 73 ++++++++++++++++++++++++++++++++++++-------- net/l2tp/l2tp_core.h | 3 ++ net/l2tp/l2tp_ip.c | 17 ++++++++--- net/l2tp/l2tp_ip6.c | 18 ++++++++--- 4 files changed, 88 insertions(+), 23 deletions(-)
net/l2tp/l2tp_core.c +60 -13
@@ -277,6 +277,55 @@ struct l2tp_session *l2tp_session_find(struct net *net, struct l2tp_tunnel *tunn }EXPORT_SYMBOL_GPL (l2tp_session_find); +/* Like l2tp_session_find() but takes a reference on the returned session. +* Optionally calls session->ref() too if do_ref is true. +*/ +struct l2tp_session *l2tp_session_get (struct net *net, +struct l2tp_tunnel *tunnel, +u32 session_id,bool do_ref) +{ +struct hlist_head *session_list; +struct l2tp_session *session; + +if (!tunnel) { +struct l2tp_net *pn =l2tp_pernet (net); + + session_list =l2tp_session_id_hash_2 (pn, session_id); + +rcu_read_lock_bh (); +hlist_for_each_entry_rcu (session, session_list, global_hlist) { +if (session->session_id == session_id) { +l2tp_session_inc_refcount (session); +if (do_ref && session->ref) + session->ref (session); +rcu_read_unlock_bh (); + +return session; + } + } +rcu_read_unlock_bh (); + +return NULL ; + } + + session_list =l2tp_session_id_hash (tunnel, session_id); +read_lock_bh (&tunnel->hlist_lock); +hlist_for_each_entry (session, session_list, hlist) { +if (session->session_id == session_id) { +l2tp_session_inc_refcount (session); +if (do_ref && session->ref) + session->ref (session); +read_unlock_bh (&tunnel->hlist_lock); + +return session; + } + } +read_unlock_bh (&tunnel->hlist_lock); + +return NULL ; +} +EXPORT_SYMBOL_GPL (l2tp_session_get); +struct l2tp_session *l2tp_session_get_nth (struct l2tp_tunnel *tunnel,int nth,bool do_ref) {
@@ -636,6 +685,9 @@ discard:* a data (not control) frame before coming here. Fields up to the * session-id have already been parsed and ptr points to the data * after the session-id. +* +* session->ref() must have been called prior to l2tp_recv_common(). +* session->deref() will be called automatically after skb is processed. */ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb, unsigned char *ptr, unsigned char *optr, u16 hdrflags,
@@ -645,14 +697,6 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,int offset;u32 ns, nr; -/* The ref count is increased since we now hold a pointer to -* the session. Take care to decrement the refcnt when exiting -* this function from now on... -*/ -l2tp_session_inc_refcount (session); -if (session->ref) - (*session->ref)(session); -/* Parse and check optional cookie */ if (session->peer_cookie_len >0 ) {if (memcmp (ptr, &session->peer_cookie[0 ], session->peer_cookie_len)) {
@@ -803,8 +847,6 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,/* Try to dequeue as many skbs from reorder_q as we can. */ l2tp_recv_dequeue (session); -l2tp_session_dec_refcount (session); -return ; discard:
@@ -813,8 +855,6 @@ discard:if (session->deref) (*session->deref)(session); - -l2tp_session_dec_refcount (session); }EXPORT_SYMBOL (l2tp_recv_common);
@@ -921,8 +961,14 @@ static int l2tp_udp_recv_core(struct l2tp_tunnel *tunnel, struct sk_buff *skb, }/* Find the session context */ - session =l2tp_session_find (tunnel->l2tp_net, tunnel, session_id); + session =l2tp_session_get (tunnel->l2tp_net, tunnel, session_id, true);if (!session || !session->recv_skb) { +if (session) { +if (session->deref) + session->deref (session); +l2tp_session_dec_refcount (session); + } +/* Not found? Pass to userspace to deal with */ l2tp_info(tunnel, L2TP_MSG_DATA, "%s: no session found (%u/%u). Passing up.\n",
@@ -935,6 +981,7 @@ static int l2tp_udp_recv_core(struct l2tp_tunnel *tunnel, struct sk_buff *skb, goto error;l2tp_recv_common (session, skb, ptr, optr, hdrflags, length, payload_hook); +l2tp_session_dec_refcount (session);return 0 ;
net/l2tp/l2tp_core.h +3 -0
@@ -243,6 +243,9 @@ out:return tunnel; } +struct l2tp_session *l2tp_session_get (struct net *net, +struct l2tp_tunnel *tunnel, +u32 session_id,bool do_ref);struct l2tp_session *l2tp_session_find (struct net *net,struct l2tp_tunnel *tunnel,u32 session_id);
net/l2tp/l2tp_ip.c +12 -5
@@ -142,19 +142,19 @@ static int l2tp_ip_recv(struct sk_buff *skb) }/* Ok, this is a data packet. Lookup the session. */ - session =l2tp_session_find (net,NULL , session_id); -if (session ==NULL ) + session =l2tp_session_get (net,NULL , session_id, true); +if (!session) goto discard; tunnel = session->tunnel; -if (tunnel ==NULL ) - goto discard; +if (!tunnel) + goto discard_sess;/* Trace packet contents, if enabled */ if (tunnel->debug &L2TP_MSG_DATA ) { length =min (32u , skb->len);if (!pskb_may_pull (skb, length)) - goto discard; + goto discard_sess;/* Point to L2TP header */ optr = ptr = skb->data;
@@ -167,6 +167,7 @@ static int l2tp_ip_recv(struct sk_buff *skb) goto discard;l2tp_recv_common (session, skb, ptr, optr,0 , skb->len, tunnel->recv_payload_hook); +l2tp_session_dec_refcount (session);return 0 ;
@@ -204,6 +205,12 @@ pass_up:return sk_receive_skb (sk, skb,1 ); +discard_sess: +if (session->deref) + session->deref (session); +l2tp_session_dec_refcount (session); + goto discard; + discard_put:sock_put (sk);
net/l2tp/l2tp_ip6.c +13 -5
@@ -154,19 +154,19 @@ static int l2tp_ip6_recv(struct sk_buff *skb) }/* Ok, this is a data packet. Lookup the session. */ - session =l2tp_session_find (net,NULL , session_id); -if (session ==NULL ) + session =l2tp_session_get (net,NULL , session_id, true); +if (!session) goto discard; tunnel = session->tunnel; -if (tunnel ==NULL ) - goto discard; +if (!tunnel) + goto discard_sess;/* Trace packet contents, if enabled */ if (tunnel->debug &L2TP_MSG_DATA ) { length =min (32u , skb->len);if (!pskb_may_pull (skb, length)) - goto discard; + goto discard_sess;/* Point to L2TP header */ optr = ptr = skb->data;
@@ -180,6 +180,8 @@ static int l2tp_ip6_recv(struct sk_buff *skb)l2tp_recv_common (session, skb, ptr, optr,0 , skb->len, tunnel->recv_payload_hook); +l2tp_session_dec_refcount (session); +return 0 ; pass_up:
@@ -217,6 +219,12 @@ pass_up:return sk_receive_skb (sk, skb,1 ); +discard_sess: +if (session->deref) + session->deref (session); +l2tp_session_dec_refcount (session); + goto discard; + discard_put:sock_put (sk);
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 4/8 l2tp: ensure session can't get removed during pppol2tp_session_ioctl()
In reply to Will DeaconFrom: Guillaume Nault <[email protected]>
commit 57377d63547861919ee634b845c7caa38de4a452 upstream.
Holding a reference on session is required before calling pppol2tp_session_ioctl(). The session could get freed while processing the ioctl otherwise. Since pppol2tp_session_ioctl() uses the session's socket, we also need to take a reference on it in l2tp_session_get().
--- net/l2tp/l2tp_ppp.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-)
net/l2tp/l2tp_ppp.c +11 -4
@@ -1168,11 +1168,18 @@ static int pppol2tp_tunnel_ioctl(struct l2tp_tunnel *tunnel,if (stats.session_id !=0 ) {/* resend to session ioctl handler */ struct l2tp_session *session = -l2tp_session_find (sock_net (sk), tunnel, stats.session_id); -if (session !=NULL ) - err =pppol2tp_session_ioctl (session, cmd, arg); -else +l2tp_session_get (sock_net (sk), tunnel, + stats.session_id, true); + +if (session) { + err =pppol2tp_session_ioctl (session, cmd, + arg); +if (session->deref) + session->deref (session); +l2tp_session_dec_refcount (session); + }else { err = -EBADR ; + }break ; }#ifdef CONFIG_XFRM
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 5/8 l2tp: fix duplicate session creation
In reply to Will DeaconFrom: Guillaume Nault <[email protected]>
commit dbdbc73b44782e22b3b4b6e8b51e7a3d245f3086 upstream.
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.
--- net/l2tp/l2tp_core.c | 70 +++++++++++++++++++++++++++++++++----------- net/l2tp/l2tp_eth.c | 10 ++----- net/l2tp/l2tp_ppp.c | 60 ++++++++++++++++++------------------- 3 files changed, 84 insertions(+), 56 deletions(-)
net/l2tp/l2tp_core.c +53 -17
@@ -377,6 +377,48 @@ struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname) }EXPORT_SYMBOL_GPL (l2tp_session_find_by_ifname); +static int l2tp_session_add_to_tunnel (struct l2tp_tunnel *tunnel, +struct l2tp_session *session) +{ +struct l2tp_session *session_walk; +struct hlist_head *g_head; +struct hlist_head *head; +struct l2tp_net *pn; + + head =l2tp_session_id_hash (tunnel, session->session_id); + +write_lock_bh (&tunnel->hlist_lock); +hlist_for_each_entry (session_walk, head, hlist) +if (session_walk->session_id == session->session_id) + goto exist; + +if (tunnel->version ==L2TP_HDR_VER_3 ) { + pn =l2tp_pernet (tunnel->l2tp_net); + g_head =l2tp_session_id_hash_2 (l2tp_pernet (tunnel->l2tp_net), + session->session_id); + +spin_lock_bh (&pn->l2tp_session_hlist_lock); +hlist_for_each_entry (session_walk, g_head, global_hlist) +if (session_walk->session_id == session->session_id) + goto exist_glob; + +hlist_add_head_rcu (&session->global_hlist, g_head); +spin_unlock_bh (&pn->l2tp_session_hlist_lock); + } + +hlist_add_head (&session->hlist, head); +write_unlock_bh (&tunnel->hlist_lock); + +return 0 ; + +exist_glob: +spin_unlock_bh (&pn->l2tp_session_hlist_lock); +exist: +write_unlock_bh (&tunnel->hlist_lock); + +return -EEXIST ; +} +/* Lookup a tunnel by id */ struct l2tp_tunnel *l2tp_tunnel_find (struct net *net,u32 tunnel_id)
@@ -1792,6 +1834,7 @@ EXPORT_SYMBOL_GPL(l2tp_session_set_header_len);struct l2tp_session *l2tp_session_create (int priv_size,struct l2tp_tunnel *tunnel,u32 session_id,u32 peer_session_id,struct l2tp_session_cfg *cfg) {struct l2tp_session *session; +int err; session =kzalloc (sizeof (struct l2tp_session ) + priv_size,GFP_KERNEL );if (session !=NULL ) {
@@ -1847,6 +1890,13 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunnl2tp_session_set_header_len (session, tunnel->version); + err =l2tp_session_add_to_tunnel (tunnel, session); +if (err) { +kfree (session); + +return ERR_PTR (err); + } +/* Bump the reference count. The session context is deleted * only when this drops to zero. */
@@ -1856,28 +1906,14 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn/* Ensure tunnel socket isn't deleted */ sock_hold (tunnel->sock); -/* Add session to the tunnel's hash list */ -write_lock_bh (&tunnel->hlist_lock); -hlist_add_head (&session->hlist, -l2tp_session_id_hash (tunnel, session_id)); -write_unlock_bh (&tunnel->hlist_lock); - -/* And to the global session list if L2TPv3 */ -if (tunnel->version !=L2TP_HDR_VER_2 ) { -struct l2tp_net *pn =l2tp_pernet (tunnel->l2tp_net); - -spin_lock_bh (&pn->l2tp_session_hlist_lock); -hlist_add_head_rcu (&session->global_hlist, -l2tp_session_id_hash_2 (pn, session_id)); -spin_unlock_bh (&pn->l2tp_session_hlist_lock); - } -/* Ignore management session in session count value */ if (session->session_id !=0 )atomic_inc (&l2tp_session_count); + +return session; } -return session; +return ERR_PTR (-ENOMEM ); }EXPORT_SYMBOL_GPL (l2tp_session_create);
net/l2tp/l2tp_eth.c +2 -8
@@ -223,12 +223,6 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p goto out; } - session =l2tp_session_find (net, tunnel, session_id); -if (session) { - rc = -EEXIST ; - goto out; - } -if (cfg->ifname) { dev =dev_get_by_name (net, cfg->ifname);if (dev) {
@@ -242,8 +236,8 @@ static int l2tp_eth_create(struct net *net, u32 tunnel_id, u32 session_id, u32 p session =l2tp_session_create (sizeof (*spriv), tunnel, session_id, peer_session_id, cfg); -if (!session) { - rc = -ENOMEM ; +if (IS_ERR (session)) { + rc =PTR_ERR (session); goto out; }
net/l2tp/l2tp_ppp.c +29 -31
101 lines not on this page · raw message
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 6/8 l2tp: Refactor the codes with existing macros instead of literal number
In reply to Will DeaconFrom: Gao Feng <[email protected]>
commit 54c151d9ed1321e6e623c80ffe42cd2eb1571744 upstream.
Use PPP_ALLSTATIONS, PPP_UI, and SEND_SHUTDOWN instead of 0xff, 0x03, and 2 separately.
--- net/l2tp/l2tp_ppp.c | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-)
net/l2tp/l2tp_ppp.c +9 -11
@@ -177,7 +177,7 @@ static int pppol2tp_recv_payload_hook(struct sk_buff *skb)if (!pskb_may_pull (skb,2 ))return 1 ; -if ((skb->data[0 ] ==0xff ) && (skb->data[1 ] ==0x03 )) +if ((skb->data[0 ] ==PPP_ALLSTATIONS ) && (skb->data[1 ] ==PPP_UI ))skb_pull (skb,2 );return 0 ;
@@ -297,7 +297,6 @@ static void pppol2tp_session_sock_put(struct l2tp_session *session)static int pppol2tp_sendmsg (struct socket *sock,struct msghdr *m,size_t total_len) { -static const unsigned char ppph[2 ] = {0xff ,0x03 };struct sock *sk = sock->sk;struct sk_buff *skb;int error;
@@ -327,7 +326,7 @@ static int pppol2tp_sendmsg(struct socket *sock, struct msghdr *m, error = -ENOMEM ; skb =sock_wmalloc (sk,NET_SKB_PAD +sizeof (struct iphdr ) + uhlen + session->hdr_len + -sizeof (ppph) + total_len, +2 + total_len,/* 2 bytes for PPP_ALLSTATIONS & PPP_UI */ 0 ,GFP_KERNEL );if (!skb) goto error_put_sess_tun;
@@ -340,8 +339,8 @@ static int pppol2tp_sendmsg(struct socket *sock, struct msghdr *m,skb_reserve (skb, uhlen);/* Add PPP header */ - skb->data[0 ] = ppph[0 ]; - skb->data[1 ] = ppph[1 ]; + skb->data[0 ] =PPP_ALLSTATIONS ; + skb->data[1 ] =PPP_UI ;skb_put (skb,2 );/* Copy user data into skb */
@@ -384,7 +383,6 @@ error:*/ static int pppol2tp_xmit (struct ppp_channel *chan,struct sk_buff *skb) { -static const u8 ppph[2 ] = {0xff ,0x03 };struct sock *sk = (struct sock *) chan->private;struct sock *sk_tun;struct l2tp_session *session;
@@ -413,14 +411,14 @@ static int pppol2tp_xmit(struct ppp_channel *chan, struct sk_buff *skb)sizeof (struct iphdr ) +/* IP header */ uhlen +/* UDP header (if L2TP_ENCAPTYPE_UDP) */ session->hdr_len +/* L2TP header */ -sizeof (ppph);/* PPP header */ +2 ;/* 2 bytes for PPP_ALLSTATIONS & PPP_UI */ if (skb_cow_head (skb, headroom)) goto abort_put_sess_tun;/* Setup PPP header */ -__skb_push (skb,sizeof (ppph)); - skb->data[0 ] = ppph[0 ]; - skb->data[1 ] = ppph[1 ]; +__skb_push (skb,2 ); + skb->data[0 ] =PPP_ALLSTATIONS ; + skb->data[1 ] =PPP_UI ;local_bh_disable ();l2tp_xmit_skb (session, skb, session->hdr_len);
@@ -455,7 +453,7 @@ static void pppol2tp_session_close(struct l2tp_session *session)BUG_ON (session->magic !=L2TP_SESSION_MAGIC );if (sock) { -inet_shutdown (sock,2 ); +inet_shutdown (sock,SEND_SHUTDOWN );/* Don't let the session go away before our socket does */ l2tp_session_inc_refcount (session); }
Signature · 1 line
2.26.0.rc2.310.g2932bb562d-goog
PATCH 7/8 l2tp: ensure sessions are freed after their PPPOL2TP socket
In reply to Will DeaconFrom: Guillaume Nault <[email protected]>
commit cdd10c9627496ad25c87ce6394e29752253c69d3 upstream.
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
@@ -452,11 +452,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.26.0.rc2.310.g2932bb562d-goog
PATCH 8/8 l2tp: fix race between l2tp_session_delete() and l2tp_tunnel_closeall()
In reply to Will DeaconFrom: Guillaume Nault <[email protected]>
commit b228a94066406b6c456321d69643b0d7ce11cfa6 upstream.
There 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
@@ -1351,6 +1351,9 @@ again:hlist_del_init (&session->hlist); +if (test_and_set_bit (0 , &session->dead)) + goto again; +if (session->ref !=NULL ) (*session->ref)(session);
@@ -1799,6 +1802,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
@@ -85,6 +85,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.26.0.rc2.310.g2932bb562d-goog
Greg KH
maintainerQuoted 11 lines · Will Deacon
Hi Greg,
Syzbot has been complaining about KASAN splats due to use-after-free issues in the l2tp code on 4.4 Android kernels (although I reproduced with latest 4.4 stable on my laptop):
https://syzkaller.appspot.com/bug?id=de316389db0fa0cd7ced6e564601ea8e56625ebc
These have been fixed upstream, but for some reason didn't get picked up for stable. This series applies to 4.4.y and I've sent patches for 4.9 separately.
All now queued up, thanks.
greg k-h
Quoted 7 lines · Greg KH
Quoted 6 lines · Will Deacon
Hi Greg,
Syzbot has been complaining about KASAN splats due to use-after-free issues in the l2tp code on 4.4 Android kernels (although I reproduced with latest 4.4 stable on my laptop):
Quoted 7 lines
Quoted 5 lines
https://syzkaller.appspot.com/bug?id=de316389db0fa0cd7ced6e564601ea8e56625ebc
These have been fixed upstream, but for some reason didn't get picked up for stable. This series applies to 4.4.y and I've sent patches for 4.9 separately.
All now queued up, thanks.
Thanks, Greg.
Will