Skip to contentSkip to conversation

bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

PATCH Superseded Chengfeng Ye 7 participants 19 messages Jul 19 – Jul 25

Subsystems (6)
lore
Share & tools
lore
#0 lore raw

PATCH 1/1 bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork.

This comparison is unsafe when two threads send on the same socket:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is therefore mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0

msg_tx can only name the stack-local tmp or the shared cork. Test for tmp directly so a changed psock->cork cannot turn a shared message into an apparent local one.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")
---
 net/ipv4/tcp_bpf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
net/ipv4/tcp_bpf.c +1 -1
index 8e905b50dead..a30475afb6f8 100644
@@ -604,7 +604,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
 		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
+			if (msg_tx == &tmp)
 				sk_msg_free(sk, msg_tx);
 			goto out_err;
 		}
Signature · 1 line
2.43.0
#1 lore raw
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

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

Pre-existing issues:
- [High] Memory leak of stack-allocated tmp scatterlist pages when aborting via goto out_err.
- [High] Lost error code from sk_msg_memcopy_from_iter causes sendmsg to incorrectly return 0 instead of propagating -EFAULT.
- [High] Silent data loss on __SK_REDIRECT failure for corked messages due to masked error.
- [Medium] Overlapping memcpy() when corking an already-corked message.
--

--- Patch [1]: [PATCH] bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg() --- commit ef0d38759a207a519b5d12273a55446155a7d5f4
Author: Chengfeng Ye <[email protected]>

bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. This commit fixes a use-after-free where a stale cork could be mistaken for the local temporary message and freed again. It tests for tmp directly instead of comparing with psock->cork.

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 593

Quoted 11 lines
net/ipv4/tcp_bpf.c +1 -1
index cc0bd73f36b6d..b9f0366333653 100644
@@ -589,7 +589,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
 		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
+			if (msg_tx == &tmp)
 				sk_msg_free(sk, msg_tx);

[Severity: High]
This isn't a bug introduced by this patch, but can this leak page references or memory for the stack-allocated tmp?

If kzalloc_obj() for psock->cork fails in tcp_bpf_send_verdict(), returning -ENOMEM but leaving psock->cork_bytes > 0, and userspace retries sendmsg(), msg_tx defaults to the stack-allocated &tmp.

Data is copied into tmp, allocating pages. The code then hits:
if (psock->cork_bytes && !enospc)

    goto out_err;

And the out_err block returns without freeing tmp. Unprivileged userspace could repeatedly trigger this state after a single induced -ENOMEM, leaking pages on subsequent small sendmsg() calls.

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 609

Quoted 1 line
net/ipv4/tcp_bpf.c +0 -0
 			goto out_err;

[Severity: High]
This is a pre-existing issue, but does this goto out_err drop the error code from sk_msg_memcopy_from_iter()?

Earlier in tcp_bpf_sendmsg():
ret = sk_msg_memcopy_from_iter(sk, &msg->msg_iter, msg_tx, copy);

if (ret < 0) {
    sk_msg_trim(sk, msg_tx, osize);
    goto out_err;
}

The out_err block returns err (which is initialized to 0), rather than ret. If a local user passes a bad pointer, it will get a 0 return value instead of -EFAULT, which usually causes userspace blocking socket writes to spin endlessly.

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 595

Quoted 1 line
net/ipv4/tcp_bpf.c +0 -0
 		}

[Severity: Medium]
This isn't a bug introduced by this patch, but does tcp_bpf_send_verdict() trigger an overlapping memory copy?

If a BPF program evaluates an already-corked message and assigns a new cork_bytes threshold that exceeds the current size, tcp_bpf_send_verdict() executes:

memcpy(psock->cork, msg, sizeof(*msg));

If msg is identical to psock->cork, this results in an overlapping memory copy onto itself. This can violate the C standard and trigger UBSAN panics.

[Severity: High]
This is a pre-existing issue, but does tcp_bpf_send_verdict() silently drop data on __SK_REDIRECT failure for corked messages?

If a redirected message is corked and tcp_bpf_sendmsg_redir() fails, the code frees the message:

if (cork) {
    sk_msg_free(sk, msg);
    kfree(msg);
    msg = NULL;
    ret = 0;
}

This intentionally skips subtracting the freed bytes from *copied and overwrites ret = 0. This causes tcp_bpf_sendmsg() to assume success, continue processing the user's buffer, and return a positive byte count to userspace despite dropping the corked data.

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
reviewer Reviewed-by on v1 1/1
#2 lore raw
In reply to Chengfeng Ye
Quoted 37 lines · Chengfeng Ye

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork.

This comparison is unsafe when two threads send on the same socket:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is therefore mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0
Quoted 8 lines

