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.

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.

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


Reply via email to