The read-only BDB parser (BerkeleyRODatabase::Open() in migrate.cpp) walks B-tree pages and overflow chains by following page numbers read directly from the input file. Neither traversal tracked visited pages, so a crafted legacy wallet file containing a cyclic page reference causes the parser to loop forever — and, for cycles on an overflow chain, to grow memory without bound (each iteration appends OverflowPage.data to a std::vector).
Impact
Local denial-of-service / memory exhaustion of the wallet process. Reachable whenever a user feeds an attacker-supplied .dat file to a BDB-reading code path:
migratewalletRPCbitcoin-wallet -wallet=<path> dump
Legacy BDB wallets can no longer be created or loaded since v30.0, but the read-only parser is intentionally retained to power migratewallet and will remain in tree for the foreseeable future, so hardening it still matters.
Not remotely exploitable; no memory corruption.
Fix
Add a single shared std::unordered_set<uint32_t> visited_pages around the B-tree DFS loop, and check it inside the overflow-chain following loop as well. In a valid BDB database every physical page is referenced at most once, so a re-visit indicates corruption or a deliberate cycle and is rejected with "Cyclic page reference in BDB database".
The core change is ~11 lines in migrate.cpp.
Testing
Two new regression tests in db_tests.cpp build minimal hand-crafted BDB files that exercise each cyclic code path:
bdb_parser_btree_cycle_detection—BTREE_INTERNALpage whoseInternalRecord.page_numpoints back to itselfbdb_parser_overflow_cycle_detection—OVERFLOW_DATApage whosenext_pagepoints back to itself
Verification:
| Configuration | btree test | overflow test |
|---|---|---|
| master (no fix) | hangs until SIGTERM | hangs until SIGTERM |
| this PR | passes in ~1 ms | passes in ~1 ms |
Full db_tests suite passes with no regressions. The existing wallet_bdb_parser fuzz target is updated to accept the new error string.