Audit report AR-30
Reported privately in an independent security review, 2026-09-03.
- Report date
- 2026-09-03
- Kind
- Design
- Status on master
- Present
- Proposed issue state
- Open
Summary
When bitcoin-node binds an IPC Unix socket, it first removes any existing socket at that path, even one that a running process is still listening on. A second node configured with the same path takes over the address for new clients, while the first keeps its existing connections. The report treats this as a local availability problem; in discussion, ryanofsky pointed out that the current behaviour can be useful, and the report was withdrawn. Open design question, not a bug.
The report
Process::bind() in src/ipc/process.cpp removes an existing Unix socket path before calling bind(). Starting another IPC server with the same address can therefore unlink a live listener's socket, take over the pathname for new clients, and leave the original server reachable only through connections it had already accepted.
The report rates this as a low, local availability impact that crosses no authorization boundary: a second process needs the same filesystem permission it would need to unlink or replace the socket directly, and -ipcbind already asks for a path with secure permissions because socket access grants unauthenticated RPC access. One such bind isolates the original listener from new clients until it restarts; no network trigger, privilege escalation or funds effect was shown.
The reproduction is a unit test in src/ipc/test/ipc_tests.cpp against the real Process::bind(). It starts a live listener and records that a second bind replaces it, and separately checks that an unowned stale socket is still recovered.
Proposed change
Try bind() before touching the path. If the address is in use and the path is a socket, probe it with connect(): keep it if a listener accepts, and remove the path and retry only when the probe fails with ECONNREFUSED, the ordinary stale-socket case:
- if (fs::symlink_status(path).type() == fs::file_type::socket) {
- fs::remove(path);
- }
@@
if (::bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
return fd;
}
- int bind_error = errno;
+ int bind_error{errno};
+ if (addr.sun_family == AF_UNIX && bind_error == EADDRINUSE && fs::symlink_status(path).type() == fs::file_type::socket) {
+ ...
+ } else if (::connect(probe_fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) {
+ // live listener: keep it, fail the bind
+ } else {
+ ...
+ if (connect_error == ECONNREFUSED) {
+ fs::remove(path);
+ if (::bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == 0) return fd;
There is still a small race between the probe and the removal.
Assessment
Present on Bitcoin Core master: if (fs::symlink_status(path).type() == fs::file_type::socket) { fs::remove(path); } in ProcessImpl::bind.
ryanofsky, replying to the report, questioned whether the change is an improvement. Replacing a live socket lets a new node start before the old one stops: existing mining clients stay on the old node while new clients reach the new one, "and there's not really any downtime in between". The case can only arise when two nodes are explicitly given the same custom path, because the default path is inside the data directory, which the node locks. He suggested that if anything changes it could be an option, such as -ipcbind=unix:/path/to/node.sock,overwrite. The report was withdrawn after that exchange. Withdrawal means it is not a security priority; it does not settle the intended behaviour.
No stability effect and no bug page. Related Windows variant: AR-37, about stale sockets not being removed on Windows.
Status
Present on master, behaviour arguably intended. Next step, if wanted: decide between keeping the current replace-on-bind behaviour and documenting it as an upgrade path, or refusing to replace a live listener by default with an explicit overwrite option. No issue open.
Reported privately in an independent security review on 2026-09-03.