refactor: Use u64 over size_t for all cache sizes to fix a 32-bit overflow
What changed, and why it matters
This commit fixes a bug in Bitcoin Core that only affects 32-bit computers. When the program calculated how much database cache to allocate, it multiplied a large default cache value by 10. On 32-bit systems that multiplication overflowed, producing a much smaller or incorrect number. The fix changes the internal type from size_t (32-bit on 32-bit systems) to uint64_t (always 64-bit) so the multiplication stays correct. On normal 64-bit computers the change is harmless and just a cleanup.
Apply the patch. If running 32-bit Bitcoin Core builds, prioritize upgrading to a release containing this fix, because the overflow can silently corrupt cache allocation decisions. No immediate emergency response is needed for 64-bit deployments, but the change is safe to include.
Security signals we found
Integer overflow in cache-size calculation on 32-bit systems
Unsigned multiplication overflow explicitly mentioned in commit message with runtime sanitizer output
Type promotion from size_t to uint64_t to prevent platform-dependent width issues
Default cache value 450 MiB multiplied by 10 exceeds 32-bit size_t range
Evidence from the diff
The patch converts cache-size constants and variables from size_t to uint64_t across src/dbwrapper.h, src/kernel/caches.h, src/node/caches.cpp, src/node/caches.h, and src/txdb.h. On 32-bit platforms size_t is 32 bits, so total_cache * 10 in node::CalculateCacheSizes could wrap around the default 450 MiB cache. The overflow caused cache calculations to be wrong, potentially degrading performance or triggering unexpected behavior. The change also adds missing includes and updates a format string warning to use uint64_t. The bug was introduced in commit d06dabf26bea7d9ca8d635e8338f64aec74c56a8.
Changed components
src/node/caches.cppsrc/node/caches.hsrc/kernel/caches.hsrc/dbwrapper.hsrc/txdb.hInspect captured patch +37 / −28
diff --git a/src/dbwrapper.h b/src/dbwrapper.h
index 83da6feb..2c1f8039 100644
--- a/src/dbwrapper.h
+++ b/src/dbwrapper.h
@@ -12,11 +12,14 @@
#include <util/byte_units.h>
#include <util/check.h>
#include <util/fs.h>
+#include <util/obfuscation.h>
#include <cstddef>
+#include <cstdint>
#include <exception>
#include <memory>
#include <optional>
+#include <span>
#include <stdexcept>
#include <string>
@@ -39,7 +42,7 @@ struct DBParams {
//! Location in the filesystem where leveldb data will be stored.
fs::path path;
//! Configures various leveldb cache settings.
- size_t cache_bytes;
+ uint64_t cache_bytes;
//! If true, use leveldb's memory environment.
bool memory_only = false;
//! If true, remove all existing data.
diff --git a/src/kernel/caches.h b/src/kernel/caches.h
index aa3214e5..ad65b165 100644
--- a/src/kernel/caches.h
+++ b/src/kernel/caches.h
@@ -8,24 +8,25 @@
#include <util/byte_units.h>
#include <algorithm>
+#include <cstdint>
//! Suggested default amount of cache reserved for the kernel (bytes)
-static constexpr size_t DEFAULT_KERNEL_CACHE{450_MiB};
+static constexpr uint64_t DEFAULT_KERNEL_CACHE{450_MiB};
//! Default LevelDB write batch size
-static constexpr size_t DEFAULT_DB_CACHE_BATCH{32_MiB};
+static constexpr uint64_t DEFAULT_DB_CACHE_BATCH{32_MiB};
//! Max memory allocated to block tree DB specific cache (bytes)
-static constexpr size_t MAX_BLOCK_DB_CACHE{2_MiB};
+static constexpr uint64_t MAX_BLOCK_DB_CACHE{2_MiB};
//! Max memory allocated to coin DB specific cache (bytes)
-static constexpr size_t MAX_COINS_DB_CACHE{8_MiB};
+static constexpr uint64_t MAX_COINS_DB_CACHE{8_MiB};
namespace kernel {
struct CacheSizes {
- size_t block_tree_db;
- size_t coins_db;
- size_t coins;
+ uint64_t block_tree_db;
+ uint64_t coins_db;
+ uint64_t coins;
- CacheSizes(size_t total_cache)
+ CacheSizes(uint64_t total_cache)
{
block_tree_db = std::min(total_cache / 8, MAX_BLOCK_DB_CACHE);
total_cache -= block_tree_db;
diff --git a/src/node/caches.cpp b/src/node/caches.cpp
index c98b8ce6..7b0223e3 100644
--- a/src/node/caches.cpp
+++ b/src/node/caches.cpp
@@ -13,27 +13,31 @@
#include <tinyformat.h>
#include <util/byte_units.h>
#include <util/log.h>
+#include <util/overflow.h>
+#include <util/translation.h>
#include <algorithm>
+#include <cstdint>
+#include <limits>
#include <string>
// Unlike for the UTXO database, for the txindex scenario the leveldb cache make
// a meaningful difference: https://github.com/bitcoin/bitcoin/pull/8273#issuecomment-229601991
//! Max memory allocated to tx index DB specific cache in bytes.
-static constexpr size_t MAX_TX_INDEX_CACHE{1_GiB};
+static constexpr uint64_t MAX_TX_INDEX_CACHE{1_GiB};
//! Max memory allocated to all block filter index caches combined in bytes.
-static constexpr size_t MAX_FILTER_INDEX_CACHE{1_GiB};
+static constexpr uint64_t MAX_FILTER_INDEX_CACHE{1_GiB};
//! Max memory allocated to tx spenderindex DB specific cache in bytes.
-static constexpr size_t MAX_TXOSPENDER_INDEX_CACHE{1_GiB};
+static constexpr uint64_t MAX_TXOSPENDER_INDEX_CACHE{1_GiB};
//! Maximum dbcache size on 32-bit systems.
-static constexpr size_t MAX_32BIT_DBCACHE{1_GiB};
+static constexpr uint64_t MAX_32BIT_DBCACHE{1_GiB};
//! Larger default dbcache on 64-bit systems with enough RAM.
-static constexpr size_t HIGH_DEFAULT_DBCACHE{1_GiB};
+static constexpr uint64_t HIGH_DEFAULT_DBCACHE{1_GiB};
//! Minimum detected RAM required for HIGH_DEFAULT_DBCACHE.
static constexpr uint64_t HIGH_DEFAULT_DBCACHE_MIN_TOTAL_RAM{4_GiB};
namespace node {
-size_t GetDefaultDBCache()
+uint64_t GetDefaultDBCache()
{
if constexpr (sizeof(void*) >= 8) {
if (GetTotalRAM().value_or(0) >= HIGH_DEFAULT_DBCACHE_MIN_TOTAL_RAM) {
@@ -43,20 +47,20 @@ size_t GetDefaultDBCache()
return DEFAULT_DB_CACHE;
}
-size_t CalculateDbCacheBytes(const ArgsManager& args)
+uint64_t CalculateDbCacheBytes(const ArgsManager& args)
{
if (auto db_cache{args.GetIntArg("-dbcache")}) {
if (*db_cache < 0) db_cache = 0;
const uint64_t db_cache_bytes{SaturatingLeftShift<uint64_t>(*db_cache, 20)};
- constexpr auto max_db_cache{sizeof(void*) == 4 ? MAX_32BIT_DBCACHE : std::numeric_limits<size_t>::max()};
- return std::max<size_t>(MIN_DB_CACHE, std::min<uint64_t>(db_cache_bytes, max_db_cache));
+ constexpr uint64_t max_db_cache{sizeof(void*) == 4 ? MAX_32BIT_DBCACHE : std::numeric_limits<uint64_t>::max()};
+ return std::max<uint64_t>(MIN_DB_CACHE, std::min<uint64_t>(db_cache_bytes, max_db_cache));
}
return GetDefaultDBCache();
}
CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
{
- size_t total_cache{CalculateDbCacheBytes(args)};
+ uint64_t total_cache{CalculateDbCacheBytes(args)};
// Allocate proportional to usage pattern benefit:
// - txindex (10%): serves getrawtransaction RPCs with mostly unique,
@@ -73,7 +77,7 @@ CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
index_sizes.tx_index = std::min(total_cache * 10 / 100, args.GetBoolArg("-txindex", DEFAULT_TXINDEX) ? MAX_TX_INDEX_CACHE : 0);
index_sizes.txospender_index = std::min(total_cache * 5 / 100, args.GetBoolArg("-txospenderindex", DEFAULT_TXOSPENDERINDEX) ? MAX_TXOSPENDER_INDEX_CACHE : 0);
if (n_indexes > 0) {
- size_t max_cache = std::min(total_cache * 5 / 100, MAX_FILTER_INDEX_CACHE);
+ uint64_t max_cache = std::min(total_cache * 5 / 100, MAX_FILTER_INDEX_CACHE);
index_sizes.filter_index = max_cache / n_indexes;
total_cache -= index_sizes.filter_index * n_indexes;
}
@@ -85,7 +89,7 @@ CacheSizes CalculateCacheSizes(const ArgsManager& args, size_t n_indexes)
void LogOversizedDbCache(const ArgsManager& args) noexcept
{
if (const auto total_ram{GetTotalRAM()}) {
- const size_t db_cache{CalculateDbCacheBytes(args)};
+ const uint64_t db_cache{CalculateDbCacheBytes(args)};
if (ShouldWarnOversizedDbCache(db_cache, *total_ram)) {
InitWarning(bilingual_str{tfm::format(_("A %zu MiB dbcache may be too large for a system memory of only %zu MiB."),
db_cache >> 20, *total_ram >> 20)});
diff --git a/src/node/caches.h b/src/node/caches.h
index f4143bed..4c66d39a 100644
--- a/src/node/caches.h
+++ b/src/node/caches.h
@@ -9,20 +9,21 @@
#include <util/byte_units.h>
#include <cstddef>
+#include <cstdint>
class ArgsManager;
//! min. -dbcache (bytes)
-static constexpr size_t MIN_DB_CACHE{4_MiB};
+static constexpr uint64_t MIN_DB_CACHE{4_MiB};
//! -dbcache default (bytes)
-static constexpr size_t DEFAULT_DB_CACHE{DEFAULT_KERNEL_CACHE};
+static constexpr uint64_t DEFAULT_DB_CACHE{DEFAULT_KERNEL_CACHE};
namespace node {
-size_t GetDefaultDBCache();
+uint64_t GetDefaultDBCache();
struct IndexCacheSizes {
- size_t tx_index{0};
- size_t filter_index{0};
- size_t txospender_index{0};
+ uint64_t tx_index{0};
+ uint64_t filter_index{0};
+ uint64_t txospender_index{0};
};
struct CacheSizes {
IndexCacheSizes index;
diff --git a/src/txdb.h b/src/txdb.h
index 8d4e2cc8..d4c25f3a 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -27,7 +27,7 @@ class uint256;
//! User-controlled performance and debug options.
struct CoinsViewOptions {
//! Maximum database write batch size in bytes.
- size_t batch_write_bytes{DEFAULT_DB_CACHE_BATCH};
+ uint64_t batch_write_bytes{DEFAULT_DB_CACHE_BATCH};
//! If non-zero, randomly exit when the database is flushed with (1/ratio) probability.
int simulate_crash_ratio{0};
};
Why this scored 43/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.