Skip to content

Commit dde7205

Browse files
author
MacroFake
committed
Merge bitcoin/bitcoin#23418: Fix signed integer overflow in prioritisetransaction RPC
fa07f84 Fix signed integer overflow in prioritisetransaction RPC (MarcoFalke) fa52cf8 refactor: Replace feeDelta by m_modified_fee (MarcoFalke) Pull request description: Signed integer overflow is UB in theory, but not in practice. Still, it would be nice to avoid this UB to allow Bitcoin Core to be compiled with sanitizers such as `-ftrapv` or ubsan. It is impossible to predict when and if an overflow occurs, since the overflow caused by a prioritisetransaction RPC might only be later hit when descendant txs are added to the mempool. Since it is impossible to predict reliably, leave it up to the user to use the RPC endpoint responsibly, considering their mempool limits and usage patterns. Fixes: #20626 Fixes: #20383 Fixes: #19278 Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=34146 / https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47132 ## Steps to reproduce Build the code without the changes in this pull. Make sure to pass the sanitizer flag: ``` ./autogen.sh && ./configure --with-sanitizers=signed-integer-overflow && make clean && make -j $(nproc) ``` ### Reproduce on RPC ``` ./src/bitcoind -chain=regtest -noprinttoconsole & ./src/bitcoin-cli -chain=regtest prioritisetransaction 00000000deadbeef00000000deadbeef00000000deadbeef00000000deadbeef 0 9123456789123456789 ./src/bitcoin-cli -chain=regtest prioritisetransaction 00000000deadbeef00000000deadbeef00000000deadbeef00000000deadbeef 0 9123456789123456789 |> txmempool.cpp:920:15: runtime error: signed integer overflow: 9123456789123456789 + 9123456789123456789 cannot be represented in type 'long int' ./src/bitcoin-cli -chain=regtest stop ``` ### By fuzzing ``` wget https://github.com/bitcoin/bitcoin/files/8921302/clusterfuzz-testcase-minimized-validation_load_mempool-5599531390074880.bin.txt FUZZ=validation_load_mempool ./src/test/fuzz/fuzz ./clusterfuzz-testcase-minimized-validation_load_mempool-5599531390074880.bin.txt |> txmempool.cpp:920:15: runtime error: signed integer overflow: 7214801925397553184 + 2314885530818453536 cannot be represented in type 'long int' |> validation_load_mempool: succeeded against 1 files in 0s. ACKs for top commit: vasild: ACK fa07f84 dunxen: ACK fa07f84 LarryRuane: ACK fa07f84 Tree-SHA512: 4a357950af55a49c9113da0a50c2e743c5b752f0514dd8d16cd92bfde2f77dd0ef56aa98452626df6f7f7a5b51d1227021f6bc94091201a179f0d488ee32a0df
2 parents aaeb315 + fa07f84 commit dde7205

File tree

3 files changed

+20
-16
lines changed

3 files changed

+20
-16
lines changed

src/txmempool.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@
1414
#include <policy/policy.h>
1515
#include <policy/settings.h>
1616
#include <reverse_iterator.h>
17+
#include <util/check.h>
1718
#include <util/moneystr.h>
19+
#include <util/overflow.h>
1820
#include <util/system.h>
1921
#include <util/time.h>
2022
#include <validationinterface.h>
@@ -82,18 +84,19 @@ CTxMemPoolEntry::CTxMemPoolEntry(const CTransactionRef& tx, CAmount fee,
8284
entryHeight{entry_height},
8385
spendsCoinbase{spends_coinbase},
8486
sigOpCost{sigops_cost},
87+
m_modified_fee{nFee},
8588
lockPoints{lp},
8689
nSizeWithDescendants{GetTxSize()},
8790
nModFeesWithDescendants{nFee},
8891
nSizeWithAncestors{GetTxSize()},
8992
nModFeesWithAncestors{nFee},
9093
nSigOpCostWithAncestors{sigOpCost} {}
9194

92-
void CTxMemPoolEntry::UpdateFeeDelta(CAmount newFeeDelta)
95+
void CTxMemPoolEntry::UpdateModifiedFee(CAmount fee_diff)
9396
{
94-
nModFeesWithDescendants += newFeeDelta - feeDelta;
95-
nModFeesWithAncestors += newFeeDelta - feeDelta;
96-
feeDelta = newFeeDelta;
97+
nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, fee_diff);
98+
nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, fee_diff);
99+
m_modified_fee = SaturatingAdd(m_modified_fee, fee_diff);
97100
}
98101

