aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGavin Andresen <gavinandresen@gmail.com>2013-08-07 12:21:34 +1000
committerGavin Andresen <gavinandresen@gmail.com>2013-08-09 10:01:35 +1000
commit8dc206a1e2715be83912e039465a049b708b94c1 (patch)
treec5f8682aeca86134da42cea9dae729faf03ef73a
parentddd0e2f616be82fb57d68298be796c34268c6ae9 (diff)
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using Write/ReadCompactSize -- which, unfortunately, do not use a unique encoding. So deserializing and then re-serializing a transaction (for example) can give you different bits than you started with. That doesn't cause any problems that we are aware of, but it is exactly the type of subtle mismatch that can lead to exploits. With this pull, reading a non-canonical CompactSize throws an exception, which means nodes will ignore 'tx' or 'block' or other messages that are not properly encoded. Please check my logic... but this change is safe with respect to causing a network split. Old clients that receive non-canonically-encoded transactions or blocks deserialize them into CTransaction/CBlock structures in memory, and then re-serialize them before relaying them to peers. And please check my logic with respect to causing a blockchain split: there are no CompactSize fields in the block header, so the block hash is always canonical. The merkle root in the block header is computed on a vector<CTransaction>, so any non-canonical encoding of the transactions in 'tx' or 'block' messages is erased as they are read into memory by old clients, and does not affect the block hash. And, as noted above, old clients re-serialize (with canonical encoding) 'tx' and 'block' messages before relaying to peers.
-rw-r--r--src/main.cpp1
-rw-r--r--src/serialize.h6
-rw-r--r--src/test/serialize_tests.cpp60
3 files changed, 66 insertions, 1 deletions
diff --git a/src/main.cpp b/src/main.cpp
index 9de895374e..71815fc2a2 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -3584,7 +3584,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
{
vector<uint256> vWorkQueue;
vector<uint256> vEraseQueue;
- CDataStream vMsg(vRecv);
CTransaction tx;
vRecv >> tx;
diff --git a/src/serialize.h b/src/serialize.h
index e3d9939bcc..ca3c962240 100644
--- a/src/serialize.h
+++ b/src/serialize.h
@@ -216,18 +216,24 @@ uint64 ReadCompactSize(Stream& is)
unsigned short xSize;
READDATA(is, xSize);
nSizeRet = xSize;
+ if (nSizeRet < 253)
+ throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
else if (chSize == 254)
{
unsigned int xSize;
READDATA(is, xSize);
nSizeRet = xSize;
+ if (nSizeRet < 0x10000u)
+ throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
else
{
uint64 xSize;
READDATA(is, xSize);
nSizeRet = xSize;
+ if (nSizeRet < 0x100000000LLu)
+ throw std::ios_base::failure("non-canonical ReadCompactSize()");
}
if (nSizeRet > (uint64)MAX_SIZE)
throw std::ios_base::failure("ReadCompactSize() : size too large");
diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp
index 19ffdcab66..50139df09e 100644
--- a/src/test/serialize_tests.cpp
+++ b/src/test/serialize_tests.cpp
@@ -39,7 +39,67 @@ BOOST_AUTO_TEST_CASE(varints)
ss >> VARINT(j);
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
}
+}
+
+BOOST_AUTO_TEST_CASE(compactsize)
+{
+ CDataStream ss(SER_DISK, 0);
+ vector<char>::size_type i, j;
+
+ for (i = 1; i <= MAX_SIZE; i *= 2)
+ {
+ WriteCompactSize(ss, i-1);
+ WriteCompactSize(ss, i);
+ }
+ for (i = 1; i <= MAX_SIZE; i *= 2)
+ {
+ j = ReadCompactSize(ss);
+ BOOST_CHECK_MESSAGE((i-1) == j, "decoded:" << j << " expected:" << (i-1));
+ j = ReadCompactSize(ss);
+ BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
+ }
+}
+
+static bool isCanonicalException(const std::ios_base::failure& ex)
+{
+ return std::string("non-canonical ReadCompactSize()") == ex.what();
+}
+
+BOOST_AUTO_TEST_CASE(noncanonical)
+{
+ // Write some non-canonical CompactSize encodings, and
+ // make sure an exception is thrown when read back.
+ CDataStream ss(SER_DISK, 0);
+ vector<char>::size_type n;
+
+ // zero encoded with three bytes:
+ ss.write("\xfd\x00\x00", 3);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
+
+ // 0xfc encoded with three bytes:
+ ss.write("\xfd\xfc\x00", 3);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
+
+ // 0xfd encoded with three bytes is OK:
+ ss.write("\xfd\xfd\x00", 3);
+ n = ReadCompactSize(ss);
+ BOOST_CHECK(n == 0xfd);
+
+ // zero encoded with five bytes:
+ ss.write("\xfe\x00\x00\x00\x00", 5);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
+
+ // 0xffff encoded with five bytes:
+ ss.write("\xfe\xff\xff\x00\x00", 5);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
+
+ // zero encoded with nine bytes:
+ ss.write("\xff\x00\x00\x00\x00\x00\x00\x00\x00", 9);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
+ // 0x01ffffff encoded with nine bytes:
+ ss.write("\xff\xff\xff\xff\x01\x00\x00\x00\x00", 9);
+ BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
}
BOOST_AUTO_TEST_SUITE_END()