Steam's I/O thread is holding a dead pointer
Transport-layer race condition in Valve's GameNetworkingSockets creates a remote UAF primitive that sits below session crypto and evades EDR telemetry.
Valve’s peer-to-peer networking stack has been broken for more than two months. The fault sits in the transport layer of GameNetworkingSockets, the library that backs Steam Datagram Relay and the SDR overlay that Source 2 titles, CS2, Dota 2, and a long list of third-party engines route session traffic through. The condition is a race between the packet ingestion thread and the connection state mutation path. The result is a use-after-free on the connection object during fragment reassembly. The exposure surface is anyone reachable over the SDR mesh - which, in practice, is anyone running the Steam client.
The bug class is CWE-362 leading to CWE-416. A classic compound race. GameNetworkingSockets handles datagrams on a dedicated I/O thread. That thread parses the SteamNetworkingMessage header, looks up the connection object by handle, and dispatches the fragment to the reassembly buffer. The connection object lifetime is managed by a reference count plus a destruction queue that runs on the service thread. When a peer sends a graceful close (k_ESteamNetConnectionEnd_AppException_Generic is the common one used in fuzzing), the connection enters k_ESteamNetworkingConnectionState_ClosedByPeer. The destruction queue schedules teardown. The reference is decremented. Under load, the I/O thread can still hold a raw pointer to the connection’s fragment reassembly context after the refcount hits zero. The window is microseconds. Reachable from a remote peer. No authentication of the close primitive beyond session keys that the attacker already holds as a connected peer.
This is not a user-space application bug. The flaw lives in the transport. Above it, the SteamNetworkingSockets API surface presents a TLS-equivalent handshake - Curve25519 ECDH, a session key derived per connection, AES-GCM frame encryption. The race fires before the decrypted frame reaches the application callback. The corrupted memory is the connection state structure itself. That means the TLS-equivalent boundary is irrelevant. The attacker is not breaking the crypto. The attacker is corrupting the object that holds the crypto state. The handshake completed. The session is authenticated. The bug is in what happens after.
Reachability matters here. SDR is not a direct UDP pinhole. Valve operates relay points-of-presence globally. A Steam client opens a session to the relay mesh, and peers find each other through Valve’s matchmaking and ticket service. The transport between two peers is, from the attacker’s view, a NAT-traversed UDP flow brokered by a relay the attacker does not control. That sounds like containment. It is not. Any account capable of joining a lobby, a community server, a friend’s session, or a workshop-hosted match is in a position to send fragmented messages to another peer’s GameNetworkingSockets stack. The relay terminates link encryption, not session encryption. The malformed fragment passes through. The race fires on the receiving client.
Exploitation primitive. A malicious peer establishes a session. The peer sends a stream of fragmented reliable messages - k_nSteamNetworkingSend_Reliable - interleaved with a connection close. The fragment reassembly path allocates a buffer on the connection’s heap arena. The close path frees the connection. The reassembly thread completes the write into the freed region. From a defender’s view it is one of two outcomes. The client crashes with a SIGSEGV inside SteamNetworkingSockets.dll or the corresponding Linux shared object. Or, with a controlled heap layout, the freed slot is reclaimed by a subsequent allocation the attacker shapes through legitimate-looking session traffic - channel allocation, lane buffers, message metadata - and the write lands on a structure with function pointers. The library has several. The IConnectionTransport vtable is one. Type confusion against a re-used slot gives the attacker control of a virtual call. From there, the standard renderer-style chain applies: build a read primitive by leaking pointers through subsequent message returns, locate a usable gadget in the loaded module, and pivot.
The MITRE mapping is T1190 in the loose sense - exploitation of a public-facing application - but it sits closer to T1210, exploitation of a remote service, because the Steam client is a long-running local service that brokers external sessions. Post-exploitation is T1055 if the attacker stays in-process, T1059 if they pivot to a shell via Steam’s helper processes, and T1573 for the C2 traffic which, conveniently, looks like more Steam traffic. The exploitation does not require user interaction in the click-a-link sense. It requires the user to be in a lobby with the attacker, or in a public match, or on a workshop server that routes session traffic through the same stack.
Real-world exploitation status. Public crash reports on the GameNetworkingSockets GitHub issue tracker date back more than two months and describe segfaults during fragment reassembly under reliable message flooding. The reproducer is a fuzz harness. Valve’s response on the public tracker has been silent. The library is open source. The patch boundary is not yet drawn because the patch has not yet shipped. Source 2 titles using the bundled steam_api64.dll are exposed. Third-party engines that statically linked GameNetworkingSockets ship the bug embedded in their binary. Godot, Unreal projects using the Steamworks plugin, and any title vendoring the library have to patch independently. None of them will, because none of them know yet.
Telemetry reality. This is where the persona of the bug becomes a problem for defenders. The exploitation traffic is encrypted inside the Steam session and tunneled through the SDR relay. From the network sensor’s perspective, the flow is UDP to a valve.net relay POP. No JA3 to fingerprint. No TLS ClientHello to inspect. Suricata sees encrypted UDP. Zeek logs a udp connection with no protocol parser. Palo Alto and Fortinet App-ID classify it as steam-base. The malicious payload is indistinguishable from a multiplayer match. Network-side detection is not viable.
Endpoint telemetry is thinner than it should be. The exploitation occurs inside steamwebhelper.exe or the game process that statically linked the library. Sysmon Event ID 7 fires when GameNetworkingSockets is loaded - once, at game launch. Event ID 10, process access, will fire if the post-exploitation step touches another process. The exploitation itself produces no Sysmon event. The crash variant produces an Application Error in the Windows Application log with a faulting module of steamnetworkingsockets.dll, a faulting offset that lands inside the reassembly function, and an access violation code. That is the most reliable signal. Most SOCs do not forward Application errors from gaming-class processes. Most EDR products treat Steam-tree processes as low-priority noise. CrowdStrike, SentinelOne, and Defender for Endpoint will all log the crash. None will alert on it by default. The detection gap is policy, not capability.
For detection engineering, the actionable correlation is application crashes in Steam-tree binaries paired with inbound network activity in the seconds preceding the crash. A rule that fires on Application Error event 1000 with a faulting module matching steamnetworkingsockets or gamenetworkingsockets, joined to a netflow record showing a session to an SDR relay in the previous thirty seconds, is the cheapest signal available. It will not catch the silent exploitation variant. It will catch the crashes that come from imperfect heap grooming, which is most of them.
The residual exposure after a patch ships is wide. GameNetworkingSockets is vendored across an ecosystem that does not update on Valve’s schedule. Titles that shipped a year ago and will never patch carry the bug for the life of the binary. Workshop content that brokers sessions through the library has no update path at all. The patch boundary, when it lands, will be a version bump in the public GitHub repository and a silent update to the Steam client. The downstream consumers will lag by months or never.
The technical reality is that a transport-layer race condition in a library this widely deployed is a credential-less, low-interaction, remote memory corruption primitive that bypasses the session-layer crypto by sitting underneath it, evades network inspection by being tunneled through a vendor relay, and lives in a process class that defenders have trained their EDR to ignore. The bug has been public for more than two months. The patch is not out. The detection signal exists but is not collected. That is the exposure.
Keep Reading
heap exploitationSandi Metz's 2016 essay is a bug-class taxonomy
The 2016 'wrong abstraction' rule read as a memory-corruption bug class: use-after-free, type confusion, double-free, from DirtyCOW to Dirty Pipe and DirtyCred.
embedded securityNo mitigations, full code execution
Memory corruption in shared embedded firmware components grants attacker code execution at firmware privilege, replicated across hundreds of vendor devices.
IOCCC 2025IOCCC 2025 ships glibc tcache poisoning primitives
The 29th IOCCC's 2025 winners distill heap metadata corruption, tcache poisoning, and type confusion into legal C - the same primitives behind modern CVEs.
Stay in the loop
New writing delivered when it's ready. No schedule, no spam.