tcp: challenge ACK for non-exact RST in SYN-RECEIVED

The SYN-RECEIVED request-socket path in tcp_check_req() accepts an
in-window RST without requiring SEG.SEQ to exactly match RCV.NXT.  A
non-exact RST therefore removes the request instead of eliciting a
challenge ACK.

RFC 9293 section 3.10.7.4 applies the RFC 5961 reset check in
SYN-RECEIVED: an exact RST resets the connection, while a non-exact
in-window RST must trigger a challenge ACK and be dropped.

Apply that check before the ACK-field validation, following the RFC
sequence-number, RST, then ACK processing order.  Factor the per-netns
challenge ACK quota out of tcp_send_challenge_ack() so request sockets
can share it.  Use the request socket's send_ack() callback and its own
out-of-window ACK timestamp to send and rate-limit the response.

Reported-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reported-by: Yizhou Zhao <zhaoyz24@mails.tsinghua.edu.cn>
Reported-by: Ao Wang <wangao@seu.edu.cn>
Reported-by: Xuewei Feng <fengxw06@126.com>
Reported-by: Qi Li <qli01@tsinghua.edu.cn>
Reported-by: Ke Xu <xuke@tsinghua.edu.cn>
Fixes: 282f23c6ee ("tcp: implement RFC 5961 3.2")
Cc: stable@vger.kernel.org
Signed-off-by: Yuxiang Yang <yangyx22@mails.tsinghua.edu.cn>
Reviewed-by: Eric Dumazet <edumazet@google.com>
Link: https://patch.msgid.link/20260717081443.809393-2-yangyx22@mails.tsinghua.edu.cn
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Yuxiang Yang 2026-07-17 08:14:42 +00:00 committed by Jakub Kicinski
parent 9dfd800b37
commit a28c4fcbf7
3 changed files with 56 additions and 14 deletions

View File

@ -1974,6 +1974,8 @@ static inline void tcp_fast_path_check(struct sock *sk)
bool tcp_oow_rate_limited(struct net *net, const struct sk_buff *skb,
int mib_idx, u32 *last_oow_ack_time);
void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb,
struct request_sock *req);
static inline void tcp_mib_init(struct net *net)
{

View File

@ -4038,24 +4038,17 @@ static void tcp_send_ack_reflect_ect(struct sock *sk, bool accecn_reflector)
__tcp_send_ack(sk, tp->rcv_nxt, flags);
}
/* RFC 5961 7 [ACK Throttling] */
static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
/* Consume one slot from the per-netns RFC 5961 challenge ACK quota.
* Returns true if a challenge ACK may be sent.
*/
static bool tcp_challenge_ack_allowed(struct net *net)
{
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
u32 count, now, ack_limit;
/* First check our per-socket dupack rate limit. */
if (__tcp_oow_rate_limited(net,
LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
&tp->last_oow_ack_time))
return;
ack_limit = READ_ONCE(net->ipv4.sysctl_tcp_challenge_ack_limit);
if (ack_limit == INT_MAX)
goto send_ack;
return true;
/* Then check host-wide RFC 5961 rate limit. */
now = jiffies / HZ;
if (now != READ_ONCE(net->ipv4.tcp_challenge_timestamp)) {
u32 half = (ack_limit + 1) >> 1;
@ -4067,12 +4060,49 @@ static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
count = READ_ONCE(net->ipv4.tcp_challenge_count);
if (count > 0) {
WRITE_ONCE(net->ipv4.tcp_challenge_count, count - 1);
send_ack:
return true;
}
return false;
}
/* RFC 5961 7 [ACK Throttling] */
static void tcp_send_challenge_ack(struct sock *sk, bool accecn_reflector)
{
struct tcp_sock *tp = tcp_sk(sk);
struct net *net = sock_net(sk);
/* First check our per-socket dupack rate limit. */
if (__tcp_oow_rate_limited(net,
LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
&tp->last_oow_ack_time))
return;
/* Then check the per-netns RFC 5961 rate limit. */
if (tcp_challenge_ack_allowed(net)) {
NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK);
tcp_send_ack_reflect_ect(sk, accecn_reflector);
}
}
/* Send a challenge ACK from a SYN-RECEIVED request socket. Uses
* __tcp_oow_rate_limited() directly so that an RST carrying payload
* cannot bypass the per-request rate limit.
*/
void tcp_reqsk_send_challenge_ack(struct sock *sk, struct sk_buff *skb,
struct request_sock *req)
{
struct net *net = sock_net(sk);
if (__tcp_oow_rate_limited(net, LINUX_MIB_TCPACKSKIPPEDCHALLENGE,
&tcp_rsk(req)->last_oow_ack_time))
return;
if (tcp_challenge_ack_allowed(net)) {
NET_INC_STATS(net, LINUX_MIB_TCPCHALLENGEACK);
req->rsk_ops->send_ack(sk, skb, req);
}
}
static void tcp_store_ts_recent(struct tcp_sock *tp)
{
tp->rx_opt.ts_recent = tp->rx_opt.rcv_tsval;

View File

@ -833,7 +833,7 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
* elsewhere and is checked directly against the child socket rather
* than req because user data may have been sent out.
*/
if ((flg & TCP_FLAG_ACK) && !fastopen &&
if ((flg & TCP_FLAG_ACK) && !(flg & TCP_FLAG_RST) && !fastopen &&
(TCP_SKB_CB(skb)->ack_seq !=
tcp_rsk(req)->snt_isn + 1))
return sk;
@ -872,6 +872,16 @@ struct sock *tcp_check_req(struct sock *sk, struct sk_buff *skb,
flg &= ~TCP_FLAG_SYN;
}
/* RFC 5961 section 3.2, as clarified by RFC 9293 section
* 3.10.7.4, requires a challenge ACK for a non-exact
* in-window RST in SYN-RECEIVED.
*/
if ((flg & TCP_FLAG_RST) &&
TCP_SKB_CB(skb)->seq != tcp_rsk(req)->rcv_nxt) {
tcp_reqsk_send_challenge_ack(sk, skb, req);
return NULL;
}
/* RFC793: "second check the RST bit" and
* "fourth, check the SYN bit"
*/