Audit report AR-37
Reported privately in an independent security review, 2026-09-18.
- Report date
- 2026-09-18
- Kind
- Bug
- Status on master
- Not applicable
- Proposed issue state
- Closed
Summary
On Windows, a stale Unix-domain socket file left behind by an unclean exit would not be removed on the next start, because Microsoft's std::filesystem does not report such a path as a socket, so binding the IPC socket would fail. Bitcoin Core does not build IPC on Windows today (ENABLE_IPC is forced off), so no supported build is affected, and the report was withdrawn for that reason. Not applicable now; relevant once Windows IPC support lands.
The report
ProcessImpl::bind() in src/ipc/process.cpp removes an existing path before binding only if fs::symlink_status(path).type() == fs::file_type::socket, so that a stale socket is replaced while any other file at the configured path is left alone. Microsoft's filesystem implementation does not classify Windows AF_UNIX socket files (which are reparse points) as sockets, so the stale endpoint is not removed and the bind fails. Removing whatever is at the path is not safe either, since -ipcbind accepts custom paths where an ordinary file, symlink or directory might sit.
The report needs Windows, the optional -ipcbind listener, and an unclean exit that leaves the socket file behind; the next start then fails to bind until the operator deletes the file by hand. No remote trigger, authorization bypass or data corruption was shown.
Its test in src/ipc/test/ipc_tests.cpp checks that a stale socket is replaced and that a directory at the endpoint survives. That test runs only on POSIX, and its non-socket fixture is a directory, not a Windows socket file.
Proposed change
On Windows, open the path as a reparse point and remove it only if its reparse tag is IO_REPARSE_TAG_AF_UNIX; keep the existing check on POSIX:
+static bool IsUnixSocket(const fs::path& path)
+{
+#ifdef WIN32
+ HANDLE handle{CreateFileW(path.c_str(), FILE_READ_ATTRIBUTES, ...,
+ OPEN_EXISTING, FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, nullptr)};
+ ...
+ FILE_ATTRIBUTE_TAG_INFO info{};
+ GetFileInformationByHandleEx(handle, FileAttributeTagInfo, &info, sizeof(info))
+ ...
+ return (info.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0 && info.ReparseTag == IO_REPARSE_TAG_AF_UNIX;
+#else
+ return fs::symlink_status(path).type() == fs::file_type::socket;
+#endif
+}
@@ ProcessImpl::bind
- if (fs::symlink_status(path).type() == fs::file_type::socket) {
+ if (IsUnixSocket(path)) {
fs::remove(path);
The report rejected removing the endpoint unconditionally because it could delete an unrelated path chosen by the operator.
Assessment
Not reachable on Bitcoin Core master: cmake_dependent_option(ENABLE_IPC ... ON "NOT WIN32" OFF), and src/ipc is only built when that option is on. process.cpp also still includes POSIX socket headers unconditionally, so the patch cannot be compiled on Windows as is. The report was withdrawn on that basis; that is a statement about current reachability, not about the idea.
The in-progress Windows IPC port (bitcoin#32387) changes the same check differently: under #ifdef WIN32 it also treats an empty regular file as a stale socket (fs::is_regular_file(path) && fs::file_size(path) == 0), since sockets show up that way to std::filesystem on Windows. The reparse-tag check proposed here is stricter, because an empty regular file at the path could be something else, and is worth comparing when that port is reviewed.
No stability effect today and no bug page. Related POSIX behaviour, replacing a live socket: AR-30.
Status
Not applicable to any supported build. Next step: consider the reparse-tag check during review of the Windows IPC port.
Reported privately in an independent security review on 2026-09-18.