http: Use SO_EXCLUSIVEADDRUSE on Windows #36169

pull hodlinator wants to merge 2 commits into bitcoin:master from hodlinator:2026/09/http_exclusive_socket changing 2 files +25 −0
  1. hodlinator commented at 8:33 PM on September 4, 2026: contributor

    Problem

    HTTPServer::BindAndStartListening() unconditionally enables SO_REUSEADDR before binding the RPC listener. On Windows, a reuse-enabled listener does not reserve the port exclusively: another local process can request SO_REUSEADDR and bind to the same port (see https://learn.microsoft.com/en-us/windows/win32/winsock/using-so-reuseaddr-and-so-exclusiveaddruse).

    If the competing socket receives a new connection, it can capture the HTTP Basic Authorization header (including the cookie credential) and proxy or issue privileged RPC calls as the victim. This crosses a local-user boundary and can expose wallet-controlling RPC credentials.

    Fix

    Have Windows use SO_EXCLUSIVEADDRUSE instead which makes the port exclusive to the process which first requests it, while retaining the restart-friendly behavior which SO_REUSEADDR enabled. Abort if another process is already bound to the port.

    Further context & rationale

    This issue is new in our homegrown HTTP server implementation, since libevent had a guard against setting SO_REUSEADDR on Windows, see evutil_make_listen_socket_reuseable() https://github.com/libevent/libevent/blob/d82464a277d0f42703702c4dfd9af6af38595a83/evutil.c#L483. libevent does not reference SO_EXCLUSIVEADDRUSE.

    Why should we not just avoid SO_REUSEADDR on Windows and skip SO_EXCLUSIVEADDRUSE like the libevent approach? Because setting either option makes the process less prone to failing to bind to a port after having been restarted. Not sure why this wasn't an issue before, maybe the node startup was usually slow enough to time out the port before we tried to re-bind it on Windows.


    Discovered by Project Loupe.

  2. windows: Use SO_EXCLUSIVEADDRUSE over SO_REUSEADDR
    The latter allows other processes to bind to the same socket and intercept traffic on this platform.
    af65069fd1
  3. qa: Verify HTTP listen port exclusivity bcb09b3f4a
  4. DrahtBot added the label RPC/REST/ZMQ on Sep 4, 2026
  5. DrahtBot commented at 8:33 PM on September 4, 2026: contributor

    <!--e57a25ab6845829454e8d69fc972939a-->

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    <!--006a51241073e994b41acfe9ec718e94-->

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/36169.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

    See the guideline and AI policy for information on the review process.

    Type Reviewers
    ACK sedited, pinheadmz, jeanpablojp
    Concept ACK winterrdog

    If your review is incorrectly listed, please copy-paste <code>&lt;!--meta-tag:bot-skip--&gt;</code> into the comment that the bot should ignore.

    <!--5faf32d7da4f0f540f40219e4f7537a3-->

  6. sedited added this to the milestone 32.0 on Sep 5, 2026
  7. sedited commented at 9:13 AM on September 5, 2026: contributor

    Can you add the relevant snippet from libevent (evutil_make_listen_socket_reuseable?) and its previous call graph to the pull request description?

  8. hodlinator commented at 12:12 PM on September 5, 2026: contributor

    Updated with more context regarding previous HTTP implementation.

    Started adding call graph information from https://github.com/bitcoin/bitcoin/blob/31.x/src/httpserver.cpp#L343 out into libevent but as setting the port for reuse seems fully disabled on Windows I decided that was what was the actually relevant thing. Let me know if you prefer further changes.

  9. pinheadmz commented at 12:50 PM on September 5, 2026: member

    concept ACK Thanks for taking this on and @instagibbs for the report.

  10. hodlinator commented at 3:15 PM on September 5, 2026: contributor

    Amended the PR desc further, reasoning around why we want to diverge from the socket behavior libevent had on Windows.

  11. winterrdog commented at 4:37 PM on September 5, 2026: contributor

    Concept ACK

    this is a great find

  12. sedited approved
  13. sedited commented at 8:55 PM on September 5, 2026: contributor

    utACK bcb09b3f4aec73b5e17d1266ec21983d7add118c

    Couldn't get emulation into a state to test this locally, but did a CI run with just the functional test changes: https://github.com/sedited/bitcoin/actions/runs/33968452903.

    Because setting either option makes the process less prone to failing to bind to a port after having been restarted. Not sure why this wasn't an issue before, maybe the node startup was usually slow enough to time out the port before we tried to re-bind it on Windows.

    I'm not following here; how does this manifest, and why would it be less prone to failing to bind? The Application Strategies section in the microsoft learn post you linked makes me think it might make it a bit harder to re-bind, or have no effect on that at all.

  14. DrahtBot requested review from winterrdog on Sep 5, 2026
  15. DrahtBot requested review from pinheadmz on Sep 5, 2026
  16. pinheadmz commented at 9:37 PM on September 5, 2026: member

    A malicious program can use SO_REUSEADDR to forcibly bind sockets already in use for standard network protocol services in order to deny access to those service. No special privileges are required to use this option.

    OMG windows

  17. hodlinator commented at 9:59 PM on September 5, 2026: contributor

    re #36169#pullrequestreview-5122973979:

    Because setting either option makes the process less prone to failing to bind to a port after having been restarted. Not sure why this wasn't an issue before, maybe the node startup was usually slow enough to time out the port before we tried to re-bind it on Windows.

    I'm not following here; how does this manifest, and why would it be less prone to failing to bind? The Application Strategies section in the microsoft learn post you linked makes me think it might make it a bit harder to re-bind, or have no effect on that at all.

    When processes restart sockets they've used they can end up in TIME_WAIT state. This can be experienced by removing the SO_REUSEADDR logic entirely from master and running the functional tests on Linux. New bitcoind instances fail to bind their sockets after the old process using it has gone away.

    For some reason disabling the SO_REUSEADDR code like libevent did on Windows did not introduce such issues with TIME_WAIT when reusing ports on that OS in the functional tests. Maybe it's more forgiving than Linux when it notices it's the same .EXE restarting or something.

    From looking at the tables showing the results of two processes binding to the same port with varying options, it appears that post-Windows Server 2003^1 the main benefit of SO_EXCLUSIVEADDRUSE over not setting any options is that another process under the same user account cannot hijack the socket's traffic using SO_REUSEADDR.

  18. pinheadmz commented at 10:04 PM on September 5, 2026: member

    My understanding is that SO_EXCLUSIVEADDRUSE only provides the process-exclusive security and does not provide the quick restart feature linux provides for SO_REUSEADDR. If thats correct this PR is actually restoring our original behavior after temporarily allowing (on master) the quick restart feature on windows.

    I tried to catch a binding error on windows by quickly restarting while an HTTP connection is open and waiting for a response, but I couldn't manage it manually

  19. pinheadmz commented at 10:05 PM on September 5, 2026: member

    ACK bcb09b3f4aec73b5e17d1266ec21983d7add118c

    Read the microsoft docs and reviewed the code changes. Built and tested on windows 10 with msvc (running in virt-manager on debian). Test fails on master, passes on branch.

    <details><summary>Show Signature</summary>

    -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    ACK bcb09b3f4aec73b5e17d1266ec21983d7add118c
    -----BEGIN PGP SIGNATURE-----
    
    iQJPBAEBCAA5FiEE5hdzzW4BBA4vG9eM5+KYS2KJyToFAmqckeIbFIAAAAAABAAO
    bWFudTIsMi41KzEuMTIsMCwzAAoJEOfimEtiick6wQ0P/i3xQiAXNTNNZmi9jXBN
    SdktrrroklRfGs0+lNoTs1LmqQr+vggCLNd7zi1vM7KRRpiIQpBk2kj1DkVxhczR
    dIzmmHj3EUpmY9keBNvhxJjZr/NBE1j8M02QY4upABVol4MohV1O+klr73HNlxgJ
    /Y/WyVKnywBuNORyZ/qkmHaVADmsECAE+wEEM1dtEuPD2u79iGlDIUtogzPUhpTj
    15wfwWJw0jZuBwTnh7I8wmb6Teki6168uAq4Zt///j+8U67oAgnv7d/LL7Wxb2IP
    XFva7YvyjkNthEUag73gkISJLyQW7xc+1fZQcZtIpqqxzGLRS3E4Q0zrRK9+zTeC
    CWap0iBVCAY4yMbi8uWKqiRMhPj6KTo50TG2TaQ3CCv+EIvqTxYAxvOpYmnr4KD2
    MnrK8ZMkTmpkC2GOHLFlmTuYatSKhRpdxaSO4VWCQCZ188W2qaOkmfHHi25cB7Un
    bo0I2pCOHkUiiw5XhJevRFO+oe38k78csH7qhU/rYai7TIFXdYUaCw4QKKTGXCuw
    CzmRC3b3+tooF6vJaY1EwDLCYCu67AZmGnQ5/vkiGB1+7pnk8FF+0jZVPInrAZWy
    O/s8YyLf7hkflFmasTWmL1h+gBjwhkXYaX3rPcq8m1ApirOkLC3y8hGCaD+0O4jZ
    ezFM6u7BGLIsCsnaZqvikT/c
    =4uoj
    -----END PGP SIGNATURE-----
    

    pinheadmz's public key is on openpgp.org

    </details>

  20. jeanpablojp commented at 11:12 PM on September 5, 2026: contributor

    tACK bcb09b3f4aec73b5e17d1266ec21983d7add118c

    nit: Exclusivity is per socket and the default bind list has two endpoints, ::1 and 127.0.0.1. InitHTTPServer warns per failed endpoint and only gives up when all of them fail, which is the old behaviour and covers anyone who has only IPv4 or only IPv6. I put a listener on 127.0.0.1 before starting the node. It started, warned in debug.log and served on ::1 only, and since bitcoin-cli's -rpcconnect defaults to 127.0.0.1, the next call handed its Authorization header to the other process.

    With -rpcbind and one endpoint the node does abort. Does requiring both endpoints to bind make any sense on Windows?

  21. sedited merged this on Sep 6, 2026
  22. sedited closed this on Sep 6, 2026

  23. winterrdog commented at 11:51 AM on September 6, 2026: contributor

    post-merge crACK bcb09b3f4aec73b5e17d1266ec21983d7add118c

    also, i found this issue helpful while looking into this: https://github.com/python-trio/trio/issues/39

    it's kind of quirky how Windows chose to handle socket reuse, initially


pinheadmz

Milestone
32.0


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-09-08 12:50 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me