Skip to content

Commit eb4aef2

Browse files
committed
fix: bound CoinJoin entry intake
1 parent 0be42b2 commit eb4aef2

3 files changed

Lines changed: 88 additions & 7 deletions

File tree

src/coinjoin/coinjoin.h

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@ class CCoinJoinEntry
154154
CTransactionRef txCollateral;
155155
// memory only
156156
CService addr;
157+
bool fHasOversizedTxOut{false};
157158

158159
CCoinJoinEntry() :
159160
txCollateral(MakeTransactionRef(CMutableTransaction{}))
@@ -167,9 +168,41 @@ class CCoinJoinEntry
167168
{
168169
}
169170

170-
SERIALIZE_METHODS(CCoinJoinEntry, obj)
171+
template <typename Stream>
172+
void Serialize(Stream& s) const
171173
{
172-
READWRITE(obj.vecTxDSIn, obj.txCollateral, obj.vecTxOut);
174+
s << vecTxDSIn << txCollateral << vecTxOut;
175+
}
176+
177+
template <typename Stream>
178+
void Unserialize(Stream& s)
179+
{
180+
vecTxDSIn.clear();
181+
vecTxOut.clear();
182+
fHasOversizedTxOut = false;
183+
184+
const size_t txdsin_size = ReadCompactSize(s);
185+
if (txdsin_size > COINJOIN_ENTRY_MAX_SIZE) {
186+
throw std::ios_base::failure("CCoinJoinEntry::vecTxDSIn size too large");
187+
}
188+
vecTxDSIn.reserve(txdsin_size);
189+
while (vecTxDSIn.size() < txdsin_size) {
190+
vecTxDSIn.emplace_back();
191+
s >> vecTxDSIn.back();
192+
}
193+
194+
s >> txCollateral;
195+
196+
const size_t txout_size = ReadCompactSize(s);
197+
if (txout_size > COINJOIN_ENTRY_MAX_SIZE) {
198+
fHasOversizedTxOut = true;
199+
return;
200+
}
201+
vecTxOut.reserve(txout_size);
202+
while (vecTxOut.size() < txout_size) {
203+
vecTxOut.emplace_back();
204+
s >> vecTxOut.back();
205+
}
173206
}
174207

175208
bool AddScriptSig(const CTxIn& txin);

src/coinjoin/server.cpp

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -592,19 +592,28 @@ bool CCoinJoinServer::AddEntry(const CCoinJoinEntry& entry, PoolMessage& nMessag
592592
return false;
593593
}
594594

595-
if (!CoinJoin::IsCollateralValid(m_chainman, m_isman, mempool, *entry.txCollateral)) {
596-
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: collateral not valid!\n", __func__);
597-
nMessageIDRet = ERR_INVALID_COLLATERAL;
595+
if (entry.vecTxDSIn.size() > COINJOIN_ENTRY_MAX_SIZE) {
596+
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: too many inputs! %d/%d\n", __func__, entry.vecTxDSIn.size(), COINJOIN_ENTRY_MAX_SIZE);
597+
nMessageIDRet = ERR_MAXIMUM;
598+
ConsumeCollateral(entry.txCollateral);
598599
return false;
599600
}
600601

601-
if (entry.vecTxDSIn.size() > COINJOIN_ENTRY_MAX_SIZE) {
602-
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: too many inputs! %d/%d\n", __func__, entry.vecTxDSIn.size(), COINJOIN_ENTRY_MAX_SIZE);
602+
if (entry.fHasOversizedTxOut || entry.vecTxOut.size() > COINJOIN_ENTRY_MAX_SIZE) {
603+
const size_t txout_size{entry.fHasOversizedTxOut ? COINJOIN_ENTRY_MAX_SIZE + 1 : entry.vecTxOut.size()};
604+
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: too many outputs! %d/%d\n", __func__,
605+
static_cast<int>(txout_size), static_cast<int>(COINJOIN_ENTRY_MAX_SIZE));
603606
nMessageIDRet = ERR_MAXIMUM;
604607
ConsumeCollateral(entry.txCollateral);
605608
return false;
606609
}
607610

611+
if (!CoinJoin::IsCollateralValid(m_chainman, m_isman, mempool, *entry.txCollateral)) {
612+
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- ERROR: collateral not valid!\n", __func__);
613+
nMessageIDRet = ERR_INVALID_COLLATERAL;
614+
return false;
615+
}
616+
608617
std::vector<CTxIn> vin;
609618
for (const auto& txin : entry.vecTxDSIn) {
610619
LogPrint(BCLog::COINJOIN, "CCoinJoinServer::%s -- txin=%s\n", __func__, txin.ToString());

src/test/coinjoin_inouts_tests.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include <coinjoin/common.h>
1515
#include <evo/chainhelper.h>
1616
#include <script/script.h>
17+
#include <streams.h>
1718
#include <uint256.h>
1819
#include <util/check.h>
1920
#include <util/time.h>
@@ -155,6 +156,44 @@ BOOST_AUTO_TEST_CASE(entry_addscriptsig_matches_and_rejects)
155156
}
156157
}
157158

159+
BOOST_AUTO_TEST_CASE(entry_rejects_oversized_inputs_before_materializing)
160+
{
161+
CDataStream stream{SER_NETWORK, PROTOCOL_VERSION};
162+
WriteCompactSize(stream, COINJOIN_ENTRY_MAX_SIZE + 1);
163+
164+
CCoinJoinEntry entry;
165+
BOOST_CHECK_THROW(stream >> entry, std::ios_base::failure);
166+
BOOST_CHECK(entry.vecTxDSIn.empty());
167+
}
168+
169+
BOOST_AUTO_TEST_CASE(entry_flags_oversized_outputs_before_materializing)
170+
{
171+
CDataStream stream{SER_NETWORK, PROTOCOL_VERSION};
172+
WriteCompactSize(stream, 0);
173+
stream << MakeTransactionRef(CMutableTransaction{});
174+
WriteCompactSize(stream, COINJOIN_ENTRY_MAX_SIZE + 1);
175+
176+
CCoinJoinEntry entry;
177+
BOOST_CHECK_NO_THROW(stream >> entry);
178+
BOOST_CHECK(entry.fHasOversizedTxOut);
179+
BOOST_CHECK(entry.vecTxOut.empty());
180+
}
181+
182+
BOOST_AUTO_TEST_CASE(entry_accepts_max_sized_vectors)
183+
{
184+
CCoinJoinEntry entry;
185+
entry.vecTxDSIn.resize(COINJOIN_ENTRY_MAX_SIZE); // exactly the cap must be accepted
186+
entry.vecTxOut.resize(COINJOIN_ENTRY_MAX_SIZE);
187+
188+
CDataStream stream{SER_NETWORK, PROTOCOL_VERSION};
189+
stream << entry;
190+
191+
CCoinJoinEntry roundtripped;
192+
BOOST_CHECK_NO_THROW(stream >> roundtripped);
193+
BOOST_CHECK_EQUAL(roundtripped.vecTxDSIn.size(), COINJOIN_ENTRY_MAX_SIZE);
194+
BOOST_CHECK_EQUAL(roundtripped.vecTxOut.size(), COINJOIN_ENTRY_MAX_SIZE);
195+
}
196+
158197
BOOST_AUTO_TEST_CASE(queue_timeout_bounds)
159198
{
160199
CCoinJoinQueue dsq;

0 commit comments

Comments
 (0)