Commit 9998d225bb for qemu.org

commit 9998d225bb95c0baa17d51f760e46a51886ccfdf
Author: Denis V. Lunev <den@openvz.org>
Date:   Thu Aug 20 13:52:26 2026 +0200

    block/nbd: clear reply.cookie when the reply is rejected

    nbd_receive_replies() reads a reply header into s->reply and, when the
    header turns out to be unusable, reports a channel error and returns
    without touching it. The cookie stays there until the request which
    owns the reply clears it, and until then the waiters are explicitly
    allowed to look at a cookie which is not theirs:

        if (s->reply.cookie != 0) {
            ind2 = COOKIE_TO_INDEX(s->reply.cookie);
            assert(!s->requests[ind2].receiving);

    Two of the error paths leave a value chosen by the server behind: one
    returns before the cookie is validated at all, the other returns
    because that validation has failed. A waiter which picks such a cookie
    up turns it into an index which is not in requests[] and accesses the
    array out of bounds, at an offset the server controls.

    The reply is of no use to anybody at this point, so clear the cookie
    before the mutex is released and keep the invariant that a non-zero
    s->reply.cookie is always an index of a live request.

    Observing the stale cookie takes a second thread, which a multiqueue
    configuration provides. Within one AioContext there is no yield point
    between the failed read and the clearing done by the owner in
    nbd_co_receive_one_chunk(), so nothing else of this node runs in
    between. The parked waiters cannot see it either, as they are woken
    only after the cookie has been cleared. What can get in is a request
    entering nbd_receive_replies() afresh, one just sent or one back for
    its next reply chunk, because that path takes the mutex without
    looking at the state.

    Cc: Eric Blake <eblake@redhat.com>
    Cc: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
    Signed-off-by: Denis V. Lunev <den@openvz.org>
    Reviewed-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
    Message-ID: <20260820115228.587427-2-den@openvz.org>
    Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>

diff --git a/block/nbd.c b/block/nbd.c
index 5d231d5c4e..d9b776283f 100644
--- a/block/nbd.c
+++ b/block/nbd.c
@@ -466,20 +466,19 @@ static coroutine_fn int nbd_receive_replies(BDRVNBDState *s, uint64_t cookie,
             error_setg(errp, "server dropped connection");
         }
         if (ret < 0) {
-            nbd_channel_error(s, ret);
-            return ret;
+            goto err;
         }
         if (nbd_reply_is_structured(&s->reply) &&
             s->info.mode < NBD_MODE_STRUCTURED) {
-            nbd_channel_error(s, -EINVAL);
+            ret = -EINVAL;
             error_setg(errp, "unexpected structured reply");
-            return -EINVAL;
+            goto err;
         }
         ind2 = COOKIE_TO_INDEX(s->reply.cookie);
         if (ind2 >= MAX_NBD_REQUESTS || !s->requests[ind2].coroutine) {
-            nbd_channel_error(s, -EINVAL);
+            ret = -EINVAL;
             error_setg(errp, "unexpected cookie value");
-            return -EINVAL;
+            goto err;
         }
         if (s->reply.cookie == cookie) {
             /* We are done */
@@ -487,6 +486,13 @@ static coroutine_fn int nbd_receive_replies(BDRVNBDState *s, uint64_t cookie,
         }
         nbd_recv_coroutine_wake_one(&s->requests[ind2]);
     }
+
+err:
+    /* Waiters look at this cookie, so do not leave a rejected one behind. */
+    s->reply.cookie = 0;
+    nbd_channel_error(s, ret);
+
+    return ret;
 }

 static int coroutine_fn GRAPH_RDLOCK