[GH-ISSUE #212] keepalive flag is read after the skb is freed (3.0.20260805) #122

Closed
opened 2026-10-13 21:06:46 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @wobujidao on GitHub (Aug 10, 2026).
Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/212

Thanks for the keepalive fix in 3.0.20260805 — it does solve the old S4 > 0 case, but the new flag is read after the skb is already gone:

/* send.c, wg_packet_create_data_done() */
if (likely(!wg_socket_send_skb_to_peer(peer, skb,
                PACKET_CB(skb)->ds) && !PACKET_CB(skb)->is_keepalive))
        data_sent = true;

wg_socket_send_skb_to_peer() consumes the skb on every path — that's why it saves skb_len before sending — so !PACKET_CB(skb)->is_keepalive reads freed memory. When the recycled memory reads as false, the keepalive counts as data, the "no reply in 15 s" timer is armed, and the peer starts a new handshake. Every handshake ends with another keepalive, so it loops: one handshake every ~15 s for as long as the other side stays quiet. Unlike the old bug, this also happens with plain S4=0.

Measured with an idle peer (120 s window, dynamic debug on, counting handshakes the server started itself):

module config self-initiated
3.0.20260731-04 S4=0 0
3.0.20260805 S4=0 7
3.0.20260805 S4=16 + header protection 6
3.0.20260805 + patch below both of the above 0

Being a use-after-free, it is timing dependent: an idle test box often hides it, a box with real traffic shows it immediately. On one of our exits a user's phone was rekeying every 15–17 s while every other peer kept the normal 120 s interval.

Reading the flag before the send fixes it — upstream WireGuard computes it before the send for the same reason:

 	skb_list_walk_safe(first, skb, next) {
-		if (likely(!wg_socket_send_skb_to_peer(peer, skb,
-				PACKET_CB(skb)->ds) && !PACKET_CB(skb)->is_keepalive))
+		const bool is_keepalive = PACKET_CB(skb)->is_keepalive;
+
+		if (likely(!wg_socket_send_skb_to_peer(peer, skb,
+				PACKET_CB(skb)->ds) && !is_keepalive))
 			data_sent = true;
 	}

Tested on Debian 13 / kernel 6.12 and Ubuntu / kernel 6.8.

Originally created by @wobujidao on GitHub (Aug 10, 2026). Original GitHub issue: https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/issues/212 Thanks for the keepalive fix in 3.0.20260805 — it does solve the old `S4 > 0` case, but the new flag is read after the skb is already gone: ```c /* send.c, wg_packet_create_data_done() */ if (likely(!wg_socket_send_skb_to_peer(peer, skb, PACKET_CB(skb)->ds) && !PACKET_CB(skb)->is_keepalive)) data_sent = true; ``` `wg_socket_send_skb_to_peer()` consumes the skb on every path — that's why it saves `skb_len` before sending — so `!PACKET_CB(skb)->is_keepalive` reads freed memory. When the recycled memory reads as false, the keepalive counts as data, the "no reply in 15 s" timer is armed, and the peer starts a new handshake. Every handshake ends with another keepalive, so it loops: one handshake every ~15 s for as long as the other side stays quiet. Unlike the old bug, this also happens with plain `S4=0`. Measured with an idle peer (120 s window, dynamic debug on, counting handshakes the server started itself): | module | config | self-initiated | |---|---|---| | 3.0.20260731-04 | `S4=0` | 0 | | 3.0.20260805 | `S4=0` | 7 | | 3.0.20260805 | `S4=16` + header protection | 6 | | 3.0.20260805 + patch below | both of the above | 0 | Being a use-after-free, it is timing dependent: an idle test box often hides it, a box with real traffic shows it immediately. On one of our exits a user's phone was rekeying every 15–17 s while every other peer kept the normal 120 s interval. Reading the flag before the send fixes it — upstream WireGuard computes it before the send for the same reason: ```diff skb_list_walk_safe(first, skb, next) { - if (likely(!wg_socket_send_skb_to_peer(peer, skb, - PACKET_CB(skb)->ds) && !PACKET_CB(skb)->is_keepalive)) + const bool is_keepalive = PACKET_CB(skb)->is_keepalive; + + if (likely(!wg_socket_send_skb_to_peer(peer, skb, + PACKET_CB(skb)->ds) && !is_keepalive)) data_sent = true; } ``` Tested on Debian 13 / kernel 6.12 and Ubuntu / kernel 6.8.
Author
Owner

@ValdikSS commented on GitHub (Aug 13, 2026):

Fixed in github.com/amnezia-vpn/amneziawg-linux-kernel-module@0d5f03c543

<!-- gh-comment-id:5286309868 --> @ValdikSS commented on GitHub (Aug 13, 2026): Fixed in https://github.com/amnezia-vpn/amneziawg-linux-kernel-module/commit/0d5f03c543b709ab3c2e8d3f4a36994fbe249dc0
Author
Owner

@wobujidao commented on GitHub (Aug 14, 2026):

Confirmed, thank you. We have been running the same one-liner on top of 3.0.20260805 on our exits since 2026-08-10, and the fix in 0d5f03c is identical in effect.

Re-measured numbers from the report, idle peer, 120 s window, counting handshakes the server started itself:

  • stock 3.0.20260805: 7 (S4=0) and 6 (S4=16 + header protection)
  • with the flag read before the send: 0 in both cases

Feel free to close this one.

<!-- gh-comment-id:5289613610 --> @wobujidao commented on GitHub (Aug 14, 2026): Confirmed, thank you. We have been running the same one-liner on top of 3.0.20260805 on our exits since 2026-08-10, and the fix in 0d5f03c is identical in effect. Re-measured numbers from the report, idle peer, 120 s window, counting handshakes the server started itself: - stock 3.0.20260805: 7 (S4=0) and 6 (S4=16 + header protection) - with the flag read before the send: 0 in both cases Feel free to close this one.
Author
Owner

@ygurov commented on GitHub (Aug 14, 2026):

Hi @wobujidao,
Yep, it was fixed by PR #213. Thanks for debugging and reporting the issue from your side but that one was faster 🥇

<!-- gh-comment-id:5291076618 --> @ygurov commented on GitHub (Aug 14, 2026): Hi @wobujidao, Yep, it was fixed by PR #213. Thanks for debugging and reporting the issue from your side but that one was faster 🥇
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
amnezia-vpn/amneziawg-linux-kernel-module#122
No description provided.