`Tx.Check` can hang or dereference invalid pages when branch references are corrupt
Description
I was experimenting with the bbolt source code and intentionally corrupted
database files when I noticed two cases where Tx.Check does not handle
corrupt branch-page references safely.
I wrote two test cases that reproduce the problems:
- A branch page references an already visited page, forming a cycle.
- A branch page references a page ID equal to the database high-water mark.
Both tests currently fail.
Cyclic page reference
The reachability check detects that a page has multiple references, but the page traversal continues descending into it.
For example, a branch page might reference itself:
root page → root pageOr several pages might form a longer cycle:
A → B → C → AIn either case, the checker repeatedly follows the cycle. The goroutine running Tx.Check never finishes, leaving callers waiting indefinitely while ranging over the error channel.
Out-of-bounds branch reference
The recursive traversal dereferences branch page IDs before validating them
against tx.meta.Pgid().
The second test sets a branch reference to the database high-water mark. This
is invalid because valid page IDs must be below that value. Instead of returning
a clear out of bounds error, the checker attempts to load the page and may
report a recovered panic or another validation error.
The page ID should be checked before it is dereferenced, using
pageID >= hwm as the invalid boundary condition.
I also found #877: Check of corrupted file deadlocks, which looks related because it also reports Tx.Check hanging on a corrupted database. However, I’m not sure whether it has the same underlying cause.
I have a proposed fix along with two regression tests. Once it is open, I will link it here. Thanks!
Source: etcd-io/bbolt