http: Require default IPv4 bind to succeed (again) #36187

pull hodlinator wants to merge 4 commits into bitcoin:master from hodlinator:2026/09/req_all_binds changing 3 files +106 −12
  1. hodlinator commented at 7:51 PM on September 7, 2026: contributor

    Problem

    When bitcoind starts and the HTTP IPv4 listen address is occupied by another process, but the IPv6 address is available, the server fails open and generates a cookie file. When bitcoin-cli is run it picks up the cookie file and sends the credentials in plain text to the non-bitcoind process running on the default IPv4 port. An attacker can then use the credentials to commandeer bitcoind over IPv6.

    Solution

    Safe defaults:

    • When running with default settings, we now require that the IPv4 bind succeeds.
    • When -rpcbind is specified, we now require all HTTP listen socket binds to succeed (fail closed-behavior).

    Node runners who only have IPv6 are now required to override -rpcbind to only specify the working interface if they were not doing so already.

    The problem of bitcoin-cli sending the credentials to what could possibly be the wrong process remains in non-default setups.

    History

    c1d79812f428860e6f624835851d6f3ecd86bbb3 (#1822) from 2012 seems to have introduced this kind of fail open-behavior, making IPv4 continue even though IPv6 was not available (what we want to return to). Later changes made IPv4/IPv6 equivalent, only requiring one.

    #14968 from 2018 was an attempt to change the behavior to require all binds to succeed. But it ran into issues with poor IPv6 support on CI. Notable is that towards the end they wanted to still allow fail-open for the default bind addresses, but ran into issues with libevent.

    Severity

    This credential exfiltration attack requires capability to launch processes on the bitcoind host machine. If we are on the same account we can already read the cookie credentials off disk. So it's only really interesting when an attacker is on a different user account on the same machine.

  2. DrahtBot added the label RPC/REST/ZMQ on Sep 7, 2026
  3. DrahtBot commented at 7:51 PM on September 7, 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/36187.

    <!--021abf342d371248e50ceaed478a90ca-->

    Reviews

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

    Type Reviewers
    Concept ACK l0rinc, janb84

    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-->

  4. l0rinc commented at 7:55 PM on September 7, 2026: contributor

    Concept ACK, thanks for pushing the fix

  5. hodlinator marked this as a draft on Sep 7, 2026
  6. hodlinator renamed this:
    http: Require all binds to succeed
    http: Require default IPv4 bind to succeed (again)
    on Sep 7, 2026
  7. hodlinator commented at 8:07 PM on September 7, 2026: contributor

    For some reason thought that we weren't enabling the HTTP subsystem by default. Going to re-work the code slightly. Put in draft.

  8. test: characterize partial HTTP binds
    When the IPv4 RPC address is already owned, the server starts on its remaining IPv6 listener for both default and explicit bind configurations. bitcoin-cli then sends the generated cookie to the other process on its default IPv4 destination.
    
    Record the partial startup and exact captured Authorization credential for the following fail-closed fix.
    376e758991
  9. test: Remove attempt to bind RPC to 127.1.1.1
    The next commit requires all binds to succeed, and without this change that one would Mac CI would fail with:
    [InitHTTPServer] [warning] Binding RPC on address 127.1.1.1:19036 failed: Unable to bind to 127.1.1.1:19036 on this computer (bind returned error Can't assign requested address (49))
    
    The options in this test are there to verify log censorship of sensitive data and bind-addresses were made non-sensitive in #26829 / b9d567454159f062ce84353f5821d6e6daf433bd. That commit added the missing -rpcallowip option to actually make -rpcbind not be ignored any longer. That commit added 127.0.0.1 in addition to the already existing 127.1.1.1, probably because the latter would fail by itself.
    c309e5fb73
  10. hodlinator force-pushed on Sep 7, 2026
  11. DrahtBot added the label CI failed on Sep 7, 2026
  12. DrahtBot removed the label CI failed on Sep 7, 2026
  13. hodlinator marked this as ready for review on Sep 8, 2026
  14. http: fail closed on partial binds
    When any RPC bind succeeds, initialization ignores every other endpoint failure. A process that already owns the default IPv4 port can therefore receive bitcoin-cli credentials while the node continues on IPv6.
    
    Switch to safe defaults through only making the default IPv6 address optional, requiring all others. Hosts intentionally providing only an IPv6 interface must use -rpcbind to set that endpoint explicitly.
    
    This carries the fail-closed policy from bitcoin/bitcoin#14968 now that the in-tree socket implementation reports each bind result.
    
    Co-authored-by: laanwj <126646+laanwj@users.noreply.github.com>
    Co-authored-by: Lőrinc <pap.lorinc@gmail.com>
    58eebaa3f4
  15. http: Only log *unique* attempts to bind to *resolved addresses*
    Also:
    * Document why it's not worth logging endpoints_seen collisions
    * Make lookup error more explanatory (helps explain why this fails: bitcoind -rpcbind=localhost -rpcallowip=127.0.0.1)
    64fd5cef0c
  16. hodlinator force-pushed on Sep 8, 2026
  17. in test/functional/rpc_bind.py:123 in 64fd5cef0c
     118 | +                self.cleanup_partially_started_nodes()
     119 | +        self.nodes[0].rpchost = None
     120 | +
     121 | +        assert_equal(started, False)
     122 | +        assert_equal(credential_captured, False)
     123 | +        assert started or 'Unable to start HTTP server' in error
    


    janb84 commented at 9:18 AM on September 8, 2026:

    NIT: Leftover assert on started, Line 121 asserts that started is false so started cannot be true at this point.

            assert 'Unable to start HTTP server' in error
    
  18. in src/httpserver.cpp:230 in 64fd5cef0c
     228 | -        endpoints.emplace_back("::1", http_port);
     229 | +        // We allow the default IPv6 bind to fail, but IPv4 is required even
     230 | +        // with the default port as bitcoin-cli will default to handing RPC
     231 | +        // credentials in plaintext to whatever process is running on that port.
     232 | +        endpoints.push_back({.address = "::1", .port = http_port, .required = false});
     233 |          endpoints.emplace_back("127.0.0.1", http_port);
    


    janb84 commented at 9:48 AM on September 8, 2026:

    NIT: I would keep the style of the 2 lines the same (helps the OCD people)

            endpoints.push_back({.address = "127.0.0.1", .port = http_port});
    
  19. in src/httpserver.cpp:1366 in 64fd5cef0c
    1366 |              if (!result) {
    1367 | -                LogWarning("Binding RPC on address %s failed: %s", addr->ToStringAddrPort(), result.error());
    1368 | +                LogWarning("Binding %s RPC on address %s failed: %s",
    1369 | +                           required ? "required" : "optional",
    1370 | +                           addr->ToStringAddrPort(), result.error());
    1371 | +                if (required) required_failure = true;
    


    janb84 commented at 10:25 AM on September 8, 2026:

    non blocking, micro nit: Currently even the required entry/bindings failure only logs as a LogWarning. Is that the correct level for something that aborts startup ?

    if (required) {
        LogError("Binding required RPC endpoint %s failed: %s", addr->ToStringAddrPort(), result.error());
        required_failure = true;
    } else {
        LogWarning("Binding optional RPC endpoint %s failed: %s", addr->ToStringAddrPort(), result.error());
    }
    
  20. janb84 commented at 10:27 AM on September 8, 2026: contributor

    Concept ACK 64fd5cef0c938d3d1e7f69d6be2f255b6eafc1e2

    Looks good, some suggestion NITS.

    NITl Commit message of c309e5fb73 has a typo / grammar issue "and without this change that one would Mac CI would fail with:"


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 11:51 UTC

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