Skip to content

Commit 68f88bc

Browse files
committed
Merge bitcoin/bitcoin#26186: rpc: Sanitize label name in various RPCs with tests
65e78bd test: Invalid label name coverage (Aurèle Oulès) 552b51e refactor: Add sanity checks in LabelFromValue (Aurèle Oulès) 67e7ba8 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: ACK 65e78bd achow101: ACK 65e78bd furszy: diff ACK 65e78bd stickies-v: re-ACK 65e78bd theStack: re-ACK 65e78bd Tree-SHA512: ad99f2824d4cfae352166b76da4ca0069b7c2eccf81aaa0654be25bbb3c6e5d6b005d93960f3f4154155f80e12be2d0cebd5529922ae3d2a36ee4eed82440b31
2 parents b264410 + 65e78bd commit 68f88bc

File tree

5 files changed

+56
-24
lines changed

5 files changed

+56
-24
lines changed

src/wallet/rpc/addresses.cpp

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,7 @@ RPCHelpMan getnewaddress()
4343
}
4444

4545
// Parse the label first so we don't generate a key if there's an error
46-
std::string label;
47-
if (!request.params[0].isNull())
48-
label = LabelFromValue(request.params[0]);
46+
const std::string label{LabelFromValue(request.params[0])};
4947

5048
OutputType output_type = pwallet->m_default_address_type;
5149
if (!request.params[1].isNull()) {
@@ -140,7 +138,7 @@ RPCHelpMan setlabel()
140138
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid Bitcoin address");
141139
}
142140

143-
std::string label = LabelFromValue(request.params[1]);
141+
const std::string label{LabelFromValue(request.params[1])};
144142

