Use-after-free of cached checkpoint list in __wti_block_checkpoint_extlist_dump

XMLWordPrintableJSON

    • Type: Bug
    • Resolution: Fixed
    • Priority: Major - P3
    • WT12.0.0
    • 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.

            Assignee:
            Etienne Petrel
            Reporter:
            Etienne Petrel
            Votes:
            0 Vote for this issue
            Watchers:
            1 Start watching this issue

              Created:
              Updated:
              Resolved: