Merge bitcoin/bitcoin#36169: http: Use SO_EXCLUSIVEADDRUSE on Windows
What changed, and why it matters
This update fixes a Windows-only security hole in Bitcoin Core's built-in web server. Previously, the software allowed another program running on the same computer to grab the same network port and potentially steal the secret RPC password from incoming connections. The patch makes the port exclusive on Windows and adds a test to confirm no other process can hijack it.
Apply the merge. Ensure Windows nodes upgrade to a build containing this commit. Operators running RPC on shared/multi-user Windows hosts should treat prior versions as locally vulnerable to credential theft and privileged RPC proxying.
Security signals we found
Credential interception via shared local port binding
Local privilege boundary crossing
Windows-specific socket option misuse
Regression from libevent's prior guard against SO_REUSEADDR on Windows
Functional test added to enforce port exclusivity
Evidence from the diff
On Windows, SO_REUSEADDR does not prevent another process from binding to the same address/port. Bitcoin Core’s HTTPServer::BindAndStartListening() previously enabled SO_REUSEADDR unconditionally, so a local attacker could bind a competing socket to the RPC port, accept connections, and capture HTTP Basic Authorization headers (including cookie credentials). The patch replaces SO_REUSEADDR with SO_EXCLUSIVEADDRUSE on Windows, preserving restart-friendly binding while preventing port sharing. Non-Windows behavior remains unchanged. A functional test verifies that a second process cannot bind the port even with SO_REUSEADDR set.
Changed components
src/httpserver.cpptest/functional/interface_http.pyInspect captured patch +25 / −0
### src/httpserver.cpp
@@ -722,6 +722,17 @@ util::Expected<void, std::string> HTTPServer::BindAndStartListening(const CServi
NetworkErrorString(WSAGetLastError()))};
}
+#ifdef WIN32
+ // Prevent another application from binding to the same address and port and
+ // intercepting RPC credentials.
+ // SO_REUSEADDR on Windows is non-exclusive so another process could bind to
+ // the same port.
+ if (sock->SetSockOpt(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
+ return util::Unexpected{strprintf("Cannot set SO_EXCLUSIVEADDRUSE on %s listen socket: %s",
+ to.ToStringAddrPort(),
+ NetworkErrorString(WSAGetLastError()))};
+ }
+#else
// Allow binding if the port is still in TIME_WAIT state after
// the program was closed and restarted.
if (sock->SetSockOpt(SOL_SOCKET, SO_REUSEADDR, &SOCKET_OPTION_TRUE, sizeof(SOCKET_OPTION_TRUE)) == SOCKET_ERROR) {
@@ -730,6 +741,7 @@ util::Expected<void, std::string> HTTPServer::BindAndStartListening(const CServi
to.ToStringAddrPort(),
NetworkErrorString(WSAGetLastError()));
}
+#endif
// some systems don't have IPV6_V6ONLY but are always v6only; others do have the option
// and enable it by default or not. Try to enable it, if possible.
### test/functional/interface_http.py
@@ -134,6 +134,7 @@ def run_test(self):
self.node.reuse_http_connections = False
self.check_default_connection()
+ self.check_socket_exclusivity()
self.check_keepalive_connection()
self.check_close_connection()
self.check_excessive_request_size()
@@ -174,6 +175,18 @@ def check_default_connection(self):
assert conn.sock_closed()
+ def check_socket_exclusivity(self):
+ self.log.info("Checking that another process cannot bind the HTTP listen port")
+ url = urllib.parse.urlparse(self.node.url)
+ with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as competing_listener:
+ competing_listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+ # Ill-configured sockets permit port reuse unless the original
+ # listener requested exclusive address use.
+ assert_raises(
+ OSError,
+ lambda: competing_listener.bind((url.hostname, url.port)))
+
+
def check_keepalive_connection(self):
self.log.info("Checking keep-alive connection persistence")
conn = BitcoinHTTPConnection(self.node)Why this scored 69/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.