Merge bitcoin/bitcoin#36092: fix: UB sanitizer in mempool estimator logging
What changed, and why it matters
This change fixes a logging statement that could trigger an 'unsigned integer overflow' warning when Bitcoin Core is run with special sanitizer checks. It only affects what gets printed to logs when reading a fee-estimator data file with an extremely large block height (near the 64-bit maximum). It does not change transaction validation, consensus rules, or network behavior. The fix replaces a plain addition with a 'saturating add' helper so the number stops at the maximum instead of wrapping around.
No urgent action required beyond applying the patch. Operators running with UBSan will no longer see sanitizer aborts on crafted/malformed fee-estimator files. Consider whether the fuzz corpus should retain the crashing input as a regression test.
Security signals we found
UndefinedBehaviorSanitizer unsigned-integer-overflow triggered in CI fuzz target
Fix uses overflow-saturating arithmetic helper
Issue is in logging/validation of persisted fee-estimator state, not consensus-critical path
Evidence from the diff
In MemPoolFeeRateEstimator::Read, the code compared blocks[i].m_height against blocks[i-1].m_height + 1 and also printed that sum in a LogWarning. If blocks[i-1].m_height is UINT64_MAX, the expression blocks[i-1].m_height + 1 overflows, which is defined behavior for unsigned integers in C++ but is flagged by UBSan’s unsigned-integer-overflow check. The patch uses util/overflow.h’s SaturatingAdd to compute expected_height, so the value saturates at UINT64_MAX and the sanitizer is no longer triggered. The functional behavior (returning false on non-consecutive heights) is unchanged.
Changed components
src/policy/fees/mempool_estimator.cppMemPoolFeeRateEstimator::Readpolicy_estimator_io fuzz targetInspect captured patch +4 / −2
### src/policy/fees/mempool_estimator.cpp
@@ -18,6 +18,7 @@
#include <util/feefrac.h>
#include <util/fees.h>
#include <util/fs.h>
+#include <util/overflow.h>
#include <util/syserror.h>
#include <validation.h>
@@ -207,10 +208,11 @@ bool MemPoolFeeRateEstimator::Read(AutoFile& file)
return false;
}
for (size_t i = 1; i < blocks.size(); ++i) {
- if (blocks[i].m_height != blocks[i - 1].m_height + 1) {
+ const uint64_t expected_height{SaturatingAdd(blocks[i - 1].m_height, uint64_t{1})};
+ if (blocks[i].m_height != expected_height) {
LogWarning("%s: Non-consecutive block heights read, expected height %s but found %s; ignoring file",
FeeRateEstimatorTypeToString(FeeRateEstimatorType::MEMPOOL_POLICY),
- blocks[i - 1].m_height + 1, blocks[i].m_height);
+ expected_height, blocks[i].m_height);
return false;
}
}Why this scored 26/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.