DuplicateHandle works on sockets, mostly | purplesyringa's blog<br>DuplicateHandle works on sockets, mostly<br>August 19, 2026WinAPI represents most “things” as HANDLEs: files, cross-process mutexes, threads, jobs, etc. Sockets, however, are a notable exception: while on Unix systems they’re no different from files, Windows denotes them by a different type: SOCKET.The reason for this is that sockets are a userland abstraction provided by Winsock2, rather than a kernel primitive. At least on paper. As such, operating on them as on handles is incorrect. For example, MSDN says that even if casting a SOCKET to HANDLE and calling DuplicateHandle works, it messes up some refcounts.This post explores just how far this difference lies, and when you can get away with using handle methods for sockets when you have to.DuplicateHandle<br>Today I’ll focus on DuplicateHandle in particular, because it’s by far the most painful API to avoid. It’s the only way to freely move handles across processes in WinAPI:BOOL DuplicateHandle(<br>[in] HANDLE hSourceProcessHandle,<br>[in] HANDLE hSourceHandle,<br>[in] HANDLE hTargetProcessHandle,<br>[out] LPHANDLE lpTargetHandle,<br>[in] DWORD dwDesiredAccess,<br>[in] BOOL bInheritHandle,<br>[in] DWORD dwOptions<br>);<br>DuplicateHandle takes source and target process handles, so you can not only inject handles into other processes, but also steal them. I use this heavily in my IPC library, which allows processes to send and receive handles through streams, UDS-style. I inject handles into a broker process on send and steal them from the broker on receive. For instance, this allows a work-stealing process pool to operate on handles.Winsock2 does offer a function to duplicate sockets – WSADuplicateSocketW – but its API is much more limited than DuplicateHandle. Compare:int WSAAPI WSADuplicateSocketW(<br>[in] SOCKET s,<br>[in] DWORD dwProcessId,<br>[out] LPWSAPROTOCOL_INFOW lpProtocolInfo<br>);<br>First, it doesn’t take a source process, so you can’t steal sockets. (Technically you can use CreateRemoteThread, but it’s less reliable, slow, and requires synchronization and more privileges.) That means that you can’t use the broker pattern and need to know which process you’re passing the socket to ahead of time.Second, it takes the PID of the target process, not the handle, which means it has to reopen the process, asserting the PROCESS_DUP_HANDLE access right. This goes against the principle of least privilege – normally you can open a process and then drop the PROCESS_DUP_HANDLE right without invalidating already open process handles, or pass the handle to a less privileged context, but here you can’t do that. (It’s also racy unless you protect against PID reuse by keeping a process handle around.)My goal for today is to achieve the effect of WSADuplicateSocketW through a low-level interface that can be made to work like DuplicateHandle.How it’s made<br>The reason DuplicateHandle is (said to be) broken is that sockets are a userland abstraction. But what does that really mean?Windows supports custom socket implementations, called Layered Service Providers, used by e.g. some proxies. There are two types of LSPs: IFS (installable file system) and non-IFS. IFS sockets are real OS-level handles, while non-IFS sockets are purely userland-side. Non-IFS LSPs are a nightmare: they work by redirecting Winsock2 methods in userland, like WSARecv, which means that they completely fail when passed to native APIs, like ReadFile. WSADuplicateSocketW for such sockets works because it’s one of the methods they redirect.But it seems like Microsoft got fed up with crashes from broken non-IFS implementations, and LSPs have been deprecated entirely for 10 years now. So we can reasonably assume all sockets to be IFS-style native handles. In fact, multiple popular programs already do so: Rust’s tokio treats all sockets as IFS handles, and Zig uses a networking implementation that bypasses Winsock2 entirely.Even MSDN mentions that SOCKET values can be accepted by some handle APIs:The GetHandleInformation function can be used to determine if a socket handle was created with the WSA_FLAG_NO_HANDLE_INHERIT flag set.<br>So why shouldn’t DuplicateHandle be used for SOCKETs? Well, even though most socket details are maintained by the kernel, Winsock2 still has some internal bookkeeping. Winsock2 keeps a mapping from socket handles to that data, and creating sockets without going through Winsock2 doesn’t populate it, which can cause issues. Similarly, closing socket handles directly doesn’t free it.Bookkeeping<br>We can inspect the data stored in userland by looking closer at the WSADuplicateSocketW API. The official way to pass sockets across processes involves running WSADuplicateSocketW to obtain a blob representing a socket, and then reassembling it on the other end with WSASocketW. Given that no one would trust userland to pass across kernel data, we can speculate that this blob contains exactly the userland accounting data, plus some way to access the kernel handle.Since...