RPC: Strictly enforce the type of parameters passed by name

This commit is contained in:
Ryan Ofsky 2024-01-12 10:38:00 +05:00 committed by Luke Dashjr
parent 28023ff415
commit 7cd0315bc4
4 changed files with 21 additions and 7 deletions

View file

@ -9,6 +9,7 @@
#include <any> #include <any>
#include <optional> #include <optional>
#include <string> #include <string>
#include <vector>
#include <univalue.h> #include <univalue.h>
#include <util/fs.h> #include <util/fs.h>
@ -38,6 +39,9 @@ public:
std::optional<UniValue> id = UniValue::VNULL; std::optional<UniValue> id = UniValue::VNULL;
std::string strMethod; std::string strMethod;
UniValue params; 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; enum Mode { EXECUTE, GET_HELP, GET_ARGS } mode = EXECUTE;
std::string URI; std::string URI;
std::string authUser; std::string authUser;

View file

@ -397,9 +397,11 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
for (const auto& [argNamePattern, named_only]: argNames) { for (const auto& [argNamePattern, named_only]: argNames) {
std::vector<std::string> vargNames = SplitString(argNamePattern, '|'); std::vector<std::string> vargNames = SplitString(argNamePattern, '|');
auto fr = argsIn.end(); auto fr = argsIn.end();
std::string fr_name;
for (const std::string & argName : vargNames) { for (const std::string & argName : vargNames) {
fr = argsIn.find(argName); fr = argsIn.find(argName);
if (fr != argsIn.end()) { if (fr != argsIn.end()) {
fr_name = argName;
break; break;
} }
} }
@ -424,6 +426,7 @@ static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, c
// but not at the end (for backwards compatibility with calls // but not at the end (for backwards compatibility with calls
// that act based on number of specified parameters). // that act based on number of specified parameters).
out.params.push_back(UniValue()); out.params.push_back(UniValue());
out.param_names.emplace_back(std::nullopt);
} }
hole = 0; hole = 0;
if (!initial_param) initial_param = &argNamePattern; if (!initial_param) initial_param = &argNamePattern;
@ -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()); throw JSONRPCError(RPC_INVALID_PARAMETER, "Parameter " + fr->first + " conflicts with parameter " + options.getKeys().front());
} }
out.params.push_back(*fr->second); out.params.push_back(*fr->second);
out.param_names.emplace_back(fr_name);
argsIn.erase(fr); argsIn.erase(fr);
} }
if (!options.empty()) { if (!options.empty()) {

View file

@ -669,7 +669,7 @@ UniValue RPCHelpMan::HandleRequest(const JSONRPCRequest& request) const
UniValue arg_mismatch{UniValue::VOBJ}; UniValue arg_mismatch{UniValue::VOBJ};
for (size_t i{0}; i < m_args.size(); ++i) { for (size_t i{0}; i < m_args.size(); ++i) {
const auto& arg{m_args.at(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()) { if (!match.isTrue()) {
arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match)); arg_mismatch.pushKV(strprintf("Position %s (%s)", i + 1, arg.m_names), std::move(match));
} }
@ -923,18 +923,24 @@ static std::optional<UniValue::VType> ExpectedType(RPCArg::Type type)
NONFATAL_UNREACHABLE(); 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 (m_opts.skip_type_check) return true;
if (IsOptional() && request.isNull()) 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 names = SplitString(m_names, '|');
const auto exp_type{ExpectedType(type)}; 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) return true; // nothing to check
if (*exp_type == request.getType()) { if (*exp_type == request.getType()) {
return true; 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))); return strprintf("JSON value of type %s is not of expected type %s", uvTypeName(request.getType()), uvTypeName(*ExpectedType(m_type)));
} }

View file

@ -284,7 +284,7 @@ struct RPCArg {
* Check whether the request JSON type matches. * Check whether the request JSON type matches.
* Returns true if type matches, or object describing error(s) if not. * 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 */ /** Return the first of all aliases */
std::string GetFirstName() const; std::string GetFirstName() const;