wallet, bdbro: Enforce overflow data lengths
What changed, and why it matters
This patch fixes a bug in Bitcoin Core's wallet migration code that reads old Berkeley DB wallet files. A malformed wallet file could claim an overflow data record was extremely long, causing the software to loop through pages forever. The fix checks that the claimed length fits within the actual file size and stops reading if the collected data exceeds that claimed length. This prevents a denial-of-service via infinite loop when opening a crafted wallet file.
Users who migrate old Berkeley DB wallets should upgrade to a release containing this commit. Treat any wallet file from an untrusted source as potentially malicious and avoid running migration on it. Developers should ensure similar length validations exist for other record types and page chains in the BDB parser.
Security signals we found
Unbounded loop / potential infinite loop on attacker-controlled file input
Missing validation of length field parsed from binary file format
Denial-of-service vector in wallet migration path
Input validation hardening for Berkeley DB read-only parser
Evidence from the diff
In BerkeleyRODatabase::Open(), overflow records in BDB files supply an item_len field stating the total data length. Previously the code trusted this value and followed next_page pointers until reaching page 0, without bounding the loop. A maliciously large item_len or circular/lengthy next_page chain could cause unbounded page reads. The patch computes max_data_size from outer_meta.last_page * page_size and throws if item_len exceeds it, and also throws if accumulated data.size() exceeds item_len. This bounds both the declared length and the actual read volume.
Changed components
src/wallet/migrate.cppBerkeleyRODatabase::Open()Overflow record parsing in BDB wallet migrationInspect captured patch +7 / −0
diff --git a/src/wallet/migrate.cpp b/src/wallet/migrate.cpp
index 1869a456..8cfb74f0 100644
--- a/src/wallet/migrate.cpp
+++ b/src/wallet/migrate.cpp
@@ -626,6 +626,7 @@ void BerkeleyRODatabase::Open()
if (inner_meta.last_page > outer_meta.last_page) {
throw std::runtime_error("Subdatabase last page is greater than database last page");
}
+ uint64_t max_data_size = static_cast<uint64_t>(outer_meta.last_page) * page_size;
// Make sure encryption is disabled
if (inner_meta.encrypt_algo != 0) {
@@ -673,6 +674,9 @@ void BerkeleyRODatabase::Open()
} else if (const OverflowRecord* orec = std::get_if<OverflowRecord>(&rec)) {
if (orec->m_header.deleted) continue;
uint32_t next_page = orec->page_number;
+ if (orec->item_len > max_data_size) {
+ throw std::runtime_error("Overflow record has an impossible length");
+ }
while (next_page != 0) {
SeekToPage(db_file, next_page, page_size);
PageHeader opage_header(next_page, inner_meta.other_endian);
@@ -683,6 +687,9 @@ void BerkeleyRODatabase::Open()
OverflowPage opage(opage_header);
db_file >> opage;
data.insert(data.end(), opage.data.begin(), opage.data.end());
+ if (data.size() > orec->item_len) {
+ throw std::runtime_error("Overflow record data is larger than stated size");
+ }
next_page = opage_header.next_page;
}
}
Why this scored 60/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.