msg_tx can only name the stack-local tmp or the shared cork. Test for tmp directly so a changed psock->cork cannot turn a shared message into an apparent local one.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")

---

Hi Chengfeng,

The patch looks good:

There is one caveat: Normally we ignore pre-existing issues Sashiko finds while reviewing the patch that are unrelated to the change itself. For this function, however, I think we should make an exception because it has multiple glaring issues we can fix more cleanly if we do it all at once. E.g., tmp never gets cleaned up even if there are allocations hanging off of it.

Would you be willing to expand the patch that addresses the Sashiko comments, even if unrelated to your fix? That would save us the time to review the inevitable followups and provide more coherent refactoring.

Quoted 16 lines
 net/ipv4/tcp_bpf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
net/ipv4/tcp_bpf.c +1 -1
index 8e905b50dead..a30475afb6f8 100644
@@ -604,7 +604,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
 		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
+			if (msg_tx == &tmp)
 				sk_msg_free(sk, msg_tx);
 			goto out_err;
 		}
#3 lore raw

Dear Emil,

Thanks for your effort in reviewing the patch! I have sent a new v2 patch to address the Sashiko comments on that function. I will wait until later to see if any more related issues are flagged by Sashiko.

#4 lore raw

PATCH v2 1/1 bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Chengfeng Ye

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path used the current value of psock->cork to decide whether msg_tx named the stack-local temporary message.

Two senders can therefore interleave as follows:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0

The same unclear ownership also leaves several error paths inconsistent. A failed cork allocation leaves cork_bytes armed, a subsequent temporary message can escape without releasing its pages, iterator errors are returned as success, and a failed redirect of a corked message discards data while reporting it as sent. Re-evaluating an existing cork can also copy the object onto itself.

Make temporary ownership explicit by freeing only &tmp at the common exit. Reset cork_bytes when allocating the persistent cork fails, propagate iterator errors, preserve redirect failures and clear the copied count when corked data is discarded. Skip the copy when the message is already the persistent cork.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")

---

Changes in v2:
- Address Sashiko review findings for stale cork state, iterator-error propagation, cork self-copy, and masked redirect failures.
- Keep the original stale-cork use-after-free fix.

Sashiko: https://sashiko.dev/#/patchset/20260719161630.2901208-1-nicoyip.dev%40gmail.com

 net/ipv4/tcp_bpf.c | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)
net/ipv4/tcp_bpf.c +10 -7
index 8e905b50dead..0594ee4013b0 100644
@@ -443,12 +443,14 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->cork = kzalloc_obj(*psock->cork,
 						  GFP_ATOMIC | __GFP_NOWARN);
 			if (!psock->cork) {
+				psock->cork_bytes = 0;
 				sk_msg_free(sk, msg);
 				*copied = 0;
 				return -ENOMEM;
 			}
 		}
-		memcpy(psock->cork, msg, sizeof(*msg));
+		if (psock->cork != msg)
+			memcpy(psock->cork, msg, sizeof(*msg));
 		return 0;
 	}
 
@@ -495,14 +497,15 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		if (unlikely(ret < 0)) {
 			int free = sk_msg_free(sk, msg);
 
-			if (!cork)
+			if (cork)
+				*copied = 0;
+			else
 				*copied -= free;
 		}
 		if (cork) {
 			sk_msg_free(sk, msg);
 			kfree(msg);
 			msg = NULL;
-			ret = 0;
 		}
 		break;
 	case __SK_DROP:
@@ -579,6 +582,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 					       copy);
 		if (ret < 0) {
 			sk_msg_trim(sk, msg_tx, osize);
+			err = ret;
 			goto out_err;
 		}
 
@@ -603,13 +607,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
-		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
-				sk_msg_free(sk, msg_tx);
+		if (err)
 			goto out_err;
-		}
 	}
 out_err:
+	if (msg_tx == &tmp)
+		sk_msg_free(sk, msg_tx);
 	if (err < 0)
 		err = sk_stream_error(sk, msg->msg_flags, err);
 	release_sock(sk);
Signature · 1 line
2.43.0
#5 lore raw

Re: [PATCH v2] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider: - [Critical] Unconditionally freeing the stack-allocated temporary message (`&tmp`) on the common exit path…

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Unconditionally freeing the stack-allocated temporary message (&tmp) on the common exit path causes a Use-After-Free of corked pages.
- [High] Setting *copied = 0 on failed redirects of a corked message wipes out the success count of independent messages sent earlier in the same syscall. --

--- Patch [1]: [PATCH v2] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg() --- commit e8fa0f17c7ce1143b0d6775fd8a3f303b4e73ff2
Author: Chengfeng Ye <[email protected]>

bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. This commit makes temporary ownership explicit by freeing only &tmp at the common exit, resets cork_bytes when allocating the persistent cork fails, propagates iterator errors, and clears the copied count when corked data is discarded.

