]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
l2tp: prevent possible tunnel refcount underflow
authorJames Chapman <jchapman@katalix.com>
Mon, 29 Jul 2024 15:38:10 +0000 (16:38 +0100)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 10 Oct 2024 10:03:06 +0000 (12:03 +0200)
[ Upstream commit 24256415d18695b46da06c93135f5b51c548b950 ]

When a session is created, it sets a backpointer to its tunnel. When
the session refcount drops to 0, l2tp_session_free drops the tunnel
refcount if session->tunnel is non-NULL. However, session->tunnel is
set in l2tp_session_create, before the tunnel refcount is incremented
by l2tp_session_register, which leaves a small window where
session->tunnel is non-NULL when the tunnel refcount hasn't been
bumped.

Moving the assignment to l2tp_session_register is trivial but
l2tp_session_create calls l2tp_session_set_header_len which uses
session->tunnel to get the tunnel's encap. Add an encap arg to
l2tp_session_set_header_len to avoid using session->tunnel.

If l2tpv3 sessions have colliding IDs, it is possible for
l2tp_v3_session_get to race with l2tp_session_register and fetch a
session which doesn't yet have session->tunnel set. Add a check for
this case.

Signed-off-by: James Chapman <jchapman@katalix.com>
Signed-off-by: Tom Parkin <tparkin@katalix.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
net/l2tp/l2tp_core.c
net/l2tp/l2tp_core.h
net/l2tp/l2tp_netlink.c
net/l2tp/l2tp_ppp.c

