Skip to content

Commit 57889e6

Browse files
committed
bitcoin-tx: Stricter check for valid integers
Just calling atoi to convert strings to integers does not check for valid integers very thoroughly; in particular, it just ignores everything starting from the first non-numeral character. Even a string like "foo" is fine and silently returns 0. This meant that bitcoin-tx would not fail if such a string was passed in various places where an integer is expected (like the locktime or an input/output index); this means that it would, for instance, silently accept a typo and interpret it in an unexpected way. In this change, we use ParseInt64 for parsing strings to integers, which actually verifies that the full string is valid as number. New tests in the bitcoin-util-test cover the new error paths.
1 parent 0212187 commit 57889e6

File tree

2 files changed

+75
-22
lines changed

2 files changed

+75
-22
lines changed

src/bitcoin-tx.cpp

+20-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright (c) 2009-2017 The Bitcoin Core developers
1+
// Copyright (c) 2009-2018 The Bitcoin Core developers
22
// Distributed under the MIT software license, see the accompanying
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

@@ -193,27 +193,27 @@ static CAmount ExtractAndValidateValue(const std::string& strValue)
193193

194194
static void MutateTxVersion(CMutableTransaction& tx, const std::string& cmdVal)
195195
{
196-
int64_t newVersion = atoi64(cmdVal);
197-
if (newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION)
198-
throw std::runtime_error("Invalid TX version requested");
196+
int64_t newVersion;
197+
if (!ParseInt64(cmdVal, &newVersion) || newVersion < 1 || newVersion > CTransaction::MAX_STANDARD_VERSION)
198+
throw std::runtime_error("Invalid TX version requested: '" + cmdVal + "'");
199199

200200
tx.nVersion = (int) newVersion;
201201
}
202202

203203
static void MutateTxLocktime(CMutableTransaction& tx, const std::string& cmdVal)
204204
{
205-
int64_t newLocktime = atoi64(cmdVal);
206-
if (newLocktime < 0LL || newLocktime > 0xffffffffLL)
207-
throw std::runtime_error("Invalid TX locktime requested");
205+
int64_t newLocktime;
206+
if (!ParseInt64(cmdVal, &newLocktime) || newLocktime < 0LL || newLocktime > 0xffffffffLL)
207+
throw std::runtime_error("Invalid TX locktime requested: '" + cmdVal + "'");
208208

209209
tx.nLockTime = (unsigned int) newLocktime;
210210
}
211211

