The read-only BDB parser in migrate.cpp runs on any file passed to migratewallet
(RPC or GUI) or bitcoin-wallet. #34959 added checks for btree levels and overflow
lengths, which stop simple cycles, but a crafted file can still make the parser hang
or use a lot of memory:
- A btree page can be the child of more than one internal page. Its level still checks out, so it gets parsed again for every path that reaches it. On master an ~8 KB file built this way doesn't finish.
- An overflow page with no data doesn't count toward the length #34959 checks, so two such pages pointing at each other loop forever. Separately, any number of records can point at the same overflow chain, and each one reads and stores its own copy.
- All of a page's index entries can point at the same record, which then gets read
and kept once per entry. A single 64 KB page can turn into ~500 MB this way. On
master a 256 KB file took
migratewalletfrom 54 MB to ~800 MB.
The first two are fixed by tracking visited pages (the approach from #34946): btree pages in one set, and overflow pages in another that covers the whole tree. For the third, a page is rejected once its records add up to more than the page size.
This shouldn't affect real wallets. In a valid btree every page has a single parent and every overflow page belongs to a single record. Records on a page can't overlap either, since the parser already rejects databases that allow duplicates.
Each fix comes with a test in db_tests.cpp that builds the bad file by hand, plus a
test that a page filled to the last byte still parses. wallet_migration.py passes
with v28.2.
The last commit adds two errors from #34959 to the wallet_bdb_parser fuzz target.
They were missing, so the target aborted on inputs that reach them. I ran into this
while feeding mutated copies of the test files through the target.
Supersedes #35992.