Quoted 4 lines
net/ipv4/tcp_bpf.c +0 -0
index cc0bd73f36b6d..ce06bc1cff2b6 100644

[ ... ]

net/ipv4/tcp_bpf.c static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, · line 488

Quoted 9 lines
net/ipv4/tcp_bpf.c +3 -1
@@ -480,14 +482,15 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		if (unlikely(ret < 0)) {
 			int free = sk_msg_free(sk, msg);
 
-			if (!cork)
+			if (cork)
+				*copied = 0;
+			else
 				*copied -= free;

[Severity: High]
Does setting *copied to 0 here erase the accumulated byte count of any independent messages that were successfully transmitted earlier in the same sendmsg syscall?

In tcp_bpf_sendmsg(), the copied variable tracks the total bytes successfully sent across multiple iterations of the while loop. Because copied is zeroed when a corked message fails redirect, tcp_bpf_sendmsg() returns the error code instead of the number of bytes successfully sent prior to the failure.

Will this cause userspace to assume the entire buffer failed to send and retry sending the first parts, leading to silent data duplication on the network?

Quoted 3 lines
net/ipv4/tcp_bpf.c +0 -0
 		}
 		if (cork) {
 			sk_msg_free(sk, msg);

[ ... ]

Quoted 4 lines
net/ipv4/tcp_bpf.c +0 -0
@@ -588,13 +592,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 600

Quoted 10 lines
net/ipv4/tcp_bpf.c +3 -4
-		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
-				sk_msg_free(sk, msg_tx);
+		if (err)
 			goto out_err;
-		}
 	}
 out_err:
+	if (msg_tx == &tmp)
+		sk_msg_free(sk, msg_tx);

[Severity: Critical]
Does unconditionally freeing the stack-allocated temporary message (&tmp) here cause a use-after-free of corked pages?

When a new cork is initiated on the final iteration of tcp_bpf_sendmsg(), msg_tx points to the local stack variable tmp. tcp_bpf_send_verdict() decides to cork it, allocates psock->cork, and performs a shallow copy of tmp via memcpy(). It returns 0 without clearing tmp.sg.size or tmp.sg.data.

When the while loop finishes, execution falls through to out_err. Because msg_tx is still &tmp and its state was never cleared after the shallow copy, sk_msg_free() iterates over the scatterlist and frees all elements.

Doesn't this leave psock->cork holding dangling pointers to these freed pages, meaning a subsequent send or socket close will trigger a use-after-free?

Quoted 2 lines
net/ipv4/tcp_bpf.c +0 -0
 	if (err < 0)
 		err = sk_stream_error(sk, msg->msg_flags, err);
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#6 lore raw

PATCH v3 1/1 bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Chengfeng Ye

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path used the current value of psock->cork to decide whether msg_tx named the stack-local temporary message.

Two senders can therefore interleave as follows:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0

Make temporary ownership explicit by freeing only the stack-local message at the common exit. When a verdict moves that message into the persistent cork, use sk_msg_xfer_full() to clear the source and record the persistent cork as the current owner.

The related failure paths must also distinguish bytes in the current message from bytes sent by earlier loop iterations. Track the former in msg_copied. If cork allocation or redirect fails, subtract only the unsent bytes belonging to that message, preserving the syscall-wide count for data already sent. Also reset cork_bytes after allocation failure, skip a self-transfer of an existing cork, and propagate iterator errors.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")

---

Changes in v3:
- Transfer the temporary message into the persistent cork with sk_msg_xfer_full(), leaving the source empty at the common exit.
- Track the current message contribution separately so cork failures retain the count of bytes sent by earlier iterations.
- Address the two ownership and return-value issues reported by Sashiko.

Changes in v2:
- Address Sashiko review findings for stale cork state, iterator-error propagation, cork self-copy, and masked redirect failures.
- Keep the original stale-cork use-after-free fix.

Sashiko: https://sashiko.dev/#/patchset/20260719161630.2901208-1-nicoyip.dev%40gmail.com

 net/ipv4/tcp_bpf.c | 41 +++++++++++++++++++++++++++++------------
 1 file changed, 29 insertions(+), 12 deletions(-)
net/ipv4/tcp_bpf.c +29 -12
index 8e905b50dead..fbb11b5abcd4 100644
@@ -416,7 +416,9 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 }
 
 static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