99102
void CTxMemPoolEntry::UpdateLockPoints(const LockPoints& lp)
@@ -435,7 +438,7 @@ void CTxMemPoolEntry::UpdateDescendantState(int64_t modifySize, CAmount modifyFe
435438
{
436439
nSizeWithDescendants += modifySize;
437440
assert(int64_t(nSizeWithDescendants) > 0);
438-
nModFeesWithDescendants += modifyFee;
441+
nModFeesWithDescendants = SaturatingAdd(nModFeesWithDescendants, modifyFee);
439442
nCountWithDescendants += modifyCount;
440443
assert(int64_t(nCountWithDescendants) > 0);
441444
}
@@ -444,7 +447,7 @@ void CTxMemPoolEntry::UpdateAncestorState(int64_t modifySize, CAmount modifyFee,
444447
{
445448
nSizeWithAncestors += modifySize;
446449
assert(int64_t(nSizeWithAncestors) > 0);
447-
nModFeesWithAncestors += modifyFee;
450+
nModFeesWithAncestors = SaturatingAdd(nModFeesWithAncestors, modifyFee);
448451
nCountWithAncestors += modifyCount;
449452
assert(int64_t(nCountWithAncestors) > 0);
450453
nSigOpCostWithAncestors += modifySigOps;
@@ -483,8 +486,10 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces
483486
// Update transaction for any feeDelta created by PrioritiseTransaction
484487
CAmount delta{0};
485488
ApplyDelta(entry.GetTx().GetHash(), delta);
489+
// The following call to UpdateModifiedFee assumes no previous fee modifications
490+
Assume(entry.GetFee() == entry.GetModifiedFee());
486491
if (delta) {
487-
mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
492+
mapTx.modify(newit, [&delta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(delta); });
488493
}
489494

490495
// Update cachedInnerUsage to include contained transaction's usage.
@@ -917,10 +922,10 @@ void CTxMemPool::PrioritiseTransaction(const uint256& hash, const CAmount& nFeeD
917922
{
918923
LOCK(cs);
919924
CAmount &delta = mapDeltas[hash];
920-
delta += nFeeDelta;
925+
delta = SaturatingAdd(delta, nFeeDelta);
921926
txiter it = mapTx.find(hash);
922927
if (it != mapTx.end()) {
923-
mapTx.modify(it, [&delta](CTxMemPoolEntry& e) { e.UpdateFeeDelta(delta); });
928+
mapTx.modify(it, [&nFeeDelta](CTxMemPoolEntry& e) { e.UpdateModifiedFee(nFeeDelta); });
924929
// Now update all ancestors' modified fees with descendants
925930
setEntries setAncestors;
926931
uint64_t nNoLimit = std::numeric_limits<uint64_t>::max();

src/txmempool.h

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ class CTxMemPoolEntry
101101
const unsigned int entryHeight; //!< Chain height when entering the mempool
102102
const bool spendsCoinbase; //!< keep track of transactions that spend a coinbase
103103
const int64_t sigOpCost; //!< Total sigop cost
104-
CAmount feeDelta{0}; //!< Used for determining the priority of the transaction for mining in a block
104+
CAmount m_modified_fee; //!< Used for determining the priority of the transaction for mining in a block
105105
LockPoints lockPoints; //!< Track the height and time at which tx was final
106106

107107
// Information about descendants of this transaction that are in the
@@ -131,17 +131,16 @@ class CTxMemPoolEntry
131131
std::chrono::seconds GetTime() const { return std::chrono::seconds{nTime}; }
132132
unsigned int GetHeight() const { return entryHeight; }
133133
int64_t GetSigOpCost() const { return sigOpCost; }
134-
CAmount GetModifiedFee() const { return nFee + feeDelta; }
134+
CAmount GetModifiedFee() const { return m_modified_fee; }
135135
size_t DynamicMemoryUsage() const { return nUsageSize; }
136136
const LockPoints& GetLockPoints() const { return lockPoints; }
137137

138138
// Adjusts the descendant state.
139139
void UpdateDescendantState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount);
140140
// Adjusts the ancestor state
141141
void UpdateAncestorState(int64_t modifySize, CAmount modifyFee, int64_t modifyCount, int64_t modifySigOps);
142-
// Updates the fee delta used for mining priority score, and the
143-
// modified fees with descendants/ancestors.
144-
void UpdateFeeDelta(CAmount newFeeDelta);
142+
// Updates the modified fees with descendants/ancestors.
143+
void UpdateModifiedFee(CAmount fee_diff);
145144
// Update the LockPoints after a reorg
146145
void UpdateLockPoints(const LockPoints& lp);
147146

test/sanitizer_suppressions/ubsan

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
# -fsanitize=undefined suppressions
22
# =================================
3-
# This would be `signed-integer-overflow:CTxMemPool::PrioritiseTransaction`,
3+
# The suppressions would be `sanitize-type:ClassName::MethodName`,
44
# however due to a bug in clang the symbolizer is disabled and thus no symbol
55
# names can be used.
66
# See https://github.com/google/sanitizers/issues/1364
7-
signed-integer-overflow:txmempool.cpp
7+
88
# https://github.com/bitcoin/bitcoin/pull/21798#issuecomment-829180719
99
signed-integer-overflow:policy/feerate.cpp
1010

0 commit comments

Comments
 (0)