Skip to content

Commit ee7b67e

Browse files
committed
Merge #9753: Add static_assert to prevent VARINT(<signed value>)
499d95e Add static_assert to prevent VARINT(<signed value>) (Russell Yanofsky) Pull request description: 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 a compile error by default if called with an signed value, and it updates existing broken uses of VARINT to pass a special flag that lets them keep working with no changes in behavior. There is some discussion about this issue here: bitcoin/bitcoin#9693 (comment). I think another good change along these lines would be to make `GetSizeOfVarInt` and `WriteVarInt` throw exceptions if they are passed numbers less than 0 to serialize. But unlike this change, that would be a change in runtime behavior, and need more consideration. Tree-SHA512: 082c65598cfac6dc1da042bdb47dbc9d5d789fc849fe52921cc238578588f4e5ff976c8b4b2ce42cb75290eb14f3b42ea76e26202c223c5b2aa63ef45c2ea3cc
2 parents ebdf84c + 499d95e commit ee7b67e

File tree

6 files changed

+57
-33
lines changed

6 files changed

+57
-33
lines changed

src/chain.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct CDiskBlockPos
9191

9292
template <typename Stream, typename Operation>
9393
inline void SerializationOp(Stream& s, Operation ser_action) {
94-
READWRITE(VARINT(nFile));
94+
READWRITE(VARINT(nFile, VarIntMode::NONNEGATIVE_SIGNED));
9595
READWRITE(VARINT(nPos));
9696
}
9797

