Skip to content

fix(core-amqp): closing a uAMQP sender with an unsettled send is a use-after-free #7350

Description

@j7nw4r

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs-triageWorkflow: This is a new issue that needs to be triaged to the appropriate team.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions