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
1Patches
6f998b2c4bec4ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
b9142cf4e066ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
94560267d6c4ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
94560267d6c4ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
b9142cf4e066ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
f998b2c4bec4ovpn: tcp - don't deref NULL sk_socket member after tcp_close()
1 file changed · +13 −2
drivers/net/ovpn/tcp.c+13 −2 modifieddiff --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
3News mentions
0No linked articles in our index yet.