mirror of
https://github.com/torvalds/linux.git
synced 2026-03-08 02:44:41 +01:00
tcp: fix potential race in tcp_v6_syn_recv_sock()
Code in tcp_v6_syn_recv_sock() after the call to tcp_v4_syn_recv_sock()
is done too late.
After tcp_v4_syn_recv_sock(), the child socket is already visible
from TCP ehash table and other cpus might use it.
Since newinet->pinet6 is still pointing to the listener ipv6_pinfo
bad things can happen as syzbot found.
Move the problematic code in tcp_v6_mapped_child_init()
and call this new helper from tcp_v4_syn_recv_sock() before
the ehash insertion.
This allows the removal of one tcp_sync_mss(), since
tcp_v4_syn_recv_sock() will call it with the correct
context.
Fixes: 1da177e4c3 ("Linux-2.6.12-rc2")
Reported-by: syzbot+937b5bbb6a815b3e5d0b@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/netdev/69949275.050a0220.2eeac1.0145.GAE@google.com/
Signed-off-by: Eric Dumazet <edumazet@google.com>
Reviewed-by: Kuniyuki Iwashima <kuniyu@google.com>
Link: https://patch.msgid.link/20260217161205.2079883-1-edumazet@google.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
8bf22c33e7
commit
858d2a4f67
9 changed files with 66 additions and 66 deletions
|
|
@ -42,7 +42,9 @@ struct inet_connection_sock_af_ops {
|
|||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req);
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk));
|
||||
u16 net_header_len;
|
||||
int (*setsockopt)(struct sock *sk, int level, int optname,
|
||||
sockptr_t optval, unsigned int optlen);
|
||||
|
|
|
|||
|
|
@ -544,7 +544,9 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
|
|||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req);
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk));
|
||||
int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb);
|
||||
int tcp_v4_connect(struct sock *sk, struct sockaddr_unsized *uaddr, int addr_len);
|
||||
int tcp_connect(struct sock *sk);
|
||||
|
|
|
|||
|
|
@ -203,7 +203,7 @@ struct sock *tcp_get_cookie_sock(struct sock *sk, struct sk_buff *skb,
|
|||
bool own_req;
|
||||
|
||||
child = icsk->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
|
||||
NULL, &own_req);
|
||||
NULL, &own_req, NULL);
|
||||
if (child) {
|
||||
refcount_set(&req->rsk_refcnt, 1);
|
||||
sock_rps_save_rxhash(child, skb);
|
||||
|
|
|
|||
|
|
@ -333,7 +333,7 @@ static struct sock *tcp_fastopen_create_child(struct sock *sk,
|
|||
bool own_req;
|
||||
|
||||
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
|
||||
NULL, &own_req);
|
||||
NULL, &own_req, NULL);
|
||||
if (!child)
|
||||
return NULL;
|
||||
|
||||
|
|
|
|||
|
|
@ -1705,7 +1705,9 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
|
|||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req)
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk))
|
||||
{
|
||||
struct inet_request_sock *ireq;
|
||||
bool found_dup_sk = false;
|
||||
|
|
@ -1757,6 +1759,10 @@ struct sock *tcp_v4_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
|
|||
}
|
||||
sk_setup_caps(newsk, dst);
|
||||
|
||||
#if IS_ENABLED(CONFIG_IPV6)
|
||||
if (opt_child_init)
|
||||
opt_child_init(newsk, sk);
|
||||
#endif
|
||||
tcp_ca_openreq_child(newsk, dst);
|
||||
|
||||
tcp_sync_mss(newsk, dst4_mtu(dst));
|
||||
|
|
|
|||
|
|
@ -925,7 +925,7 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
|
|||
* socket is created, wait for troubles.
|
||||
*/
|
||||
child = inet_csk(sk)->icsk_af_ops->syn_recv_sock(sk, skb, req, NULL,
|
||||
req, &own_req);
|
||||
req, &own_req, NULL);
|
||||
if (!child)
|
||||
goto listen_overflow;
|
||||
|
||||
|
|
|
|||
|
|
@ -1312,11 +1312,48 @@ static void tcp_v6_restore_cb(struct sk_buff *skb)
|
|||
sizeof(struct inet6_skb_parm));
|
||||
}
|
||||
|
||||
/* Called from tcp_v4_syn_recv_sock() for v6_mapped children. */
|
||||
static void tcp_v6_mapped_child_init(struct sock *newsk, const struct sock *sk)
|
||||
{
|
||||
struct inet_sock *newinet = inet_sk(newsk);
|
||||
struct ipv6_pinfo *newnp;
|
||||
|
||||
newinet->pinet6 = newnp = tcp_inet6_sk(newsk);
|
||||
newinet->ipv6_fl_list = NULL;
|
||||
|
||||
memcpy(newnp, tcp_inet6_sk(sk), sizeof(struct ipv6_pinfo));
|
||||
|
||||
newnp->saddr = newsk->sk_v6_rcv_saddr;
|
||||
|
||||
inet_csk(newsk)->icsk_af_ops = &ipv6_mapped;
|
||||
if (sk_is_mptcp(newsk))
|
||||
mptcpv6_handle_mapped(newsk, true);
|
||||
newsk->sk_backlog_rcv = tcp_v4_do_rcv;
|
||||
#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
|
||||
tcp_sk(newsk)->af_specific = &tcp_sock_ipv6_mapped_specific;
|
||||
#endif
|
||||
|
||||
newnp->ipv6_mc_list = NULL;
|
||||
newnp->ipv6_ac_list = NULL;
|
||||
newnp->pktoptions = NULL;
|
||||
newnp->opt = NULL;
|
||||
|
||||
/* tcp_v4_syn_recv_sock() has initialized newinet->mc_{index,ttl} */
|
||||
newnp->mcast_oif = newinet->mc_index;
|
||||
newnp->mcast_hops = newinet->mc_ttl;
|
||||
|
||||
newnp->rcv_flowinfo = 0;
|
||||
if (inet6_test_bit(REPFLOW, sk))
|
||||
newnp->flow_label = 0;
|
||||
}
|
||||
|
||||
static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *skb,
|
||||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req)
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk))
|
||||
{
|
||||
const struct ipv6_pinfo *np = tcp_inet6_sk(sk);
|
||||
struct inet_request_sock *ireq;
|
||||
|
|
@ -1332,61 +1369,10 @@ static struct sock *tcp_v6_syn_recv_sock(const struct sock *sk, struct sk_buff *
|
|||
#endif
|
||||
struct flowi6 fl6;
|
||||
|
||||
if (skb->protocol == htons(ETH_P_IP)) {
|
||||
/*
|
||||
* v6 mapped
|
||||
*/
|
||||
|
||||
newsk = tcp_v4_syn_recv_sock(sk, skb, req, dst,
|
||||
req_unhash, own_req);
|
||||
|
||||
if (!newsk)
|
||||
return NULL;
|
||||
|
||||
newinet = inet_sk(newsk);
|
||||
newinet->pinet6 = tcp_inet6_sk(newsk);
|
||||
newinet->ipv6_fl_list = NULL;
|
||||
|
||||
newnp = tcp_inet6_sk(newsk);
|
||||
newtp = tcp_sk(newsk);
|
||||
|
||||
memcpy(newnp, np, sizeof(struct ipv6_pinfo));
|
||||
|
||||
newnp->saddr = newsk->sk_v6_rcv_saddr;
|
||||
|
||||
inet_csk(newsk)->icsk_af_ops = &ipv6_mapped;
|
||||
if (sk_is_mptcp(newsk))
|
||||
mptcpv6_handle_mapped(newsk, true);
|
||||
newsk->sk_backlog_rcv = tcp_v4_do_rcv;
|
||||
#if defined(CONFIG_TCP_MD5SIG) || defined(CONFIG_TCP_AO)
|
||||
newtp->af_specific = &tcp_sock_ipv6_mapped_specific;
|
||||
#endif
|
||||
|
||||
newnp->ipv6_mc_list = NULL;
|
||||
newnp->ipv6_ac_list = NULL;
|
||||
newnp->pktoptions = NULL;
|
||||
newnp->opt = NULL;
|
||||
newnp->mcast_oif = inet_iif(skb);
|
||||
newnp->mcast_hops = ip_hdr(skb)->ttl;
|
||||
newnp->rcv_flowinfo = 0;
|
||||
if (inet6_test_bit(REPFLOW, sk))
|
||||
newnp->flow_label = 0;
|
||||
|
||||
/*
|
||||
* No need to charge this sock to the relevant IPv6 refcnt debug socks count
|
||||
* here, tcp_create_openreq_child now does this for us, see the comment in
|
||||
* that function for the gory details. -acme
|
||||
*/
|
||||
|
||||
/* It is tricky place. Until this moment IPv4 tcp
|
||||
worked with IPv6 icsk.icsk_af_ops.
|
||||
Sync it now.
|
||||
*/
|
||||
tcp_sync_mss(newsk, inet_csk(newsk)->icsk_pmtu_cookie);
|
||||
|
||||
return newsk;
|
||||
}
|
||||
|
||||
if (skb->protocol == htons(ETH_P_IP))
|
||||
return tcp_v4_syn_recv_sock(sk, skb, req, dst,
|
||||
req_unhash, own_req,
|
||||
tcp_v6_mapped_child_init);
|
||||
ireq = inet_rsk(req);
|
||||
|
||||
if (sk_acceptq_is_full(sk))
|
||||
|
|
|
|||
|
|
@ -808,7 +808,9 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk,
|
|||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req)
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk))
|
||||
{
|
||||
struct mptcp_subflow_context *listener = mptcp_subflow_ctx(sk);
|
||||
struct mptcp_subflow_request_sock *subflow_req;
|
||||
|
|
@ -855,7 +857,7 @@ static struct sock *subflow_syn_recv_sock(const struct sock *sk,
|
|||
|
||||
create_child:
|
||||
child = listener->icsk_af_ops->syn_recv_sock(sk, skb, req, dst,
|
||||
req_unhash, own_req);
|
||||
req_unhash, own_req, opt_child_init);
|
||||
|
||||
if (child && *own_req) {
|
||||
struct mptcp_subflow_context *ctx = mptcp_subflow_ctx(child);
|
||||
|
|
|
|||
|
|
@ -124,7 +124,9 @@ static struct sock *smc_tcp_syn_recv_sock(const struct sock *sk,
|
|||
struct request_sock *req,
|
||||
struct dst_entry *dst,
|
||||
struct request_sock *req_unhash,
|
||||
bool *own_req)
|
||||
bool *own_req,
|
||||
void (*opt_child_init)(struct sock *newsk,
|
||||
const struct sock *sk))
|
||||
{
|
||||
struct smc_sock *smc;
|
||||
struct sock *child;
|
||||
|
|
@ -142,7 +144,7 @@ static struct sock *smc_tcp_syn_recv_sock(const struct sock *sk,
|
|||
|
||||
/* passthrough to original syn recv sock fct */
|
||||
child = smc->ori_af_ops->syn_recv_sock(sk, skb, req, dst, req_unhash,
|
||||
own_req);
|
||||
own_req, opt_child_init);
|
||||
/* child must not inherit smc or its ops */
|
||||
if (child) {
|
||||
rcu_assign_sk_user_data(child, NULL);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue