On Wed, 2026-02-04 at 15:14 +0100, Ralf Lici wrote:
> When processing TCP stream data in ovpn_tcp_recv, we receive large
> cloned skbs from __strp_rcv that may contain multiple coalesced
> packets.
> The current implementation has two bugs:
> 
> 1. Header offset overflow: Using pskb_pull with large offsets on
>    coalesced skbs causes skb->data - skb->head to exceed the u16
> storage
>    of skb->network_header. This causes skb_reset_network_header to
> fail
>    on the inner decapsulated packet, resulting in packet drops.
> 
> 2. Unaligned protocol headers: Extracting packets from arbitrary
>    positions within the coalesced TCP stream provides no alignment
>    guarantees for the packet data causing performance penalties on
>    architectures without efficient unaligned access. Additionally,
>    openvpn's 2-byte length prefix on TCP packets causes the subsequent
>    4-byte opcode and packet ID fields to be inherently misaligned.
> 
> Fix both issues by allocating a new skb for each openvpn packet and
> using skb_copy_bits to extract only the packet content into the new
> buffer, skipping the 2-byte length prefix. Also, check the length
> before
> invoking the function that performs the allocation to avoid creating
> an
> invalid skb.
> 
> If the packet has to be forwarded to userspace the 2-byte prefix can
> be
> pushed to the head safely, without misalignment.
> 
> As a side effect, this approach also avoids the expensive
> linearization
> that pskb_pull triggers on cloned skbs with page fragments. In
> testing,
> this resulted in TCP throughput improvements of up to 74%.
> 
> Fixes: 11851cbd60ea ("ovpn: implement TCP transport")
> Signed-off-by: Ralf Lici <[email protected]>
> ---
>  drivers/net/ovpn/tcp.c | 53 ++++++++++++++++++++++++++++-------------
> -
>  1 file changed, 36 insertions(+), 17 deletions(-)
> 
> diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
> index b7348da9b040..ffe8db69d76d 100644
> --- a/drivers/net/ovpn/tcp.c
> +++ b/drivers/net/ovpn/tcp.c
> @@ -70,37 +70,56 @@ static void ovpn_tcp_to_userspace(struct ovpn_peer
> *peer, struct sock *sk,
>       peer->tcp.sk_cb.sk_data_ready(sk);
>  }
>  
> -static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
> +static struct sk_buff *ovpn_tcp_skb_packet(const struct ovpn_peer
> *peer,
> +                                        struct sk_buff *orig_skb,
> +                                        const int pkt_len, const
> int pkt_off)
>  {
> -     struct ovpn_peer *peer = container_of(strp, struct ovpn_peer,
> tcp.strp);
> -     struct strp_msg *msg = strp_msg(skb);
> -     size_t pkt_len = msg->full_len - 2;
> -     size_t off = msg->offset + 2;
> -     u8 opcode;
> +     struct sk_buff *ovpn_skb;
> +     int err;
>  
> -     /* ensure skb->data points to the beginning of the openvpn
> packet */
> -     if (!pskb_pull(skb, off)) {
> -             net_warn_ratelimited("%s: packet too small for peer
> %u\n",
> -                                  netdev_name(peer->ovpn->dev),
> peer->id);
> +     /* create a new skb with only the content of the current
> packet */
> +     ovpn_skb = netdev_alloc_skb(peer->ovpn->dev, pkt_len);
> +     if (unlikely(!ovpn_skb))
>               goto err;
> -     }
>  
> -     /* strparser does not trim the skb for us, therefore we do it
> now */
> -     if (pskb_trim(skb, pkt_len) != 0) {
> -             net_warn_ratelimited("%s: trimming skb failed for
> peer %u\n",
> +     skb_copy_header(ovpn_skb, orig_skb);
> +     err = skb_copy_bits(orig_skb, pkt_off, skb_put(ovpn_skb,
> pkt_len),
> +                         pkt_len);
> +     if (unlikely(err)) {
> +             net_warn_ratelimited("%s: skb_copy_bits failed for
> peer %u\n",
>                                    netdev_name(peer->ovpn->dev),
> peer->id);
> +             kfree_skb(ovpn_skb);
>               goto err;
>       }
>  
> -     /* we need the first 4 bytes of data to be accessible
> +     consume_skb(orig_skb);
> +     return ovpn_skb;
> +err:
> +     kfree_skb(orig_skb);
> +     return NULL;
> +}
> +
> +static void ovpn_tcp_rcv(struct strparser *strp, struct sk_buff *skb)
> +{
> +     struct ovpn_peer *peer = container_of(strp, struct ovpn_peer,
> tcp.strp);
> +     struct strp_msg *msg = strp_msg(skb);
> +     int pkt_len = msg->full_len - 2;
> +     u8 opcode;
> +
> +     /* we need at least 4 bytes of data in the packet
>        * to extract the opcode and the key ID later on
>        */
> -     if (!pskb_may_pull(skb, OVPN_OPCODE_SIZE)) {
> +     if (unlikely(pkt_len < OVPN_OPCODE_SIZE)) {
>               net_warn_ratelimited("%s: packet too small to fetch
> opcode for peer %u\n",
>                                    netdev_name(peer->ovpn->dev),
> peer->id);
>               goto err;
>       }
>  
> +     /* extract the packet into a new skb */
> +     skb = ovpn_tcp_skb_packet(peer, skb, pkt_len, msg->offset +
> 2);
> +     if (unlikely(!skb))
> +             goto err;
> +
>       /* DATA_V2 packets are handled in kernel, the rest goes to
> user space */
>       opcode = ovpn_opcode_from_skb(skb, 0);
>       if (unlikely(opcode != OVPN_DATA_V2)) {
> @@ -113,7 +132,7 @@ static void ovpn_tcp_rcv(struct strparser *strp,
> struct sk_buff *skb)
>               /* The packet size header must be there when sending
> the packet
>                * to userspace, therefore we put it back
>                */
> -             skb_push(skb, 2);
> +             *((__force __be16 *)__skb_push(skb, 2)) =
> cpu_to_be16(pkt_len);
>               ovpn_tcp_to_userspace(peer, strp->sk, skb);
>               return;
>       }

This issue manifests as the following warning (on
skb_reset_network_header):


[  126.764066] ------------[ cut here ]------------
[  126.764541] WARNING: CPU: 1 PID: 22 at ./include/linux/skbuff.h:3122
ovpn_decrypt_post+0x531/0x7b0 [ovpn]
[  126.765698] Modules linked in: ovpn ip6_udp_tunnel udp_tunnel chacha
libchacha chacha20poly1305 libpoly1305 veth [last unloaded:
ip6_udp_tunnel]
[  126.767085] CPU: 1 UID: 0 PID: 22 Comm: ksoftirqd/1 Not tainted
6.17.0+ #222 PREEMPT(none) 
[  126.768053] RIP: 0010:ovpn_decrypt_post+0x531/0x7b0 [ovpn]
[  126.768769] Code: cc cc cc cc 48 2b 93 c8 00 00 00 83 c2 28 0f 88 62
02 00 00 89 c8 29 f0 39 d0 0f 82 2e 02 00 00 b8 86 dd ff ff e9 68 fe ff
ff <0f> 0b e9 20 fc ff ff 0f 0b e9 35 fc ff ff 39 c1 0f 82 5b fc ff ff
[  126.770834] RSP: 0018:ffffc900000d77f0 EFLAGS: 00010216
[  126.771503] RAX: 000000000001034c RBX: ffff88810c7ac100 RCX:
0000000000000588
[  126.772386] RDX: ffff888116ee0000 RSI: 0000000000000000 RDI:
ffffffff8359f040
[  126.773251] RBP: ffffc900000d7820 R08: 00000000000005a0 R09:
ffff888116ef034c
[  126.774126] R10: 0000000000000000 R11: 0000000000000001 R12:
ffff888116bec800
[  126.774980] R13: ffff888108118000 R14: 0000000000000018 R15:
ffff888103a6cc00
[  126.775871] FS:  0000000000000000(0000) GS:ffff8882f446b000(0000)
knlGS:0000000000000000
[  126.776839] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[  126.777605] CR2: 00007f5f9d2fc000 CR3: 000000011659b000 CR4:
0000000000750eb0
[  126.778434] PKRU: 55555554
[  126.778887] Call Trace:
[  126.779324]  <TASK>
[  126.779728]  ovpn_recv+0x15b/0x3a0 [ovpn]
[  126.780299]  ovpn_tcp_rcv+0xbd/0x320 [ovpn]
[  126.780886]  __strp_recv+0x172/0x640
[  126.781430]  strp_recv+0x27/0x30
[  126.781931]  __tcp_read_sock+0x92/0x260
[  126.782483]  ? __pfx_strp_recv+0x10/0x10
[  126.783055]  tcp_read_sock+0x1b/0x30
[  126.783586]  strp_read_sock+0xce/0xe0
[  126.784162]  strp_data_ready+0x6a/0xa0
[  126.784721]  ovpn_tcp_data_ready+0x90/0x220 [ovpn]
[  126.785441]  tcp_data_ready+0x30/0xd0
[  126.785992]  tcp_data_queue+0x8a4/0x1000
[  126.786560]  tcp_rcv_established+0x328/0xca0
[  126.787170]  tcp_v4_do_rcv+0x18f/0x3c0
[  126.787726]  tcp_v4_rcv+0xf51/0x1520
[  126.788264]  ip_protocol_deliver_rcu+0x4a/0x200
[  126.788898]  ? process_backlog+0x14b/0x630
[  126.789552]  ip_local_deliver_finish+0xd7/0x220
[  126.790193]  ip_local_deliver+0x63/0x250
[  126.790769]  ? lock_is_held_type+0x9c/0x110
[  126.791366]  ? process_backlog+0x14b/0x630
[  126.791995]  ip_rcv+0x26f/0x340
[  126.792516]  ? process_backlog+0x14b/0x630
[  126.793114]  ? process_backlog+0x14b/0x630
[  126.793714]  __netif_receive_skb_one_core+0x6b/0x80
[  126.794358]  __netif_receive_skb+0x16/0x60
[  126.794951]  process_backlog+0x18b/0x630
[  126.795515]  ? process_backlog+0x6a/0x630
[  126.796109]  __napi_poll.constprop.0+0x2e/0x1e0
[  126.796721]  net_rx_action+0x384/0x420
[  126.797252]  ? local_clock_noinstr+0x13/0xf0
[  126.797861]  handle_softirqs+0xc8/0x3f0
[  126.798401]  ? __pfx_smpboot_thread_fn+0x10/0x10
[  126.799024]  run_ksoftirqd+0x36/0x50
[  126.799548]  smpboot_thread_fn+0xfe/0x230
[  126.800110]  kthread+0x103/0x210
[  126.800602]  ? __pfx_kthread+0x10/0x10
[  126.801145]  ret_from_fork+0x18a/0x1e0
[  126.801705]  ? __pfx_kthread+0x10/0x10
[  126.802284]  ret_from_fork_asm+0x1a/0x30
[  126.802879]  </TASK>
[  126.803288] irq event stamp: 41024
[  126.803799] hardirqs last  enabled at (41032): [<ffffffff8137676c>]
__up_console_sem+0x5c/0x70
[  126.804799] hardirqs last disabled at (41039): [<ffffffff81376751>]
__up_console_sem+0x41/0x70
[  126.805812] softirqs last  enabled at (33936): [<ffffffff812e6486>]
run_ksoftirqd+0x36/0x50
[  126.806819] softirqs last disabled at (33941): [<ffffffff812e6486>]
run_ksoftirqd+0x36/0x50
[  126.807822] ---[ end trace 0000000000000000 ]---

-- 
Ralf Lici
Mandelbit Srl


_______________________________________________
Openvpn-devel mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/openvpn-devel

Reply via email to