@@ -386,13 +386,13 @@ class CDiskBlockIndex : public CBlockIndex
386386
inline void SerializationOp(Stream& s, Operation ser_action) {
387387
int _nVersion = s.GetVersion();
388388
if (!(s.GetType() & SER_GETHASH))
389-
READWRITE(VARINT(_nVersion));
389+
READWRITE(VARINT(_nVersion, VarIntMode::NONNEGATIVE_SIGNED));
390390

391-
READWRITE(VARINT(nHeight));
391+
READWRITE(VARINT(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
392392
READWRITE(VARINT(nStatus));
393393
READWRITE(VARINT(nTx));
394394
if (nStatus & (BLOCK_HAVE_DATA | BLOCK_HAVE_UNDO))
395-
READWRITE(VARINT(nFile));
395+
READWRITE(VARINT(nFile, VarIntMode::NONNEGATIVE_SIGNED));
396396
if (nStatus & BLOCK_HAVE_DATA)
397397
READWRITE(VARINT(nDataPos));
398398
if (nStatus & BLOCK_HAVE_UNDO)

src/rpc/blockchain.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -834,18 +834,18 @@ static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash,
834834
{
835835
assert(!outputs.empty());
836836
ss << hash;
837-
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase);
837+
ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase, VarIntMode::NONNEGATIVE_SIGNED);
838838
stats.nTransactions++;
839839
for (const auto output : outputs) {
840840
ss << VARINT(output.first + 1);
841841
ss << output.second.out.scriptPubKey;
842-
ss << VARINT(output.second.out.nValue);
842+
ss << VARINT(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED);
843843
stats.nTransactionOutputs++;
844844
stats.nTotalAmount += output.second.out.nValue;
845845
stats.nBogoSize += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ +
846846
2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */;
847847
}
848-
ss << VARINT(0);
848+
ss << VARINT(0u);
849849
}
850850

851851
//! Calculate statistics about the unspent transaction output set

src/serialize.h

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -296,9 +296,31 @@ uint64_t ReadCompactSize(Stream& is)
296296
* 2^32: [0x8E 0xFE 0xFE 0xFF 0x00]
297297
*/
298298

299-
template<typename I>
299+
/**
300+
* Mode for encoding VarInts.
301+
*
302+
* Currently there is no support for signed encodings. The default mode will not
303+
* compile with signed values, and the legacy "nonnegative signed" mode will
304+
* accept signed values, but improperly encode and decode them if they are
305+
* negative. In the future, the DEFAULT mode could be extended to support
306+
* negative numbers in a backwards compatible way, and additional modes could be
307+
* added to support different varint formats (e.g. zigzag encoding).
308+
*/
309+
enum class VarIntMode { DEFAULT, NONNEGATIVE_SIGNED };
310+
311+
template <VarIntMode Mode, typename I>
312+
struct CheckVarIntMode {
313+
constexpr CheckVarIntMode()
314+
{
315+
static_assert(Mode != VarIntMode::DEFAULT || std::is_unsigned<I>::value, "Unsigned type required with mode DEFAULT.");
316+
static_assert(Mode != VarIntMode::NONNEGATIVE_SIGNED || std::is_signed<I>::value, "Signed type required with mode NONNEGATIVE_SIGNED.");
317+
}
318+
};
319+
320+
template<VarIntMode Mode, typename I>
300321
inline unsigned int GetSizeOfVarInt(I n)
301322
{
323+
CheckVarIntMode<Mode, I>();
302324
int nRet = 0;
303325
while(true) {
304326
nRet++;
@@ -312,9 +334,10 @@ inline unsigned int GetSizeOfVarInt(I n)
312334
template<typename I>
313335
inline void WriteVarInt(CSizeComputer& os, I n);
314336

315-
template<typename Stream, typename I>
337+
template<typename Stream, VarIntMode Mode, typename I>
316338
void WriteVarInt(Stream& os, I n)
317339
{
340+
CheckVarIntMode<Mode, I>();
318341
unsigned char tmp[(sizeof(n)*8+6)/7];
319342
int len=0;
320343
while(true) {
@@ -329,9 +352,10 @@ void WriteVarInt(Stream& os, I n)
329352
} while(len--);
330353
}
331354

332-
template<typename Stream, typename I>
355+
template<typename Stream, VarIntMode Mode, typename I>
333356
I ReadVarInt(Stream& is)
334357
{
358+
CheckVarIntMode<Mode, I>();
335359
I n = 0;
336360
while(true) {
337361
unsigned char chData = ser_readdata8(is);
@@ -351,7 +375,7 @@ I ReadVarInt(Stream& is)
351375
}
352376

353377
#define FLATDATA(obj) CFlatData((char*)&(obj), (char*)&(obj) + sizeof(obj))
354-
#define VARINT(obj) WrapVarInt(REF(obj))
378+
#define VARINT(obj, ...) WrapVarInt<__VA_ARGS__>(REF(obj))
355379
#define COMPACTSIZE(obj) CCompactSize(REF(obj))
356380
#define LIMITED_STRING(obj,n) LimitedString< n >(REF(obj))
357381

@@ -395,7 +419,7 @@ class CFlatData
395419
}
396420
};
397421

398-
template<typename I>
422+
template<VarIntMode Mode, typename I>
399423
class CVarInt
400424
{
401425
protected:
@@ -405,12 +429,12 @@ class CVarInt
405429

406430
template<typename Stream>
407431
void Serialize(Stream &s) const {
408-
WriteVarInt<Stream,I>(s, n);
432+
WriteVarInt<Stream,Mode,I>(s, n);
409433
}
410434

411435
template<typename Stream>
412436
void Unserialize(Stream& s) {
413-
n = ReadVarInt<Stream,I>(s);
437+
n = ReadVarInt<Stream,Mode,I>(s);
414438
}
415439
};
416440

@@ -461,8 +485,8 @@ class LimitedString
461485
}
462486
};
463487

464-
template<typename I>
465-
CVarInt<I> WrapVarInt(I& n) { return CVarInt<I>(n); }
488+
template<VarIntMode Mode=VarIntMode::DEFAULT, typename I>
489+
CVarInt<Mode, I> WrapVarInt(I& n) { return CVarInt<Mode, I>{n}; }
466490

