mirror of
https://github.com/bitcoin/bitcoin.git
synced 2026-07-24 23:28:41 +02:00
wallet, bdbro: Validate btree page levels
BTree pages contain the level in the BTree that the page is supposed to be at. The root starts at some level between 1 and 255, leaves are always level 1. Internal pages must be a level that is one less than its parent. Validating that pages are at their expected level (except for the root page) enforces that no cycles can occur.
This commit is contained in:
@@ -633,10 +633,17 @@ void BerkeleyRODatabase::Open()
|
||||
throw std::runtime_error("BDB builtin encryption is not supported");
|
||||
}
|
||||
|
||||
// Read the root's level from its header
|
||||
// Note that we will read the root page twice in order to process it.
|
||||
SeekToPage(db_file, inner_meta.root, page_size);
|
||||
PageHeader root_header(inner_meta.root, inner_meta.other_endian);
|
||||
db_file >> root_header;
|
||||
|
||||
// Do a DFS through the BTree, starting at root
|
||||
std::vector<uint32_t> pages{inner_meta.root};
|
||||
// We track the expected level of each page in order to avoid loops
|
||||
std::vector<std::pair<uint32_t, uint32_t>> pages{{inner_meta.root, root_header.level}};
|
||||
while (pages.size() > 0) {
|
||||
uint32_t curr_page = pages.back();
|
||||
auto [curr_page, expected_level] = pages.back();
|
||||
// It turns out BDB completely ignores this last_page field and doesn't actually update it to the correct
|
||||
// last page. While we should be checking this, we can't.
|
||||
// This is left commented out as a reminder to not accidentally implement this in the future.
|
||||
@@ -647,17 +654,23 @@ void BerkeleyRODatabase::Open()
|
||||
SeekToPage(db_file, curr_page, page_size);
|
||||
PageHeader header(curr_page, inner_meta.other_endian);
|
||||
db_file >> header;
|
||||
if (header.level != expected_level) {
|
||||
throw std::runtime_error("BTree page has an unexpected level");
|
||||
}
|
||||
switch (header.type) {
|
||||
case PageType::BTREE_INTERNAL: {
|
||||
InternalPage int_page(header);
|
||||
db_file >> int_page;
|
||||
for (const InternalRecord& rec : int_page.records) {
|
||||
if (rec.m_header.deleted) continue;
|
||||
pages.push_back(rec.page_num);
|
||||
pages.emplace_back(rec.page_num, header.level - 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PageType::BTREE_LEAF: {
|
||||
if (header.level != 1) {
|
||||
throw std::runtime_error("BTree Leaf page is not at level 1");
|
||||
}
|
||||
RecordsPage rec_page(header);
|
||||
db_file >> rec_page;
|
||||
if (rec_page.records.size() % 2 != 0) {
|
||||
|
||||
@@ -74,14 +74,15 @@ FUZZ_TARGET(wallet_bdb_parser, .init = initialize_wallet_bdb_parser)
|
||||
error.original == "Internal record position not in page" ||
|
||||
error.original == "LSNs are not reset, this database is not completely flushed. Please reopen then close the database with a version that has BDB support" ||
|
||||
error.original == "Records page has odd number of records" ||
|
||||
error.original == "Bad overflow record page type") {
|
||||
// Do nothing
|
||||
} else if (error.original == "Subdatabase last page is greater than database last page" ||
|
||||
error.original == "Page number is greater than database last page" ||
|
||||
error.original == "Last page number could not fit in file" ||
|
||||
error.original == "Subdatabase has an unexpected name" ||
|
||||
error.original == "Unsupported BDB data file version number" ||
|
||||
error.original == "BDB builtin encryption is not supported") {
|
||||
error.original == "Bad overflow record page type" ||
|
||||
error.original == "BTree page has an unexpected level" ||
|
||||
error.original == "BTree Leaf page is not at level 1" ||
|
||||
error.original == "Subdatabase last page is greater than database last page" ||
|
||||
error.original == "Page number is greater than database last page" ||
|
||||
error.original == "Last page number could not fit in file" ||
|
||||
error.original == "Subdatabase has an unexpected name" ||
|
||||
error.original == "Unsupported BDB data file version number" ||
|
||||
error.original == "BDB builtin encryption is not supported") {
|
||||
} else {
|
||||
throw std::runtime_error(error.original);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user