Skip to content

Commit ca40ef6

Browse files
committed
Merge #8312: Fix mempool DoS vulnerability from malleated transactions
46c9620 Test that unnecessary witnesses can't be used for mempool DoS (Suhas Daftuar) bb66a11 Fix DoS vulnerability in mempool acceptance (Suhas Daftuar)
2 parents 4324bd2 + 46c9620 commit ca40ef6

File tree

2 files changed

+56
-8
lines changed

2 files changed

+56
-8
lines changed

qa/rpc-tests/p2p-segwit.py

Lines changed: 51 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ def __init__(self):
4343
self.last_pong = msg_pong(0)
4444
self.sleep_time = 0.05
4545
self.getdataset = set()
46+
self.last_reject = None
4647

4748
def add_connection(self, conn):
4849
self.connection = conn
@@ -68,7 +69,7 @@ def on_pong(self, conn, message):
6869

6970
def on_reject(self, conn, message):
7071
self.last_reject = message
71-
#print message
72+
#print (message)
7273

7374
# Syncing helpers
7475
def sync(self, test_function, timeout=60):
@@ -136,13 +137,17 @@ def request_block(self, blockhash, inv_type, timeout=60):
136137
self.wait_for_block(blockhash, timeout)
137138
return self.last_block
138139

139-
def test_transaction_acceptance(self, tx, with_witness, accepted):
140+
def test_transaction_acceptance(self, tx, with_witness, accepted, reason=None):
140141
tx_message = msg_tx(tx)
141142
if with_witness:
142143
tx_message = msg_witness_tx(tx)
143144
self.send_message(tx_message)
144145
self.sync_with_ping()
145146
assert_equal(tx.hash in self.connection.rpc.getrawmempool(), accepted)
147+
if (reason != None and not accepted):
148+
# Check the rejection reason as well.
149+
with mininode_lock:
150+
assert_equal(self.last_reject.reason, reason)
146151

147152
# Test whether a witness block had the correct effect on the tip
148153
def test_witness_block(self, block, accepted, with_witness=True):
@@ -277,9 +282,52 @@ def test_unnecessary_witness_before_segwit_activation(self):
277282
self.test_node.sync_with_ping()
278283
assert_equal(self.nodes[0].getbestblockhash(), block.hash)
279284

285+
sync_blocks(self.nodes)
286+
287+
# Create a p2sh output -- this is so we can pass the standardness
288+
# rules (an anyone-can-spend OP_TRUE would be rejected, if not wrapped
289+
# in P2SH).
290+
p2sh_program = CScript([OP_TRUE])
291+
p2sh_pubkey = hash160(p2sh_program)
292+
scriptPubKey = CScript([OP_HASH160, p2sh_pubkey, OP_EQUAL])
293+
294+
# Now check that unnecessary witnesses can't be used to blind a node
295+
# to a transaction, eg by violating standardness checks.
296+
tx2 = CTransaction()
297+
tx2.vin.append(CTxIn(COutPoint(tx.sha256, 0), b""))
298+
tx2.vout.append(CTxOut(tx.vout[0].nValue-1000, scriptPubKey))
299+
tx2.rehash()
300+
self.test_node.test_transaction_acceptance(tx2, False, True)
301+
self.nodes[0].generate(1)
302+
sync_blocks(self.nodes)
303+
304+
# We'll add an unnecessary witness to this transaction that would cause
305+
# it to be too large according to IsStandard.
306+
tx3 = CTransaction()
307+
tx3.vin.append(CTxIn(COutPoint(tx2.sha256, 0), CScript([p2sh_program])))
308+
tx3.vout.append(CTxOut(tx2.vout[0].nValue-1000, scriptPubKey))
309+
tx3.wit.vtxinwit.append(CTxinWitness())
310+
tx3.wit.vtxinwit[0].scriptWitness.stack = [b'a'*400000]
311+
tx3.rehash()
312+
self.std_node.test_transaction_acceptance(tx3, True, False, b'no-witness-yet')
313+
314+
# If we send without witness, it should be accepted.
315+
self.std_node.test_transaction_acceptance(tx3, False, True)
316+
317+
# Now create a new anyone-can-spend utxo for the next test.
318+
tx4 = CTransaction()
319+
tx4.vin.append(CTxIn(COutPoint(tx3.sha256, 0), CScript([p2sh_program])))
320+
tx4.vout.append(CTxOut(tx3.vout[0].nValue-1000, CScript([OP_TRUE])))
321+
tx4.rehash()
322+
self.test_node.test_transaction_acceptance(tx3, False, True)
323+
self.test_node.test_transaction_acceptance(tx4, False, True)
324+
325+
self.nodes[0].generate(1)
326+
sync_blocks(self.nodes)
327+
280328
# Update our utxo list; we spent the first entry.
281329
self.utxo.pop(0)
282-
self.utxo.append(UTXO(tx.sha256, 0, tx.vout[0].nValue))
330+
self.utxo.append(UTXO(tx4.sha256, 0, tx4.vout[0].nValue))
283331

284332

285333
# Mine enough blocks for segwit's vb state to be 'started'.

src/main.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1132,11 +1132,6 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
11321132
if (tx.IsCoinBase())
11331133
return state.DoS(100, false, REJECT_INVALID, "coinbase");
11341134

1135-
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
1136-
string reason;
1137-
if (fRequireStandard && !IsStandardTx(tx, reason))
1138-
return state.DoS(0, false, REJECT_NONSTANDARD, reason);
1139-
11401135
// Don't relay version 2 transactions until CSV is active, and we can be
11411136
// sure that such transactions will be mined (unless we're on
11421137
// -testnet/-regtest).
@@ -1150,6 +1145,11 @@ bool AcceptToMemoryPoolWorker(CTxMemPool& pool, CValidationState& state, const C
11501145
return state.DoS(0, false, REJECT_NONSTANDARD, "no-witness-yet", true);
11511146
}
11521147

1148+
// Rather not work on nonstandard transactions (unless -testnet/-regtest)
1149+
string reason;
1150+
if (fRequireStandard && !IsStandardTx(tx, reason))
1151+
return state.DoS(0, false, REJECT_NONSTANDARD, reason);
1152+
11531153
// Only accept nLockTime-using transactions that can be mined in the next
11541154
// block; we don't want our mempool filled up with transactions that can't
11551155
// be mined yet.

0 commit comments

Comments
 (0)