VYPR
Unrated severityNVD Advisory· Published May 27, 2026· Updated May 27, 2026

CVE-2026-45929

CVE-2026-45929

Description

In the Linux kernel, the following vulnerability has been resolved:

ovpn: fix possible use-after-free in ovpn_net_xmit

When building the skb_list in ovpn_net_xmit, skb_share_check will free the original skb if it is shared. The current implementation continues to use the stale skb pointer for subsequent operations: - peer lookup, - skb_dst_drop (even though all segments produced by skb_gso_segment will have a dst attached), - ovpn_peer_stats_increment_tx.

Fix this by moving the peer lookup and skb_dst_drop before segmentation so that the original skb is still valid when used. Return early if all segments fail skb_share_check and the list ends up empty. Also switch ovpn_peer_stats_increment_tx to use skb_list.next; the next patch fixes the stats logic.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Use-after-free in Linux kernel OVPN driver's ovpn_net_xmit due to skb_share_check freeing the shared skb while stale pointer is used for subsequent operations.

Vulnerability

A use-after-free vulnerability exists in the Linux kernel's OVPN driver, specifically in the ovpn_net_xmit function [1]. When building the skb list, skb_share_check may free the original skb if it is shared, but the code continues to use the stale skb pointer for peer lookup, skb_dst_drop, and ovpn_peer_stats_increment_tx. This affects versions prior to the fix commit [1].

Exploitation

An attacker with the ability to trigger transmission of shared SKBs through the OVPN interface can cause the vulnerable code path to be exercised [1]. No special privileges are required beyond the ability to send network traffic; the race condition occurs during normal packet processing.

Impact

A successful exploit could lead to a use-after-free condition, potentially resulting in arbitrary code execution, denial of service (system crash), or information disclosure [1]. The impact depends on the memory state at the time of the error.

Mitigation

The fix was applied in the Linux kernel commit [1] and subsequently included in stable kernel releases [1]. Users should update to the latest kernel version containing the fix. No workaround is available other than applying the patch.

AI Insight generated on May 27, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

6
3e4fbcb4e078

ovpn: fix possible use-after-free in ovpn_net_xmit

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitRalf LiciJan 30, 2026Fixed in 6.18.14via kernel-cna
1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    
442915c96a9b

ovpn: fix possible use-after-free in ovpn_net_xmit

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitRalf LiciJan 30, 2026Fixed in 6.19.4via kernel-cna
1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    
a5ec7baa44ea

ovpn: fix possible use-after-free in ovpn_net_xmit

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitRalf LiciJan 30, 2026Fixed in 7.0via kernel-cna
1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    
a5ec7baa44ea

ovpn: fix possible use-after-free in ovpn_net_xmit

1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    
3e4fbcb4e078

ovpn: fix possible use-after-free in ovpn_net_xmit

1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    
442915c96a9b

ovpn: fix possible use-after-free in ovpn_net_xmit

