diff --git a/src/core_read.cpp b/src/core_read.cpp --- a/src/core_read.cpp +++ b/src/core_read.cpp @@ -179,6 +179,29 @@ return result; } +// Check that all of the input and output scripts of a transaction contains +// valid opcodes +bool CheckTxScriptsSanity(const CMutableTransaction &tx) { + // Check input scripts for non-coinbase txs + if (!CTransaction(tx).IsCoinBase()) { + for (const auto &i : tx.vin) { + if (!i.scriptSig.HasValidOps() || + i.scriptSig.size() > MAX_SCRIPT_SIZE) { + return false; + } + } + } + // Check output scripts + for (const auto &o : tx.vout) { + if (!o.scriptPubKey.HasValidOps() || + o.scriptPubKey.size() > MAX_SCRIPT_SIZE) { + return false; + } + } + + return true; +} + bool DecodeHexTx(CMutableTransaction &tx, const std::string &strHexTx) { if (!IsHex(strHexTx)) { return false; @@ -189,14 +212,14 @@ CDataStream ssData(txData, SER_NETWORK, PROTOCOL_VERSION); try { ssData >> tx; - if (!ssData.empty()) { - return false; + if (ssData.eof() && CheckTxScriptsSanity(tx)) { + return true; } - } catch (const std::exception &) { - return false; + } catch (const std::exception &e) { + // Fall through. } - return true; + return false; } bool DecodeHexBlockHeader(CBlockHeader &header, const std::string &hex_header) { diff --git a/src/script/script.h b/src/script/script.h --- a/src/script/script.h +++ b/src/script/script.h @@ -192,6 +192,9 @@ OP_INVALIDOPCODE = 0xff, }; +// Maximum value that an opcode can be +static const unsigned int MAX_OPCODE = FIRST_UNDEFINED_OP_VALUE - 1; + const char *GetOpName(opcodetype opcode); /** @@ -566,6 +569,9 @@ bool IsPushOnly(const_iterator pc) const; bool IsPushOnly() const; + /** Check if the script contains valid OP_CODES */ + bool HasValidOps() const; + /** * Returns whether the script is guaranteed to fail at execution, regardless * of the initial stack. This allows outputs to be pruned instantly when diff --git a/src/script/script.cpp b/src/script/script.cpp --- a/src/script/script.cpp +++ b/src/script/script.cpp @@ -562,3 +562,16 @@ opcodeRet = static_cast(opcode); return true; } + +bool CScript::HasValidOps() const { + CScript::const_iterator it = begin(); + while (it < end()) { + opcodetype opcode; + std::vector item; + if (!GetOp(it, opcode, item) || opcode > MAX_OPCODE || + item.size() > MAX_SCRIPT_ELEMENT_SIZE) { + return false; + } + } + return true; +} diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -3026,6 +3026,34 @@ } } +BOOST_AUTO_TEST_CASE(script_HasValidOps) { + // Exercise the HasValidOps functionality + CScript script; + // Normal script + script = + ScriptFromHex("76a9141234567890abcdefa1a2a3a4a5a6a7a8a9a0aaab88ac"); + BOOST_CHECK(script.HasValidOps()); + script = + ScriptFromHex("76a914ff34567890abcdefa1a2a3a4a5a6a7a8a9a0aaab88ac"); + BOOST_CHECK(script.HasValidOps()); + // Script with OP_INVALIDOPCODE explicit + script = ScriptFromHex("ff88ac"); + BOOST_CHECK(!script.HasValidOps()); + // Script with undefined opcode + script = ScriptFromHex("88acc0"); + BOOST_CHECK(!script.HasValidOps()); + + // Check all non push opcodes. + for (uint8_t opcode = OP_1NEGATE; opcode < FIRST_UNDEFINED_OP_VALUE; + opcode++) { + script = CScript() << opcode; + BOOST_CHECK(script.HasValidOps()); + } + + script = CScript() << FIRST_UNDEFINED_OP_VALUE; + BOOST_CHECK(!script.HasValidOps()); +} + BOOST_AUTO_TEST_CASE(script_can_append_self) { CScript s, d; diff --git a/test/functional/test_framework/blocktools.py b/test/functional/test_framework/blocktools.py --- a/test/functional/test_framework/blocktools.py +++ b/test/functional/test_framework/blocktools.py @@ -10,7 +10,6 @@ OP_DUP, OP_EQUALVERIFY, OP_HASH160, - OP_PUSHDATA2, OP_RETURN, OP_TRUE, ) @@ -178,7 +177,7 @@ def send_big_transactions(node, utxos, num, fee_multiplier): from .cashaddr import decode txids = [] - padding = "1"*(512*127) + padding = "1"*512 addrHash = decode(node.getnewaddress())[2] for _ in range(num): @@ -186,13 +185,14 @@ utxo = utxos.pop() txid = int(utxo['txid'], 16) ctx.vin.append(CTxIn(COutPoint(txid, int(utxo["vout"])), b"")) - ctx.vout.append(CTxOut(0, CScript( - [OP_RETURN, OP_PUSHDATA2, len(padding), bytes(padding, 'utf-8')]))) ctx.vout.append( CTxOut(int(satoshi_round(utxo['amount']*COIN)), CScript([OP_DUP, OP_HASH160, addrHash, OP_EQUALVERIFY, OP_CHECKSIG]))) + for i in range(0, 127): + ctx.vout.append(CTxOut(0, CScript( + [OP_RETURN, bytes(padding, 'utf-8')]))) # Create a proper fee for the transaction to be mined - ctx.vout[1].nValue -= int(fee_multiplier * node.calculate_fee(ctx)) + ctx.vout[0].nValue -= int(fee_multiplier * node.calculate_fee(ctx)) signresult = node.signrawtransactionwithwallet( ToHex(ctx), None, "NONE|FORKID") txid = node.sendrawtransaction(signresult["hex"], True)