From nobody Sun Jun 7 22:17:48 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=linux.alibaba.com ARC-Seal: i=1; a=rsa-sha256; t=1780563505; cv=none; d=zohomail.com; s=zohoarc; b=n8pEL5lnjSYKXcK/nb+7g+I1W5ndXS8YhUoFK9p/0ayJZJsOWg/pAZE0iVkwVNlAtUFMJnAiKAyNrVdRcIB2WWkDzg6KSCeh+tKrKtC5PjFHrKV1WlEJrvy6zZ019HQFPHxSYWa9uvuzOLXmii40AdDtIHCzx66h9rHCuUapmvg= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1780563505; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=ome00y6NCajRZ9+WaJy0c0RVI8D3yqZmz0PyuC8oox8=; b=WekU5iAVOblU9h72A8GA25k2+bNFgfvdsRRuFBVVT0etxnN78Jq2ajRqdiqpzXs60h+E4jY+x3OZW1zeSxOhvQ/f65zVxMEKU3Bd6NAce8k6KRhZZgfbX45ax4FmxQEPYw1Dj8l+ZivwN91vINZ9QgcjfaJ4eBztHvAjcwHbXYg= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1780563505078755.7784966310763; Thu, 4 Jun 2026 01:58:25 -0700 (PDT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wV3tB-00041E-1n; Thu, 04 Jun 2026 04:57:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV3st-0003zQ-Vs; Thu, 04 Jun 2026 04:57:05 -0400 Received: from [115.124.30.100] (helo=out30-100.freemail.mail.aliyun.com) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wV3si-0002d0-QS; Thu, 04 Jun 2026 04:56:58 -0400 Received: from localhost(mailfrom:guobin@linux.alibaba.com fp:SMTPD_---0X49lhnY_1780563384 cluster:ay36) by smtp.aliyun-inc.com; Thu, 04 Jun 2026 16:56:25 +0800 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linux.alibaba.com; s=default; t=1780563386; h=From:To:Subject:Date:Message-ID:MIME-Version; bh=ome00y6NCajRZ9+WaJy0c0RVI8D3yqZmz0PyuC8oox8=; b=fYvngzZfdGYRH+fRgN0mZ4F+seXjA1sOFwih3nU5/vBjVQF5bray8ViiIZ8qknlDXKOk6YVw8upBtTbteEuym0oRjxm+jLPn+w0x7nQjb++lKimfJ6lhz3i2zc8g178juC+ndcqcteLK2pTpmEHu+X2+Y433a4jqAuMXkwUMm90= X-Alimail-AntiSpam: AC=PASS; BC=-1|-1; BR=01201311R101e4; CH=green; DM=||false|; DS=||; FP=0|-1|-1|-1|0|-1|-1|-1; HT=maildocker-contentspam033045098064; MF=guobin@linux.alibaba.com; NM=1; PH=DS; RN=9; SR=0; TI=SMTPD_---0X49lhnY_1780563384; From: Bin Guo To: qemu-devel@nongnu.org Cc: mst@redhat.com, stefanha@redhat.com, kwolf@redhat.com, hreitz@redhat.com, pbonzini@redhat.com, fam@euphon.net, qemu-block@nongnu.org, qemu-stable@nongnu.org Subject: [PATCH] hw/virtio: reject malformed VirtQueueElement on migration load Date: Thu, 4 Jun 2026 16:56:24 +0800 Message-ID: <20260604085624.53815-1-guobin@linux.alibaba.com> X-Mailer: git-send-email 2.50.1 MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-Host-Lookup-Failed: Reverse DNS lookup failed for 115.124.30.100 (deferred) Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists1p.gnu.org; Received-SPF: pass client-ip=115.124.30.100; envelope-from=guobin@linux.alibaba.com; helo=out30-100.freemail.mail.aliyun.com X-Spam_score_int: -166 X-Spam_score: -16.7 X-Spam_bar: ---------------- X-Spam_report: (-16.7 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, ENV_AND_HDR_SPF_MATCH=-0.5, RCVD_IN_DNSWL_NONE=-0.0001, RDNS_NONE=0.793, SPF_PASS=-0.001, T_SPF_HELO_TEMPERROR=0.01, UNPARSEABLE_RELAY=0.001, USER_IN_DEF_DKIM_WL=-7.5, USER_IN_DEF_SPF_WL=-7.5 autolearn=no autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: qemu development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @linux.alibaba.com) X-ZM-MESSAGEID: 1780563508182158500 Content-Type: text/plain; charset="utf-8" qemu_get_virtqueue_element() asserts on in_num/out_num read straight from the migration stream, then feeds them to virtqueue_alloc_element() as allocation sizes. A malformed stream from the source crashes the destination; with NDEBUG the asserts vanish and the heap allocation is driven by attacker-controlled values - the reason the in-tree TODO forbade NDEBUG builds. The hole is reachable on every inbound migration of a virtio-blk or virtio-scsi guest. Reject in_num/out_num that overflow their arrays, and reject in_num + out_num > VIRTQUEUE_MAX_SIZE (a single chain cannot exceed one ring). The OR short-circuits so the sum is only evaluated once both counts are individually bounded. virtio_blk_load_device() returns -EINVAL through its existing int return. virtio_scsi_load_request() has no error channel in its SCSIBusInfo hook signature and mirrors the existing error_report() + exit(1) used a few lines below for parse failures. Document the new NULL return on the prototype in virtio.h. Cc: qemu-stable@nongnu.org Fixes: 8059feee0041 ("virtio: introduce virtio_map") Signed-off-by: Bin Guo --- hw/block/virtio-blk.c | 3 +++ hw/scsi/virtio-scsi.c | 5 +++++ hw/virtio/virtio.c | 19 +++++++++++++------ include/hw/virtio/virtio.h | 4 ++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/hw/block/virtio-blk.c b/hw/block/virtio-blk.c index 9cb9f1fb2b..97482c7981 100644 --- a/hw/block/virtio-blk.c +++ b/hw/block/virtio-blk.c @@ -1378,6 +1378,9 @@ static int virtio_blk_load_device(VirtIODevice *vdev,= QEMUFile *f, } =20 req =3D qemu_get_virtqueue_element(vdev, f, sizeof(VirtIOBlockReq)= ); + if (!req) { + return -EINVAL; + } virtio_blk_init_request(s, virtio_get_queue(vdev, vq_idx), req); =20 WITH_QEMU_LOCK_GUARD(&s->rq_lock) { diff --git a/hw/scsi/virtio-scsi.c b/hw/scsi/virtio-scsi.c index 6c73768011..bb2412e89c 100644 --- a/hw/scsi/virtio-scsi.c +++ b/hw/scsi/virtio-scsi.c @@ -274,6 +274,11 @@ static void *virtio_scsi_load_request(QEMUFile *f, SCS= IRequest *sreq) assert(n < vs->conf.num_queues); req =3D qemu_get_virtqueue_element(vdev, f, sizeof(VirtIOSCSIReq) + vs->cdb_size); + if (!req) { + error_report("virtio-scsi: failed to load request from migration " + "stream (queue=3D%u)", n); + exit(1); + } virtio_scsi_init_req(s, vs->cmd_vqs[n], req); =20 if (virtio_scsi_parse_req(req, sizeof(VirtIOSCSICmdReq) + vs->cdb_size, diff --git a/hw/virtio/virtio.c b/hw/virtio/virtio.c index 63e2faee99..24df1d0300 100644 --- a/hw/virtio/virtio.c +++ b/hw/virtio/virtio.c @@ -2167,13 +2167,20 @@ void *qemu_get_virtqueue_element(VirtIODevice *vdev= , QEMUFile *f, size_t sz) =20 qemu_get_buffer(f, (uint8_t *)&data, sizeof(VirtQueueElementOld)); =20 - /* TODO: teach all callers that this can fail, and return failure inst= ead - * of asserting here. - * This is just one thing (there are probably more) that must be - * fixed before we can allow NDEBUG compilation. + /* + * Bound the untrusted counts before they reach + * virtqueue_alloc_element() as allocation sizes. A single chain + * cannot exceed the ring size, so the sum is bounded too. */ - assert(ARRAY_SIZE(data.in_addr) >=3D data.in_num); - assert(ARRAY_SIZE(data.out_addr) >=3D data.out_num); + if (data.in_num > ARRAY_SIZE(data.in_addr) || + data.out_num > ARRAY_SIZE(data.out_addr) || + data.in_num + data.out_num > VIRTQUEUE_MAX_SIZE) { + error_report("virtio: malformed VirtQueueElement on migration load= : " + "in_num=3D%u out_num=3D%u (max=3D%u)", + data.in_num, data.out_num, + (unsigned)VIRTQUEUE_MAX_SIZE); + return NULL; + } =20 elem =3D virtqueue_alloc_element(sz, data.out_num, data.in_num); elem->index =3D data.index; diff --git a/include/hw/virtio/virtio.h b/include/hw/virtio/virtio.h index 6344bd7b68..3034257505 100644 --- a/include/hw/virtio/virtio.h +++ b/include/hw/virtio/virtio.h @@ -281,6 +281,10 @@ void virtqueue_fill(VirtQueue *vq, const VirtQueueElem= ent *elem, void virtqueue_map(VirtIODevice *vdev, VirtQueueElement *elem); void *virtqueue_pop(VirtQueue *vq, size_t sz); unsigned int virtqueue_drop_all(VirtQueue *vq); +/** + * Returns NULL if the migration stream encodes an out-of-range + * descriptor count; callers must check before dereferencing. + */ void *qemu_get_virtqueue_element(VirtIODevice *vdev, QEMUFile *f, size_t s= z); void qemu_put_virtqueue_element(VirtIODevice *vdev, QEMUFile *f, VirtQueueElement *elem); --=20 2.50.1 (Apple Git-155)