1 file changed · +31 22
  • drivers/net/ovpn/io.c+31 22 modified
    diff --git a/drivers/net/ovpn/io.c b/drivers/net/ovpn/io.c
    index 3e9e7f8444b344..f70c58b10599bc 100644
    --- a/drivers/net/ovpn/io.c
    +++ b/drivers/net/ovpn/io.c
    @@ -365,7 +365,27 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     	/* verify IP header size in network packet */
     	proto = ovpn_ip_check_protocol(skb);
     	if (unlikely(!proto || skb->protocol != proto))
    -		goto drop;
    +		goto drop_no_peer;
    +
    +	/* retrieve peer serving the destination IP of this packet */
    +	peer = ovpn_peer_get_by_dst(ovpn, skb);
    +	if (unlikely(!peer)) {
    +		switch (skb->protocol) {
    +		case htons(ETH_P_IP):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    +					    netdev_name(ovpn->dev),
    +					    &ip_hdr(skb)->daddr);
    +			break;
    +		case htons(ETH_P_IPV6):
    +			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    +					    netdev_name(ovpn->dev),
    +					    &ipv6_hdr(skb)->daddr);
    +			break;
    +		}
    +		goto drop_no_peer;
    +	}
    +	/* dst was needed for peer selection - it can now be dropped */
    +	skb_dst_drop(skb);
     
     	if (skb_is_gso(skb)) {
     		segments = skb_gso_segment(skb, 0);
    @@ -396,34 +416,24 @@ netdev_tx_t ovpn_net_xmit(struct sk_buff *skb, struct net_device *dev)
     
     		__skb_queue_tail(&skb_list, curr);
     	}
    -	skb_list.prev->next = NULL;
     
    -	/* retrieve peer serving the destination IP of this packet */
    -	peer = ovpn_peer_get_by_dst(ovpn, skb);
    -	if (unlikely(!peer)) {
    -		switch (skb->protocol) {
    -		case htons(ETH_P_IP):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI4\n",
    -					    netdev_name(ovpn->dev),
    -					    &ip_hdr(skb)->daddr);
    -			break;
    -		case htons(ETH_P_IPV6):
    -			net_dbg_ratelimited("%s: no peer to send data to dst=%pI6c\n",
    -					    netdev_name(ovpn->dev),
    -					    &ipv6_hdr(skb)->daddr);
    -			break;
    -		}
    -		goto drop;
    +	/* no segments survived: don't jump to 'drop' because we already
    +	 * incremented the counter for each failure in the loop
    +	 */
    +	if (unlikely(skb_queue_empty(&skb_list))) {
    +		ovpn_peer_put(peer);
    +		return NETDEV_TX_OK;
     	}
    -	/* dst was needed for peer selection - it can now be dropped */
    -	skb_dst_drop(skb);
    +	skb_list.prev->next = NULL;
     
    -	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb->len);
    +	ovpn_peer_stats_increment_tx(&peer->vpn_stats, skb_list.next->len);
     	ovpn_send(ovpn, skb_list.next, peer);
     
     	return NETDEV_TX_OK;
     
     drop:
    +	ovpn_peer_put(peer);
    +drop_no_peer:
     	dev_dstats_tx_dropped(ovpn->dev);
     	skb_tx_error(skb);
     	kfree_skb_list(skb);
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Use-after-free in ovpn_net_xmit because skb_share_check can free the original skb, but the stale pointer is later used for peer lookup, skb_dst_drop, and stats accounting."

Attack vector

An attacker can trigger this by sending a GSO packet through the ovpn interface whose skb is shared (refcount > 1). When skb_gso_segment processes the skb, the loop calls skb_share_check on each segment, which may free the original skb. The original code then dereferences the freed skb pointer for peer lookup via ovpn_peer_get_by_dst, skb_dst_drop, and ovpn_peer_stats_increment_tx, leading to a use-after-free. No special privileges are required beyond the ability to send network traffic through the ovpn device.

Affected code

The vulnerable function is ovpn_net_xmit in drivers/net/ovpn/io.c. The original code performed peer lookup and skb_dst_drop after the GSO segmentation loop, at which point skb_share_check may have freed the original skb.

What the fix does

The patch moves peer lookup (ovpn_peer_get_by_dst) and skb_dst_drop before the GSO segmentation loop, so these operations use the original skb while it is still valid [patch_id=2661248]. It also replaces the stale skb->len reference in ovpn_peer_stats_increment_tx with skb_list.next->len, using the first segment from the list instead of the freed original. A new early-return path is added when the segment list is empty (all segments failed skb_share_check), avoiding a jump to the drop label that would double-free. The drop label now calls ovpn_peer_put(peer) and a new drop_no_peer label handles the case where no peer was found.

Preconditions

  • networkAttacker must be able to send network traffic through the ovpn interface
  • inputThe transmitted skb must be shared (refcount > 1) so that skb_share_check frees the original

Generated on May 27, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

3

News mentions

0

No linked articles in our index yet.