I’ve noticed a rare use-after-scope race in submitblock while testing Bitcoin ABC built in debug mode, and the involved code is identical to Core so I believe the same concern exists here.
Essentially the issue is that UnregisterValidationInterface does disconnect the slots promptly, but the slots may be in the process of executing at that precise moment, in the scheduler thread. Even though UpdatedBlockTip is an empty function for the submitblock_StateCatcher sc, it does get wrapped by std::bind and various internal boost wrappers so there’s a tiny time window to have a problem. The race looks something like this (Sched = scheduler thread, RPC = thread calling submitblock function)
- [Sched] Invoke UpdatedBlockTip signal.
- [Sched] Observe the connection for UpdatedBlockTip and extract the slot-to-be-executed.
- [RPC]
submitblockdisconnects the connection. - [Sched] Start executing the slot, starting with various boost wrappers, passing through std::bind, then a call to the CValidationInterface’s virtual UpdatedBlockTip method.
- [RPC]
submitblockfunction completes;scis destroyed and zeroed. - [Sched] Look up the
scvtable entry for UpdatedBlockTip and call it. Segfault becausesc’s vtable pointer has been zeroed.
Boost signals2 thread safety discussion: https://www.boost.org/doc/libs/1_55_0/doc/html/signals2/thread-safety.html
Note that since we unlock the connection’s mutex before executing its associated slot, it is possible a slot will still be executing after it has been disconnected by a connection::disconnect(), if the disconnect was called concurrently with signal invocation.
This was the direct fix in Bitcoin ABC however it does not address the bigger issue: 1) not all signals are queued at the scheduler and so there could be a race with a third thread, and 2) there are other places in the code base (like wallet) that seem to not wait after unregistering. I am not super familiar with signals2 so I’m not sure what is the ‘correct’ fix. But the TL;DR is that scoped_connection only protects the lifecycle of the std::bind and not anything deeper.
Relevant code
The scope in submitblock:
https://github.com/bitcoin/bitcoin/blob/651e34388832149402fea0d26f3dc13bbe197f5a/src/rpc/mining.cpp#L781-L792
The submitblock_StateCatcher definition:
https://github.com/bitcoin/bitcoin/blob/651e34388832149402fea0d26f3dc13bbe197f5a/src/rpc/mining.cpp#L713-L729