l2tp: fix usage of l2tp_session_find()
Subsystems (3)
cover l2tp: fix usage of l2tp_session_find()
l2tp_session_find() doesn't take a reference on the session returned to its caller. Virtually all l2tp_session_find() users are racy, either because the session can disappear from under them or because they take a reference too late. This leads to bugs like 'use after free' or failure to notice duplicate session creations.
In some cases, taking a reference on the session is not enough. The special callbacks .ref() and .deref() also have to be called in cases where the PPP pseudo-wire uses the socket associated with the session. Therefore, when looking up a session, we also have to pass a flag indicating if the .ref() callback has to be called.
In the future, we probably could drop the .ref() and .deref() callbacks entirely by protecting the .sock field of struct pppol2tp_session with RCU, thus allowing it to be freed and set to NULL even if the L2TP session is still alive.
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: hold session while sending creation notifications
l2tp: take a reference on sessions used in genetlink handlers
net/l2tp/l2tp_core.c | 152 ++++++++++++++++++++++++++++++++++++++---------- net/l2tp/l2tp_core.h | 6 +- net/l2tp/l2tp_eth.c | 10 +--- net/l2tp/l2tp_ip.c | 17 ++++-- net/l2tp/l2tp_ip6.c | 18 ++++-- net/l2tp/l2tp_netlink.c | 45 +++++++++----- net/l2tp/l2tp_ppp.c | 75 +++++++++++++----------- 7 files changed, 222 insertions(+), 101 deletions(-)
Signature · 1 line
2.11.0
PATCH 1/5 l2tp: fix race in l2tp_recv_common()
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
@@ -278,6 +278,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_find_nth (struct l2tp_tunnel *tunnel,int nth) {int hash;
@@ -633,6 +682,9 @@ static int l2tp_recv_data_seq(struct l2tp_session *session, struct sk_buff *skb)* 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,
@@ -642,14 +694,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)) {
@@ -802,8 +846,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:
@@ -812,8 +854,6 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,if (session->deref) (*session->deref)(session); - -l2tp_session_dec_refcount (session); }EXPORT_SYMBOL (l2tp_recv_common);
@@ -920,8 +960,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",
@@ -930,6 +976,7 @@ static int l2tp_udp_recv_core(struct l2tp_tunnel *tunnel, struct sk_buff *skb, }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
@@ -230,6 +230,9 @@ static inline struct l2tp_tunnel *l2tp_sock_to_tunnel(struct sock *sk)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
@@ -143,19 +143,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;
@@ -165,6 +165,7 @@ static int l2tp_ip_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 ;
@@ -203,6 +204,12 @@ static int l2tp_ip_recv(struct sk_buff *skb)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
@@ -156,19 +156,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;
@@ -179,6 +179,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:
@@ -216,6 +218,12 @@ static int l2tp_ip6_recv(struct sk_buff *skb)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.11.0
PATCH 2/5 l2tp: ensure session can't get removed during pppol2tp_session_ioctl()
In reply to Guillaume NaultHolding 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
@@ -1141,11 +1141,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.11.0
PATCH 3/5 l2tp: fix duplicate session creation
In reply to Guillaume Naultl2tp_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
@@ -374,6 +374,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)
@@ -1785,6 +1827,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 ) {
@@ -1840,6 +1883,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. */
@@ -1849,28 +1899,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
@@ -221,12 +221,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) {
@@ -240,8 +234,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.11.0
PATCH 4/5 l2tp: hold session while sending creation notifications
In reply to Guillaume Naultl2tp_session_find() doesn't take any reference on the returned session. Therefore, the session may disappear while sending the notification.
Use l2tp_session_get() instead and decrement session's refcount once the notification is sent.
--- net/l2tp/l2tp_netlink.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-)
net/l2tp/l2tp_netlink.c +4 -2
@@ -642,10 +642,12 @@ static int l2tp_nl_cmd_session_create(struct sk_buff *skb, struct genl_info *inf session_id, peer_session_id, &cfg);if (ret >=0 ) { - session =l2tp_session_find (net, tunnel, session_id); -if (session) + session =l2tp_session_get (net, tunnel, session_id, false); +if (session) { ret =l2tp_session_notify (&l2tp_nl_family, info, session,L2TP_CMD_SESSION_CREATE ); +l2tp_session_dec_refcount (session); + } } out:
Signature · 1 line
2.11.0
PATCH 5/5 l2tp: take a reference on sessions used in genetlink handlers
In reply to Guillaume NaultCallers of l2tp_nl_session_find() need to hold a reference on the returned session since there's no guarantee that it isn't going to disappear from under them.
Relying on the fact that no l2tp netlink message may be processed concurrently isn't enough: sessions can be deleted by other means (e.g. by closing the PPPOL2TP socket of a ppp pseudowire).
l2tp_nl_cmd_session_delete() is a bit special: it runs a callback function that may require a previous call to session->ref(). In particular, for ppp pseudowires, the callback is l2tp_session_delete(), which then calls pppol2tp_session_close() and dereferences the PPPOL2TP socket. The socket might already be gone at the moment l2tp_session_delete() calls session->ref(), so we need to take a reference during the session lookup. So we need to pass the do_ref variable down to l2tp_session_get() and l2tp_session_get_by_ifname().
Since all callers have to be updated, l2tp_session_find_by_ifname() and l2tp_nl_session_find() are renamed to reflect their new behaviour.
--- net/l2tp/l2tp_core.c | 9 +++++++-- net/l2tp/l2tp_core.h | 3 ++- net/l2tp/l2tp_netlink.c | 39 ++++++++++++++++++++++++++------------- 3 files changed, 35 insertions(+), 16 deletions(-)
net/l2tp/l2tp_core.c +7 -2
@@ -352,7 +352,8 @@ EXPORT_SYMBOL_GPL(l2tp_session_find_nth);/* Lookup a session by interface name. * This is very inefficient but is only used by management interfaces. */ -struct l2tp_session *l2tp_session_find_by_ifname (struct net *net,char *ifname) +struct l2tp_session *l2tp_session_get_by_ifname (struct net *net,char *ifname, +bool do_ref) {struct l2tp_net *pn =l2tp_pernet (net);int hash;
@@ -362,7 +363,11 @@ struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname)for (hash =0 ; hash <L2TP_HASH_SIZE_2 ; hash++) {hlist_for_each_entry_rcu (session, &pn->l2tp_session_hlist[hash], global_hlist) {if (!strcmp (session->ifname, ifname)) { +l2tp_session_inc_refcount (session); +if (do_ref && session->ref) + session->ref (session);rcu_read_unlock_bh (); +return session; } }
@@ -372,7 +377,7 @@ struct l2tp_session *l2tp_session_find_by_ifname(struct net *net, char *ifname)return NULL ; } -EXPORT_SYMBOL_GPL (l2tp_session_find_by_ifname); +EXPORT_SYMBOL_GPL (l2tp_session_get_by_ifname);static int l2tp_session_add_to_tunnel (struct l2tp_tunnel *tunnel,struct l2tp_session *session)
net/l2tp/l2tp_core.h +2 -1
@@ -237,7 +237,8 @@ struct l2tp_session *l2tp_session_find(struct net *net,struct l2tp_tunnel *tunnel, u32 session_id);struct l2tp_session *l2tp_session_find_nth (struct l2tp_tunnel *tunnel,int nth); -struct l2tp_session *l2tp_session_find_by_ifname (struct net *net,char *ifname); +struct l2tp_session *l2tp_session_get_by_ifname (struct net *net,char *ifname, +bool do_ref);struct l2tp_tunnel *l2tp_tunnel_find (struct net *net,u32 tunnel_id);struct l2tp_tunnel *l2tp_tunnel_find_nth (struct net *net,int nth);
net/l2tp/l2tp_netlink.c +26 -13
@@ -48,7 +48,8 @@ static int l2tp_nl_session_send(struct sk_buff *skb, u32 portid, u32 seq,/* Accessed under genl lock */ static const struct l2tp_nl_cmd_ops *l2tp_nl_cmd_ops[__L2TP_PWTYPE_MAX]; -static struct l2tp_session *l2tp_nl_session_find (struct genl_info *info) +static struct l2tp_session *l2tp_nl_session_get (struct genl_info *info, +bool do_ref) {u32 tunnel_id;u32 session_id;
@@ -59,14 +60,15 @@ static struct l2tp_session *l2tp_nl_session_find(struct genl_info *info)if (info->attrs[L2TP_ATTR_IFNAME ]) { ifname =nla_data (info->attrs[L2TP_ATTR_IFNAME ]); - session =l2tp_session_find_by_ifname (net, ifname); + session =l2tp_session_get_by_ifname (net, ifname, do_ref); }else if ((info->attrs[L2TP_ATTR_SESSION_ID ]) && (info->attrs[L2TP_ATTR_CONN_ID ])) { tunnel_id =nla_get_u32 (info->attrs[L2TP_ATTR_CONN_ID ]); session_id =nla_get_u32 (info->attrs[L2TP_ATTR_SESSION_ID ]); tunnel =l2tp_tunnel_find (net, tunnel_id);if (tunnel) - session =l2tp_session_find (net, tunnel, session_id); + session =l2tp_session_get (net, tunnel, session_id, + do_ref); }return session;
@@ -660,7 +662,7 @@ static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *infstruct l2tp_session *session;u16 pw_type; - session =l2tp_nl_session_find (info); + session =l2tp_nl_session_get (info, true);if (session ==NULL ) { ret = -ENODEV ; goto out;
@@ -674,6 +676,10 @@ static int l2tp_nl_cmd_session_delete(struct sk_buff *skb, struct genl_info *infif (l2tp_nl_cmd_ops[pw_type] && l2tp_nl_cmd_ops[pw_type]->session_delete) ret = (*l2tp_nl_cmd_ops[pw_type]->session_delete)(session); +if (session->deref) + session->deref (session); +l2tp_session_dec_refcount (session); + out:return ret; }
@@ -683,7 +689,7 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *infint ret =0 ;struct l2tp_session *session; - session =l2tp_nl_session_find (info); + session =l2tp_nl_session_get (info, false);if (session ==NULL ) { ret = -ENODEV ; goto out;
@@ -718,6 +724,8 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *inf ret =l2tp_session_notify (&l2tp_nl_family, info, session,L2TP_CMD_SESSION_MODIFY ); +l2tp_session_dec_refcount (session); + out:return ret; }
@@ -813,29 +821,34 @@ static int l2tp_nl_cmd_session_get(struct sk_buff *skb, struct genl_info *info)struct sk_buff *msg;int ret; - session =l2tp_nl_session_find (info); + session =l2tp_nl_session_get (info, false);if (session ==NULL ) { ret = -ENODEV ; - goto out; + goto err; } msg =nlmsg_new (NLMSG_DEFAULT_SIZE ,GFP_KERNEL );if (!msg) { ret = -ENOMEM ; - goto out; + goto err_ref; } ret =l2tp_nl_session_send (msg, info->snd_portid, info->snd_seq,0 , session,L2TP_CMD_SESSION_GET );if (ret <0 ) - goto err_out; + goto err_ref_msg; -return genlmsg_unicast (genl_info_net (info), msg, info->snd_portid); + ret =genlmsg_unicast (genl_info_net (info), msg, info->snd_portid); -err_out: -nlmsg_free (msg); +l2tp_session_dec_refcount (session); -out: +return ret; + +err_ref_msg: +nlmsg_free (msg); +err_ref: +l2tp_session_dec_refcount (session); +err:return ret; }
Signature · 1 line
2.11.0
Re: [PATCH net 5/5] l2tp: take a reference on sessions used in genetlink handlers
Quoted 17 lines · Guillaume Nault
Callers of
l2tp_nl_session_find()need to hold a reference on the returned session since there's no guarantee that it isn't going to disappear from under them.Relying on the fact that no l2tp netlink message may be processed concurrently isn't enough: sessions can be deleted by other means (e.g. by closing the PPPOL2TP socket of a ppp pseudowire).
l2tp_nl_cmd_session_delete()is a bit special: it runs a callback function that may require a previous call tosession->ref(). In particular, for ppp pseudowires, the callback isl2tp_session_delete(), which then callspppol2tp_session_close()and dereferences the PPPOL2TP socket. The socket might already be gone at the momentl2tp_session_delete()callssession->ref(), so we need to take a reference during the session lookup. So we need to pass thedo_refvariable down tol2tp_session_get()andl2tp_session_get_by_ifname().
Quoted 4 lines
Since all callers have to be updated,
l2tp_session_find_by_ifname()andl2tp_nl_session_find()are renamed to reflect their new behaviour.Fixes 33f72e6f0c67 ("l2tp : multicast notification to the registered listeners")
Sorry, it should have been
Commit 33f72e6f0c67 ("l2tp : multicast notification to the registered listeners") just worsened the existing race conditions.
David, do you want me to repost this series with the new Fixes tag?
David Miller
maintainerFrom: Guillaume Nault <[email protected]>
Date: Fri, 31 Mar 2017 13:02:23 +0200
Quoted 12 lines
l2tp_session_find()doesn't take a reference on the session returned to its caller. Virtually alll2tp_session_find()users are racy, either because the session can disappear from under them or because they take a reference too late. This leads to bugs like 'use after free' or failure to notice duplicate session creations.In some cases, taking a reference on the session is not enough. The special callbacks .ref() and .deref() also have to be called in cases where the PPP pseudo-wire uses the socket associated with the session. Therefore, when looking up a session, we also have to pass a flag indicating if the .ref() callback has to be called.
Quoted 4 lines
In the future, we probably could drop the .ref() and .deref() callbacks entirely by protecting the .sock field of struct
pppol2tp_sessionwith RCU, thus allowing it to be freed and set to NULL even if the L2TP session is still alive.
Series applied with the Fixes: tag of patch #5 updated.
Thanks.