From: netdev-bot+sashiko@kernel.org
To: inbal.lipshtat@mail.huji.ac.il
Cc: edumazet@google.com, ncardwell@google.com, kuniyu@google.com,
netdev@vger.kernel.org, davem@davemloft.net, kuba@kernel.org,
pabeni@redhat.com, horms@kernel.org,
linux-kernel@vger.kernel.org, amit.klein@mail.huji.ac.il,
tamir.shahar1@mail.huji.ac.il
Subject: Re: [PATCH] tcp: validate old ACKs before fast path data processing
Date: Thu, 10 Sep 2026 06:35:10 +0000 [thread overview]
Message-ID: <178902211070.219967.15947434635255374738@kernel.org> (raw)
In-Reply-To: <20260906123151.1391349-1-inbal.lipshtat@mail.huji.ac.il>
Thank you for your contribution! Sashiko AI review found 4 potential
issue(s) to consider:
Critical: 0 · High: 0 · Medium: 1 · Low: 3
- [Medium] Incomplete RFC5961 enforcement: the new ACK-acceptability
check is only added to the data-carrying fast-path branch of…
- [Low] The patch fixes a remotely triggerable, security-relevant defect
(the ESTABLISHED fast path accepting payload from a segment whose…
- [Low] No in-tree test covers the newly added enforcement.
- [Low] The rationale comment for the tp->bytes_acked term is lost.
--
tcp: validate old ACKs before fast path data processing
This adds an RFC5961 5.2 SEG.ACK acceptability test to the ESTABLISHED
fast path in tcp_rcv_established(), before the segment payload is queued,
sending a challenge ACK and dropping the segment when the ACK is too old.
The existing test in tcp_ack() is factored into a new tcp_ack_too_old()
helper shared by both call sites.
Link: https://www.cve.org/CVERecord?id=CVE-2023-52881
A few questions below.
[Severity: Low]
The changelog names both CVE-2023-52881 and commit 3d501dd326fb1c7 ("tcp:
do not accept ACK of bytes we never sent"), but the tag block has no
Fixes: line and no stable routing indication.
Should this carry a Fixes: tag so stable selection and CVE correlation
have something to key on? The candidates implied by the changelog are the
original RFC5961 5.2 mitigation (which only touched tcp_ack()) and/or
3d501dd326fb1c7.
[Severity: Low]
The changelog carries a working reproducer, but nothing in-tree exercises
the new hunk. The existing script
tools/testing/selftests/net/packetdrill/tcp_rfc5961_ack-out-of-window.pkt
injects only zero-length probes for both edges:
+0 < . 1:1(0) ack 2 win 1000
...
+0 < . 1:1(0) ack 1000 win 1000
Those take the len == tcp_header_len pure-ACK branch (or the slow path),
so that test passes identically with and without this patch.
Could the packetdrill script from the changelog (500-byte priming segment
followed by the 1000-byte segment with ack 2794967397) be added next to
the existing rfc5961 case, so a later refactor of the fast path cannot
silently drop the check?
> diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c
> index 0f60a1dbf9274..6ebf72cd9a755 100644
> --- a/net/ipv4/tcp_input.c
> +++ b/net/ipv4/tcp_input.c
> @@ -4272,6 +4272,17 @@ static void tcp_rack_update_reo_wnd(struct sock *sk, struct rate_sample *rs)
> }
> }
>
> +/* Validates that the ACK is older than the acceptable historical ACK window*/
> +static inline bool tcp_ack_too_old(const struct tcp_sock *tp, u32 ack,
> + u32 snd_una)
> +{
> + u32 max_window;
> +
> + max_window = min_t(u64, tp->max_window, tp->bytes_acked);
> +
> + return before(ack, snd_una - max_window);
> +}
> +
[Severity: Low]
The helper comment describes a plain MAX.SND.WND window, but the code also
bounds by tp->bytes_acked, which is strictly tighter than RFC5961. The
comment explaining that term is deleted in the hunk below and not
reproduced here:
- /* do not accept ACK for bytes we never sent. */
- max_window = min_t(u64, tp->max_window, tp->bytes_acked);
Since the helper now has two call sites, could the "do not accept ACK for
bytes we never sent" rationale be moved into tcp_ack_too_old() alongside
the RFC5961 note?
> /* This routine deals with incoming acks, but not outgoing ones. */
> static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
> {
> @@ -4303,12 +4314,8 @@ static int tcp_ack(struct sock *sk, const struct sk_buff *skb, int flag)
> * then we can probably ignore it.
> */
> if (before(ack, prior_snd_una)) {
> - u32 max_window;
> -
> - /* do not accept ACK for bytes we never sent. */
> - max_window = min_t(u64, tp->max_window, tp->bytes_acked);
> /* RFC 5961 5.2 [Blind Data Injection Attack].[Mitigation] */
> - if (before(ack, prior_snd_una - max_window)) {
> + if (tcp_ack_too_old(tp, ack, prior_snd_una)) {
> if (!(flag & FLAG_NO_CHALLENGE_ACK))
> tcp_send_challenge_ack(sk, false);
> return -SKB_DROP_REASON_TCP_TOO_OLD_ACK;
[ ... ]
> @@ -6614,6 +6621,15 @@ void tcp_rcv_established(struct sock *sk, struct sk_buff *skb)
> if ((int)skb->truesize > sk->sk_forward_alloc)
> goto step5;
>
> + if (unlikely(before(TCP_SKB_CB(skb)->ack_seq, tp->snd_una))) {
> + if (tcp_ack_too_old(tp, TCP_SKB_CB(skb)->ack_seq,
> + tp->snd_una)) {
> + tcp_send_challenge_ack(sk, false);
> + reason = SKB_DROP_REASON_TCP_TOO_OLD_ACK;
> + goto discard;
> + }
> + }
> +
[Severity: Medium]
The new check sits inside the else branch of if (len <= tcp_header_len),
so it only covers data-bearing segments. Was the sibling pure-ACK branch
intentionally left out?
That branch still does this, in this order, in tcp_rcv_established():
if (len <= tcp_header_len) {
if (len == tcp_header_len) {
if (tcp_header_len ==
(sizeof(struct tcphdr) + TCPOLEN_TSTAMP_ALIGNED) &&
tp->rcv_nxt == tp->rcv_wup)
flag |= __tcp_replace_ts_recent(tp, delta);
tcp_ecn_received_counters(sk, skb, 0);
...
tcp_ack(sk, skb, flag);
__kfree_skb(skb);
tcp_data_snd_check(sk);
tp->rcv_rtt_last_tsecr = tp->rx_opt.rcv_tsecr;
return;
__tcp_replace_ts_recent() stores unconditionally:
net/ipv4/tcp_input.c:__tcp_replace_ts_recent() {
tcp_store_ts_recent(tp);
return tstamp_delta > 0 ? FLAG_TS_PROGRESS : 0;
}
So for a bare ACK matching tp->pred_flags with seq == tp->rcv_nxt,
!after(ack_seq, tp->snd_nxt), and before(ack_seq, tp->snd_una -
min(max_window, bytes_acked)), tp->rx_opt.ts_recent is advanced to the
segment's TSval, the ECN receive counters are bumped, and
tp->rcv_rtt_last_tsecr is taken from that segment.
tcp_ack() then reaches the same tcp_ack_too_old() test, sends the
challenge ACK and returns -SKB_DROP_REASON_TCP_TOO_OLD_ACK, but the
return value is still discarded here and nothing is rolled back or
accounted as a drop.
This is the same condition the changelog describes:
"In the fast path, tcp_ack() is also called,
but only after the data from the segment is processed.
Furthermore, the return value from tcp_ack() is not checked."
The slow path does not have this ordering problem, since tcp_ack() runs
the too-old test before the FLAG_UPDATE_TS_RECENT handling, and step5
checks (int)reason < 0.
Would hoisting the new check up to just after the header prediction test,
before if (len <= tcp_header_len), cover both branches with a single call
site instead of leaving the pure-ACK branch as is?
> /* Predicted packet is in window by definition.
> * seq == rcv_nxt and rcv_wup <= rcv_nxt.
> * Hence, check seq<=rcv_wup reduces to:
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260906123151.1391349-1-inbal.lipshtat%40mail.huji.ac.il
prev parent reply other threads:[~2026-09-10 6:35 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-06 12:31 [PATCH] tcp: validate old ACKs before fast path data processing Inbal Schussheim
2026-09-06 15:21 ` Eric Dumazet
2026-09-10 6:35 ` netdev-bot+sashiko [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=178902211070.219967.15947434635255374738@kernel.org \
--to=netdev-bot+sashiko@kernel.org \
--cc=amit.klein@mail.huji.ac.il \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=horms@kernel.org \
--cc=inbal.lipshtat@mail.huji.ac.il \
--cc=kuba@kernel.org \
--cc=kuniyu@google.com \
--cc=linux-kernel@vger.kernel.org \
--cc=ncardwell@google.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=tamir.shahar1@mail.huji.ac.il \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.