-
Type:
Bug
-
Resolution: Fixed
-
Priority:
Major - P3
-
Affects Version/s: None
-
Component/s: Checkpoints
-
None
-
Storage Engines - Persistence
-
146.176
-
SE Persistence backlog
-
None
Summary
__wti_block_checkpoint_extlist_dump (the diagnostic extent-list dump run when a block read fails its checksum) operates on, and frees, the btree's shared cached checkpoint list. This corrupts/frees btree->ckpt, a use-after-free.
This was found while investigating WT-15887 and is split off from it. In diagnostic builds the bug is normally masked because the spurious __assert_ckpt_matches assertion (tracked in WT-15887) aborts on the same path first.
Root cause
_wti_block_checkpoint_extlist_dump calls _wt_meta_ckptlist_get(..., update=false, ...). In its cached path that function returns the live btree->ckpt by reference (*ckptbasep = btree->ckpt), not a copy. The dump then:
- attaches transient block-manager state to every entry (ckpt_iter->bpriv = calloc(...)), and
- on cleanup calls __wt_ckptlist_free(&ckptbase), freeing the entire list.
Because ckptbase aliases btree->ckpt, this frees btree->ckpt without setting it to NULL, leaving a dangling pointer. Legitimate callers of _wt_meta_ckptlist_get (for example _checkpoint_tree) either re-own the list or free it and null btree->ckpt; the dump does neither.
Impact
When the diagnostic assert does not abort first (for example with debug_mode=(corruption_abort=false)), reading a corrupt extent-list block during checkpoint processing segfaults instead of returning a handled WT_PANIC. Only reachable on the corrupt-block read path, so production impact is limited to corruption handling, but it is a genuine memory-safety defect.
Reproducer
test/suite/test_prepare_hs06.py deterministically corrupts a checkpoint's alloc extent-list block and drives the checkpoint-drop read. With corruption_abort=false and the spurious assert bypassed, the current code segfaults (use-after-free); with the fix it returns a handled WT_PANIC.
Proposed fix
Have _wti_block_checkpoint_extlist_dump build a private checkpoint list from the metadata (via wt_metadata_search + wt_meta_ckptlist_get_from_config) instead of calling _wt_meta_ckptlist_get. This avoids touching the shared cached list entirely, and as a side effect stops the extent-list dump from tripping the checkpoint-validate diagnostics.
Tasks
- Change __wti_block_checkpoint_extlist_dump to operate on a private ckptlist copy.
- Add/keep test/suite/test_prepare_hs06.py as the regression test.
Definition of Done
- Reading a corrupt extent-list block during checkpoint no longer crashes; it surfaces the existing WT_ERROR/WT_PANIC handling.
- test_prepare_hs06.py passes with the fix and crashes/segfaults without it.
- No regressions in the salvage/verify/corruption Python suites.