-				struct sk_msg *msg, int *copied, int flags)
+				struct sk_msg *msg, int *copied,
+				u32 msg_copied, bool *corked,
+				int flags)
 {
 	bool cork = false, enospc = sk_msg_full(msg), redir_ingress;
 	struct sock *sk_redir;
@@ -443,12 +445,18 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->cork = kzalloc_obj(*psock->cork,
 						  GFP_ATOMIC | __GFP_NOWARN);
 			if (!psock->cork) {
-				sk_msg_free(sk, msg);
-				*copied = 0;
+				int free;
+
+				psock->cork_bytes = 0;
+				free = sk_msg_free(sk, msg);
+				*copied -= min_t(u32, msg_copied, free);
 				return -ENOMEM;
 			}
 		}
-		memcpy(psock->cork, msg, sizeof(*msg));
+		if (psock->cork != msg) {
+			sk_msg_xfer_full(psock->cork, msg);
+			*corked = true;
+		}
 		return 0;
 	}
 
@@ -495,14 +503,15 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		if (unlikely(ret < 0)) {
 			int free = sk_msg_free(sk, msg);
 
-			if (!cork)
+			if (cork)
+				*copied -= min_t(u32, msg_copied, free);
+			else
 				*copied -= free;
 		}
 		if (cork) {
 			sk_msg_free(sk, msg);
 			kfree(msg);
 			msg = NULL;
-			ret = 0;
 		}
 		break;
 	case __SK_DROP:
@@ -534,6 +543,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	struct sk_msg tmp, *msg_tx = NULL;
 	int copied = 0, err = 0, ret = 0;
 	struct sk_psock *psock;
+	u32 msg_copied = 0;
 	long timeo;
 	int flags;
 
@@ -548,7 +558,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	lock_sock(sk);
 	timeo = sock_sndtimeo(sk, msg->msg_flags & MSG_DONTWAIT);
 	while (msg_data_left(msg)) {
-		bool enospc = false;
+		bool corked = false, enospc = false;
 		u32 copy, osize;
 
 		if (sk->sk_err) {
@@ -560,9 +570,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 		if (!sk_stream_memory_free(sk))
 			goto wait_for_sndbuf;
 		if (psock->cork) {
+			if (msg_tx != psock->cork)
+				msg_copied = 0;
 			msg_tx = psock->cork;
 		} else {
 			msg_tx = &tmp;
+			msg_copied = 0;
 			sk_msg_init(msg_tx);
 		}
 
@@ -579,10 +592,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 					       copy);
 		if (ret < 0) {
 			sk_msg_trim(sk, msg_tx, osize);
+			err = ret;
 			goto out_err;
 		}
 
 		copied += ret;
+		msg_copied += ret;
 		if (psock->cork_bytes) {
 			if (size > psock->cork_bytes)
 				psock->cork_bytes = 0;
@@ -595,21 +610,23 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 			psock->cork_bytes = 0;
 		}
 
-		err = tcp_bpf_send_verdict(sk, psock, msg_tx, &copied, flags);
+		err = tcp_bpf_send_verdict(sk, psock, msg_tx, &copied,
+					   msg_copied, &corked, flags);
 		if (unlikely(err < 0))
 			goto out_err;
+		if (corked)
+			msg_tx = psock->cork;
 		continue;
 wait_for_sndbuf:
 		set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
-		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
-				sk_msg_free(sk, msg_tx);
+		if (err)
 			goto out_err;
-		}
 	}
 out_err:
+	if (msg_tx == &tmp)
+		sk_msg_free(sk, msg_tx);
 	if (err < 0)
 		err = sk_stream_error(sk, msg->msg_flags, err);
 	release_sock(sk);
Signature · 1 line
2.43.0
#7 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

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

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

New issues:
- [High] ABA problem in pointer comparison causes msg_copied to retain stale values, leading to incorrect rollback of copied.

Pre-existing issues:
- [High] Incomplete fix leads to integer underflow in copied for __SK_PASS and __SK_DROP verdicts when a persistent cork fails.
- [High] Premature cork completion due to subtracting loop-invariant size instead of the copied amount ret. --

--- Patch [1]: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg() --- commit bd0bfd2d01fcc018a6a3e62db13f0096892e960b
Author: Chengfeng Ye <[email protected]>

bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path used the current value of psock->cork to decide whether msg_tx named the stack-local temporary message.

Quoted 7 lines
net/ipv4/tcp_bpf.c +0 -0
index cc0bd73f36b6d..fcf535b7a7546 100644
@@ -428,12 +430,18 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->cork = kzalloc_obj(*psock->cork,
 						  GFP_ATOMIC | __GFP_NOWARN);

net/ipv4/tcp_bpf.c static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock, · line 439

Quoted 10 lines
net/ipv4/tcp_bpf.c +5 -2
 			if (!psock->cork) {
-				sk_msg_free(sk, msg);
-				*copied = 0;
+				int free;
+
+				psock->cork_bytes = 0;
+				free = sk_msg_free(sk, msg);
+				*copied -= min_t(u32, msg_copied, free);
 				return -ENOMEM;
 			}

