kernel: guard btck::Handle move-assignment against self-move

The move-assignment operator for btck::Handle<> unconditionally called
DestroyFunc(m_ptr) before reading the source pointer. On a self-move
(h = std::move(h)), this destroyed the held resource and then reassigned
the now-dangling pointer back to m_ptr via std::exchange, leading to a
double-free when the object is later destroyed.

Mirror the existing self-check in the copy-assignment operator by
guarding the move-assignment with 'if (this != &other)' so a self-move
becomes a no-op, leaving the object in a valid state as required by the
standard library.

Handle<> is the base of 16 public types in the kernel C++ API wrapper
(Transaction, Block, BlockHeader, ChainParams, Context, Coin,
BlockValidationState, ScriptPubkey, TransactionOutput, Txid, OutPoint,
TransactionInput, PrecomputedTransactionData, BlockHash,
BlockSpentOutputs, TransactionSpentOutputs), so self-move can arise
from generic algorithms operating on containers of these types.

Extend CheckHandle in test_kernel to cover self-move-assignment for
every Handle-derived type.
This commit is contained in:
Thomas
2026-04-23 12:46:39 +02:00
parent cd7865b0ce
commit 14547eb489
2 changed files with 14 additions and 2 deletions

View File

@@ -341,8 +341,10 @@ public:
Handle(Handle&& other) noexcept : m_ptr(other.m_ptr) { other.m_ptr = nullptr; }
Handle& operator=(Handle&& other) noexcept
{
DestroyFunc(m_ptr);
m_ptr = std::exchange(other.m_ptr, nullptr);
if (this != &other) {
DestroyFunc(m_ptr);
m_ptr = std::exchange(other.m_ptr, nullptr);
}
return *this;
}

View File

@@ -315,6 +315,16 @@ void CheckHandle(T object, T distinct_object)
if constexpr (HasToBytes<T>) {
check_equal(object2.ToBytes(), object3.ToBytes());
}
// Self move-assignment must not destroy the held resource.
// Use a reference to avoid -Wself-move warnings.
original_ptr = object2.get();
auto& object2_ref = object2;
object2 = std::move(object2_ref);
BOOST_CHECK_EQUAL(object2.get(), original_ptr);
if constexpr (HasToBytes<T>) {
check_equal(object2.ToBytes(), object3.ToBytes());
}
}
template <typename RangeType>