kernel: btck_chainstate_manager_get_best_entry returns null after SetWipeDbs(true, true), accessors then SEGV #35293

issue csjones opened this issue on May 14, 2026
  1. csjones commented at 7:57 PM on May 14, 2026: none

    Summary

    Opening a chainstate manager with SetWipeDbs(true, true) against a previously-populated data directory leaves chainman.m_best_header null, so btck_chainstate_manager_get_best_entry returns a null btck_BlockTreeEntry* (assumed never null but not explicitly documented that way). Every entry accessor then reinterpret-casts the null and SEGVs.

    Reproduction

    Regression test added at repro-kernel-best-entry-null-after-wipe: ./build/bin/test_kernel --run_test=btck_get_best_entry_after_full_wipe_regression crashes with SIGSEGV. Fresh data dir with the same flags works and the bug is specifically a wipe over a previously-populated dir.

    Stack trace

    (lldb) breakpoint set --name __cxa_throw
    Breakpoint 1: where = libc++abi.dylib`__cxa_throw, address = 0x00000001845fa040
    (lldb) run --run_test=btck_get_best_entry_after_full_wipe_regression
    Process 85763 launched: '/Users/csjones/Developer/bitcoin/build/bin/test_kernel' (arm64)
    Running 1 test case...
    Kernel warning was unset.
    Process 85763 stopped
    * thread [#1](/bitcoin-bitcoin/1/), queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
        frame [#0](/bitcoin-bitcoin/0/): 0x00000001845fa040 libc++abi.dylib`__cxa_throw
    libc++abi.dylib`__cxa_throw:
    ->  0x1845fa040 <+0>:  pacibsp 
        0x1845fa044 <+4>:  stp    x22, x21, [sp, #-0x30]!
        0x1845fa048 <+8>:  stp    x20, x19, [sp, [#0](/bitcoin-bitcoin/0/)x10]
        0x1845fa04c <+12>: stp    x29, x30, [sp, [#0](/bitcoin-bitcoin/0/)x20]
    Target 0: (test_kernel) stopped.
    (lldb) bt
    * thread [#1](/bitcoin-bitcoin/1/), queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame [#0](/bitcoin-bitcoin/0/): 0x00000001845fa040 libc++abi.dylib`__cxa_throw
        frame [#1](/bitcoin-bitcoin/1/): 0x0000000100051e5c test_kernel`btck_BlockTreeEntry const* btck::check<btck_BlockTreeEntry const*>(ptr=0x0000000000000000) at bitcoinkernel_wrapper.h:187:9 [opt] [inlined]
        frame [#2](/bitcoin-bitcoin/2/): 0x0000000100051e2c test_kernel`btck::View<btck_BlockTreeEntry>::View(this=<unavailable>, ptr=0x0000000000000000) at bitcoinkernel_wrapper.h:330:45 [opt] [inlined]
        frame [#3](/bitcoin-bitcoin/3/): 0x0000000100051e2c test_kernel`btck::BlockTreeEntry::BlockTreeEntry(this=<unavailable>, entry=0x0000000000000000) at bitcoinkernel_wrapper.h:915:11 [opt] [inlined]
        frame [#4](/bitcoin-bitcoin/4/): 0x0000000100051e2c test_kernel`btck::BlockTreeEntry::BlockTreeEntry(this=<unavailable>, entry=0x0000000000000000) at bitcoinkernel_wrapper.h:916:5 [opt] [inlined]
        frame [#5](/bitcoin-bitcoin/5/): 0x0000000100051e2c test_kernel`btck::ChainMan::GetBestEntry(this=<unavailable>) const at bitcoinkernel_wrapper.h:1339:16 [opt] [inlined]
        frame [#6](/bitcoin-bitcoin/6/): 0x0000000100051e2c test_kernel`btck_get_best_entry_after_full_wipe_regression::test_method(this=<unavailable>) at test_kernel.cpp:964:35 [opt]
        frame [#7](/bitcoin-bitcoin/7/): 0x000000010005145c test_kernel`btck_get_best_entry_after_full_wipe_regression_invoker() at test_kernel.cpp:938:1 [opt]
        frame [#8](/bitcoin-bitcoin/8/): 0x0000000100090bcc test_kernel`boost::function_n<void>::operator()(this=<unavailable>) const at function_template.hpp:789:14 [opt] [inlined]
        frame [#9](/bitcoin-bitcoin/9/): 0x0000000100090bb4 test_kernel`boost::detail::forward::operator()(this=<unavailable>) at execution_monitor.ipp:1416:32 [opt] [inlined]
        frame [#10](/bitcoin-bitcoin/10/): 0x0000000100090bb0 test_kernel`boost::detail::function::function_obj_invoker<boost::detail::forward, int>::invoke(function_obj_ptr=<unavailable>) at function_template.hpp:79:18 [opt]
        frame [#11](/bitcoin-bitcoin/11/): 0x00000001000141e4 test_kernel`boost::function_n<int>::operator()(this=0x000000016fdfe4b8) const at function_template.hpp:789:14 [opt] [inlined]
        frame [#12](/bitcoin-bitcoin/12/): 0x00000001000141cc test_kernel`int boost::detail::do_invoke<boost::shared_ptr<boost::detail::translator_holder_base>, boost::function<int ()>>(tr=0x0000000100325810, F=0x000000016fdfe4b8) at execution_monitor.ipp:329:30 [opt] [inlined]
        frame [#13](/bitcoin-bitcoin/13/): 0x00000001000141b0 test_kernel`boost::execution_monitor::catch_signals(this=0x00000001003257f8, F=0x000000016fdfe4b8) at execution_monitor.ipp:931:16 [opt]
        frame [#14](/bitcoin-bitcoin/14/): 0x0000000100014460 test_kernel`boost::execution_monitor::execute(this=0x00000001003257f8, F=0x000000016fdfe4b8) at execution_monitor.ipp:1329:16 [opt]
        frame [#15](/bitcoin-bitcoin/15/): 0x000000010000fe0c test_kernel`boost::execution_monitor::vexecute(this=0x00000001003257f8, F=0x0000000100a63218) at execution_monitor.ipp:1425:5 [opt] [inlined]
        frame [#16](/bitcoin-bitcoin/16/): 0x000000010000fdf0 test_kernel`boost::unit_test::unit_test_monitor_t::execute_and_translate(this=0x00000001003257f8, func=0x0000000100a63218, timeout_microseconds=0) at unit_test_monitor.ipp:49:9 [opt]
        frame [#17](/bitcoin-bitcoin/17/): 0x0000000100011200 test_kernel`boost::unit_test::framework::state::execute_test_tree(this=<unavailable>, tu_id=<unavailable>, timeout_microseconds=0, p_random_generator=0x000000016fdfe750) at framework.ipp:815:44 [opt]
        frame [#18](/bitcoin-bitcoin/18/): 0x0000000100011340 test_kernel`boost::unit_test::framework::state::execute_test_tree(this=<unavailable>, tu_id=<unavailable>, timeout_microseconds=0, p_random_generator=0x0000000000000000) at framework.ipp:740:54 [opt]
        frame [#19](/bitcoin-bitcoin/19/): 0x000000010000efd4 test_kernel`boost::unit_test::framework::run(id=1, continue_test=<unavailable>) at framework.ipp:1722:29 [opt]
        frame [#20](/bitcoin-bitcoin/20/): 0x000000010001f220 test_kernel`boost::unit_test::unit_test_main(init_func=<unavailable>, argc=<unavailable>, argv=<unavailable>) at unit_test_main.ipp:250:9 [opt]
        frame [#21](/bitcoin-bitcoin/21/): 0x000000018429bda4 dyld`start + 6992
    

    Idea for a fix

    Document the null return and add null guards to the entry accessors.

    Related

    #27587 #24303

    Environment

    Bitcoin Core ddb94fd3e (master, 2026-05-14) macOS 26.4.1 arm64 Apple clang version 21.0.0 (clang-2100.0.123.102) cmake -B build -DBUILD_TESTS=ON -DBUILD_KERNEL_LIB=ON -DENABLE_IPC=OFF -DCMAKE_BUILD_TYPE=Debug (no extra patches).

    Additional info

    Manually discovered in an package I am building using libbitcoinkernel bindings for another language. I then used AI to create the repro-kernel-best-entry-null-after-wipe test case based on that.

    Assisted-by: Claude Opus 4.7

  2. sedited commented at 8:34 PM on May 14, 2026: contributor

    Thank you for reporting! Triggering the pre-conditions for a reindex by wiping data will leave the chainman in an unusable state. This has to be remediated by importing blocks. I'm not sure if handling this invalid state is a sane thing to do. I'd rather document that a wipe has to be followed by import blocks before using the chainman for something else (as it is done for the existing tests). Also seems like this is already handled by the pointer check in the kernel c++ wrapper that correctly throws an exception.

  3. csjones commented at 9:40 PM on May 14, 2026: none

    Thanks for the quick response and agreed that wipe -> importBlocks is the right lifecycle and that btck::check<> in the wrapper handles the C++ case correctly.

    One small follow-up: would you be open to a doc-only patch on bitcoinkernel.h itself? Specifically, amending the @return comment on btck_chainstate_manager_get_best_entry to note that it may return null on a wiped manager before blocks are imported, plus a one-line note on the entry accessors that they are undefined on null input?

    My reasoning is that language bindings that generate from the C header don't see bitcoinkernel_wrapper.h. Without the null note in the header, they generate non-failable signatures and rediscover the SEGV (like I just did). With the note, each binding maintainer can add their language's equivalent of btck::check<>.

    Happy to PR if useful.


github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2026-05-15 03:12 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me