Skip to content

Commit

Permalink
RPC: Strictly enforce the type of parameters passed by name
Browse files Browse the repository at this point in the history
  • Loading branch information
ryanofsky authored and luke-jr committed Nov 17, 2024
1 parent 28023ff commit 7cd0315
Show file tree
Hide file tree
Showing 4 changed files with 21 additions and 7 deletions.
4 changes: 4 additions & 0 deletions src/rpc/request.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <any>
#include <optional>
#include <string>
#include <vector>

#include <univalue.h>
#include <util/fs.h>
Expand Down Expand Up @@ -38,6 +39,9 @@ class JSONRPCRequest
std::optional<UniValue> id = UniValue::VNULL;
std::string strMethod;
UniValue params;
//! List of original parameter names after transformNamedArguments is
//! called and params is changed from an object to an array.
std::vector<std::optional<std::string>> param_names;
enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
std::string URI;
std::string authUser;
Expand Down
4 changes: 4 additions & 0 deletions src/rpc/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -397,9 +397,11 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
for (const auto& [argNamePattern, named_only]: argNames) {
std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
auto fr = argsIn.end();
std::string fr_name;
for (const std::string & argName : vargNames) {
fr = argsIn.find(argName);
if (fr != argsIn.end()) {
fr_name = argName;
break;
}
}
Expand All @@ -424,6 +426,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
// but not at the end (for backwards compatibility with calls
// that act based on number of specified parameters).
out.params.push_back(UniValue());
out.param_names.emplace_back(std::nullopt);
}
hole = 0;
if (!initial_param) initial_param = &argNamePattern;
Expand All @@ -440,6 +443,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front());
}
out.params.push_back(*fr->second);
out.param_names.emplace_back(fr_name);
argsIn.erase(fr);
}
if (!options.empty()) {
Expand Down
18 changes: 12 additions & 6 deletions src/rpc/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,7 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
UniValue arg_mismatch{UniValue::VOBJ};
for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg{m_args.at(i)};
UniValue match{arg.MatchesType(request.params[i])};
UniValue match{arg.MatchesType(request.params[i], i < request.param_names.size() ? request.param_names[i] : std::nullopt)};
if (!match.isTrue()) {
arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match));
}
Expand Down Expand Up @@ -923,18 +923,24 @@ static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type)
NONFATAL_UNREACHABLE();
}

UniValue RPCArg::MatchesType(const UniValue& request) const
UniValue RPCArg::MatchesType(const UniValue& request, const std::optional<std::string>& param_name) const
{
if (m_opts.skip_type_check) return true;
if (IsOptional() && request.isNull()) return true;
for (auto type : m_type_per_name.empty() ? std::vector<RPCArg::Type>{m_type} : m_type_per_name) {
const auto exp_type{ExpectedType(type)};
const auto names = SplitString(m_names, '|');
size_t i = 0;
do {
// If parameter was passed by name, only allow the specified type for
// that name. Otherwise allow any of the specified types.
if (param_name && i < names.size() && *param_name != names[i]) {
continue;
}
const auto exp_type{ExpectedType(i < m_type_per_name.size() ? m_type_per_name[i] : m_type)};
if (!exp_type) return true; // nothing to check

if (*exp_type == request.getType()) {
return true;
}
}
} while (++i < names.size());
return strprintf("JSON value of type %s is not of expected type %s", uvTypeName(request.getType()), uvTypeName(*ExpectedType(m_type)));
}

Expand Down
2 changes: 1 addition & 1 deletion src/rpc/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,7 @@ struct RPCArg {
* Check whether the request JSON type matches.
* Returns true if type matches, or object describing error(s) if not.
*/
UniValue MatchesType(const UniValue& request) const;
UniValue MatchesType(const UniValue& request, const std::optional<std::string>& param_name) const;

/** Return the first of all aliases */
std::string GetFirstName() const;
Expand Down

0 comments on commit 7cd0315

Please sign in to comment.