DCCP sockets in DCCP_REQUESTING state do not check the sequence number or acknowledgment number for incoming Reset, CloseReq, and Close packets.
As a result, an attacker can send a spoofed Reset packet while the client is in the requesting state. The client will accept the packet without any verification before receiving the reply from server and immediately close the connection, causing a denial of service (DoS) attack. The vulnerability makes the attacker able to drop the pending connection for a specific 5-tuple. Moreover, an off-path attacker with modestly higher outbound bandwidth can continually inject forged control packets to the victim client and prevent connection establishment to a given destination port on a server, causing a port-level DoS.
This patch moves the processing of Reset, Close, and CloseReq packets into dccp_rcv_request_sent_state_process() and validates the ack number before accepting them.
This patch should be applied to stable versions *only* before Linux 6.16, since DCCP implementation is removed in Linux 6.16.
Affected versions include: - 3.1-3.19 - 4.0-4.20 - 5.0-5.19 - 6.0-6.15
We tested it on Ubuntu 24.04 LTS (Linux 6.8) and it worked as expected.
Fixes: c0c2015056d7b ("dccp: Clean up slow-path input processing") Signed-off-by: Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn --- net/dccp/input.c | 54 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 22 deletions(-)
diff --git a/net/dccp/input.c b/net/dccp/input.c index 2cbb757a8..0b1ffb044 100644 --- a/net/dccp/input.c +++ b/net/dccp/input.c @@ -397,21 +397,22 @@ static int dccp_rcv_request_sent_state_process(struct sock *sk, * / * Response processing continues in Step 10; Reset * processing continues in Step 9 * / */ + struct dccp_sock *dp = dccp_sk(sk); + + if (!between48(DCCP_SKB_CB(skb)->dccpd_ack_seq, + dp->dccps_awl, dp->dccps_awh)) { + dccp_pr_debug("invalid ackno: S.AWL=%llu, " + "P.ackno=%llu, S.AWH=%llu\n", + (unsigned long long)dp->dccps_awl, + (unsigned long long)DCCP_SKB_CB(skb)->dccpd_ack_seq, + (unsigned long long)dp->dccps_awh); + goto out_invalid_packet; + } + if (dh->dccph_type == DCCP_PKT_RESPONSE) { const struct inet_connection_sock *icsk = inet_csk(sk); - struct dccp_sock *dp = dccp_sk(sk); - long tstamp = dccp_timestamp(); - - if (!between48(DCCP_SKB_CB(skb)->dccpd_ack_seq, - dp->dccps_awl, dp->dccps_awh)) { - dccp_pr_debug("invalid ackno: S.AWL=%llu, " - "P.ackno=%llu, S.AWH=%llu\n", - (unsigned long long)dp->dccps_awl, - (unsigned long long)DCCP_SKB_CB(skb)->dccpd_ack_seq, - (unsigned long long)dp->dccps_awh); - goto out_invalid_packet; - }
+ long tstamp = dccp_timestamp(); /* * If option processing (Step 8) failed, return 1 here so that * dccp_v4_do_rcv() sends a Reset. The Reset code depends on @@ -496,6 +497,13 @@ static int dccp_rcv_request_sent_state_process(struct sock *sk, } dccp_send_ack(sk); return -1; + } else if (dh->dccph_type == DCCP_PKT_RESET) { + dccp_rcv_reset(sk, skb); + return 0; + } else if (dh->dccph_type == DCCP_PKT_CLOSEREQ) { + return dccp_rcv_closereq(sk, skb); + } else if (dh->dccph_type == DCCP_PKT_CLOSE) { + return dccp_rcv_close(sk, skb); }
out_invalid_packet: @@ -658,17 +666,19 @@ int dccp_rcv_state_process(struct sock *sk, struct sk_buff *skb, * Set TIMEWAIT timer * Drop packet and return */ - if (dh->dccph_type == DCCP_PKT_RESET) { - dccp_rcv_reset(sk, skb); - return 0; - } else if (dh->dccph_type == DCCP_PKT_CLOSEREQ) { /* Step 13 */ - if (dccp_rcv_closereq(sk, skb)) - return 0; - goto discard; - } else if (dh->dccph_type == DCCP_PKT_CLOSE) { /* Step 14 */ - if (dccp_rcv_close(sk, skb)) + if (sk->sk_state != DCCP_REQUESTING) { + if (dh->dccph_type == DCCP_PKT_RESET) { + dccp_rcv_reset(sk, skb); return 0; - goto discard; + } else if (dh->dccph_type == DCCP_PKT_CLOSEREQ) { /* Step 13 */ + if (dccp_rcv_closereq(sk, skb)) + return 0; + goto discard; + } else if (dh->dccph_type == DCCP_PKT_CLOSE) { /* Step 14 */ + if (dccp_rcv_close(sk, skb)) + return 0; + goto discard; + } }
switch (sk->sk_state) {
On Mon, Nov 03, 2025 at 09:23:07AM +0800, Yizhou Zhao wrote:
DCCP sockets in DCCP_REQUESTING state do not check the sequence number or acknowledgment number for incoming Reset, CloseReq, and Close packets.
As a result, an attacker can send a spoofed Reset packet while the client is in the requesting state. The client will accept the packet without any verification before receiving the reply from server and immediately close the connection, causing a denial of service (DoS) attack. The vulnerability makes the attacker able to drop the pending connection for a specific 5-tuple. Moreover, an off-path attacker with modestly higher outbound bandwidth can continually inject forged control packets to the victim client and prevent connection establishment to a given destination port on a server, causing a port-level DoS.
This patch moves the processing of Reset, Close, and CloseReq packets into dccp_rcv_request_sent_state_process() and validates the ack number before accepting them.
This patch should be applied to stable versions *only* before Linux 6.16, since DCCP implementation is removed in Linux 6.16.
Affected versions include:
- 3.1-3.19
- 4.0-4.20
- 5.0-5.19
- 6.0-6.15
We tested it on Ubuntu 24.04 LTS (Linux 6.8) and it worked as expected.
Fixes: c0c2015056d7b ("dccp: Clean up slow-path input processing") Signed-off-by: Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn
net/dccp/input.c | 54 ++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 22 deletions(-)
<formletter>
This is not the correct way to submit patches for inclusion in the stable kernel tree. Please read: https://www.kernel.org/doc/html/latest/process/stable-kernel-rules.html for how to do this properly.
</formletter>
Dear Greg,
Thank you for the guidance.
I'm sorry that I haven't explicitly stated that this should be a stable-only fix. DCCP was removed upstream in v6.16, so the issue cannot be fixed in mainline. I missed including that context in the original submission.
Jakub confirmed that this fix may proceed as a stable-only patch for branches which still contain DCCP. [1]
Do you prefer that I submit a v2 explicitly marked as a stable-only fix? Just want to ensure I follow the expected process.
BTW: I could not find specific guidance in the stable-kernel rules on how a stable-only patch (for upstream-removed code) should be formatted. Could you advise a sample expected structure?
Thank you for your time and review.
Your Sincerely, Yizhou Zhao
[1] https://lore.kernel.org/netdev/20251103154439.58c3664c@kernel.org/
On Tue, Nov 04, 2025 at 05:58:08PM +0800, Yizhou Zhao wrote:
Dear Greg,
Thank you for the guidance.
I'm sorry, I don't have any context here. Remember, some of us get 1000+ emails a day and don't remember what we wrote in response a few hours ago :)
I'm sorry that I haven't explicitly stated that this should be a stable-only fix. DCCP was removed upstream in v6.16, so the issue cannot be fixed in mainline. I missed including that context in the original submission.
Why not remove dccp in older kernels as well?
Jakub confirmed that this fix may proceed as a stable-only patch for branches which still contain DCCP. [1]
Do you prefer that I submit a v2 explicitly marked as a stable-only fix? Just want to ensure I follow the expected process.
Yes, it needs to be blindingly obvious that a patch is "stable only" including the reason why in great detail.
But really, why not just do what is in mainline and remove dccp as obviously no one is actually using it anymore?
thanks,
greg k-h
linux-stable-mirror@lists.linaro.org