Skip to content

Commit fbbbc59

Browse files
committed
Merge bitcoin/bitcoin#23227: bitcoin-tx: Avoid treating integer overflow as OP_0
fa43e7c bitcoin-tx: Avoid treating overflow as OP_0 (MarcoFalke) fa053c0 style: Fix whitespace in Parse* functions (MarcoFalke) fa03dec refactor: Use C++11 range based for loop in ParseScript (MarcoFalke) fad55e7 doc: Fixup ToIntegral docs (MarcoFalke) Pull request description: Seems odd to treat integer overflow as `OP_0`, so fix that. ACKs for top commit: theStack: re-ACK fa43e7c shaavan: ACK fa43e7c Tree-SHA512: 1bbe2de62d853badc18d57d169c6e78ddcdff037e5a85357995dead11c8e67a4fe35087e08a181c60753f8ce91058b7fcc06f5b7901afedc78fbacea8bc3ef4f
2 parents e418a8e + fa43e7c commit fbbbc59

File tree

5 files changed

+32
-35
lines changed

5 files changed

+32
-35
lines changed

src/core_read.cpp

Lines changed: 22 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,20 @@ opcodetype ParseOpCode(const std::string& s)
2626
{
2727
static std::map<std::string, opcodetype> mapOpNames;
2828

29-
if (mapOpNames.empty())
30-
{
31-
for (unsigned int op = 0; op <= MAX_OPCODE; op++)
32-
{
29+
if (mapOpNames.empty()) {
30+
for (unsigned int op = 0; op <= MAX_OPCODE; op++) {
3331
// Allow OP_RESERVED to get into mapOpNames
34-
if (op < OP_NOP && op != OP_RESERVED)
32+
if (op < OP_NOP && op != OP_RESERVED) {
3533
continue;
34+
}
3635

3736
std::string strName = GetOpName(static_cast<opcodetype>(op));
38-
if (strName == "OP_UNKNOWN")
37+
if (strName == "OP_UNKNOWN") {
3938
continue;
39+
}
4040
mapOpNames[strName] = static_cast<opcodetype>(op);
4141
// Convenience: OP_ADD and just ADD are both recognized:
42-
if (strName.compare(0, 3, "OP_") == 0) { // strName starts with "OP_"
42+
if (strName.compare(0, 3, "OP_") == 0) { // strName starts with "OP_"
4343
mapOpNames[strName.substr(3)] = static_cast<opcodetype>(op);
4444
}
4545
}
@@ -59,44 +59,35 @@ CScript ParseScript(const std::string& s)
5959
std::vector<std::string> words;
6060
boost::algorithm::split(words, s, boost::algorithm::is_any_of(" \t\n"), boost::algorithm::token_compress_on);
6161

62-
for (std::vector<std::string>::const_iterator w = words.begin(); w != words.end(); ++w)
63-
{
64-
if (w->empty())
65-
{
62+
for (const std::string& w : words) {
63+
if (w.empty()) {
6664
// Empty string, ignore. (boost::split given '' will return one word)
67-
}
68-
else if (std::all_of(w->begin(), w->end(), ::IsDigit) ||
69-
(w->front() == '-' && w->size() > 1 && std::all_of(w->begin()+1, w->end(), ::IsDigit)))
65+
} else if (std::all_of(w.begin(), w.end(), ::IsDigit) ||
66+
(w.front() == '-' && w.size() > 1 && std::all_of(w.begin() + 1, w.end(), ::IsDigit)))
7067
{
7168
// Number
72-
int64_t n = LocaleIndependentAtoi<int64_t>(*w);
69+
const auto num{ToIntegral<int64_t>(w)};
7370

74-
//limit the range of numbers ParseScript accepts in decimal
75-
//since numbers outside -0xFFFFFFFF...0xFFFFFFFF are illegal in scripts
76-
if (n > int64_t{0xffffffff} || n < -1 * int64_t{0xffffffff}) {
71+
// limit the range of numbers ParseScript accepts in decimal
72+
// since numbers outside -0xFFFFFFFF...0xFFFFFFFF are illegal in scripts
73+
if (!num.has_value() || num > int64_t{0xffffffff} || num < -1 * int64_t{0xffffffff}) {
7774
throw std::runtime_error("script parse error: decimal numeric value only allowed in the "
7875
"range -0xFFFFFFFF...0xFFFFFFFF");
7976
}
8077

81-
result << n;
82-
}
83-
else if (w->substr(0,2) == "0x" && w->size() > 2 && IsHex(std::string(w->begin()+2, w->end())))
84-
{
78+
result << num.value();
79+
} else if (w.substr(0, 2) == "0x" && w.size() > 2 && IsHex(std::string(w.begin() + 2, w.end()))) {
8580
// Raw hex data, inserted NOT pushed onto stack:
86-
std::vector<unsigned char> raw = ParseHex(std::string(w->begin()+2, w->end()));
81+
std::vector<unsigned char> raw = ParseHex(std::string(w.begin() + 2, w.end()));
8782
result.insert(result.end(), raw.begin(), raw.end());
88-
}
89-
else if (w->size() >= 2 && w->front() == '\'' && w->back() == '\'')
90-
{
83+
} else if (w.size() >= 2 && w.front() == '\'' && w.back() == '\'') {
9184
// Single-quoted string, pushed as data. NOTE: this is poor-man's
9285
// parsing, spaces/tabs/newlines in single-quoted strings won't work.
93-
std::vector<unsigned char> value(w->begin()+1, w->end()-1);
86+
std::vector<unsigned char> value(w.begin() + 1, w.end() - 1);
9487
result << value;
95-
}
96-
else
97-
{
88+
} else {
9889
// opcode, e.g. OP_ADD or ADD:
99-
result << ParseOpCode(*w);
90+
result << ParseOpCode(w);
10091
}
10192
}
10293

src/test/script_parse_tests.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ BOOST_AUTO_TEST_CASE(parse_script)
3838
{"'17'", "023137"},
3939
{"ELSE", "67"},
4040
{"NOP10", "b9"},
41-
{"11111111111111111111", "00"},
4241
};
4342
std::string all_in;
4443
std::string all_out;
@@ -49,6 +48,7 @@ BOOST_AUTO_TEST_CASE(parse_script)
4948
}
5049
BOOST_CHECK_EQUAL(HexStr(ParseScript(all_in)), all_out);
5150

51+
BOOST_CHECK_EXCEPTION(ParseScript("11111111111111111111"), std::runtime_error, HasReason("script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF"));
5252
BOOST_CHECK_EXCEPTION(ParseScript("11111111111"), std::runtime_error, HasReason("script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF"));
5353
BOOST_CHECK_EXCEPTION(ParseScript("OP_CHECKSIGADD"), std::runtime_error, HasReason("script parse error: unknown opcode"));
5454
}

src/util/strencodings.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ void SplitHostPort(std::string in, uint16_t& portOut, std::string& hostOut);
7272

7373
// LocaleIndependentAtoi is provided for backwards compatibility reasons.
7474
//
75-
// New code should use the ParseInt64/ParseUInt64/ParseInt32/ParseUInt32 functions
75+
// New code should use ToIntegral or the ParseInt* functions
7676
// which provide parse error feedback.
7777
//
7878
// The goal of LocaleIndependentAtoi is to replicate the exact defined behaviour
@@ -125,7 +125,7 @@ constexpr inline bool IsSpace(char c) noexcept {
125125
/**
126126
* Convert string to integral type T. Leading whitespace, a leading +, or any
127127
* trailing character fail the parsing. The required format expressed as regex
128-
* is `-?[0-9]+`.
128+
* is `-?[0-9]+`. The minus sign is only permitted for signed integer types.
129129
*
130130
* @returns std::nullopt if the entire string could not be parsed, or if the
131131
* parsed value is not in the range representable by the type T.

test/lint/lint-locale-dependence.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ export LC_ALL=C
3838
# https://stackoverflow.com/a/34878283 for more details.
3939

4040
# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent stoul/strtol with locale
41-
# independent ToIntegral<T>(...).
41+
# independent ToIntegral<T>(...) or the ParseInt*() functions.
4242
# TODO: Reduce KNOWN_VIOLATIONS by replacing uses of locale dependent snprintf with strprintf.
4343
KNOWN_VIOLATIONS=(
4444
"src/bitcoin-tx.cpp.*stoul"

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,12 @@
294294
"output_cmp": "txcreatescript4.json",
295295
"description": "Create a new transaction with a single output script (OP_DROP) in a P2SH, wrapped in a P2SH (output as json)"
296296
},
297+
{ "exec": "./bitcoin-tx",
298+
"args": ["-create", "outscript=0:999999999999999999999999999999"],
299+
"return_code": 1,
300+
"error_txt": "error: script parse error: decimal numeric value only allowed in the range -0xFFFFFFFF...0xFFFFFFFF",
301+
"description": "Try to parse an output script with a decimal number above the allowed range"
302+
},
297303
{ "exec": "./bitcoin-tx",
298304
"args": ["-create", "outscript=0:9999999999"],
299305
"return_code": 1,

0 commit comments

Comments
 (0)