Netdev List
 help / color / mirror / Atom feed
From: netdev-bot+sashiko@kernel.org
To: omermetekaya0@gmail.com
Cc: john.fastabend@gmail.com, jakub@cloudflare.com,
	jiayuan.chen@linux.dev, edumazet@kernel.org,
	ncardwell@google.com, kuniyu@google.com, davem@davemloft.net,
	kuba@kernel.org, pabeni@redhat.com, horms@kernel.org,
	daniel@iogearbox.net, ast@kernel.org, netdev@vger.kernel.org,
	bpf@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: Re: [PATCH] bpf, sockmap: fix cork data loss after redirect
Date: Sun, 15 Nov 2026 11:54:18 +0000	[thread overview]
Message-ID: <179474365842.2199510.4530388049204286583@kernel.org> (raw)
In-Reply-To: <20261114115222.180345-1-omermetekaya0@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential
issue(s) to consider.

Critical: 0 · High: 1 (1) · Medium: 1 · Low: 0

If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).

- [High] The patch adds a memory leak in tcp_bpf_send_verdict()
  (net/ipv4/tcp_bpf.c).
- [Medium] bpf, sockmap: stale msg->cork_bytes re-corks data in
  tcp_bpf_send_verdict()

Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch
  makes it reachable in more ways.

--

bpf, sockmap: fix cork data loss after redirect

This patch changes the cork cleanup in the __SK_REDIRECT case of
tcp_bpf_send_verdict(). The corked sk_msg is now freed only when it has
been fully consumed or the redirect failed. Otherwise it is kept so the
more_data loop can send the remaining bytes.