145143
if (pwallet->IsMine(dest)) {
146144
pwallet->SetAddressBook(dest, label, "receive");
@@ -258,9 +256,7 @@ RPCHelpMan addmultisigaddress()
258256

259257
LOCK2(pwallet->cs_wallet, spk_man.cs_KeyStore);
260258

261-
std::string label;
262-
if (!request.params[2].isNull())
263-
label = LabelFromValue(request.params[2]);
259+
const std::string label{LabelFromValue(request.params[2])};
264260

265261
int required = request.params[0].getInt<int>();
266262

@@ -662,7 +658,7 @@ RPCHelpMan getaddressesbylabel()
662658

663659
LOCK(pwallet->cs_wallet);
664660

665-
std::string label = LabelFromValue(request.params[0]);
661+
const std::string label{LabelFromValue(request.params[0])};
666662

667663
// Find all addresses that have the given label
668664
UniValue ret(UniValue::VOBJ);

src/wallet/rpc/backup.cpp

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -156,9 +156,7 @@ RPCHelpMan importprivkey()
156156
EnsureWalletIsUnlocked(*pwallet);
157157

158158
std::string strSecret = request.params[0].get_str();
159-
std::string strLabel;
160-
if (!request.params[1].isNull())
161-
strLabel = request.params[1].get_str();
159+
const std::string strLabel{LabelFromValue(request.params[1])};
162160

163161
// Whether to perform rescan after import
164162
if (!request.params[2].isNull())
@@ -249,9 +247,7 @@ RPCHelpMan importaddress()
249247

250248
EnsureLegacyScriptPubKeyMan(*pwallet, true);
251249

252-
std::string strLabel;
253-
if (!request.params[1].isNull())
254-
strLabel = request.params[1].get_str();
250+
const std::string strLabel{LabelFromValue(request.params[1])};
255251

256252
// Whether to perform rescan after import
257253
bool fRescan = true;
@@ -442,9 +438,7 @@ RPCHelpMan importpubkey()
442438

443439
EnsureLegacyScriptPubKeyMan(*pwallet, true);
444440

445-
std::string strLabel;
446-
if (!request.params[1].isNull())
447-
strLabel = request.params[1].get_str();
441+
const std::string strLabel{LabelFromValue(request.params[1])};
448442

449443
// Whether to perform rescan after import
450444
bool fRescan = true;
@@ -1170,7 +1164,7 @@ static UniValue ProcessImport(CWallet& wallet, const UniValue& data, const int64
11701164
if (internal && data.exists("label")) {
11711165
throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label");
11721166
}
1173-
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
1167+
const std::string label{LabelFromValue(data["label"])};
11741168
const bool add_keypool = data.exists("keypool") ? data["keypool"].get_bool() : false;
11751169

11761170
// Add to keypool only works with privkeys disabled
@@ -1464,7 +1458,7 @@ static UniValue ProcessDescriptorImport(CWallet& wallet, const UniValue& data, c
14641458
const std::string& descriptor = data["desc"].get_str();
14651459
const bool active = data.exists("active") ? data["active"].get_bool() : false;
14661460
const bool internal = data.exists("internal") ? data["internal"].get_bool() : false;
1467-
const std::string& label = data.exists("label") ? data["label"].get_str() : "";
1461+
const std::string label{LabelFromValue(data["label"])};
14681462

14691463
// Parse descriptor string
14701464
FlatSigningProvider keys;

src/wallet/rpc/transactions.cpp

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -486,7 +486,7 @@ RPCHelpMan listtransactions()
486486

487487
std::optional<std::string> filter_label;
488488
if (!request.params[0].isNull() && request.params[0].get_str() != "*") {
489-
filter_label = request.params[0].get_str();
489+
filter_label.emplace(LabelFromValue(request.params[0]));
490490
if (filter_label.value().empty()) {
491491
throw JSONRPCError(RPC_INVALID_PARAMETER, "Label argument must be a valid label name or \"*\".");
492492
}
@@ -634,10 +634,9 @@ RPCHelpMan listsinceblock()
634634
bool include_removed = (request.params[3].isNull() || request.params[3].get_bool());
635635
bool include_change = (!request.params[4].isNull() && request.params[4].get_bool());
636636

637+
// Only set it if 'label' was provided.
637638
std::optional<std::string> filter_label;
638-
if (!request.params[5].isNull()) {
639-
filter_label = request.params[5].get_str();
640-
}
639+
if (!request.params[5].isNull()) filter_label.emplace(LabelFromValue(request.params[5]));
641640

642641
int depth = height ? wallet.GetLastBlockHeight() + 1 - *height : -1;
643642

src/wallet/rpc/util.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,10 @@ const LegacyScriptPubKeyMan& EnsureConstLegacyScriptPubKeyMan(const CWallet& wal
132132

133133
std::string LabelFromValue(const UniValue& value)
134134
{
135-
std::string label = value.get_str();
135+
static const std::string empty_string;
136+
if (value.isNull()) return empty_string;
137+
138+
const std::string& label{value.get_str()};
136139
if (label == "*")
137140
throw JSONRPCError(RPC_WALLET_INVALID_LABEL_NAME, "Invalid label name");
138141
return label;

test/functional/wallet_labels.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,44 @@ def set_test_params(self):
2828
def skip_test_if_missing_module(self):
2929
self.skip_if_no_wallet()
3030

31+
def invalid_label_name_test(self):
32+
node = self.nodes[0]
33+
address = node.getnewaddress()
34+
pubkey = node.getaddressinfo(address)['pubkey']
35+
rpc_calls = [
36+
[node.getnewaddress],
37+
[node.setlabel, address],
38+
[node.getaddressesbylabel],
39+
[node.importpubkey, pubkey],
40+
[node.addmultisigaddress, 1, [pubkey]],
41+
[node.getreceivedbylabel],
42+
[node.listsinceblock, node.getblockhash(0), 1, False, True, False],
43+
]
44+
if self.options.descriptors:
45+
response = node.importdescriptors([{
46+
'desc': f'pkh({pubkey})',
47+
'label': '*',
48+
'timestamp': 'now',
49+
}])
50+
else:
51+
rpc_calls.extend([
52+
[node.importprivkey, node.dumpprivkey(address)],
53+
[node.importaddress, address],
54+
])
55+
56+
response = node.importmulti([{
57+
'scriptPubKey': {'address': address},
58+
'label': '*',
59+
'timestamp': 'now',
60+
}])
61+
62+
assert_equal(response[0]['success'], False)
63+
assert_equal(response[0]['error']['code'], -11)
64+
assert_equal(response[0]['error']['message'], "Invalid label name")
65+
66+
for rpc_call in rpc_calls:
67+
assert_raises_rpc_error(-11, "Invalid label name", *rpc_call, "*")
68+
3169
def run_test(self):
3270
# Check that there's no UTXO on the node
3371
node = self.nodes[0]
@@ -138,6 +176,8 @@ def run_test(self):
138176
# in the label. This is a no-op.
139177
change_label(node, labels[2].addresses[0], labels[2], labels[2])
140178

179+
self.invalid_label_name_test()
180+
141181
if self.options.descriptors:
142182
# This is a descriptor wallet test because of segwit v1+ addresses
143183
self.log.info('Check watchonly labels')

0 commit comments

Comments
 (0)