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

CVE-2026-45918

CVE-2026-45918

Description

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

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

When deleting a peer in case of keepalive expiration, the peer is removed from the OpenVPN hashtable and is temporary inserted in a "release list" for further processing.

This happens in: ovpn_peer_keepalive_work() unlock_ovpn(release_list)

This processing includes detaching from the socket being used to talk to this peer, by restoring its original proto and socket ops/callbacks.

In case of TCP it may happen that, while the peer is sitting in the release list, userspace decides to close the socket. This will result in a concurrent execution of:

tcp_close(sk) __tcp_close(sk) sock_orphan(sk) sk_set_socket(sk, NULL)

The last function call will set sk->sk_socket to NULL.

When the releasing routine is resumed, ovpn_tcp_socket_detach() will attempt to dereference sk->sk_socket to restore its original ops member. This operation will crash due to sk->sk_socket being NULL.

Fix this race condition by testing-and-accessing sk->sk_socket atomically under sk->sk_callback_lock.

AI Insight

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

Race condition in ovpn TCP socket detach leads to NULL dereference when peer deletion and socket close occur concurrently.

Vulnerability

In the Linux kernel's OpenVPN (ovpn) TCP implementation, a race condition exists in ovpn_tcp_socket_detach(). When a peer is deleted due to keepalive expiration, it is placed in a release list and processed asynchronously. During this window, if userspace closes the socket, tcp_close() calls sock_orphan(), which sets sk->sk_socket to NULL. Subsequently, the releasing routine dereferences sk->sk_socket without checking, causing a NULL pointer dereference and crash.

Exploitation

An attacker who can trigger keepalive expiration for a peer and simultaneously cause userspace to close the associated socket can exploit this race condition. The attacker needs no special privileges beyond those required to establish the VPN connection and trigger the expiration (e.g., by ceasing keepalive responses). The race window is between peer deletion and socket release.

Impact

Successful exploitation results in a NULL pointer dereference in the kernel, leading to a system crash (denial of service). No privilege escalation or information disclosure is expected; the primary impact is denial of service.

Mitigation

This vulnerability is fixed in the Linux kernel commit [1] (b9142cf4e066c825ec68752a7dcaceda700bbe26). Users should apply the patch or update to a kernel version containing the fix. No workarounds are documented.

[1]: https://git.kernel.org/stable/c/b9142cf4e066c825ec68752a7dcaceda700bbe26

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
f998b2c4bec4

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAntonio QuartulliFeb 12, 2026Fixed in 6.18.14via kernel-cna
1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    
b9142cf4e066

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAntonio QuartulliFeb 12, 2026Fixed in 6.19.4via kernel-cna
1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    
94560267d6c4

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAntonio QuartulliFeb 12, 2026Fixed in 7.0via kernel-cna
1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    
94560267d6c4

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    
b9142cf4e066

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    
f998b2c4bec4

ovpn: tcp - don't deref NULL sk_socket member after tcp_close()

1 file changed · +13 2
  • drivers/net/ovpn/tcp.c+13 2 modified
    diff --git a/drivers/net/ovpn/tcp.c b/drivers/net/ovpn/tcp.c
    index f0b4e07ba9245a..ec2bbc28c19666 100644
    --- a/drivers/net/ovpn/tcp.c
    +++ b/drivers/net/ovpn/tcp.c
    @@ -199,7 +199,19 @@ void ovpn_tcp_socket_detach(struct ovpn_socket *ovpn_sock)
     	sk->sk_data_ready = peer->tcp.sk_cb.sk_data_ready;
     	sk->sk_write_space = peer->tcp.sk_cb.sk_write_space;
     	sk->sk_prot = peer->tcp.sk_cb.prot;
    -	sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +
    +	/* tcp_close() may race this function and could set
    +	 * sk->sk_socket to NULL. It does so by invoking
    +	 * sock_orphan(), which holds sk_callback_lock before
    +	 * doing the assignment.
    +	 *
    +	 * For this reason we acquire the same lock to avoid
    +	 * sk_socket to disappear under our feet
    +	 */
    +	write_lock_bh(&sk->sk_callback_lock);
    +	if (sk->sk_socket)
    +		sk->sk_socket->ops = peer->tcp.sk_cb.ops;
    +	write_unlock_bh(&sk->sk_callback_lock);
     
     	rcu_assign_sk_user_data(sk, NULL);
     }
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"Missing synchronization in ovpn_tcp_socket_detach() allows a NULL-pointer dereference of sk->sk_socket when userspace closes the TCP socket concurrently with peer release processing."

Attack vector

An attacker who can trigger keepalive expiration on an OpenVPN TCP peer (causing the peer to be placed on a release list) and simultaneously cause userspace to close the associated socket will create a race condition. The kernel's tcp_close() path calls sock_orphan(), which sets sk->sk_socket to NULL under sk_callback_lock. When the deferred release routine resumes and calls ovpn_tcp_socket_detach(), it dereferences sk->sk_socket without holding the lock, leading to a NULL-pointer crash. The attacker must be able to influence both the keepalive timeout and the socket close timing from userspace.

Affected code

The vulnerable function is ovpn_tcp_socket_detach() in drivers/net/ovpn/tcp.c. The original code unconditionally dereferenced sk->sk_socket->ops without holding sk_callback_lock, allowing a concurrent tcp_close() to set sk->sk_socket to NULL via sock_orphan() [patch_id=2661358].

What the fix does

The patch wraps the sk->sk_socket access in ovpn_tcp_socket_detach() with write_lock_bh(&sk->sk_callback_lock) / write_unlock_bh(&sk->sk_callback_lock). It also adds a NULL check before dereferencing sk->sk_socket->ops. This matches the lock already held by sock_orphan() when it sets sk->sk_socket to NULL, ensuring that the socket pointer cannot be cleared between the test and the access. The fix closes the race condition that previously caused a kernel crash [patch_id=2661358].

Preconditions

  • configThe OpenVPN TCP peer must be using TCP transport (not UDP)
  • inputThe peer must be placed on the release list via keepalive expiration (ovpn_peer_keepalive_work)
  • inputUserspace must close the associated TCP socket while the peer is still on the release list
  • inputThe attacker must be able to influence the timing of both the keepalive expiration and the socket close

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.