[Severity: High]
This is a pre-existing issue, but does the __SK_PASS path need this same msg_copied bounds check?

When the verdict evaluates to __SK_PASS but tcp_bpf_push() fails, the error path unconditionally subtracts the full freed amount from copied:

tcp_bpf_send_verdict() {
...

        case __SK_PASS:
                ret = tcp_bpf_push(sk, msg, tosend, flags, true);
                if (unlikely(ret)) {
                        *copied -= sk_msg_free(sk, msg);
                        break;
                }

...
}

Similarly, does the __SK_DROP path have the same issue?

tcp_bpf_send_verdict() {
...

        case __SK_DROP:
        default:
                sk_msg_free(sk, msg);
                sk_msg_apply_bytes(psock, tosend);
                *copied -= (tosend + delta);
                return -EACCES;
}

If data accumulates in a persistent cork across multiple syscalls, subtracting the full size here causes copied to underflow. Could this be returned to userspace as a bogus error code or a massive positive return value?

[ ... ]

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 561

Quoted 8 lines
net/ipv4/tcp_bpf.c +2 -0
@@ -545,9 +555,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 		if (!sk_stream_memory_free(sk))
 			goto wait_for_sndbuf;
 		if (psock->cork) {
+			if (msg_tx != psock->cork)
+				msg_copied = 0;
 			msg_tx = psock->cork;
 		} else {

[Severity: High]
Is this pointer comparison vulnerable to an ABA problem?

If sk_stream_wait_memory() is called later in this loop, it drops the socket lock. While asleep, a concurrent thread could acquire the lock, complete and free the existing psock->cork.

Another thread could then allocate a new psock->cork, and the slab allocator might return the exact same memory address.

When this thread wakes up and reacquires the lock, the check (msg_tx != psock->cork) would evaluate to false because the pointers match, and msg_copied wouldn't be reset. On a subsequent error, could this cause an artificially inflated msg_copied to be subtracted from the total copied amount?

[ ... ]

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 588

Quoted 13 lines
net/ipv4/tcp_bpf.c +2 -0
@@ -564,10 +577,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 					       copy);
 		if (ret < 0) {
 			sk_msg_trim(sk, msg_tx, osize);
+			err = ret;
 			goto out_err;
 		}
 
 		copied += ret;
