Add static_assert to prevent VARINT(<signed value>)

Using VARINT with signed types is dangerous because negative values will appear
to serialize correctly, but then deserialize as positive values mod 128.

This commit changes the VARINT macro to trigger an error by default if called
with an signed value, and updates broken uses of VARINT to pass a special flag
that lets them keep working with no change in behavior.
This commit is contained in:
Russell Yanofsky 2017-02-13 13:41:02 -05:00
parent 7be9a9a570
commit 499d95e278
6 changed files with 57 additions and 33 deletions

View file

@ -91,7 +91,7 @@ struct CDiskBlockPos
template <typename Stream, typename Operation>
inline void SerializationOp(Stream& s, Operation ser_action) {
READWRITE(VARINT(nFile));
READWRITE(VARINT(nFile, VarIntMode::NONNEGATIVE_SIGNED));
READWRITE(VARINT(nPos));
}
@ -386,13 +386,13 @@ public:
inline void SerializationOp(Stream& s, Operation ser_action) {
int _nVersion = s.GetVersion();
if (!(s.GetType() & SER_GETHASH))
READWRITE(VARINT(_nVersion));
READWRITE(VARINT(_nVersion, VarIntMode::NONNEGATIVE_SIGNED));
READWRITE(VARINT(nHeight));
READWRITE(VARINT(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
READWRITE(VARINT(nStatus));
READWRITE(VARINT(nTx));
if (nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO))
READWRITE(VARINT(nFile));
READWRITE(VARINT(nFile, VarIntMode::NONNEGATIVE_SIGNED));
if (nStatus & BLOCK_HAVE_DATA)
READWRITE(VARINT(nDataPos));
if (nStatus & BLOCK_HAVE_UNDO)

View file

@ -834,18 +834,18 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
{
assert(!outputs.empty());
ss << hash;
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase);
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase, VarIntMode::NONNEGATIVE_SIGNED);
stats.nTransactions++;
for (const auto output : outputs) {
ss << VARINT(output.first + 1);
ss << output.second.out.scriptPubKey;
ss << VARINT(output.second.out.nValue);
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
stats.nTransactionOutputs++;
stats.nTotalAmount += output.second.out.nValue;
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
}
ss << VARINT(0);
ss << VARINT(0u);
}
//! Calculate statistics about the unspent transaction output set

View file