212212
static void MutateTxRBFOptIn(CMutableTransaction& tx, const std::string& strInIdx)
213213
{
214214
// parse requested index
215-
int inIdx = atoi(strInIdx);
216-
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) {
215+
int64_t inIdx;
216+
if (!ParseInt64(strInIdx, &inIdx) || inIdx < 0 || inIdx >= static_cast<int64_t>(tx.vin.size())) {
217217
throw std::runtime_error("Invalid TX input index '" + strInIdx + "'");
218218
}
219219

@@ -248,10 +248,10 @@ static void MutateTxAddInput(CMutableTransaction& tx, const std::string& strInpu
248248
static const unsigned int maxVout = MAX_BLOCK_WEIGHT / (WITNESS_SCALE_FACTOR * minTxOutSz);
249249

250250
// extract and validate vout
251-
std::string strVout = vStrInputParts[1];
252-
int vout = atoi(strVout);
253-
if ((vout < 0) || (vout > (int)maxVout))
254-
throw std::runtime_error("invalid TX input vout");
251+
const std::string& strVout = vStrInputParts[1];
252+
int64_t vout;
253+
if (!ParseInt64(strVout, &vout) || vout < 0 || vout > static_cast<int64_t>(maxVout))
254+
throw std::runtime_error("invalid TX input vout '" + strVout + "'");
255255

256256
// extract the optional sequence number
257257
uint32_t nSequenceIn=std::numeric_limits<unsigned int>::max();
@@ -481,10 +481,9 @@ static void MutateTxAddOutScript(CMutableTransaction& tx, const std::string& str
481481
static void MutateTxDelInput(CMutableTransaction& tx, const std::string& strInIdx)
482482
{
483483
// parse requested deletion index
484-
int inIdx = atoi(strInIdx);
485-
if (inIdx < 0 || inIdx >= (int)tx.vin.size()) {
486-
std::string strErr = "Invalid TX input index '" + strInIdx + "'";
487-
throw std::runtime_error(strErr.c_str());
484+
int64_t inIdx;
485+
if (!ParseInt64(strInIdx, &inIdx) || inIdx < 0 || inIdx >= static_cast<int64_t>(tx.vin.size())) {
486+
throw std::runtime_error("Invalid TX input index '" + strInIdx + "'");
488487
}
489488

490489
// delete input from transaction
@@ -494,10 +493,9 @@ static void MutateTxDelInput(CMutableTransaction& tx, const std::string& strInId
494493
static void MutateTxDelOutput(CMutableTransaction& tx, const std::string& strOutIdx)
495494
{
496495
// parse requested deletion index
497-
int outIdx = atoi(strOutIdx);
498-
if (outIdx < 0 || outIdx >= (int)tx.vout.size()) {
499-
std::string strErr = "Invalid TX output index '" + strOutIdx + "'";
500-
throw std::runtime_error(strErr.c_str());
496+
int64_t outIdx;
497+
if (!ParseInt64(strOutIdx, &outIdx) || outIdx < 0 || outIdx >= static_cast<int64_t>(tx.vout.size())) {
498+
throw std::runtime_error("Invalid TX output index '" + strOutIdx + "'");
501499
}
502500

503501
// delete output from transaction
@@ -593,7 +591,7 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr)
593591

594592
uint256 txid = ParseHashStr(prevOut["txid"].get_str(), "txid");
595593

596-
int nOut = atoi(prevOut["vout"].getValStr());
594+
const int nOut = prevOut["vout"].get_int();
597595
if (nOut < 0)
598596
throw std::runtime_error("vout must be positive");
599597

test/util/data/bitcoin-util-test.json

+55
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
"output_cmp": "blanktxv2.json",
2727
"description": "Creates a blank transaction when nothing is piped into bitcoin-tx (output in json)"
2828
},
29+
{ "exec": "./bitcoin-tx",
30+
"args": ["-create", "nversion=1foo"],
31+
"return_code": 1,
32+
"error_txt": "error: Invalid TX version requested",
33+
"description": "Tests the check for invalid nversion value"
34+
},
2935
{ "exec": "./bitcoin-tx",
3036
"args": ["-", "delin=1"],
3137
"input": "tx394b54bb.hex",
@@ -45,6 +51,13 @@
4551
"error_txt": "error: Invalid TX input index '31'",
4652
"description": "Attempts to delete an input with a bad index from a transaction. Expected to fail."
4753
},
54+
{ "exec": "./bitcoin-tx",
55+
"args": ["-", "delin=1foo"],
56+
"input": "tx394b54bb.hex",
57+
"return_code": 1,
58+
"error_txt": "error: Invalid TX input index",
59+
"description": "Tests the check for an invalid input index with delin"
60+
},
4861
{ "exec": "./bitcoin-tx",
4962
"args": ["-", "delout=1"],
5063
"input": "tx394b54bb.hex",
@@ -64,6 +77,13 @@
6477
"error_txt": "error: Invalid TX output index '2'",
6578
"description": "Attempts to delete an output with a bad index from a transaction. Expected to fail."
6679
},
80+
{ "exec": "./bitcoin-tx",
81+
"args": ["-", "delout=1foo"],
82+
"input": "tx394b54bb.hex",
83+
"return_code": 1,
84+
"error_txt": "error: Invalid TX output index",
85+
"description": "Tests the check for an invalid output index with delout"
86+
},
6787
{ "exec": "./bitcoin-tx",
6888
"args": ["-", "locktime=317000"],
6989
"input": "tx394b54bb.hex",
@@ -76,6 +96,29 @@
7696
"output_cmp": "tt-locktime317000-out.json",
7797
"description": "Adds an nlocktime to a transaction (output in json)"
7898
},
99+
{ "exec": "./bitcoin-tx",
100+
"args": ["-create", "locktime=317000foo"],
101+
"return_code": 1,
102+
"error_txt": "error: Invalid TX locktime requested",
103+
"description": "Tests the check for invalid locktime value"
104+
},
105+
{ "exec": "./bitcoin-tx",
106+
"args":
107+
["-create",
108+
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0",
109+
"replaceable=0foo"],
110+
"return_code": 1,
111+
"error_txt": "error: Invalid TX input index",
112+
"description": "Tests the check for an invalid input index with replaceable"
113+
},
114+
{ "exec": "./bitcoin-tx",
115+
"args":
116+
["-create",
117+
"in=5897de6bd6027a475eadd57019d4e6872c396d0716c4875a5f1a6fcfdf385c1f:0x"],
118+
"return_code": 1,
119+
"error_txt": "error: invalid TX input vout",
120+
"description": "Tests the check for an invalid vout value when adding an input"
121+
},
79122
{ "exec": "./bitcoin-tx",
80123
"args":
81124
["-create",
@@ -225,6 +268,18 @@
225268
"output_cmp": "txcreatesignv2.hex",
226269
"description": "Creates a new transaction with a single input and a single output, and then signs the transaction"
227270
},
271+
{ "exec": "./bitcoin-tx",
272+
"args":
273+
["-create",
274+
"in=4d49a71ec9da436f71ec4ee231d04f292a29cd316f598bb7068feccabdc59485:0",
275+
"set=privatekeys:[\"5HpHagT65TZzG1PH3CSu63k8DbpvD8s5ip4nEB3kEsreAnchuDf\"]",
276+
"set=prevtxs:[{\"txid\":\"4d49a71ec9da436f71ec4ee231d04f292a29cd316f598bb7068feccabdc59485\",\"vout\":\"0foo\",\"scriptPubKey\":\"76a91491b24bf9f5288532960ac687abb035127b1d28a588ac\"}]",
277+
"sign=ALL",
278+
"outaddr=0.001:193P6LtvS4nCnkDvM9uXn1gsSRqh4aDAz7"],
279+
"return_code": 1,
280+
"error_txt": "error: prevtxs internal object typecheck fail",
281+
"description": "Tests the check for invalid vout index in prevtxs for sign"
282+
},
228283
{ "exec": "./bitcoin-tx",
229284
"args":
230285
["-create", "outpubkey=0:02a5613bd857b7048924264d1e70e08fb2a7e6527d32b7ab1bb993ac59964ff397", "nversion=1"],

0 commit comments

Comments
 (0)