Skip to content

Commit 30e8a79

Browse files
committed
Merge bitcoin/bitcoin#30482: rest: Reject truncated hex txid early in getutxos parsing
fac0c3d doc: Add release notes for two pull requests (MarcoFalke) fa7b57e refactor: Replace ParseHashStr with FromHex (MarcoFalke) fa90777 rest: Reject truncated hex txid early in getutxos parsing (MarcoFalke) fab6ddb refactor: Expose FromHex in transaction_identifier (MarcoFalke) fad2991 refactor: Implement strict uint256::FromHex() (MarcoFalke) fa103db scripted-diff: Rename SetHex to SetHexDeprecated (MarcoFalke) fafe4b8 test: refactor: Replace SetHex with uint256 constructor directly (MarcoFalke) Pull request description: In `rest_getutxos` truncated txids such as `aa` or `ff` are accepted. This is brittle at best. Fix it by rejecting any truncated (or overlarge) input. ---- Review note: This also starts a major refactor to rework hex parsing in Bitcoin Core, meaning that a few refactor commits are included as well. They are explained individually in the commit message and the work will be continued in the future. ACKs for top commit: stickies-v: re-ACK fac0c3d - only doc and test updates to address review comments, thanks! hodlinator: ACK fac0c3d Tree-SHA512: 473feb3fcf6118443435d1dd321006135b0b54689bfbbcb1697bb5811a449bef51f475c715de6911ff3c4ea3bdb75f601861ff93347bc4414d6b9e5298105dd7
2 parents 955f173 + fac0c3d commit 30e8a79

16 files changed

+98
-82
lines changed

doc/release-notes-30482.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Updated REST APIs
2+
-----------------
3+
- Parameter validation for `/rest/getutxos` has been improved by rejecting
4+
truncated or overly large txids and malformed outpoint indices by raising an
5+
HTTP_BAD_REQUEST "Parse error". Previously, these malformed requests would be
6+
silently handled. (#30482, #30444)

src/bitcoin-tx.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -264,8 +264,8 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
264264
throw std::runtime_error("TX input missing separator");
265265

266266
// extract and validate TXID
267-
uint256 txid;
268-
if (!ParseHashStr(vStrInputParts[0], txid)) {
267+
auto txid{Txid::FromHex(vStrInputParts[0])};
268+
if (!txid) {
269269
throw std::runtime_error("invalid TX input txid");
270270
}
271271

@@ -285,7 +285,7 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
285285
}
286286

287287
// append to transaction input list
288-
CTxIn txin(Txid::FromUint256(txid), vout, CScript(), nSequenceIn);
288+
CTxIn txin(*txid, vout, CScript(), nSequenceIn);
289289
tx.vin.push_back(txin);
290290
}
291291

@@ -625,16 +625,16 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
625625
if (!prevOut.checkObject(types))
626626
throw std::runtime_error("prevtxs internal object typecheck fail");
627627

