Skip to content

Commit e208fb5

Browse files
committed
cli: Sanitize ports in rpcconnect and rpcport
Adds error handling of invalid ports to rpcconnect and rpcport, with associated functional tests.
1 parent b940619 commit e208fb5

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

src/bitcoin-cli.cpp

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -743,8 +743,35 @@ static UniValue CallRPC(BaseRequestHandler* rh, const std::string& strMethod, co
743743
// 2. port in -rpcconnect (ie following : in ipv4 or ]: in ipv6)
744744
// 3. default port for chain
745745
uint16_t port{BaseParams().RPCPort()};
746-
SplitHostPort(gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT), port, host);
747-
port = static_cast<uint16_t>(gArgs.GetIntArg("-rpcport", port));
746+
{
747+
uint16_t rpcconnect_port{0};
748+
const std::string rpcconnect_str = gArgs.GetArg("-rpcconnect", DEFAULT_RPCCONNECT);
749+
if (!SplitHostPort(rpcconnect_str, rpcconnect_port, host)) {
750+
// Uses argument provided as-is
751+
// (rather than value parsed)
752+
// to aid the user in troubleshooting
753+
throw std::runtime_error(strprintf("Invalid port provided in -rpcconnect: %s", rpcconnect_str));
754+
} else {
755+
if (rpcconnect_port != 0) {
756+
// Use the valid port provided in rpcconnect
757+
port = rpcconnect_port;
758+
} // else, no port was provided in rpcconnect (continue using default one)
759+
}
760+
761+
if (std::optional<std::string> rpcport_arg = gArgs.GetArg("-rpcport")) {
762+
// -rpcport was specified
763+
const uint16_t rpcport_int{ToIntegral<uint16_t>(rpcport_arg.value()).value_or(0)};
764+
if (rpcport_int == 0) {
765+
// Uses argument provided as-is
766+
// (rather than value parsed)
767+
// to aid the user in troubleshooting
768+
throw std::runtime_error(strprintf("Invalid port provided in -rpcport: %s", rpcport_arg.value()));
769+
}
770+
771+
// Use the valid port provided
772+
port = rpcport_int;
773+
}
774+
}
748775

749776
// Obtain event base
750777
raii_event_base base = obtain_event_base();

test/functional/interface_bitcoin_cli.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,15 @@
88
import re
99

1010
from test_framework.blocktools import COINBASE_MATURITY
11+
from test_framework.netutil import test_ipv6_local
1112
from test_framework.test_framework import BitcoinTestFramework
1213
from test_framework.util import (
1314
assert_equal,
1415
assert_greater_than_or_equal,
1516
assert_raises_process_error,
1617
assert_raises_rpc_error,
1718
get_auth_cookie,
19+
rpc_port,
1820
)
1921
import time
2022

@@ -107,6 +109,53 @@ def run_test(self):
107109
self.log.info("Test connecting to a non-existing server")
108110
assert_raises_process_error(1, "Could not connect to the server", self.nodes[0].cli('-rpcport=1').echo)
109111

112+
self.log.info("Test handling of invalid ports in rpcconnect")
113+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:notaport", self.nodes[0].cli("-rpcconnect=127.0.0.1:notaport").echo)
114+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:-1", self.nodes[0].cli("-rpcconnect=127.0.0.1:-1").echo)
115+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:0", self.nodes[0].cli("-rpcconnect=127.0.0.1:0").echo)
116+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: 127.0.0.1:65536", self.nodes[0].cli("-rpcconnect=127.0.0.1:65536").echo)
117+
118+
self.log.info("Checking for IPv6")
119+
have_ipv6 = test_ipv6_local()
120+
if not have_ipv6:
121+
self.log.info("Skipping IPv6 tests")
122+
123+
if have_ipv6:
124+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:notaport", self.nodes[0].cli("-rpcconnect=[::1]:notaport").echo)
125+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:-1", self.nodes[0].cli("-rpcconnect=[::1]:-1").echo)
126+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:0", self.nodes[0].cli("-rpcconnect=[::1]:0").echo)
127+
assert_raises_process_error(1, "Invalid port provided in -rpcconnect: [::1]:65536", self.nodes[0].cli("-rpcconnect=[::1]:65536").echo)
128+
129+
self.log.info("Test handling of invalid ports in rpcport")
130+
assert_raises_process_error(1, "Invalid port provided in -rpcport: notaport", self.nodes[0].cli("-rpcport=notaport").echo)
131+
assert_raises_process_error(1, "Invalid port provided in -rpcport: -1", self.nodes[0].cli("-rpcport=-1").echo)
132+
assert_raises_process_error(1, "Invalid port provided in -rpcport: 0", self.nodes[0].cli("-rpcport=0").echo)
133+
assert_raises_process_error(1, "Invalid port provided in -rpcport: 65536", self.nodes[0].cli("-rpcport=65536").echo)
134+
135+
self.log.info("Test port usage preferences")
136+
node_rpc_port = rpc_port(self.nodes[0].index)
137+
# Prevent bitcoin-cli from using existing rpcport in conf
138+
conf_rpcport = "rpcport=" + str(node_rpc_port)
139+
self.nodes[0].replace_in_config([(conf_rpcport, "#" + conf_rpcport)])
140+
# prefer rpcport over rpcconnect
141+
assert_raises_process_error(1, "Could not connect to the server 127.0.0.1:1", self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}", "-rpcport=1").echo)
142+
if have_ipv6:
143+
assert_raises_process_error(1, "Could not connect to the server ::1:1", self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}", "-rpcport=1").echo)
144+
145+
assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=127.0.0.1:18999", f'-rpcport={node_rpc_port}').getblockcount())
146+
if have_ipv6:
147+
assert_equal(BLOCKS, self.nodes[0].cli("-rpcconnect=[::1]:18999", f'-rpcport={node_rpc_port}').getblockcount())
148+
149+
# prefer rpcconnect port over default
150+
assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=127.0.0.1:{node_rpc_port}").getblockcount())
151+
if have_ipv6:
152+
assert_equal(BLOCKS, self.nodes[0].cli(f"-rpcconnect=[::1]:{node_rpc_port}").getblockcount())
153+
154+
# prefer rpcport over default
155+
assert_equal(BLOCKS, self.nodes[0].cli(f'-rpcport={node_rpc_port}').getblockcount())
156+
# Re-enable rpcport in conf if present
157+
self.nodes[0].replace_in_config([("#" + conf_rpcport, conf_rpcport)])
158+
110159
self.log.info("Test connecting with non-existing RPC cookie file")
111160
assert_raises_process_error(1, "Could not locate RPC credentials", self.nodes[0].cli('-rpccookiefile=does-not-exist', '-rpcpassword=').echo)
112161

0 commit comments

Comments
 (0)