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 verification and immediately close the connection, causing a denial of service (DoS) attack.
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 fix should apply to stable versions *only* in Linux 5.x and 6.x. Note that DCCP was removed in Linux 6.16, so this patch is only relevant for older versions. We tested it on Ubuntu 24.04 LTS (Linux 6.8) and it worked as expected.
Signed-off-by: Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn Cc: stable@vger.kernel.org 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) {
Hi Yizhou,
On Mon, Sep 29, 2025 at 10:34 AM Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn 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 verification and immediately close the connection, causing a denial of service (DoS) attack.
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 fix should apply to stable versions *only* in Linux 5.x and 6.x. Note that DCCP was removed in Linux 6.16, so this patch is only relevant for older versions. We tested it on Ubuntu 24.04 LTS (Linux 6.8) and it worked as expected.
Signed-off-by: Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn Cc: stable@vger.kernel.org Signed-off-by: Yizhou Zhao zhaoyz24@mails.tsinghua.edu.cn
I believe DCCP has been removed by the following series: commit 8bb3212be4b45f7a6089e45dda7dfe9abcee4d65 Merge: ba5560e53dac 235bd9d21fcd Author: Jakub Kicinski kuba@kernel.org Date: Fri Apr 11 18:58:13 2025 -0700
Merge branch 'net-retire-dccp-socket'
Kuniyuki Iwashima says:
==================== net: Retire DCCP socket.
So I assume you probably target an older kernel? If you want to fix an old kernel bug, please specify its exact kernel version and then add the "Fixes: " tag to point out what commit causes the issue you're faced with.
Thanks, Jason
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) {
-- 2.34.1
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
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 verification and immediately close the connection, causing a denial of service (DoS) attack.
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.
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 Cc: stable@vger.kernel.org --- 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) {
linux-stable-mirror@lists.linaro.org