Summary
MessageSender::Close() frees an in-flight send operation without cancelling its transfer, so a disposition that arrives after the close reaches freed memory. The process stops with SIGSEGV. One unsettled send is enough to reproduce this, and concurrency is not necessary. A container run reproduced it 4 times out of 4 on the uAMQP transport.
Motivation
messagesender_close calls indicate_all_messages_as_error (sdk/core/azure-core-amqp/src/impl/uamqp/vendor/azure-uamqp-c/src/message_sender.c:1027). That function fires each on_message_send_complete and then calls async_operation_destroy, but it never cancels message_with_callback->transfer_async_operation. messagesender_send_cancel_handler does cancel it (message_sender.c:1043), so the two teardown paths disagree.
link_detach in the ATTACHED state only sends the detach frame and moves to HALF_ATTACHED_ATTACH_SENT (link.c:1439), so link->pending_deliveries still points at the operation that indicate_all_messages_as_error freed. When the peer settles the message, link_frame_received calls on_delivery_settled with the freed context (link.c:584). That reaches CompletionOperation::OnOperationFn (sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/common/completion_operation.hpp:31), which already took ownership and deleted the object on the first callback. The second call copies a dead std::function and the process stops.
The defect predates PR 7346. It reproduces on that branch and on its merge base 95ff788c5. No CI leg builds the uAMQP transport, so no pipeline covers this path.
Proposal
Cancel transfer_async_operation on the close path, so indicate_all_messages_as_error matches messagesender_send_cancel_handler. Alternatively, remove the delivery from link->pending_deliveries before the operation is freed, so a late disposition finds no context.
Add a regression test that closes a sender while one send is unsettled and then settles the message from the peer. Such a test needs the socket based mock broker, which builds only on Linux and Windows, and it needs a uAMQP configure with -DDISABLE_RUST_IN_BUILD=ON.
Validation
Reproduced in an Ubuntu 22.04 container on linux/arm64 with -DDISABLE_RUST_IN_BUILD=ON -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug. The test binary exits 139 on every attempt. The rest of that suite passes, at 215 tests from 24 suites.
Summary
MessageSender::Close()frees an in-flight send operation without cancelling its transfer, so a disposition that arrives after the close reaches freed memory. The process stops with SIGSEGV. One unsettled send is enough to reproduce this, and concurrency is not necessary. A container run reproduced it 4 times out of 4 on the uAMQP transport.Motivation
messagesender_closecallsindicate_all_messages_as_error(sdk/core/azure-core-amqp/src/impl/uamqp/vendor/azure-uamqp-c/src/message_sender.c:1027). That function fires eachon_message_send_completeand then callsasync_operation_destroy, but it never cancelsmessage_with_callback->transfer_async_operation.messagesender_send_cancel_handlerdoes cancel it (message_sender.c:1043), so the two teardown paths disagree.link_detachin theATTACHEDstate only sends the detach frame and moves toHALF_ATTACHED_ATTACH_SENT(link.c:1439), solink->pending_deliveriesstill points at the operation thatindicate_all_messages_as_errorfreed. When the peer settles the message,link_frame_receivedcallson_delivery_settledwith the freed context (link.c:584). That reachesCompletionOperation::OnOperationFn(sdk/core/azure-core-amqp/inc/azure/core/amqp/internal/common/completion_operation.hpp:31), which already took ownership and deleted the object on the first callback. The second call copies a deadstd::functionand the process stops.The defect predates PR 7346. It reproduces on that branch and on its merge base
95ff788c5. No CI leg builds the uAMQP transport, so no pipeline covers this path.Proposal
Cancel
transfer_async_operationon the close path, soindicate_all_messages_as_errormatchesmessagesender_send_cancel_handler. Alternatively, remove the delivery fromlink->pending_deliveriesbefore the operation is freed, so a late disposition finds no context.Add a regression test that closes a sender while one send is unsettled and then settles the message from the peer. Such a test needs the socket based mock broker, which builds only on Linux and Windows, and it needs a uAMQP configure with
-DDISABLE_RUST_IN_BUILD=ON.Validation
Reproduced in an Ubuntu 22.04 container on
linux/arm64with-DDISABLE_RUST_IN_BUILD=ON -DBUILD_TESTING=ON -DCMAKE_BUILD_TYPE=Debug. The test binary exits 139 on every attempt. The rest of that suite passes, at 215 tests from 24 suites.