A use-after-free in Linux vsock allows an autobiound socket accepted via accept() to trigger an extra refcount decrement when bound to VMADDR_PORT_ANY, corrupting socket lifecycle state.
A newly discovered flaw in Linux allows attackers to crash computers or potentially take control of them through a specific type of network connection called vsock. Think of vsock as a special mailbox that virtual machines use to talk to each other securely.
The problem is a "use-after-free" bug, which works like this: imagine a librarian hands you a book, then accidentally destroys that book while you're still reading it. If you turn the page, you're accessing something that no longer safely exists. In this case, Linux is mismanaging a piece of memory — it frees up space that's still being used, and attackers can exploit that confusion.
Here's what makes this dangerous: the vulnerability sits in how Linux handles connections when a program doesn't specify which port to use. Linux picks one automatically, but it's not tracking that memory properly. An attacker can send specially crafted connection requests that trigger this forgotten-but-still-being-used memory, potentially corrupting data or running malicious code with kernel-level powers.
The people most at risk are those running Linux on virtual machines, especially in cloud environments where multiple virtual machines share hardware. Anyone using Docker containers, virtual private servers, or running Linux virtualization on their own machines could be affected.
The good news: no one is actively exploiting this yet, and the vulnerability requires local access or the ability to create network connections to the targeted system.
What you should do: First, check if your Linux system has updates available and install them promptly. Second, if you run cloud services or virtual machines, prioritize patching your Linux kernel. Third, limit network access to your systems to only trusted sources — don't leave vsock connections open to untrusted networks.
Want the full technical analysis? Click "Technical" above.
▶ Attack flow — CVE-2025-38618 · Use After Free
Vulnerability Overview
CVE-2025-38618 is a use-after-free in the Linux kernel's vsock subsystem, introduced by the autobind-to-VMADDR_PORT_ANY path. When a listening socket autobinds to VMADDR_PORT_ANY (0xFFFFFFFF) and a client connects, the socket returned by accept() inherits VMADDR_PORT_ANY as its local port. That accepted socket is not on the unbound sockets list, but the missing guard in __vsock_bind_connectible() allows a subsequent bind() call on it to proceed — decrementing the socket's refcount an extra time and producing a UAF primitive on the vsock_sock object.
CVSS 7.8 (local, high severity). No in-the-wild exploitation reported. Requires the ability to create and manipulate AF_VSOCK sockets, which is available to unprivileged users inside a VM guest.
Affected Component
Subsystem:net/vmw_vsock/af_vsock.c Function:__vsock_bind_connectible(), vsock_autobind(), vsock_accept() Kernel versions: See NVD. Regression relative to the fix in fcdd2242c023 ("vsock: Keep the binding until socket destruction").
Root Cause Analysis
The bug lives at the intersection of two behaviors: (1) autobind assigns VMADDR_PORT_ANY as a sentinel meaning "no port yet assigned", and (2) __vsock_bind_connectible() previously only blocked re-binding sockets already on the bound list, not sockets carrying the VMADDR_PORT_ANY sentinel.
The autobind path for a connecting socket:
/* net/vmw_vsock/af_vsock.c */
static int vsock_autobind(struct vsock_sock *vsk)
{
struct sockaddr_vm local;
/* Assign VMADDR_PORT_ANY — transport will allocate a real port later */
vsock_addr_init(&local, VMADDR_CID_ANY, VMADDR_PORT_ANY);
/* BUG: this marks the socket as "bound" with port == 0xFFFFFFFF */
return __vsock_bind(sock_net(sk_vsock(vsk)),
vsk, &local);
}
The vulnerable guard in __vsock_bind_connectible() before the patch:
static int __vsock_bind_connectible(struct vsock_sock *vsk,
struct sockaddr_vm *addr)
{
/* Retrieve the global unbound-sockets list lock */
spin_lock_bh(&vsock_table_lock);
/*
* Only reject rebind if the socket is already on the bound list.
* BUG: does NOT reject VMADDR_PORT_ANY — an accept()ed socket
* that inherited port 0xFFFFFFFF will pass this check even
* though it was never placed on the unbound list.
*/
if (!list_empty(&vsk->bound_table)) { // BUG: insufficient guard
spin_unlock_bh(&vsock_table_lock);
return -EINVAL;
}
/* Remove from unbound list — decrements refcount */
__vsock_remove_bound(vsk); // BUG: called on a socket
// not on the list;
// extra sock_put() here
vsock_insert_bound(vsk, addr->svm_port);
spin_unlock_bh(&vsock_table_lock);
return 0;
}
The accept() path that produces the dangling socket:
static int vsock_accept(struct socket *sock, struct socket *newsock, int flags,
bool kern)
{
struct vsock_sock *vconnected;
/* ... wait for connection ... */
vconnected = vsock_sk(connected);
/*
* The accepted socket inherits the listener's local port.
* If the listener autobiound to VMADDR_PORT_ANY, the accepted
* socket's svm_local.svm_port == VMADDR_PORT_ANY (0xFFFFFFFF).
* It is NOT added to vsock_unbound_sockets — only fully bound
* sockets go on vsock_bound_table.
*/
vsock_addr_init(&vconnected->local_addr,
vconnected->local_addr.svm_cid,
vsk->local_addr.svm_port); /* inherits 0xFFFFFFFF */
/* accepted socket is never inserted into unbound list */
return 0;
}
Root cause:__vsock_bind_connectible() failed to reject VMADDR_PORT_ANY as a bind target, allowing an accept()-returned socket that inherited the sentinel port to trigger __vsock_remove_bound() on a socket never placed on the unbound list, producing an extra sock_put() and a use-after-free on the vsock_sock object.
LIFECYCLE — NORMAL BOUND SOCKET:
socket() -> vsock_sock allocated, sk.refcnt = 1
bind() -> __vsock_remove_bound() (from unbound list, refcnt OK)
vsock_insert_bound()
close() -> sock_put() -> refcnt = 0 -> vsock_sk_destruct() -> kfree
LIFECYCLE — VULNERABLE AUTOBIND + ACCEPT PATH:
[listener] socket() refcnt=1, port=VMADDR_PORT_ANY
autobind() port stays 0xFFFFFFFF, NOT on unbound list
listen()
accept() ──────────────────────────────────────────────┐
│
[accepted] vsock_sock allocated, refcnt=1 │
inherits svm_port = 0xFFFFFFFF ◄────────────────────────┘
NOT inserted into vsock_unbound_sockets
[attacker] bind(accepted_fd, port=VMADDR_PORT_ANY)
-> __vsock_bind_connectible()
-> list_empty(&vsk->bound_table) == true (passes check)
-> __vsock_remove_bound(vsk)
-> list_del(&vsk->bound_table) // UB: not on list
-> sock_put(sk_vsock(vsk)) // EXTRA decrement
refcnt: 1 -> 0
-> vsock_sk_destruct() called EARLY
-> vsock_sock object FREED <── UAF object
[later] any operation on accepted_fd dereferences freed vsock_sock
OR transport layer callback fires on dangling pointer
SLAB STATE AFTER EARLY FREE:
[ kmalloc-vsock_sock slab ]
slot N: [ FREED vsock_sock ] <- refilled by next allocation
subsequent sock ops = type confusion / UAF
Exploitation Mechanics
EXPLOIT CHAIN (local privilege escalation from unprivileged VM guest):
1. Open AF_VSOCK socket, set SO_REUSEADDR.
sockfd = socket(AF_VSOCK, SOCK_STREAM, 0);
2. Bind listener to VMADDR_PORT_ANY:
addr.svm_port = VMADDR_PORT_ANY; // 0xFFFFFFFF
bind(sockfd, &addr, sizeof(addr));
// autobind fires: port stays 0xFFFFFFFF, socket NOT on unbound list
3. listen(sockfd, 5);
4. From a second thread/process: connect() to the listener's CID.
// Triggers vsock_accept() path in kernel
5. accept(sockfd, ...) -> acceptfd
// acceptfd->vsock_sock.local_addr.svm_port = 0xFFFFFFFF
// acceptfd's vsock_sock is NOT on any list
6. bind(acceptfd, VMADDR_PORT_ANY):
// Passes list_empty() guard (bound_table is empty)
// __vsock_remove_bound() -> sock_put() -> refcnt hits 0
// vsock_sk_destruct() fires -> vsock_sock slab slot freed
7. Heap spray: fill freed slab slot with controlled data
// e.g., via sendmsg() on another socket to groom kmalloc-N cache
// or via io_uring fixed buffer registration if slab sizes align
8. Trigger transport callback (e.g., virtio_transport_recv_pkt) on
acceptfd — now dereferences attacker-controlled vsock_sock.transport
pointer -> arbitrary function pointer call
9. Pivot to kernel RIP via fake transport->ops->release or
->notify_send_pre function pointer embedded in sprayed data.
Patch Analysis
// BEFORE (vulnerable): net/vmw_vsock/af_vsock.c
static int __vsock_bind_connectible(struct vsock_sock *vsk,
struct sockaddr_vm *addr)
{
spin_lock_bh(&vsock_table_lock);
if (!list_empty(&vsk->bound_table)) {
// Only blocks re-bind if already on bound list.
// Does NOT block VMADDR_PORT_ANY as a bind target.
spin_unlock_bh(&vsock_table_lock);
return -EINVAL;
}
__vsock_remove_bound(vsk);
vsock_insert_bound(vsk, addr->svm_port);
spin_unlock_bh(&vsock_table_lock);
return 0;
}
// AFTER (patched):
static int __vsock_bind_connectible(struct vsock_sock *vsk,
struct sockaddr_vm *addr)
{
spin_lock_bh(&vsock_table_lock);
if (!list_empty(&vsk->bound_table)) {
spin_unlock_bh(&vsock_table_lock);
return -EINVAL;
}
// NEW GUARD: explicitly reject VMADDR_PORT_ANY as a bind target.
// Prevents an accept()ed socket that inherited port 0xFFFFFFFF
// from passing through to __vsock_remove_bound().
if (addr->svm_port == VMADDR_PORT_ANY) {
spin_unlock_bh(&vsock_table_lock);
return -EINVAL;
}
__vsock_remove_bound(vsk);
vsock_insert_bound(vsk, addr->svm_port);
spin_unlock_bh(&vsock_table_lock);
return 0;
}
The fix is minimal and surgical: a single sentinel check before the unbound-list removal path. It mirrors the spirit of fcdd2242c023, which preserved binding across the socket lifetime to prevent analogous extra-sock_put() scenarios. The new guard closes the gap where VMADDR_PORT_ANY acted as a bypass token for the existing list_empty check.
Detection and Indicators
Kernel KASAN / KFENCE output — look for UAF reports in vsock_sk_destruct or vsock_transport dereference paths:
==================================================================
BUG: KASAN: use-after-free in vsock_sk_destruct+0x...
Read of size 8 at addr ffff888... by task poc/...
CPU: X PID: Y Comm: poc
Call Trace:
vsock_sk_destruct
__sk_destruct
sk_free
sock_put <-- double sock_put
__vsock_bind_connectible
vsock_bind
__sys_bind
==================================================================
The final bind() on the accepted socket with VMADDR_PORT_ANY is the trigger. No legitimate application performs this sequence; flag it as anomalous in seccomp/audit policy.
Remediation
Patch immediately: Apply the upstream fix that adds the VMADDR_PORT_ANY guard to __vsock_bind_connectible(). Verify your distribution kernel includes the commit.
Mitigate at the module level: If vsock is not required in the guest, unload vmw_vsock_virtio_transport / vmw_vsock_vmci_transport or add install vsock /bin/false to modprobe config.
Unprivileged user namespaces: Restricting AF_VSOCK socket creation to privileged users via LSM policy (SELinux socket vsock_socket create deny for unconfined users) reduces attack surface.
Enable KFENCE/KASAN in staging/test kernels to catch any related UAF regressions in the vsock bind/autobind path before promoting to production.
Audit vsock usage: Review whether guest workloads require VMADDR_PORT_ANY autobind semantics at all; if not, a local seccomp filter blocking bind(AF_VSOCK, port=0xFFFFFFFF) provides defense-in-depth.