Skip to content

Commit 6d58a5c

Browse files
committed
Merge #14685: fix a deserialization overflow edge case
b08af10 disallow oversized CBlockHeaderAndShortTxIDs (Kaz Wesley) 6bed4b3 fix a deserialization overflow edge case (Kaz Wesley) 051faf7 add a test demonstrating an overflow in a deserialization edge case (Kaz Wesley) Pull request description: A specially-constructed BlockTransactionsRequest can cause `offset` to wrap in deserialization. In the current code, there is not any way this could be dangerous; but disallowing it reduces the potential for future surprises. Tree-SHA512: 1aaf7636e0801a905ed8807d0d1762132ac8b4421a600c35fb6d5e5033c6bfb587d8668cd9f48c7a08a2ae793a677b7649661e3ae248ab4f8499ab7b6ede483c
2 parents 3573997 + b08af10 commit 6d58a5c

File tree

2 files changed

+51
-3
lines changed

2 files changed

+51
-3
lines changed

src/blockencodings.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ class BlockTransactionsRequest {
5252
}
5353
}
5454

55-
uint16_t offset = 0;
55+
int32_t offset = 0;
5656
for (size_t j = 0; j < indexes.size(); j++) {
57-
if (uint64_t(indexes[j]) + uint64_t(offset) > std::numeric_limits<uint16_t>::max())
57+
if (int32_t(indexes[j]) + offset > std::numeric_limits<uint16_t>::max())
5858
throw std::ios_base::failure("indexes overflowed 16 bits");
5959
indexes[j] = indexes[j] + offset;
60-
offset = indexes[j] + 1;
60+
offset = int32_t(indexes[j]) + 1;
6161
}
6262
} else {
6363
for (size_t i = 0; i < indexes.size(); i++) {
@@ -186,6 +186,9 @@ class CBlockHeaderAndShortTxIDs {
186186

187187
READWRITE(prefilledtxn);
188188

189+
if (BlockTxCount() > std::numeric_limits<uint16_t>::max())
190+
throw std::ios_base::failure("indexes overflowed 16 bits");
191+
189192
if (ser_action.ForRead())
190193
FillShortTxIDSelector();
191194
}

src/test/blockencodings_tests.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -344,4 +344,49 @@ BOOST_AUTO_TEST_CASE(TransactionsRequestSerializationTest) {
344344
BOOST_CHECK_EQUAL(req1.indexes[3], req2.indexes[3]);
345345
}
346346

347+
BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationMaxTest) {
348+
// Check that the highest legal index is decoded correctly
349+
BlockTransactionsRequest req0;
350+
req0.blockhash = InsecureRand256();
351+
req0.indexes.resize(1);
352+
req0.indexes[0] = 0xffff;
353+
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
354+
stream << req0;
355+
356+
BlockTransactionsRequest req1;
357+
stream >> req1;
358+
BOOST_CHECK_EQUAL(req0.indexes.size(), req1.indexes.size());
359+
BOOST_CHECK_EQUAL(req0.indexes[0], req1.indexes[0]);
360+
}
361+
362+
BOOST_AUTO_TEST_CASE(TransactionsRequestDeserializationOverflowTest) {
363+
// Any set of index deltas that starts with N values that sum to (0x10000 - N)
364+
// causes the edge-case overflow that was originally not checked for. Such
365+
// a request cannot be created by serializing a real BlockTransactionsRequest
366+
// due to the overflow, so here we'll serialize from raw deltas.
367+
BlockTransactionsRequest req0;
368+
req0.blockhash = InsecureRand256();
369+
req0.indexes.resize(3);
370+
req0.indexes[0] = 0x7000;
371+
req0.indexes[1] = 0x10000 - 0x7000 - 2;
372+
req0.indexes[2] = 0;
373+
CDataStream stream(SER_NETWORK, PROTOCOL_VERSION);
374+
stream << req0.blockhash;
375+
WriteCompactSize(stream, req0.indexes.size());
376+
WriteCompactSize(stream, req0.indexes[0]);
377+
WriteCompactSize(stream, req0.indexes[1]);
378+
WriteCompactSize(stream, req0.indexes[2]);
379+
380+
BlockTransactionsRequest req1;
381+
try {
382+
stream >> req1;
383+
// before patch: deserialize above succeeds and this check fails, demonstrating the overflow
384+
BOOST_CHECK(req1.indexes[1] < req1.indexes[2]);
385+
// this shouldn't be reachable before or after patch
386+
BOOST_CHECK(0);
387+
} catch(std::ios_base::failure &) {
388+
// deserialize should fail
389+
}
390+
}
391+
347392
BOOST_AUTO_TEST_SUITE_END()

0 commit comments

Comments
 (0)