+		msg_copied += ret;
 		if (psock->cork_bytes) {
 			if (size > psock->cork_bytes)
 				psock->cork_bytes = 0;

[Severity: High]
This is a pre-existing issue, but does the cork_bytes accounting use the wrong size variable here?

tcp_bpf_sendmsg() {
...

                copied += ret;
                msg_copied += ret;
                if (psock->cork_bytes) {
                        if (size > psock->cork_bytes)
                                psock->cork_bytes = 0;
                        else
                                psock->cork_bytes -= size;

...
}

Here, size is the total loop-invariant syscall size, while ret is the dynamically copied amount for this iteration. If ret < size (e.g., due to scatterlist limits or memory exhaustion), subtracting size will deplete cork_bytes faster than data is actually copied.

Could this cause premature cork completion and truncated messages being sent to the BPF program?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
maintainer
#8 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Chengfeng Ye
Quoted 1 line · On Fri, 24 Jul 2026 00:26:02 +0800 Chengfeng Ye wrote:

Subject: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Since you are spamming the list with the reposts of this could you please add [PATCH bpf] to the subject so that netdev CI is not confused into caring?

maintainer
#9 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Quoted 6 lines · Jakub Kicinski
Quoted 1 line · On Fri, 24 Jul 2026 00:26:02 +0800 Chengfeng Ye wrote:

Subject: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Since you are spamming the list with the reposts of this could you please add [PATCH bpf] to the subject so that netdev CI is not confused into caring?

+1 because we need bpf CI to run sockmap selftests on this patch. Please point your agent at Documentation/bpf/bpf_devel_QA.rst.

Contrary to Emil's ask, I'd recommend fixing one issue at a time. Reviewing multiple fixes crammed into one patch is harder, IMO.

#10 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Quoted 9 lines · Jakub Sitnicki
Quoted 6 lines · Jakub Kicinski
Quoted 1 line · On Fri, 24 Jul 2026 00:26:02 +0800 Chengfeng Ye wrote:

Subject: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Since you are spamming the list with the reposts of this could you please add [PATCH bpf] to the subject so that netdev CI is not confused into caring?

Quoted 5 lines

+1 because we need bpf CI to run sockmap selftests on this patch. Please point your agent at Documentation/bpf/bpf_devel_QA.rst.

Contrary to Emil's ask, I'd recommend fixing one issue at a time. Reviewing multiple fixes crammed into one patch is harder, IMO.

Sorry for the disturbance. I was trying to fix the other issues reported by sashiko,
but it seems it is harder to fix multiple bugs in one patch despite being the same
function, as more issues are flagged by sashiko when a new patch is sent...

Then please just take the original v1 patch for reference and ignore v2 and v3, and I will stop sending new patches for this report until there is further patch review.
If there is a new patch, [PATCH bpf] will be used properly.

Best, Chengfeng

maintainer
#11 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

Quoted 4 lines · Chengfeng Ye

Then please just take the original v1 patch for reference and ignore v2 and v3, and I will stop sending new patches for this report until there is further patch review.
If there is a new patch, [PATCH bpf] will be used properly.

You need to resend v1 with "PATCH bpf" prefix for bpf CI to pick it up.

#12 lore raw

PATCH v4 1/1 bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork.

This comparison is unsafe when two threads send on the same socket:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is therefore mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0

msg_tx can only name the stack-local tmp or the shared cork. Check for tmp directly so a changed psock->cork cannot turn a shared message into an apparent local one.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")

---

Changes in v4:
- Use the PATCH bpf prefix.
- Restore the minimal v1 fix, as suggested by Jakub Sitnicki.
- Drop the changes from v2 and v3 for other issues flagged by sashiko.

Please queue this fix for stable kernels affected by the Fixes commit.

 net/ipv4/tcp_bpf.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
net/ipv4/tcp_bpf.c +1 -1
index 8e905b50dead..a30475afb6f8 100644
@@ -604,7 +604,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
 		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
+			if (msg_tx == &tmp)
 				sk_msg_free(sk, msg_tx);
 			goto out_err;
 		}
Signature · 1 line
2.43.0
maintainer Reviewed-by on v4 1/1
#13 lore raw
Quoted 37 lines · Chengfeng Ye

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork.

This comparison is unsafe when two threads send on the same socket:

Thread A Thread B
msg_tx = psock->cork
sk_msg_alloc() fails sk_stream_wait_memory()
releases the socket lock acquires the socket lock
completes the cork psock->cork = NULL frees the cork
reacquires the socket lock
msg_tx != psock->cork
sk_msg_free(msg_tx)

The stale cork is therefore mistaken for the local temporary message and freed again. KASAN reported:

  BUG: KASAN: slab-use-after-free in sk_msg_free+0x49/0x50
  Read of size 4 at addr ffff88810c908800 by task poc/90
  Call Trace:
   sk_msg_free+0x49/0x50
   tcp_bpf_sendmsg+0x14f5/0x1cc0
   __sys_sendto+0x32c/0x3a0
   __x64_sys_sendto+0xdb/0x1b0
  Allocated by task 89:
   __kasan_kmalloc+0x8f/0xa0
   tcp_bpf_sendmsg+0x16b3/0x1cc0
  Freed by task 91:
   __kasan_slab_free+0x43/0x70
   kfree+0x131/0x3c0
   tcp_bpf_sendmsg+0xec3/0x1cc0
Quoted 8 lines

msg_tx can only name the stack-local tmp or the shared cork. Check for tmp directly so a changed psock->cork cannot turn a shared message into an apparent local one.

Fixes 604326b41a6f ("bpf, sockmap: convert to generic sk_msg interface")

---

Thanks!

#14 lore raw
In reply to Chengfeng Ye
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:

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

Pre-existing issues:
- [High] Concurrent sends on the same socket with an active redirect verdict cause a Use-After-Free on the target redirect socket (sk_redir).
- [High] memcpy is called with identical source and destination pointers when appending to an existing cork.
- [High] A failed allocation of psock->cork leaves psock->cork_bytes greater than zero, leading to a memory leak of the stack-allocated tmp message and silent data loss on subsequent sendmsg() calls. --

--- Patch [1]: [PATCH bpf v4] bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg() --- commit df6d10c569d9a8f6bacb9c15795f057eb688c1ef
Author: Chengfeng Ye <[email protected]>
Subject: bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. This patch fixes a use-after-free caused by an unsafe comparison between msg_tx and psock->cork, resolving it by checking directly against the stack-local tmp message.

net/ipv4/tcp_bpf.c

Quoted 4 lines
net/ipv4/tcp_bpf.c +0 -0
index 8e905b50deadb..a30475afb6f89 100644

[ ... ]

[Severity: High]
This isn't a bug introduced by this patch, but could this leak the tmp message and cause silent data loss if an earlier cork allocation fails?

In tcp_bpf_send_verdict(), psock->cork_bytes is updated before allocating psock->cork:

net/ipv4/tcp_bpf.c:tcp_bpf_send_verdict() {
    ...
	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, ...);
			if (!psock->cork) {
				sk_msg_free(sk, msg);
				*copied = 0;
				return -ENOMEM;
			}
    ...
}