> diff --git a/net/ipv4/tcp_bpf.c b/net/ipv4/tcp_bpf.c
> index 8cf63a9e7fed1..e6a8184aad6a4 100644
> --- a/net/ipv4/tcp_bpf.c
> +++ b/net/ipv4/tcp_bpf.c
> @@ -499,9 +499,13 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
>  				*copied -= free;
>  		}
>  		if (cork) {
> -			sk_msg_free(sk, msg);
> -			kfree(msg);
> -			msg = NULL;
> +			if (ret < 0 ||
> +			    !msg->sg.data[msg->sg.start].page_link ||
> +			    !msg->sg.data[msg->sg.start].length) {
> +				sk_msg_free(sk, msg);
> +				kfree(msg);
> +				msg = NULL;
> +			}

[Severity: Medium]
When the redirect keeps a partially sent cork alive, can the leftover
bytes get re-corked even though the program did not ask for it?

msg->cork_bytes is only written by sk_msg_init() and
bpf_msg_cork_bytes(). When the cork is satisfied, tcp_bpf_sendmsg()
clears psock->cork_bytes and sets psock->eval to __SK_NONE. It leaves
psock->cork->cork_bytes at the old threshold.
sk_psock_msg_verdict() does not reset it before running the program
either.

With this patch the sequence looks like this:

  - the program calls bpf_msg_cork_bytes(msg, N), and the message is
    copied into psock->cork with cork_bytes == N
  - later sends fill the cork, so msg->sg.size >= N, and the program
    returns a redirect with apply_bytes smaller than sg.size
  - tcp_bpf_sendmsg_redir() sends apply_bytes, psock->apply_bytes
    drops to 0, psock->eval becomes __SK_NONE, and msg is now kept
    alive
  - at more_data the program runs again on the leftover, and it may
    not call bpf_msg_cork_bytes() at all

msg->cork_bytes is still N, and the leftover sg.size is usually
smaller than N, so this check fires:

	if (msg->cork_bytes &&
	    msg->cork_bytes > msg->sg.size && !enospc) {
		psock->cork_bytes = msg->cork_bytes - msg->sg.size;

The leftover is parked in a new psock->cork, and nothing is sent until
the application writes another N - sg.size bytes. An application that
waits for a reply before sending more would stall. This is also the
path that orphans the detached cork, as noted above.

Before this patch the cork was always freed after the redirect, so the
stale cork_bytes was never looked at again in this case. The on-stack
msg from tcp_bpf_sendmsg() can probably hit the same stale value on
the existing more_data loop, so part of this may be pre-existing. The
patch does make the cork case reach it.

Should msg->cork_bytes be cleared when tcp_bpf_sendmsg() decides the
cork is fully accounted? Or should sk_psock_msg_verdict() reset it
before each program run? I am not sure which semantics SK_MSG programs
are expected to rely on across reruns.

[Severity: High]
Does this leak the corked struct sk_msg when data remains?

Earlier in the same __SK_REDIRECT case, the cork has already been
detached from the psock:

		if (psock->cork) {
			cork = true;
			psock->cork = NULL;
		}

Once this branch keeps msg alive, the local msg is the only reference to
the kzalloc'd cork. Neither sk_psock_cork_free() nor tcp_bpf_sendmsg() can
reach it any more.

The code then jumps back to more_data. On the next pass, the only exit
that kfree()s the struct is another __SK_REDIRECT that fully consumes the
message or fails. The other exits appear to leak it.

The first one is the re-cork path. Only bpf_msg_cork_bytes() and
sk_msg_init() write msg->cork_bytes, so it is never reset. After a
partial redirect the remaining sg.size is usually smaller than cork_bytes:

	if (msg->cork_bytes &&
	    msg->cork_bytes > msg->sg.size && !enospc) {
		psock->cork_bytes = msg->cork_bytes - msg->sg.size;
		if (!psock->cork) {
			psock->cork = kzalloc_obj(*psock->cork,
						  GFP_ATOMIC | __GFP_NOWARN);
			if (!psock->cork) {
				sk_msg_free(sk, msg);
				*copied = 0;
				return -ENOMEM;
			}
		}
		memcpy(psock->cork, msg, sizeof(*msg));
		return 0;
	}

psock->cork is NULL at this point, so a new cork is allocated, the old
struct is copied into it, and the old struct is orphaned. The -ENOMEM
branch also only calls sk_msg_free().

The second one is the __SK_PASS case. It never calls kfree(msg), whether
the push succeeds or fails. This case is reached when the program returns
SK_PASS, or when the msg_parser has been detached.

The third one is the __SK_DROP/default case. It calls sk_msg_free() and
returns -EACCES without calling kfree(msg).

Before this patch, the cork branch always freed msg and set it to NULL.
So a detached heap cork never went back through the loop.

The existing test_txmsg_cork_hangs case in test_sockmap.c sets
txmsg_redir = 1, txmsg_apply = 4097 and txmsg_cork = 4097. It probably
takes the re-cork path, because the test program calls
bpf_msg_cork_bytes() on every run. The test does not check for leaks.

Could the leftover be handed back to psock->cork? Alternatively, could the
code track that msg is a detached heap cork and kfree() it on every exit?

>  			ret = 0;
>  		}
>  		break;

[Severity: High]
This is a pre-existing issue and was not introduced by this patch, but
the patch adds another way to reach it.

tcp_bpf_send_verdict() drops the socket lock around
tcp_bpf_sendmsg_redir(). It then goes back to more_data without
re-checking psock->cork, psock->eval, psock->sk_redir or
psock->apply_bytes.

The __SK_REDIRECT case also assumes that a non-NULL psock->cork is the
local msg, and never checks msg == psock->cork. The re-cork path also
memcpy()s over any existing psock->cork.

Can a second sender on the same socket race with this window?

Thread A                                Thread B
tcp_bpf_sendmsg()
  msg_tx = &tmp (psock->cork == NULL)
  tcp_bpf_send_verdict()
    __SK_REDIRECT, apply_bytes < size
    apply_bytes hits 0, eval = __SK_NONE
    release_sock(sk)
    tcp_bpf_sendmsg_redir()             tcp_bpf_sendmsg()
                                          lock_sock(sk)
                                          program sets cork_bytes
                                          psock->cork = M2 (kzalloc)
                                          memcpy(psock->cork, msg, ...)
                                          release_sock(sk)
    lock_sock(sk)
    data remains, goto more_data

Thread A can then go one of two ways.

If it takes the re-cork path, memcpy(M2, msg) overwrites thread B's
message. B's data is lost, and its sg pages and sk_mem charge leak.

If it takes __SK_REDIRECT, it sets cork = true and psock->cork = NULL
without owning M2, so M2 leaks. An example is thread A sending exactly
3 * 4097 bytes with apply = cork = 4097.

In that case msg is still the on-stack tmp from tcp_bpf_sendmsg():

		if (psock->cork) {
			msg_tx = psock->cork;
		} else {
			msg_tx = &tmp;
			sk_msg_init(msg_tx);
		}

When the redirect fully consumes the message or fails, the cork cleanup
runs sk_msg_free(sk, msg) and kfree(msg) on a stack address.

With this patch, a detached heap cork msg can now also go around the loop
after the lock is dropped. So both outcomes can now also leak or
overwrite M2 when msg is a heap cork.

-- 
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20261114115222.180345-1-omermetekaya0%40gmail.com

      parent reply	other threads:[~2026-11-15 11:54 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-14 11:46 [PATCH] bpf, sockmap: fix cork data loss after redirect Ömer Mete Kaya
2026-11-14 16:34 ` Alexei Starovoitov
2026-11-15 11:54 ` 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=179474365842.2199510.4530388049204286583@kernel.org \
    --to=netdev-bot+sashiko@kernel.org \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=davem@davemloft.net \
    --cc=edumazet@kernel.org \
    --cc=horms@kernel.org \
    --cc=jakub@cloudflare.com \
    --cc=jiayuan.chen@linux.dev \
    --cc=john.fastabend@gmail.com \
    --cc=kuba@kernel.org \
    --cc=kuniyu@google.com \
    --cc=linux-kernel@vger.kernel.org \
    --cc=ncardwell@google.com \
    --cc=netdev@vger.kernel.org \
    --cc=omermetekaya0@gmail.com \
    --cc=pabeni@redhat.com \
    /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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox