Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RPC: Reject RPC requests with same named parameter specified multiple times #26628

Merged
merged 4 commits into from Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/release-notes-26628.md
@@ -0,0 +1,4 @@
JSON-RPC
---

The JSON-RPC server now rejects requests where a parameter is specified multiple times with the same name, instead of silently overwriting earlier parameter values with later ones. (#26628)
8 changes: 7 additions & 1 deletion src/rpc/client.cpp
Expand Up @@ -289,6 +289,9 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
std::string name = s.substr(0, pos);
std::string value = s.substr(pos+1);

// Intentionally overwrite earlier named values with later ones as a
// convenience for scripts and command line users that want to merge
// options.
if (!rpcCvtTable.convert(strMethod, name)) {
// insert string value directly
params.pushKV(name, value);
Expand All @@ -299,7 +302,10 @@ UniValue RPCConvertNamedValues(const std::string &strMethod, const std::vector<s
}

if (!positional_args.empty()) {
params.pushKV("args", positional_args);
// Use __pushKV instead of pushKV to avoid overwriting an explicit
// "args" value with an implicit one. Let the RPC server handle the
// request as given.
params.__pushKV("args", positional_args);
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
}

return params;
Expand Down
5 changes: 4 additions & 1 deletion src/rpc/server.cpp
Expand Up @@ -399,7 +399,10 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
const std::vector<UniValue>& values = in.params.getValues();
std::unordered_map<std::string, const UniValue*> argsIn;
for (size_t i=0; i<keys.size(); ++i) {
argsIn[keys[i]] = &values[i];
auto [_, inserted] = argsIn.emplace(keys[i], &values[i]);
if (!inserted) {
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + keys[i] + " specified multiple times");
}
}
// Process expected parameters. If any parameters were left unspecified in
// the request before a parameter that was specified, null values need to be
Expand Down
8 changes: 6 additions & 2 deletions src/test/rpc_tests.cpp
Expand Up @@ -84,11 +84,15 @@ BOOST_FIXTURE_TEST_SUITE(rpc_tests, RPCTestingSetup)

BOOST_AUTO_TEST_CASE(rpc_namedparams)
{
const std::vector<std::string> arg_names{{"arg1", "arg2", "arg3", "arg4", "arg5"}};
const std::vector<std::string> arg_names{"arg1", "arg2", "arg3", "arg4", "arg5"};

// Make sure named arguments are transformed into positional arguments in correct places separated by nulls
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg2": 2, "arg4": 4})"), arg_names).write(), "[null,2,null,4]");

// Make sure named argument specified multiple times raises an exception
BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"arg2": 2, "arg2": 4})"), arg_names), UniValue,
HasJSON(R"({"code":-8,"message":"Parameter arg2 specified multiple times"})"));

// Make sure named and positional arguments can be combined.
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"arg5": 5, "args": [1, 2], "arg4": 4})"), arg_names).write(), "[1,2,null,4,5]");

ryanofsky marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -100,7 +104,7 @@ BOOST_AUTO_TEST_CASE(rpc_namedparams)
BOOST_CHECK_EXCEPTION(TransformParams(JSON(R"({"args": [1,2,3], "arg4": 4, "arg2": 2})"), arg_names), UniValue,
HasJSON(R"({"code":-8,"message":"Parameter arg2 specified twice both as positional and named argument"})"));

// Make sure extra positional arguments can be passed through to the method implemenation, as long as they don't overlap with named arguments.
// Make sure extra positional arguments can be passed through to the method implementation, as long as they don't overlap with named arguments.
BOOST_CHECK_EQUAL(TransformParams(JSON(R"({"args": [1,2,3,4,5,6,7,8,9,10]})"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
BOOST_CHECK_EQUAL(TransformParams(JSON(R"([1,2,3,4,5,6,7,8,9,10])"), arg_names).write(), "[1,2,3,4,5,6,7,8,9,10]");
}
Expand Down
4 changes: 4 additions & 0 deletions test/functional/interface_bitcoin_cli.py
Expand Up @@ -90,6 +90,10 @@ def run_test(self):
assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, 1, arg1=1)
assert_raises_rpc_error(-8, "Parameter arg1 specified twice both as positional and named argument", self.nodes[0].cli.echo, 0, None, 2, arg1=1)

self.log.info("Test that later cli named arguments values silently overwrite earlier ones")
assert_equal(self.nodes[0].cli("-named", "echo", "arg0=0", "arg1=1", "arg2=2", "arg1=3").send_cli(), ['0', '3', '2'])
assert_raises_rpc_error(-8, "Parameter args specified multiple times", self.nodes[0].cli("-named", "echo", "args=[0,1,2,3]", "4", "5", "6", ).send_cli)
ryanofsky marked this conversation as resolved.
Show resolved Hide resolved

user, password = get_auth_cookie(self.nodes[0].datadir, self.chain)

self.log.info("Test -stdinrpcpass option")
Expand Down