If the allocation fails, psock->cork_bytes remains greater than zero.

Then, on a subsequent sendmsg(), tcp_bpf_sendmsg() populates the stack-local tmp message, sees the non-zero psock->cork_bytes, and jumps directly to out_err:

net/ipv4/tcp_bpf.c:tcp_bpf_sendmsg() {
    ...
		if (psock->cork_bytes) {
			if (size > psock->cork_bytes)
				psock->cork_bytes = 0;
			else
				psock->cork_bytes -= size;
			if (psock->cork_bytes && !enospc)
				goto out_err;
    ...
}

Because it bypasses the error path cleanup below, doesn't this permanently leak the tmp message's page fragments and silently drop the data?

[Severity: High]
This is a pre-existing issue, but does appending to a cork execute undefined behavior by calling memcpy() with identical pointers?

In tcp_bpf_sendmsg(), if psock->cork is already allocated, msg_tx is set to point to it and passed to tcp_bpf_send_verdict() as the msg argument. If the BPF program requests further corking, the code does this:

net/ipv4/tcp_bpf.c:tcp_bpf_send_verdict() {
    ...
		if (!psock->cork) {
			psock->cork = kzalloc_obj(*psock->cork, ...);
            ...
		}
		memcpy(psock->cork, msg, sizeof(*msg));
		return 0;
    ...
}

Since msg is exactly psock->cork here, does this result in a memcpy() with identical source and destination pointers, potentially causing a kernel panic with FORTIFY_SOURCE or KASAN?

[Severity: High]
This is a pre-existing issue, but is there a use-after-free on sk_redir when multiple threads send on the same socket concurrently?

When a BPF program returns __SK_REDIRECT with apply_bytes > 0, sk_psock_msg_verdict() takes a single reference on the target socket via sock_hold(psock->sk_redir).

If multiple threads process the remaining apply_bytes for this socket, they all read psock->sk_redir and release the socket lock to call tcp_bpf_sendmsg_redir():

net/ipv4/tcp_bpf.c:tcp_bpf_send_verdict() {
    ...
	case __SK_REDIRECT:
		redir_ingress = psock->redir_ingress;
		sk_redir = psock->sk_redir;
		sk_msg_apply_bytes(psock, tosend);
		if (!psock->apply_bytes) {
			/* Clean up before releasing the sock lock. */
			eval = psock->eval;
			psock->eval = __SK_NONE;
			psock->sk_redir = NULL;
		}
        ...
		release_sock(sk);

		origsize = msg->sg.size;
		ret = tcp_bpf_sendmsg_redir(sk_redir, redir_ingress, ...);
		sent = origsize - msg->sg.size;

		if (eval == __SK_REDIRECT)
			sock_put(sk_redir);
    ...
}

The thread that exhausts apply_bytes clears psock->sk_redir and calls sock_put(sk_redir) to drop the single reference. Since it doesn't wait for other concurrent threads to finish, could the target socket be freed while they are still executing tcp_bpf_sendmsg_redir(sk_redir)?

[ ... ]

Quoted 9 lines
net/ipv4/tcp_bpf.c +1 -1
@@ -604,7 +604,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 wait_for_memory:
 		err = sk_stream_wait_memory(sk, &timeo);
 		if (err) {
-			if (msg_tx && msg_tx != psock->cork)
+			if (msg_tx == &tmp)
 				sk_msg_free(sk, msg_tx);
 			goto out_err;
 		}
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
#15 lore raw
In reply to Chengfeng Ye
This patch was applied to bpf/bpf.git (master) by Eduard Zingerman <[email protected]>:

Hello:

This patch was applied to bpf/bpf.git (master)
by Eduard Zingerman <[email protected]>:

Quoted 8 lines · On Fri, 24 Jul 2026 18:38:56 +0800 you wrote:

tcp_bpf_sendmsg() keeps msg_tx across sk_stream_wait_memory(), which drops and reacquires the socket lock. Its error path tries to decide whether msg_tx names the local temporary message by comparing it with the current value of psock->cork.

This comparison is unsafe when two threads send on the same socket:

[...]

Here is the summary with links:
- [bpf,v4] bpf, sockmap: Fix cork use-after-free in tcp_bpf_sendmsg()

    https://git.kernel.org/bpf/bpf/c/2d66a033864e

You are awesome, thank you!

Signature · 2 lines
Deet-doot-dot, I am a bot.
https://korg.docs.kernel.org/patchwork/pwbot.html
#16 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Jakub Sitnicki
Quoted 1 line · Jakub Sitnicki

You need to resend v1 with "PATCH bpf" prefix for bpf CI to pick it up.

No problem, just resent as "[PATCH bpf v4]".

Best, Chengfeng

#17 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Chengfeng Ye
The subject line is missing the [PATCH bpf] prefix. This helps netdev CI correctly categorize the patch and prevents CI confusion when multiple versions are posted in quick succession. Could the…
Quoted 1 line

bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

The subject line is missing the [PATCH bpf] prefix. This helps netdev CI correctly categorize the patch and prevents CI confusion when multiple versions are posted in quick succession. Could the subject be updated to:

[PATCH bpf] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

(This issue was raised by Jakub Kicinski at
https://lore.kernel.org/bpf/[email protected]/)

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/30028532303

#18 lore raw

Re: [PATCH v3] bpf, sockmap: Fix cork ownership in tcp_bpf_sendmsg()

In reply to Chengfeng Ye
This patch folds several distinct changes into one: the stale-cork use-after-free fix at the common exit, a cork_bytes reset on the cork allocation failure path, the new msg_copied accounting for…
Quoted 78 lines
net/ipv4/tcp_bpf.c +19 -6
index cc0bd73f36b6..fcf535b7a754 100644
@@ -401,7 +401,9 @@ static int tcp_bpf_recvmsg(struct sock *sk, struct msghdr *msg, size_t len,
 }
 
 static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
-				struct sk_msg *msg, int *copied, int flags)
+				struct sk_msg *msg, int *copied,
+				u32 msg_copied, bool *corked,
+				int flags)
 {
 	bool cork = false, enospc = sk_msg_full(msg), redir_ingress;
 	struct sock *sk_redir;
@@ -428,12 +430,18 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 			psock->cork = kzalloc_obj(*psock->cork,
 						  GFP_ATOMIC | __GFP_NOWARN);
 			if (!psock->cork) {
-				sk_msg_free(sk, msg);
-				*copied = 0;
+				int free;
+
+				psock->cork_bytes = 0;
+				free = sk_msg_free(sk, msg);
+				*copied -= min_t(u32, msg_copied, free);
 				return -ENOMEM;
 			}
 		}
