File: include/sdbusplus/asio/object_server.hpp
Function: coroutine_method_instance::after_spawn()
Line: 277
Description
In coroutine_method_instance::after_spawn(), when the callback signature includes message::message& as the second argument (i.e., callbackWantsMessage is true), the code moves the local message::message b into the inputArgs tuple using std::move(b):
if constexpr (callbackWantsMessage<CallbackType>)
{
InputTupleType inputArgs = std::tuple_cat(
std::forward_as_tuple(std::move(yield)),
std::forward_as_tuple(std::move(b)), dbusArgs);
callFunction(ret, inputArgs, func_);
}
sdbusplus::message::message wraps an sd_bus_message* in a std::unique_ptr with a custom deleter (sd_bus_message_unref). Moving the message transfers ownership of the underlying sd_bus_message* into the tuple. When callFunction() returns (either normally or via exception), the inputArgs tuple goes out of scope and its destructor calls sd_bus_message_unref(), releasing the message.
However, if callFunction() throws an sdbusplus::exception::SdBusError, the subsequent catch block attempts to call b.new_method_errno() on the already moved-from b:
catch (const sdbusplus::exception::SdBusError& e)
{
// BUG: b's internal sd_bus_message* has been moved into inputArgs
// and already unref'd when inputArgs was destroyed.
message_t err = b.new_method_errno(e.get_errno(), e.get_error());
err.method_return();
}
This results in undefined behavior — typically a use-after-free or null-pointer dereference inside sd_bus_message_new_method_errno(), because b no longer owns a valid sd_bus_message*.
Root Cause
std::forward_as_tuple(std::move(b)) performs a move construction of message::message into the tuple. Since message::message is move-only (deleted copy constructor, defaulted move constructor), the move transfers ownership of the unique_ptr<sd_bus_message, MsgDeleter> from b to the tuple element. After callFunction() returns, inputArgs is destroyed, decrementing the reference count and potentially freeing the sd_bus_message.
Suggested Fix
Pass b by lvalue reference instead of moving it, so the message::message object remains valid in b for the exception handlers:
if constexpr (callbackWantsMessage<CallbackType>)
{
InputTupleType inputArgs = std::tuple_cat(
std::forward_as_tuple(std::move(yield)),
std::forward_as_tuple(b), // <-- pass by reference, not rvalue
dbusArgs);
callFunction(ret, inputArgs, func_);
}
This is safe because:
std::forward_as_tuple(b) creates a tuple of message::message& (lvalue reference).
The callback receives a reference to b; it can inspect the message but does not take ownership.
b remains valid throughout the try/catch scope, so b.new_method_errno() and b.new_method_error() work correctly in the exception handlers.
File: include/sdbusplus/asio/object_server.hpp
Function: coroutine_method_instance::after_spawn()
Line: 277
Description
In coroutine_method_instance::after_spawn(), when the callback signature includes message::message& as the second argument (i.e., callbackWantsMessage is true), the code moves the local message::message b into the inputArgs tuple using std::move(b):
sdbusplus::message::message wraps an sd_bus_message* in a std::unique_ptr with a custom deleter (sd_bus_message_unref). Moving the message transfers ownership of the underlying sd_bus_message* into the tuple. When callFunction() returns (either normally or via exception), the inputArgs tuple goes out of scope and its destructor calls sd_bus_message_unref(), releasing the message.
However, if callFunction() throws an sdbusplus::exception::SdBusError, the subsequent catch block attempts to call b.new_method_errno() on the already moved-from b:
This results in undefined behavior — typically a use-after-free or null-pointer dereference inside sd_bus_message_new_method_errno(), because b no longer owns a valid sd_bus_message*.
Root Cause
std::forward_as_tuple(std::move(b)) performs a move construction of message::message into the tuple. Since message::message is move-only (deleted copy constructor, defaulted move constructor), the move transfers ownership of the unique_ptr<sd_bus_message, MsgDeleter> from b to the tuple element. After callFunction() returns, inputArgs is destroyed, decrementing the reference count and potentially freeing the sd_bus_message.
Suggested Fix
Pass b by lvalue reference instead of moving it, so the message::message object remains valid in b for the exception handlers:
This is safe because:
std::forward_as_tuple(b) creates a tuple of message::message& (lvalue reference).
The callback receives a reference to b; it can inspect the message but does not take ownership.
b remains valid throughout the try/catch scope, so b.new_method_errno() and b.new_method_error() work correctly in the exception handlers.