467491
/**
468492
* Forward declarations

src/test/serialize_tests.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -177,8 +177,8 @@ BOOST_AUTO_TEST_CASE(varints)
177177
CDataStream ss(SER_DISK, 0);
178178
CDataStream::size_type size = 0;
179179
for (int i = 0; i < 100000; i++) {
180-
ss << VARINT(i);
181-
size += ::GetSerializeSize(VARINT(i), 0, 0);
180+
ss << VARINT(i, VarIntMode::NONNEGATIVE_SIGNED);
181+
size += ::GetSerializeSize(VARINT(i, VarIntMode::NONNEGATIVE_SIGNED), 0, 0);
182182
BOOST_CHECK(size == ss.size());
183183
}
184184

@@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(varints)
191191
// decode
192192
for (int i = 0; i < 100000; i++) {
193193
int j = -1;
194-
ss >> VARINT(j);
194+
ss >> VARINT(j, VarIntMode::NONNEGATIVE_SIGNED);
195195
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
196196
}
197197

@@ -205,21 +205,21 @@ BOOST_AUTO_TEST_CASE(varints)
205205
BOOST_AUTO_TEST_CASE(varints_bitpatterns)
206206
{
207207
CDataStream ss(SER_DISK, 0);
208-
ss << VARINT(0); BOOST_CHECK_EQUAL(HexStr(ss), "00"); ss.clear();
209-
ss << VARINT(0x7f); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
210-
ss << VARINT((int8_t)0x7f); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
211-
ss << VARINT(0x80); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
208+
ss << VARINT(0, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "00"); ss.clear();
209+
ss << VARINT(0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
210+
ss << VARINT((int8_t)0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
211+
ss << VARINT(0x80, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
212212
ss << VARINT((uint8_t)0x80); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
213-
ss << VARINT(0x1234); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
214-
ss << VARINT((int16_t)0x1234); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
215-
ss << VARINT(0xffff); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
213+
ss << VARINT(0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
214+
ss << VARINT((int16_t)0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
215+
ss << VARINT(0xffff, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
216216
ss << VARINT((uint16_t)0xffff); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
217-
ss << VARINT(0x123456); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
218-
ss << VARINT((int32_t)0x123456); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
217+
ss << VARINT(0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
218+
ss << VARINT((int32_t)0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
219219
ss << VARINT(0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
220220
ss << VARINT((uint32_t)0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
221221
ss << VARINT(0xffffffff); BOOST_CHECK_EQUAL(HexStr(ss), "8efefefe7f"); ss.clear();
222-
ss << VARINT(0x7fffffffffffffffLL); BOOST_CHECK_EQUAL(HexStr(ss), "fefefefefefefefe7f"); ss.clear();
222+
ss << VARINT(0x7fffffffffffffffLL, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "fefefefefefefefe7f"); ss.clear();
223223
ss << VARINT(0xffffffffffffffffULL); BOOST_CHECK_EQUAL(HexStr(ss), "80fefefefefefefefe7f"); ss.clear();
224224
}
225225

src/txdb.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ class CCoins
324324
void Unserialize(Stream &s) {
325325
unsigned int nCode = 0;
326326
// version
327-
int nVersionDummy;
327+
unsigned int nVersionDummy;
328328
::Unserialize(s, VARINT(nVersionDummy));
329329
// header code
330330
::Unserialize(s, VARINT(nCode));
@@ -351,7 +351,7 @@ class CCoins
351351
::Unserialize(s, CTxOutCompressor(vout[i]));
352352
}
353353
// coinbase height
354-
::Unserialize(s, VARINT(nHeight));
354+
::Unserialize(s, VARINT(nHeight, VarIntMode::NONNEGATIVE_SIGNED));
355355
}
356356
};
357357

src/undo.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class TxInUndoSerializer
2525
public:
2626
template<typename Stream>
2727
void Serialize(Stream &s) const {
28-
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0)));
28+
::Serialize(s, VARINT(txout->nHeight * 2 + (txout->fCoinBase ? 1 : 0), VarIntMode::NONNEGATIVE_SIGNED));
2929
if (txout->nHeight > 0) {
3030
// Required to maintain compatibility with older undo format.
3131
::Serialize(s, (unsigned char)0);
@@ -51,7 +51,7 @@ class TxInUndoDeserializer
5151
// Old versions stored the version number for the last spend of
5252
// a transaction's outputs. Non-final spends were indicated with
5353
// height = 0.
54-
int nVersionDummy;
54+
unsigned int nVersionDummy;
5555
::Unserialize(s, VARINT(nVersionDummy));
5656
}
5757
::Unserialize(s, CTxOutCompressor(REF(txout->out)));

0 commit comments

Comments
 (0)