mirror of
https://github.com/bitcoin/bitcoin.git
synced 2024-11-20 02:25:40 +01:00
Merge bitcoin/bitcoin#26186: rpc: Sanitize label name in various RPCs with tests
65e78bda7c
test: Invalid label name coverage (Aurèle Oulès)552b51e682
refactor: Add sanity checks in LabelFromValue (Aurèle Oulès)67e7ba8e1a
rpc: Sanitize label name in various RPCs (Aurèle Oulès) Pull request description: The following RPCs did not sanitize the optional label name: - importprivkey - importaddress - importpubkey - importmulti - importdescriptors - listsinceblock Thus is was possible to import an address with a label `*` which should not be possible. The wildcard label is used for backwards compatibility in the `listtransactions` rpc. I added test coverage for these RPCs. ACKs for top commit: ajtowns: ACK65e78bda7c
achow101: ACK65e78bda7c
furszy: diff ACK65e78bd
stickies-v: re-ACK65e78bda7c
theStack: re-ACK65e78bda7c
Tree-SHA512: ad99f2824d4cfae352166b76da4ca0069b7c2eccf81aaa0654be25bbb3c6e5d6b005d93960f3f4154155f80e12be2d0cebd5529922ae3d2a36ee4eed82440b31
This commit is contained in:
commit
68f88bc03f
@ -43,9 +43,7 @@ RPCHelpMan getnewaddress()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Parse the label first so we don't generate a key if there's an error
|
// Parse the label first so we don't generate a key if there's an error
|
||||||
std::string label;
|
const std::string label{LabelFromValue(request.params[0])};
|
||||||
if (!request.params[0].isNull())
|
|
||||||
label = LabelFromValue(request.params[0]);
|
|
||||||
|
|
||||||
OutputType output_type = pwallet->m_default_address_type;
|
OutputType output_type = pwallet->m_default_address_type;
|
||||||
if (!request.params[1].isNull()) {
|
if (!request.params[1].isNull()) {
|
||||||
@ -140,7 +138,7 @@ RPCHelpMan setlabel()
|
|||||||
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string label = LabelFromValue(request.params[1]);
|
const std::string label{LabelFromValue(request.params[1])};
|
||||||
|
|
||||||
if (pwallet->IsMine(dest)) {
|
if (pwallet->IsMine(dest)) {
|
||||||
pwallet->SetAddressBook(dest, label, "receive");
|
pwallet->SetAddressBook(dest, label, "receive");
|
||||||
@ -258,9 +256,7 @@ RPCHelpMan addmultisigaddress()
|
|||||||
|
|
||||||
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
|
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
|
||||||
|
|
||||||
std::string label;
|
const std::string label{LabelFromValue(request.params[2])};
|
||||||
if (!request.params[2].isNull())
|
|
||||||
label = LabelFromValue(request.params[2]);
|
|
||||||
|
|
||||||
int required = request.params[0].getInt<int>();
|
int required = request.params[0].getInt<int>();
|
||||||
|
|
||||||
@ -662,7 +658,7 @@ RPCHelpMan getaddressesbylabel()
|
|||||||
|
|
||||||
LOCK(pwallet->cs_wallet);
|
LOCK(pwallet->cs_wallet);
|
||||||
|
|
||||||
std::string label = LabelFromValue(request.params[0]);
|
const std::string label{LabelFromValue(request.params[0])};
|
||||||
|
|
||||||
// Find all addresses that have the given label
|
// Find all addresses that have the given label
|
||||||
UniValue ret(UniValue::VOBJ);
|
UniValue ret(UniValue::VOBJ);
|
||||||
|
@ -156,9 +156,7 @@ RPCHelpMan importprivkey()
|
|||||||
EnsureWalletIsUnlocked(*pwallet);
|
EnsureWalletIsUnlocked(*pwallet);
|
||||||
|
|
||||||
std::string strSecret = request.params[0].get_str();
|
std::string strSecret = request.params[0].get_str();
|
||||||
std::string strLabel;
|
const std::string strLabel{LabelFromValue(request.params[1])};
|
||||||
if (!request.params[1].isNull())
|
|
||||||
strLabel = request.params[1].get_str();
|
|
||||||
|
|
||||||
// Whether to perform rescan after import
|
// Whether to perform rescan after import
|
||||||
if (!request.params[2].isNull())
|
if (!request.params[2].isNull())
|
||||||
@ -249,9 +247,7 @@ RPCHelpMan importaddress()
|
|||||||
|
|
||||||
EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
||||||
|
|
||||||
std::string strLabel;
|
const std::string strLabel{LabelFromValue(request.params[1])};
|
||||||
if (!request.params[1].isNull())
|
|
||||||
strLabel = request.params[1].get_str();
|
|
||||||
|
|
||||||
// Whether to perform rescan after import
|
// Whether to perform rescan after import
|
||||||
bool fRescan = true;
|
bool fRescan = true;
|
||||||
@ -442,9 +438,7 @@ RPCHelpMan importpubkey()
|
|||||||
|
|
||||||
EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
EnsureLegacyScriptPubKeyMan(*pwallet, true);
|
||||||
|
|
||||||
std::string strLabel;
|
const std::string strLabel{LabelFromValue(request.params[1])};
|
||||||
if (!request.params[1].isNull())
|
|
||||||
strLabel = request.params[1].get_str();
|
|
||||||
|
|
||||||
// Whether to perform rescan after import
|
// Whether to perform rescan after import
|
||||||
bool fRescan = true;
|
bool fRescan = true;
|
||||||
@ -1170,7 +1164,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
|
|||||||
if (internal && data.exists("label")) {
|
if (internal && data.exists("label")) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
|
||||||
}
|
}
|
||||||
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
|
const std::string label{LabelFromValue(data["label"])};
|
||||||
const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
|
const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
|
||||||
|
|
||||||
// Add to keypool only works with privkeys disabled
|
// Add to keypool only works with privkeys disabled
|
||||||
@ -1464,7 +1458,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
|
|||||||
const std::string& descriptor = data["desc"].get_str();
|
const std::string& descriptor = data["desc"].get_str();
|
||||||
const bool active = data.exists("active") ? data["active"].get_bool() : false;
|
const bool active = data.exists("active") ? data["active"].get_bool() : false;
|
||||||
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
|
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
|
||||||
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
|
const std::string label{LabelFromValue(data["label"])};
|
||||||
|
|
||||||
// Parse descriptor string
|
// Parse descriptor string
|
||||||
FlatSigningProvider keys;
|
FlatSigningProvider keys;
|
||||||
|
@ -486,7 +486,7 @@ RPCHelpMan listtransactions()
|
|||||||
|
|
||||||
std::optional<std::string> filter_label;
|
std::optional<std::string> filter_label;
|
||||||
if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
|
if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
|
||||||
filter_label = request.params[0].get_str();
|
filter_label.emplace(LabelFromValue(request.params[0]));
|
||||||
if (filter_label.value().empty()) {
|
if (filter_label.value().empty()) {
|
||||||
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
|
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
|
||||||
}
|
}
|
||||||
@ -634,10 +634,9 @@ RPCHelpMan listsinceblock()
|
|||||||
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
|
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
|
||||||
bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
|
bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
|
||||||
|
|
||||||
|
// Only set it if 'label' was provided.
|
||||||
std::optional<std::string> filter_label;
|
std::optional<std::string> filter_label;
|
||||||
if (!request.params[5].isNull()) {
|
if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
|
||||||
filter_label = request.params[5].get_str();
|
|
||||||
}
|
|
||||||
|
|
||||||
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
|
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
|
||||||
|
|
||||||
|
@ -132,7 +132,10 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
|
|||||||
|
|
||||||
std::string LabelFromValue(const UniValue& value)
|
std::string LabelFromValue(const UniValue& value)
|
||||||
{
|
{
|
||||||
std::string label = value.get_str();
|
static const std::string empty_string;
|
||||||
|
if (value.isNull()) return empty_string;
|
||||||
|
|
||||||
|
const std::string& label{value.get_str()};
|
||||||
if (label == "*")
|
if (label == "*")
|
||||||
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
|
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
|
||||||
return label;
|
return label;
|
||||||
|
@ -28,6 +28,44 @@ class WalletLabelsTest(BitcoinTestFramework):
|
|||||||
def skip_test_if_missing_module(self):
|
def skip_test_if_missing_module(self):
|
||||||
self.skip_if_no_wallet()
|
self.skip_if_no_wallet()
|
||||||
|
|
||||||
|
def invalid_label_name_test(self):
|
||||||
|
node = self.nodes[0]
|
||||||
|
address = node.getnewaddress()
|
||||||
|
pubkey = node.getaddressinfo(address)['pubkey']
|
||||||
|
rpc_calls = [
|
||||||
|
[node.getnewaddress],
|
||||||
|
[node.setlabel, address],
|
||||||
|
[node.getaddressesbylabel],
|
||||||
|
[node.importpubkey, pubkey],
|
||||||
|
[node.addmultisigaddress, 1, [pubkey]],
|
||||||
|
[node.getreceivedbylabel],
|
||||||
|
[node.listsinceblock, node.getblockhash(0), 1, False, True, False],
|
||||||
|
]
|
||||||
|
if self.options.descriptors:
|
||||||
|
response = node.importdescriptors([{
|
||||||
|
'desc': f'pkh({pubkey})',
|
||||||
|
'label': '*',
|
||||||
|
'timestamp': 'now',
|
||||||
|
}])
|
||||||
|
else:
|
||||||
|
rpc_calls.extend([
|
||||||
|
[node.importprivkey, node.dumpprivkey(address)],
|
||||||
|
[node.importaddress, address],
|
||||||
|
])
|
||||||
|
|
||||||
|
response = node.importmulti([{
|
||||||
|
'scriptPubKey': {'address': address},
|
||||||
|
'label': '*',
|
||||||
|
'timestamp': 'now',
|
||||||
|
}])
|
||||||
|
|
||||||
|
assert_equal(response[0]['success'], False)
|
||||||
|
assert_equal(response[0]['error']['code'], -11)
|
||||||
|
assert_equal(response[0]['error']['message'], "Invalid label name")
|
||||||
|
|
||||||
|
for rpc_call in rpc_calls:
|
||||||
|
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
|
||||||
|
|
||||||
def run_test(self):
|
def run_test(self):
|
||||||
# Check that there's no UTXO on the node
|
# Check that there's no UTXO on the node
|
||||||
node = self.nodes[0]
|
node = self.nodes[0]
|
||||||
@ -138,6 +176,8 @@ class WalletLabelsTest(BitcoinTestFramework):
|
|||||||
# in the label. This is a no-op.
|
# in the label. This is a no-op.
|
||||||
change_label(node, labels[2].addresses[0], labels[2], labels[2])
|
change_label(node, labels[2].addresses[0], labels[2], labels[2])
|
||||||
|
|
||||||
|
self.invalid_label_name_test()
|
||||||
|
|
||||||
if self.options.descriptors:
|
if self.options.descriptors:
|
||||||
# This is a descriptor wallet test because of segwit v1+ addresses
|
# This is a descriptor wallet test because of segwit v1+ addresses
|
||||||
self.log.info('Check watchonly labels')
|
self.log.info('Check watchonly labels')
|
||||||
|
Loading…
Reference in New Issue
Block a user