index 2e86f520f799410aa946e0389a84fb58714941dd..a9cbcbc9d016d246ec2411cf213ff8b3d9bba491 100644 (file)
@@ -254,7 +254,14 @@ struct l2tp_session *l2tp_v3_session_get(const struct net *net, struct sock *sk,
 
                hash_for_each_possible_rcu(pn->l2tp_v3_session_htable, session,
                                           hlist, key) {
-                       if (session->tunnel->sock == sk &&
+                       /* session->tunnel may be NULL if another thread is in
+                        * l2tp_session_register and has added an item to
+                        * l2tp_v3_session_htable but hasn't yet added the
+                        * session to its tunnel's session_list.
+                        */
+                       struct l2tp_tunnel *tunnel = READ_ONCE(session->tunnel);
+
+                       if (tunnel && tunnel->sock == sk &&
                            refcount_inc_not_zero(&session->ref_count)) {
                                rcu_read_unlock_bh();
                                return session;
@@ -482,6 +489,7 @@ int l2tp_session_register(struct l2tp_session *session,
        }
 
        l2tp_tunnel_inc_refcount(tunnel);
+       WRITE_ONCE(session->tunnel, tunnel);
        list_add(&session->list, &tunnel->session_list);
 
        if (tunnel->version == L2TP_HDR_VER_3) {
@@ -797,7 +805,8 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
                if (!session->lns_mode && !session->send_seq) {
                        trace_session_seqnum_lns_enable(session);
                        session->send_seq = 1;
-                       l2tp_session_set_header_len(session, tunnel->version);
+                       l2tp_session_set_header_len(session, tunnel->version,
+                                                   tunnel->encap);
                }
        } else {
                /* No sequence numbers.
@@ -818,7 +827,8 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
                if (!session->lns_mode && session->send_seq) {
                        trace_session_seqnum_lns_disable(session);
                        session->send_seq = 0;
-                       l2tp_session_set_header_len(session, tunnel->version);
+                       l2tp_session_set_header_len(session, tunnel->version,
+                                                   tunnel->encap);
                } else if (session->send_seq) {
                        pr_debug_ratelimited("%s: recv data has no seq numbers when required. Discarding.\n",
                                             session->name);
@@ -1663,7 +1673,8 @@ EXPORT_SYMBOL_GPL(l2tp_session_delete);
 /* We come here whenever a session's send_seq, cookie_len or
  * l2specific_type parameters are set.
  */
-void l2tp_session_set_header_len(struct l2tp_session *session, int version)
+void l2tp_session_set_header_len(struct l2tp_session *session, int version,
+                                enum l2tp_encap_type encap)
 {
        if (version == L2TP_HDR_VER_2) {
                session->hdr_len = 6;
@@ -1672,7 +1683,7 @@ void l2tp_session_set_header_len(struct l2tp_session *session, int version)
        } else {
                session->hdr_len = 4 + session->cookie_len;
                session->hdr_len += l2tp_get_l2specific_len(session);
-               if (session->tunnel->encap == L2TP_ENCAPTYPE_UDP)
+               if (encap == L2TP_ENCAPTYPE_UDP)
                        session->hdr_len += 4;
        }
 }
@@ -1686,7 +1697,6 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
        session = kzalloc(sizeof(*session) + priv_size, GFP_KERNEL);
        if (session) {
                session->magic = L2TP_SESSION_MAGIC;
-               session->tunnel = tunnel;
 
                session->session_id = session_id;
                session->peer_session_id = peer_session_id;
@@ -1724,7 +1734,7 @@ struct l2tp_session *l2tp_session_create(int priv_size, struct l2tp_tunnel *tunn
                        memcpy(&session->peer_cookie[0], &cfg->peer_cookie[0], cfg->peer_cookie_len);
                }
 
-               l2tp_session_set_header_len(session, tunnel->version);
+               l2tp_session_set_header_len(session, tunnel->version, tunnel->encap);
 
                refcount_set(&session->ref_count, 1);
 
index 8ac81bc1bc6fa33d3cbb43da70c74c4c6d373de8..6c25c196cc2224ffa811cf3a699d887db8861ac1 100644 (file)
@@ -260,7 +260,8 @@ void l2tp_recv_common(struct l2tp_session *session, struct sk_buff *skb,
 int l2tp_udp_encap_recv(struct sock *sk, struct sk_buff *skb);
 
 /* Transmit path helpers for sending packets over the tunnel socket. */
-void l2tp_session_set_header_len(struct l2tp_session *session, int version);
+void l2tp_session_set_header_len(struct l2tp_session *session, int version,
+                                enum l2tp_encap_type encap);
 int l2tp_xmit_skb(struct l2tp_session *session, struct sk_buff *skb);
 
 /* Pseudowire management.
index d105030520f9541022d8ccb6af6f0b7887a59e45..fc43ecbd128cc5c866de4c8dc84b2c9e9e6382cf 100644 (file)
@@ -692,8 +692,10 @@ static int l2tp_nl_cmd_session_modify(struct sk_buff *skb, struct genl_info *inf
                session->recv_seq = nla_get_u8(info->attrs[L2TP_ATTR_RECV_SEQ]);
 
        if (info->attrs[L2TP_ATTR_SEND_SEQ]) {
+               struct l2tp_tunnel *tunnel = session->tunnel;
+
                session->send_seq = nla_get_u8(info->attrs[L2TP_ATTR_SEND_SEQ]);
-               l2tp_session_set_header_len(session, session->tunnel->version);
+               l2tp_session_set_header_len(session, tunnel->version, tunnel->encap);
        }
 
        if (info->attrs[L2TP_ATTR_LNS_MODE])
index 3596290047b28e65e56eec0a511e0a1263ec44c9..4f25c1212cacb1b47fb3aba76c6a1e45352c0d8d 100644 (file)
@@ -1205,7 +1205,8 @@ static int pppol2tp_session_setsockopt(struct sock *sk,
                        po->chan.hdrlen = val ? PPPOL2TP_L2TP_HDR_SIZE_SEQ :
                                PPPOL2TP_L2TP_HDR_SIZE_NOSEQ;
                }
-               l2tp_session_set_header_len(session, session->tunnel->version);
+               l2tp_session_set_header_len(session, session->tunnel->version,
+                                           session->tunnel->encap);
                break;
 
        case PPPOL2TP_SO_LNSMODE: