Hello, Hongyan,

Sorry for the delay, and thanks for the patch.

On Fri, Sep 04, 2026 at 11:31:30AM +0800, Hongyan Xu wrote:
> The destination RDMA accept path runs the incoming migration coroutine
> (mis->loadvm_co).  While blocked waiting for an RDMA completion it parks
> itself in yield_until_fd_readable() (util/qemu-coroutine-io.c) on a
> completion-channel fd.  That helper registers an fd handler whose opaque
> is a stack object (FDYieldUntilData) and only removes the handler from
> inside its own fd_coroutine_enter() callback.
> 
> rdma_cm_poll_handler() handles RDMA_CM_EVENT_DISCONNECTED /
> RDMA_CM_EVENT_DEVICE_REMOVAL by calling qemu_coroutine_enter() on the
> coroutine directly.  When the coroutine is parked in
> yield_until_fd_readable() at that moment, this bypasses
> fd_coroutine_enter(): the coroutine resumes and returns from the wait,
> but the fd handler stays registered with an opaque that points into the
> (now returned-from) stack frame.  A later event on that fd then calls
> fd_coroutine_enter() with a dangling pointer -> use-after-free.

Looks like an issue indeed, and I wonder if it's a generic coroutine api
problem.  More below.

> 
> Record, around the yield, the AioContext and fd the coroutine is parked
> on, and make rdma_cm_poll_handler() remove that fd handler before it
> re-enters the coroutine.
> 
> This is an RFC: the coroutine/thread wake-up semantics in this area were
> also discussed by Peter Xu's 2025 series ("migration/rdma: Remove
> coroutine path in qemu_rdma_wait_comp_channel" / "Remove
> rdma_cm_poll_handler"); if that direction is preferred this patch should
> be reworked accordingly.

I assume you meant this:

https://lore.kernel.org/qemu-devel/[email protected]/

I kept getting interrupted by all the things so that series was stranded
for now.. I'll higher priority of this series.. if someone is willing to
pick that work up before I do, please let me know.  It's always good to fix
problems by removing code..

> 
> Signed-off-by: Hongyan Xu <[email protected]>
> ---
>  migration/rdma.c | 40 ++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 40 insertions(+)
> 
> diff --git a/migration/rdma.c b/migration/rdma.c
> index e976739fad..66175aa60b 100644
> --- a/migration/rdma.c
> +++ b/migration/rdma.c
> @@ -360,6 +360,17 @@ typedef struct RDMAContext {
>       */
>      int migration_started_on_destination;
>  
> +    /*
> +     * While the incoming-migration coroutine is parked in
> +     * yield_until_fd_readable() on a completion channel we record the
> +     * fd and AioContext here.  The cm event handler uses this to remove
> +     * the fd handler before re-entering the coroutine; without that the
> +     * handler keeps pointing at the coroutine stack after the wait
> +     * returns, i.e. a dangling fd handler.
> +     */
> +    AioContext *wait_ctx;
> +    int wait_fd;
> +
>      int total_registrations;
>      int total_writes;
>  
> @@ -1248,7 +1259,21 @@ qemu_rdma_wait_comp_channel(RDMAContext *rdma,
>      struct rdma_cm_event *cm_event;
>  
>      if (qemu_in_coroutine()) {
> +        AioContext *ctx = qemu_get_current_aio_context();
> +
> +        /*
> +         * Record where the coroutine is parked so that
> +         * rdma_cm_poll_handler() can remove the fd handler before it
> +         * re-enters us (yield_until_fd_readable() only removes the
> +         * handler through its own fd_coroutine_enter() callback; a
> +         * direct qemu_coroutine_enter() would leave a handler whose
> +         * opaque points at our stack frame).
> +         */
> +        rdma->wait_ctx = ctx;
> +        rdma->wait_fd = comp_channel->fd;
>          yield_until_fd_readable(comp_channel->fd);
> +        rdma->wait_ctx = NULL;
> +        rdma->wait_fd = -1;
>      } else {
>          /* This is the source side, we're in a separate thread
>           * or destination prior to migration_fd_process_incoming()
> @@ -3024,6 +3049,21 @@ static void rdma_cm_poll_handler(void *opaque)
>          }
>          rdma_ack_cm_event(cm_event);
>          if (mis->loadvm_co) {
> +            /*
> +             * The incoming coroutine may be parked in
> +             * yield_until_fd_readable() on a completion channel.  Its
> +             * fd handler is normally removed by fd_coroutine_enter()
> +             * when that fd becomes readable.  If we re-enter the
> +             * coroutine directly we must remove the handler first,
> +             * otherwise it stays registered with an opaque pointing at
> +             * the (now returned-from) coroutine stack frame.
> +             */
> +            if (rdma->wait_fd >= 0 && rdma->wait_ctx) {
> +                aio_set_fd_handler(rdma->wait_ctx, rdma->wait_fd,
> +                                   NULL, NULL, NULL, NULL, NULL);
> +                rdma->wait_fd = -1;
> +                rdma->wait_ctx = NULL;
> +            }
>              qemu_coroutine_enter(mis->loadvm_co);
>          }
>          return;
> -- 
> 2.50.1.windows.1
> 

I feel like this is a bit of an overkill.

If the problem is about wrongly leftover of fd handlers, can we always
unregister it, like this?

Copy more people (both rdma and coroutines).

===8<===

diff --git a/util/qemu-coroutine-io.c b/util/qemu-coroutine-io.c
index 364f4d5abf..7bf38f4495 100644
--- a/util/qemu-coroutine-io.c
+++ b/util/qemu-coroutine-io.c
@@ -74,7 +74,7 @@ typedef struct {
 static void fd_coroutine_enter(void *opaque)
 {
     FDYieldUntilData *data = opaque;
-    aio_set_fd_handler(data->ctx, data->fd, NULL, NULL, NULL, NULL, NULL);
+    /* Postpone fd handler removal to be within the coroutine */
     qemu_coroutine_enter(data->co);
 }

@@ -89,4 +89,9 @@ void coroutine_fn yield_until_fd_readable(int fd)
     aio_set_fd_handler(data.ctx, fd, fd_coroutine_enter, NULL, NULL, NULL,
                        &data);
     qemu_coroutine_yield();
+    /*
+     * Always remove the fd handler, no matter the re-entering of this
+     * coroutine was caused by fd_coroutine_enter() or something else.
+     */
+    aio_set_fd_handler(data.ctx, fd, NULL, NULL, NULL, NULL, NULL);
 }

-- 
Peter Xu


Reply via email to