@ -296,9 +296,31 @@ uint64_t ReadCompactSize(Stream& is)
* 2^32: [0x8E 0xFE 0xFE 0xFF 0x00]
*/
template<typename I>
/**
* Mode for encoding VarInts.
*
* Currently there is no support for signed encodings. The default mode will not
* compile with signed values, and the legacy "nonnegative signed" mode will
* accept signed values, but improperly encode and decode them if they are
* negative. In the future, the DEFAULT mode could be extended to support
* negative numbers in a backwards compatible way, and additional modes could be
* added to support different varint formats (e.g. zigzag encoding).
*/
enum class VarIntMode { DEFAULT, NONNEGATIVE_SIGNED };
template <VarIntMode Mode, typename I>
struct CheckVarIntMode {
constexpr CheckVarIntMode()
{
static_assert(Mode != VarIntMode::DEFAULT || std::is_unsigned<I>::value, "Unsigned type required with mode DEFAULT.");
static_assert(Mode != VarIntMode::NONNEGATIVE_SIGNED || std::is_signed<I>::value, "Signed type required with mode NONNEGATIVE_SIGNED.");
}
};
template<VarIntMode Mode, typename I>
inline unsigned int GetSizeOfVarInt(I n)
{
CheckVarIntMode<Mode, I>();
int nRet = 0;
while(true) {
nRet++;
@ -312,9 +334,10 @@ inline unsigned int GetSizeOfVarInt(I n)
template<typename I>
inline void WriteVarInt(CSizeComputer& os, I n);
template<typename Stream, typename I>
template<typename Stream, VarIntMode Mode, typename I>
void WriteVarInt(Stream& os, I n)
{
CheckVarIntMode<Mode, I>();
unsigned char tmp[(sizeof(n)*8+6)/7];
int len=0;
while(true) {
@ -329,9 +352,10 @@ void WriteVarInt(Stream& os, I n)
} while(len--);
}
template<typename Stream, typename I>
template<typename Stream, VarIntMode Mode, typename I>
I ReadVarInt(Stream& is)
{
CheckVarIntMode<Mode, I>();
I n = 0;
while(true) {
unsigned char chData = ser_readdata8(is);
@ -351,7 +375,7 @@ I ReadVarInt(Stream& is)
}
#define FLATDATA(obj) CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))
#define VARINT(obj) WrapVarInt(REF(obj))
#define VARINT(obj, ...) WrapVarInt<__VA_ARGS__>(REF(obj))
#define COMPACTSIZE(obj) CCompactSize(REF(obj))
#define LIMITED_STRING(obj,n) LimitedString< n >(REF(obj))
@ -395,7 +419,7 @@ public:
}
};
template<typename I>
template<VarIntMode Mode, typename I>
class CVarInt
{
protected:
@ -405,12 +429,12 @@ public:
template<typename Stream>
void Serialize(Stream &s) const {
WriteVarInt<Stream,I>(s, n);
WriteVarInt<Stream,Mode,I>(s, n);
}
template<typename Stream>
void Unserialize(Stream& s) {
n = ReadVarInt<Stream,I>(s);
n = ReadVarInt<Stream,Mode,I>(s);
}
};
@ -461,8 +485,8 @@ public:
}
};
template<typename I>
CVarInt<I> WrapVarInt(I& n) { return CVarInt<I>(n); }
template<VarIntMode Mode=VarIntMode::DEFAULT, typename I>
CVarInt<Mode, I> WrapVarInt(I& n) { return CVarInt<Mode, I>{n}; }
/**
* Forward declarations

View file

@ -177,8 +177,8 @@ BOOST_AUTO_TEST_CASE(varints)
CDataStream ss(SER_DISK, 0);
CDataStream::size_type size = 0;
for (int i = 0; i < 100000; i++) {
ss << VARINT(i);
size += ::GetSerializeSize(VARINT(i), 0, 0);
ss << VARINT(i, VarIntMode::NONNEGATIVE_SIGNED);
size += ::GetSerializeSize(VARINT(i, VarIntMode::NONNEGATIVE_SIGNED), 0, 0);
BOOST_CHECK(size == ss.size());
}
@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(varints)
// decode
for (int i = 0; i < 100000; i++) {
int j = -1;
ss >> VARINT(j);
ss >> VARINT(j, VarIntMode::NONNEGATIVE_SIGNED);
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
}
@ -205,21 +205,21 @@ BOOST_AUTO_TEST_CASE(varints)
BOOST_AUTO_TEST_CASE(varints_bitpatterns)
{
CDataStream ss(SER_DISK, 0);
ss << VARINT(0); BOOST_CHECK_EQUAL(HexStr(ss), "00"); ss.clear();
ss << VARINT(0x7f); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
ss << VARINT((int8_t)0x7f); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
ss << VARINT(0x80); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
ss << VARINT(0, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "00"); ss.clear();
ss << VARINT(0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
ss << VARINT((int8_t)0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
ss << VARINT(0x80, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
ss << VARINT((uint8_t)0x80); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
ss << VARINT(0x1234); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
ss << VARINT((int16_t)0x1234); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
ss << VARINT(0xffff); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
ss << VARINT(0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
ss << VARINT((int16_t)0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
ss << VARINT(0xffff, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
ss << VARINT((uint16_t)0xffff); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
ss << VARINT(0x123456); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
ss << VARINT((int32_t)0x123456); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
ss << VARINT(0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
ss << VARINT((int32_t)0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
ss << VARINT(0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
ss << VARINT((uint32_t)0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
ss << VARINT(0xffffffff); BOOST_CHECK_EQUAL(HexStr(ss), "8efefefe7f"); ss.clear();
ss << VARINT(0x7fffffffffffffffLL); BOOST_CHECK_EQUAL(HexStr(ss), "fefefefefefefefe7f"); ss.clear();
ss << VARINT(0x7fffffffffffffffLL, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "fefefefefefefefe7f"); ss.clear();
ss << VARINT(0xffffffffffffffffULL); BOOST_CHECK_EQUAL(HexStr(ss), "80fefefefefefefefe7f"); ss.clear();
}

View file

@ -324,7 +324,7 @@ public:
void Unserialize(Stream &s) {
unsigned int nCode = 0;
// version
int nVersionDummy;
unsigned int nVersionDummy;
::Unserialize(s, VARINT(nVersionDummy));
// header code
::Unserialize(s, VARINT(nCode));
@ -351,7 +351,7 @@ public:
::Unserialize(s, CTxOutCompressor(vout[i]));
}
// coinbase height
::Unserialize(s, VARINT(nHeight));
::Unserialize(s, VARINT(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
}
};

View file

@ -25,7 +25,7 @@ class TxInUndoSerializer
public:
template<typename Stream>
void Serialize(Stream &s) const {
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0)));
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0), VarIntMode::NONNEGATIVE_SIGNED));
if (txout->nHeight > 0) {
// Required to maintain compatibility with older undo format.
::Serialize(s, (unsigned char)0);
@ -51,7 +51,7 @@ public:
// Old versions stored the version number for the last spend of
// a transaction's outputs. Non-final spends were indicated with
// height = 0.
int nVersionDummy;
unsigned int nVersionDummy;
::Unserialize(s, VARINT(nVersionDummy));
}
::Unserialize(s, CTxOutCompressor(REF(txout->out)));