-		memcpy(psock->cork, msg, sizeof(*msg));
+		if (psock->cork != msg) {
+			sk_msg_xfer_full(psock->cork, msg);
+			*corked = true;
+		}
 		return 0;
 	}
 
@@ -480,14 +488,15 @@ static int tcp_bpf_send_verdict(struct sock *sk, struct sk_psock *psock,
 		if (unlikely(ret < 0)) {
 			int free = sk_msg_free(sk, msg);
 
-			if (!cork)
+			if (cork)
+				*copied -= min_t(u32, msg_copied, free);
+			else
 				*copied -= free;
 		}
 		if (cork) {
 			sk_msg_free(sk, msg);
 			kfree(msg);
 			msg = NULL;
-			ret = 0;
 		}
 		break;
 	case __SK_DROP:
@@ -519,6 +528,7 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 	struct sk_msg tmp, *msg_tx = NULL;
 	int copied = 0, err = 0, ret = 0;
 	struct sk_psock *psock;
+	u32 msg_copied = 0;
 	long timeo;
 	int flags;
 
@@ -545,9 +555,12 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 		if (!sk_stream_memory_free(sk))
 			goto wait_for_sndbuf;
 		if (psock->cork) {
+			if (msg_tx != psock->cork)
+				msg_copied = 0;
 			msg_tx = psock->cork;
 		} else {
 			msg_tx = &tmp;
+			msg_copied = 0;
 			sk_msg_init(msg_tx);
 		}
 
@@ -580,21 +595,23 @@ static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
 			psock->cork_bytes = 0;

net/ipv4/tcp_bpf.c static int tcp_bpf_sendmsg(struct sock *sk, struct msghdr *msg, size_t size) · line 604

Quoted 10 lines
net/ipv4/tcp_bpf.c +4 -1
 		}
 
-		err = tcp_bpf_send_verdict(sk, psock, msg_tx, &copied, flags);
+		err = tcp_bpf_send_verdict(sk, psock, msg_tx, &copied,
+					   msg_copied, &corked, flags);
 		if (unlikely(err < 0))
 			goto out_err;
+		if (corked)
+			msg_tx = psock->cork;
 		continue;

This patch folds several distinct changes into one: the stale-cork use-after-free fix at the common exit, a cork_bytes reset on the cork allocation failure path, the new msg_copied accounting for the failure paths, the switch from memcpy() to sk_msg_xfer_full() for the cork transfer, and iterator error propagation via err = ret.

The upstream discussion suggested fixing one issue at a time rather than combining several fixes in a single patch:

https://lore.kernel.org/bpf/[email protected]/

Would it be clearer to split these into separate patches? A minimal single-fix version addressing just the use-after-free was posted afterwards and was the form that landed (commit 2d66a033864e).

---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/30145850605