628-
uint256 txid;
629-
if (!ParseHashStr(prevOut["txid"].get_str(), txid)) {
628+
auto txid{Txid::FromHex(prevOut["txid"].get_str())};
629+
if (!txid) {
630630
throw std::runtime_error("txid must be hexadecimal string (not '" + prevOut["txid"].get_str() + "')");
631631
}
632632

633633
const int nOut = prevOut["vout"].getInt<int>();
634634
if (nOut < 0)
635635
throw std::runtime_error("vout cannot be negative");
636636

637-
COutPoint out(Txid::FromUint256(txid), nOut);
637+
COutPoint out(*txid, nOut);
638638
std::vector<unsigned char> pkData(ParseHexUV(prevOut["scriptPubKey"], "scriptPubKey"));
639639
CScript scriptPubKey(pkData.begin(), pkData.end());
640640

src/core_io.h

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,6 @@ std::string ScriptToAsmStr(const CScript& script, const bool fAttemptSighashDeco
3737
[[nodiscard]] bool DecodeHexBlk(CBlock&, const std::string& strHexBlk);
3838
bool DecodeHexBlockHeader(CBlockHeader&, const std::string& hex_header);
3939

40-
/**
41-
* Parse a hex string into 256 bits
42-
* @param[in] strHex a hex-formatted, 64-character string
43-
* @param[out] result the result of the parsing
44-
* @returns true if successful, false if not
45-
*
46-
* @see ParseHashV for an RPC-oriented version of this
47-
*/
48-
bool ParseHashStr(const std::string& strHex, uint256& result);
4940
[[nodiscard]] util::Result<int> SighashFromStr(const std::string& sighash);
5041

5142
// core_write.cpp

src/core_read.cpp

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -234,15 +234,6 @@ bool DecodeHexBlk(CBlock& block, const std::string& strHexBlk)
234234
return true;
235235
}
236236

237-
bool ParseHashStr(const std::string& strHex, uint256& result)
238-
{
239-
if ((strHex.size() != 64) || !IsHex(strHex))
240-
return false;
241-
242-
result.SetHex(strHex);
243-
return true;
244-
}
245-
246237
util::Result<int> SighashFromStr(const std::string& sighash)
247238
{
248239
static const std::map<std::string, int> map_sighash_values = {

src/qt/transactiontablemodel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ void TransactionTableModel::updateAmountColumnTitle()
277277
void TransactionTableModel::updateTransaction(const QString &hash, int status, bool showTransaction)
278278
{
279279
uint256 updated;
280-
updated.SetHex(hash.toStdString());
280+
updated.SetHexDeprecated(hash.toStdString());
281281

282282
priv->updateWallet(walletModel->wallet(), updated, status, showTransaction);
283283
}

src/qt/transactionview.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -396,7 +396,7 @@ void TransactionView::contextualMenu(const QPoint &point)
396396

397397
// check if transaction can be abandoned, disable context menu action in case it doesn't
398398
uint256 hash;
399-
hash.SetHex(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
399+
hash.SetHexDeprecated(selection.at(0).data(TransactionTableModel::TxHashRole).toString().toStdString());
400400
abandonAction->setEnabled(model->wallet().transactionCanBeAbandoned(hash));
401401
bumpFeeAction->setEnabled(model->wallet().transactionCanBeBumped(hash));
402402
copyAddressAction->setEnabled(GUIUtil::hasEntryData(transactionView, 0, TransactionTableModel::AddressRole));
@@ -416,7 +416,7 @@ void TransactionView::abandonTx()
416416
// get the hash from the TxHashRole (QVariant / QString)
417417
uint256 hash;
418418
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
419-
hash.SetHex(hashQStr.toStdString());
419+
hash.SetHexDeprecated(hashQStr.toStdString());
420420

421421
// Abandon the wallet transaction over the walletModel
422422
model->wallet().abandonTransaction(hash);
@@ -431,7 +431,7 @@ void TransactionView::bumpFee([[maybe_unused]] bool checked)
431431
// get the hash from the TxHashRole (QVariant / QString)
432432
uint256 hash;
433433
QString hashQStr = selection.at(0).data(TransactionTableModel::TxHashRole).toString();
434-
hash.SetHex(hashQStr.toStdString());
434+
hash.SetHexDeprecated(hashQStr.toStdString());
435435

436436
// Bump tx fee over the walletModel
437437
uint256 newHash;

src/rest.cpp

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -217,9 +217,10 @@ static bool rest_headers(const std::any& context,
217217
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
218218
}
219219

220-
uint256 hash;
221-
if (!ParseHashStr(hashStr, hash))
220+
auto hash{uint256::FromHex(hashStr)};
221+
if (!hash) {
222222
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
223+
}
223224

224225
const CBlockIndex* tip = nullptr;
225226
std::vector<const CBlockIndex*> headers;
@@ -231,7 +232,7 @@ static bool rest_headers(const std::any& context,
231232
LOCK(cs_main);
232233
CChain& active_chain = chainman.ActiveChain();
233234
tip = active_chain.Tip();
234-
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(hash);
235+
const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*hash)};
235236
while (pindex != nullptr && active_chain.Contains(pindex)) {
236237
headers.push_back(pindex);
237238
if (headers.size() == *parsed_count) {
@@ -290,9 +291,10 @@ static bool rest_block(const std::any& context,
290291
std::string hashStr;
291292
const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
292293

293-
uint256 hash;
294-
if (!ParseHashStr(hashStr, hash))
294+
auto hash{uint256::FromHex(hashStr)};
295+
if (!hash) {
295296
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
297+
}
296298

297299
FlatFilePos pos{};
298300
const CBlockIndex* pblockindex = nullptr;
@@ -303,7 +305,7 @@ static bool rest_block(const std::any& context,
303305
{
304306
LOCK(cs_main);
305307
tip = chainman.ActiveChain().Tip();
306-
pblockindex = chainman.m_blockman.LookupBlockIndex(hash);
308+
pblockindex = chainman.m_blockman.LookupBlockIndex(*hash);
307309
if (!pblockindex) {
308310
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
309311
}
@@ -390,8 +392,8 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
390392
return RESTERR(req, HTTP_BAD_REQUEST, strprintf("Header count is invalid or out of acceptable range (1-%u): %s", MAX_REST_HEADERS_RESULTS, raw_count));
391393
}
392394

393-
uint256 block_hash;
394-
if (!ParseHashStr(raw_blockhash, block_hash)) {
395+
auto block_hash{uint256::FromHex(raw_blockhash)};
396+
if (!block_hash) {
395397
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + raw_blockhash);
396398
}
397399

@@ -413,7 +415,7 @@ static bool rest_filter_header(const std::any& context, HTTPRequest* req, const
413415
ChainstateManager& chainman = *maybe_chainman;
414416
LOCK(cs_main);
415417
CChain& active_chain = chainman.ActiveChain();
416-
const CBlockIndex* pindex = chainman.m_blockman.LookupBlockIndex(block_hash);
418+
const CBlockIndex* pindex{chainman.m_blockman.LookupBlockIndex(*block_hash)};
417419
while (pindex != nullptr && active_chain.Contains(pindex)) {
418420
headers.push_back(pindex);
419421
if (headers.size() == *parsed_count)
@@ -494,8 +496,8 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
494496
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid URI format. Expected /rest/blockfilter/<filtertype>/<blockhash>");
495497
}
496498

497-
uint256 block_hash;
498-
if (!ParseHashStr(uri_parts[1], block_hash)) {
499+
auto block_hash{uint256::FromHex(uri_parts[1])};
500+
if (!block_hash) {
499501
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + uri_parts[1]);
500502
}
501503

@@ -516,7 +518,7 @@ static bool rest_block_filter(const std::any& context, HTTPRequest* req, const s
516518
if (!maybe_chainman) return false;
517519
ChainstateManager& chainman = *maybe_chainman;
518520
LOCK(cs_main);
519-
block_index = chainman.m_blockman.LookupBlockIndex(block_hash);
521+
block_index = chainman.m_blockman.LookupBlockIndex(*block_hash);
520522
if (!block_index) {
521523
return RESTERR(req, HTTP_NOT_FOUND, uri_parts[1] + " not found");
522524
}
@@ -616,14 +618,14 @@ static bool rest_deploymentinfo(const std::any& context, HTTPRequest* req, const
616618
jsonRequest.params = UniValue(UniValue::VARR);
617619

618620
if (!hash_str.empty()) {
619-
uint256 hash;
620-
if (!ParseHashStr(hash_str, hash)) {
621+
auto hash{uint256::FromHex(hash_str)};
622+
if (!hash) {
621623
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hash_str);
622624
}
623625

624626
const ChainstateManager* chainman = GetChainman(context, req);
625627
if (!chainman) return false;
626-
if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(ParseHashV(hash_str, "blockhash")))) {
628+
if (!WITH_LOCK(::cs_main, return chainman->m_blockman.LookupBlockIndex(*hash))) {
627629
return RESTERR(req, HTTP_BAD_REQUEST, "Block not found");
628630
}
629631

@@ -704,9 +706,10 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
704706
std::string hashStr;
705707
const RESTResponseFormat rf = ParseDataFormat(hashStr, strURIPart);
706708

707-
uint256 hash;
708-
if (!ParseHashStr(hashStr, hash))
709+
auto hash{uint256::FromHex(hashStr)};
710+
if (!hash) {
709711
return RESTERR(req, HTTP_BAD_REQUEST, "Invalid hash: " + hashStr);
712+
}
710713

711714
if (g_txindex) {
712715
g_txindex->BlockUntilSyncedToCurrentChain();
@@ -715,7 +718,7 @@ static bool rest_tx(const std::any& context, HTTPRequest* req, const std::string
715718
const NodeContext* const node = GetNodeContext(context, req);
716719
if (!node) return false;
717720
uint256 hashBlock = uint256();
718-
const CTransactionRef tx = GetTransaction(/*block_index=*/nullptr, node->mempool.get(), hash, hashBlock, node->chainman->m_blockman);
721+
const CTransactionRef tx{GetTransaction(/*block_index=*/nullptr, node->mempool.get(), *hash, hashBlock, node->chainman->m_blockman)};
719722
if (!tx) {
720723
return RESTERR(req, HTTP_NOT_FOUND, hashStr + " not found");
721724
}
@@ -792,13 +795,14 @@ static bool rest_getutxos(const std::any& context, HTTPRequest* req, const std::
792795
if (txid_out.size() != 2) {
793796
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
794797
}
798+
auto txid{Txid::FromHex(txid_out.at(0))};
795799
auto output{ToIntegral<uint32_t>(txid_out.at(1))};
796800

797-
if (!output || !IsHex(txid_out.at(0))) {
801+
if (!txid || !output) {
798802
return RESTERR(req, HTTP_BAD_REQUEST, "Parse error");
799803
}
800804

801-
vOutPoints.emplace_back(TxidFromString(txid_out.at(0)), *output);
805+
vOutPoints.emplace_back(*txid, *output);
802806
}
803807

804808
if (vOutPoints.size() > 0)

src/rpc/mining.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -345,13 +345,11 @@ static RPCHelpMan generateblock()
345345
std::vector<CTransactionRef> txs;
346346
const auto raw_txs_or_txids = request.params[1].get_array();
347347
for (size_t i = 0; i < raw_txs_or_txids.size(); i++) {
348-
const auto str(raw_txs_or_txids[i].get_str());
348+
const auto& str{raw_txs_or_txids[i].get_str()};
349349

350-
uint256 hash;
351350
CMutableTransaction mtx;
352-
if (ParseHashStr(str, hash)) {
353-
354-
const auto tx = mempool.get(hash);
351+
if (auto hash{uint256::FromHex(str)}) {
352+
const auto tx{mempool.get(*hash)};
355353
if (!tx) {
356354
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, strprintf("Transaction %s not in mempool.", str));
357355
}

src/test/blockfilter_tests.cpp

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,7 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
149149

150150
unsigned int pos = 0;
151151
/*int block_height =*/ test[pos++].getInt<int>();
152-
uint256 block_hash;
153-
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), block_hash));
152+
BOOST_CHECK(uint256::FromHex(test[pos++].get_str()));
154153

155154
CBlock block;
156155
BOOST_REQUIRE(DecodeHexBlk(block, test[pos++].get_str()));
@@ -165,11 +164,9 @@ BOOST_AUTO_TEST_CASE(blockfilters_json_test)
165164
tx_undo.vprevout.emplace_back(txout, 0, false);
166165
}
167166

168-
uint256 prev_filter_header_basic;
169-
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), prev_filter_header_basic));
167+
uint256 prev_filter_header_basic{*Assert(uint256::FromHex(test[pos++].get_str()))};
170168
std::vector<unsigned char> filter_basic = ParseHex(test[pos++].get_str());
171-
uint256 filter_header_basic;
172-
BOOST_CHECK(ParseHashStr(test[pos++].get_str(), filter_header_basic));
169+
uint256 filter_header_basic{*Assert(uint256::FromHex(test[pos++].get_str()))};
173170

174171
BlockFilter computed_filter_basic(BlockFilterType::BASIC, block, block_undo);
175172
BOOST_CHECK(computed_filter_basic.GetFilter().GetEncoded() == filter_basic);

src/test/fuzz/hex.cpp

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@ FUZZ_TARGET(hex)
2727
assert(ToLower(random_hex_string) == hex_data);
2828
}
2929
(void)IsHexNumber(random_hex_string);
30-
uint256 result;
31-
(void)ParseHashStr(random_hex_string, result);
30+
(void)uint256::FromHex(random_hex_string);
3231
(void)uint256S(random_hex_string);
3332
try {
3433
(void)HexToPubKey(random_hex_string);

0 commit comments

Comments
 (0)