All of lore.kernel.org
 help / color / mirror / Atom feed
* [PULL 0/8] NBD patches through 2026-11-10
@ 2026-11-11  3:51 Eric Blake
  2026-11-11  3:51 ` [PULL 1/8] nbd/client-connection: Fix client returning a closed channel Eric Blake
                   ` (8 more replies)
  0 siblings, 9 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel

The following changes since commit 723622bedb0b423de7d1c61ebbe6e7a71934949c:

  Merge tag 'for-upstream' of https://repo.or.cz/qemu/kevin into staging (2026-11-09 11:58:07 -0800)

are available in the Git repository at:

  https://gitlab.com/ebblake/qemu.git tags/pull-nbd-2026-11-10

for you to fetch changes up to 1be14f69a8350349388763f83578f071fdbfee07:

  iotests: Add another nbd-robustness test (2026-11-10 11:20:24 -0600)

----------------------------------------------------------------
NBD patches through 2026-11-10

A few bug fixes:
- Guillaume Vincke: improve connection retry if NBD client hangs up quickly
- Eric Blake: fix two low-severity CVEs with non-compliant NBD clients

----------------------------------------------------------------
Eric Blake (6):
      docs: Mention security considerations for NBD
      iotests: Refactor python NBD server setup
      nbd/server: Reject rather than assert on non-compliant BLOCK_STATUS
      iotests: Add new nbd-robustness test
      nbd/server: Reject rather than assert on non-compliant NBD_OPT_SET_META_CONTEXT
      iotests: Add another nbd-robustness test

Guillaume Vincke (2):
      nbd/client-connection: Fix client returning a closed channel
      iotests: Add coverage for NBD connect thread start delay

 docs/system/security.rst                          |  23 +++
 nbd/client-connection.c                           |  32 ++++
 nbd/server.c                                      |  10 +-
 tests/qemu-iotests/iotests.py                     |  30 ++++
 tests/qemu-iotests/tests/nbd-multiconn            |  44 +----
 tests/qemu-iotests/tests/nbd-robustness           | 205 ++++++++++++++++++++++
 tests/qemu-iotests/tests/nbd-robustness.out       |   5 +
 tests/qemu-iotests/tests/nbd-stale-connection     |  60 +++++++
 tests/qemu-iotests/tests/nbd-stale-connection.out |  17 ++
 9 files changed, 388 insertions(+), 38 deletions(-)
 create mode 100755 tests/qemu-iotests/tests/nbd-robustness
 create mode 100644 tests/qemu-iotests/tests/nbd-robustness.out
 create mode 100755 tests/qemu-iotests/tests/nbd-stale-connection
 create mode 100644 tests/qemu-iotests/tests/nbd-stale-connection.out

-- 
2.56.0



^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PULL 1/8] nbd/client-connection: Fix client returning a closed channel
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 2/8] iotests: Add coverage for NBD connect thread start delay Eric Blake
                   ` (7 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Guillaume Vincke, Vladimir Sementsov-Ogievskiy,
	open list:Network Block Dev...

From: Guillaume Vincke <guillaume.vincke@ovhcloud.com>

When a server closes the connection after a successful handshake but
before running any operations, the channel returned by
nbd_co_establish_connection() is the closed one and the connect thread
is not started.

When configured with reconnect-delay=0 (the default), this situation
generates at least two IO errors. The first one happens when trying to
issue an operation to the closed socket. The connect thread will be
started on the next request, which may happen long after, and will
create the second error as nbd_co_establish_connection() returns a NULL
channel.

Peeking the channel to detect FIN or RST avoids the first IO error and
does not delay the starting of the connect thread.

Signed-off-by: Guillaume Vincke <guillaume.vincke@ovhcloud.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261105164124.249395-2-guillaume.vincke@ovhcloud.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 nbd/client-connection.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/nbd/client-connection.c b/nbd/client-connection.c
index 6a4f0807170..aec62934d1a 100644
--- a/nbd/client-connection.c
+++ b/nbd/client-connection.c
@@ -274,6 +274,23 @@ void nbd_client_connection_release(NBDClientConnection *conn)
     }
 }

+/* Only true when the peer closed the channel. Does not detect if dead. */
+static bool nbd_channel_is_closed(QIOChannel *ioc)
+{
+    char data;
+    struct iovec iov = { .iov_base = &data, .iov_len = 1 };
+    ssize_t len = -1;
+
+    if (!qio_channel_has_feature(ioc, QIO_CHANNEL_FEATURE_READ_MSG_PEEK)) {
+        return false;
+    }
+
+    len = qio_channel_readv_full(
+        ioc, &iov, 1, NULL, NULL, QIO_CHANNEL_READ_FLAG_MSG_PEEK, NULL);
+
+    return len == 0 || (len < 0 && len != QIO_CHANNEL_ERR_BLOCK);
+}
+
 /*
  * Get a new connection in context of @conn:
  *   if the thread is running, wait for completion
@@ -295,6 +312,7 @@ nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info,
                             bool blocking, Error **errp)
 {
     QemuThread thread;
+    QIOChannel *ioc;

     if (conn->do_negotiation) {
         assert(info);
@@ -308,6 +326,20 @@ nbd_co_establish_connection(NBDClientConnection *conn, NBDExportInfo *info,
         assert(!conn->wait_co);

         if (!conn->running) {
+            if (conn->sioc) {
+                /* discard if the socket got closed by its peer */
+                ioc = QIO_CHANNEL(conn->sioc);
+                if (!qio_channel_set_blocking(ioc, false, NULL) ||
+                    nbd_channel_is_closed(ioc)) {
+
+                    qio_channel_close(ioc, NULL);
+                    object_unref(OBJECT(conn->sioc));
+                    conn->sioc = NULL;
+                    object_unref(OBJECT(conn->ioc));
+                    conn->ioc = NULL;
+                }
+            }
+
             if (conn->sioc) {
                 /* Previous attempt finally succeeded in background */
                 if (conn->do_negotiation) {
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 2/8] iotests: Add coverage for NBD connect thread start delay
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
  2026-11-11  3:51 ` [PULL 1/8] nbd/client-connection: Fix client returning a closed channel Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 3/8] docs: Mention security considerations for NBD Eric Blake
                   ` (6 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Guillaume Vincke, Vladimir Sementsov-Ogievskiy, Kevin Wolf,
	Hanna Reitz, open list:Network Block Dev...

From: Guillaume Vincke <guillaume.vincke@ovhcloud.com>

Test the connect thread starting condition with reconnect-delay=0 when
the server disconnects after a successful handshake but before running
operations. Prior to the change to nbd_co_establish_connection(), this
test would demonstrate two I/O errors. The tests 264 and 277 covering
reconnection do not use reconnect-delay=0 so this case is not exercised.

Add a test that provokes multiple errors in order to put an NBD client
in the above-mentioned situation and shows that the first operation
starts the connect thread and leads to successful subsequent I/O.

Signed-off-by: Guillaume Vincke <guillaume.vincke@ovhcloud.com>
Message-ID: <20261105164124.249395-3-guillaume.vincke@ovhcloud.com>
Reviewed-by: Eric Blake <eblake@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
---
 tests/qemu-iotests/tests/nbd-stale-connection | 60 +++++++++++++++++++
 .../tests/nbd-stale-connection.out            | 17 ++++++
 2 files changed, 77 insertions(+)
 create mode 100755 tests/qemu-iotests/tests/nbd-stale-connection
 create mode 100644 tests/qemu-iotests/tests/nbd-stale-connection.out

diff --git a/tests/qemu-iotests/tests/nbd-stale-connection b/tests/qemu-iotests/tests/nbd-stale-connection
new file mode 100755
index 00000000000..e361bccfa98
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-stale-connection
@@ -0,0 +1,60 @@
+#!/usr/bin/env python3
+# group: quick
+#
+# Test the NBD client stale channel discard
+#
+# Copyright (C) 2026 OVHcloud
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import time
+import iotests
+from iotests import qemu_nbd_popen, qemu_img_create, file_path, log
+
+
+# estimated duration required by the connect thread to reach a running server
+MAX_NBD_CONNECT_DURATION = 1
+
+iotests.script_initialize(supported_fmts=['qcow2'])
+nbd_sock = file_path('nbd-sock', base_dir=iotests.sock_dir)
+disk = file_path('disk')
+qemu_img_create('-f', iotests.imgfmt, disk, '1M')
+
+with iotests.VM() as vm:
+
+    vm.launch()
+
+    with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk):
+        vm.cmd('blockdev-add', {
+            'driver': 'nbd',
+            'node-name': 'nbd0',
+            'server': {'type': 'unix', 'path': nbd_sock},
+        })
+        iotests.log('=== NBD Device connected ===')
+        log(vm.qmp('x-qemu-io', device='nbd0', command='write -P 0x5a 0 64k'))
+
+    iotests.log('=== NBD server down. Detect the break and start the connect '
+                'thread ===')
+    # client was in CONNECTED state a new operation updates to CONNECTING
+    log(vm.qmp('x-qemu-io', device='nbd0', command='write -P 0x5a 0 64k'))
+
+    with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk):
+        iotests.log('=== NBD server up. Wait for the connect thread to park '
+                    'the channel ===')
+        # in CONNECTING, the operation starts the connect thread
+        log(vm.qmp('x-qemu-io', device='nbd0', command='write -P 0x5a 0 64k'))
+        # wait for the connect thread to get a new channel and stop
+        time.sleep(MAX_NBD_CONNECT_DURATION)
+
+    iotests.log('=== NBD server down. The parked channel is closed ===')
+
+    with qemu_nbd_popen('-k', nbd_sock, '-f', iotests.imgfmt, disk):
+        iotests.log('=== NBD server up. Restart the connect thread ===')
+        # operation will determine that the channel got closed and restart the
+        # connect thread
+        log(vm.qmp('x-qemu-io', device='nbd0', command='write -P 0x5a 0 64k'))
+        # Wait for it to succeed
+        iotests.log('=== Wait for the connect thread to park the new channel. '
+                    'Subsequent operations are successful ===')
+        time.sleep(MAX_NBD_CONNECT_DURATION)
+        log(vm.qmp('x-qemu-io', device='nbd0', command='write -P 0x5a 0 64k'))
diff --git a/tests/qemu-iotests/tests/nbd-stale-connection.out b/tests/qemu-iotests/tests/nbd-stale-connection.out
new file mode 100644
index 00000000000..065ba329e96
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-stale-connection.out
@@ -0,0 +1,17 @@
+Start NBD server
+=== NBD Device connected ===
+{"return": {}}
+Kill NBD server
+=== NBD server down. Detect the break and start the connect thread ===
+{"error": {"class": "GenericError", "desc": "write failed: Input/output error"}}
+Start NBD server
+=== NBD server up. Wait for the connect thread to park the channel ===
+{"error": {"class": "GenericError", "desc": "write failed: Input/output error"}}
+Kill NBD server
+=== NBD server down. The parked channel is closed ===
+Start NBD server
+=== NBD server up. Restart the connect thread ===
+{"error": {"class": "GenericError", "desc": "write failed: Input/output error"}}
+=== Wait for the connect thread to park the new channel. Subsequent operations are successful ===
+{"return": {}}
+Kill NBD server
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 3/8] docs: Mention security considerations for NBD
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
  2026-11-11  3:51 ` [PULL 1/8] nbd/client-connection: Fix client returning a closed channel Eric Blake
  2026-11-11  3:51 ` [PULL 2/8] iotests: Add coverage for NBD connect thread start delay Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 4/8] iotests: Refactor python NBD server setup Eric Blake
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel; +Cc: Daniel P. Berrangé, Stefan Hajnoczi, Pierrick Bouvier

Document a policy that NBD connections should strongly consider using
TLS and x509 certificates to filter out unwanted clients.  If a
malicious server or client can trigger a QEMU assertion failure on a
plaintext connection, but can trivially have risk mitigated by
enabling TLS to weed out unknown partners (so that a full-on attack of
QEMU would first require chaining some other attack to convince a
"trusted" client to break protocol in the first place), then that
reduces the CVSS-BE score of the QEMU bug. In turn, our policy already
states that low severity scores can be fixed without an embargo.

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-7-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 docs/system/security.rst | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

diff --git a/docs/system/security.rst b/docs/system/security.rst
index e211fdd2344..e30f771731a 100644
--- a/docs/system/security.rst
+++ b/docs/system/security.rst
@@ -320,3 +320,26 @@ encryption and authorization control policy on client connections.
 In summary, the monitor console is considered a privileged control interface to
 QEMU and as such should only be made accessible to a trusted management
 application or user.
+
+Block device network protocols (NBD, NFS, etc.)
+'''''''''''''''''''''''''''''''''''''''''''''''
+
+When using QEMU to access remote storage over a network (such as QEMU
+acting as an NBD or NFS client) or to provide remote storage access
+(such as QEMU acting as an NBD server), it is important to realize
+that an unencrypted connection can allow any external process a
+potential attack vector via the network port. When possible, QEMU
+should be configured to require TLS to prevent a man-in-the-middle
+attack, as well as use x509 client certificates with the
+tls-creds-x509 object set with 'verify-peer=on' to check that the peer
+is allowed to connect. It may also be worth using 'tls-authz' to
+filter which certificates are allowed if the CA issues certs more
+broadly.
+
+Proper hardening of network connections cannot prevent a client from
+violating protocol, but does increase the complexity of an attack,
+because a malicious actor must first find a way to convince an
+authenticated client to send the bytes that will trigger any QEMU
+misbehavior.  Use of a hardened connection is therefore an
+environmental factor used in determining the risk and severity of a
+network-triggered bug.
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 4/8] iotests: Refactor python NBD server setup
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (2 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 3/8] docs: Mention security considerations for NBD Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 5/8] nbd/server: Reject rather than assert on non-compliant BLOCK_STATUS Eric Blake
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé, Stefan Hajnoczi, Kevin Wolf, Hanna Reitz,
	Vladimir Sementsov-Ogievskiy, open list:Block layer core

Extract code from nbd-multiconn that I plan to use in another upcoming
unit test.

Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-8-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/qemu-iotests/iotests.py          | 30 ++++++++++++++++++
 tests/qemu-iotests/tests/nbd-multiconn | 44 +++++---------------------
 2 files changed, 38 insertions(+), 36 deletions(-)

diff --git a/tests/qemu-iotests/iotests.py b/tests/qemu-iotests/iotests.py
index 642a160bb28..7017d2016cf 100644
--- a/tests/qemu-iotests/iotests.py
+++ b/tests/qemu-iotests/iotests.py
@@ -1385,6 +1385,36 @@ def case_skip(self, reason):
         case_notrun(reason)
         self.skipTest(reason)

+    @contextmanager
+    def run_nbd_server(self, nbd_sock: str,
+                       max_conn: Optional[int] = None) -> Iterator[None]:
+        args: Dict[str, Any] = {
+            'addr': {
+                'type': 'unix',
+                'data': {'path': nbd_sock}
+            }
+        }
+        if max_conn is not None:
+            args['max-connections'] = max_conn
+
+        self.vm.cmd('nbd-server-start', args)
+        try:
+            yield
+        finally:
+            self.vm.cmd('nbd-server-stop')
+
+    def add_nbd_export(self, node_name: str, name: str,
+                       writable: Optional[bool] = None) -> None:
+        args: Dict[str, Any] = {
+            'type': 'nbd',
+            'id': name,
+            'node-name': node_name,
+            'name': name,
+        }
+        if writable is not None:
+            args['writable'] = writable
+
+        self.vm.cmd('block-export-add', args)

 def notrun(reason):
     '''Skip this test suite'''
diff --git a/tests/qemu-iotests/tests/nbd-multiconn b/tests/qemu-iotests/tests/nbd-multiconn
index 479e872f2a8..96efe01e583 100755
--- a/tests/qemu-iotests/tests/nbd-multiconn
+++ b/tests/qemu-iotests/tests/nbd-multiconn
@@ -62,55 +62,27 @@ class TestNbdMulticonn(iotests.QMPTestCase):
         except OSError:
             pass

-    @contextmanager
-    def run_server(self, max_connections=None):
-        args = {
-            'addr': {
-                'type': 'unix',
-                'data': {'path': nbd_sock}
-            }
-        }
-        if max_connections is not None:
-            args['max-connections'] = max_connections
-
-        self.vm.cmd('nbd-server-start', args)
-        yield
-
-        self.vm.cmd('nbd-server-stop')
-
-    def add_export(self, name, writable=None):
-        args = {
-            'type': 'nbd',
-            'id': name,
-            'node-name': 'n',
-            'name': name,
-        }
-        if writable is not None:
-            args['writable'] = writable
-
-        self.vm.cmd('block-export-add', args)
-
     def test_default_settings(self):
-        with self.run_server():
-            self.add_export('r')
-            self.add_export('w', writable=True)
+        with self.run_nbd_server(nbd_sock):
+            self.add_nbd_export('n', 'r')
+            self.add_nbd_export('n', 'w', writable=True)
             with open_nbd('r') as h:
                 self.assertTrue(h.can_multi_conn())
             with open_nbd('w') as h:
                 self.assertTrue(h.can_multi_conn())

     def test_limited_connections(self):
-        with self.run_server(max_connections=1):
-            self.add_export('r')
-            self.add_export('w', writable=True)
+        with self.run_nbd_server(nbd_sock, max_conn=1):
+            self.add_nbd_export('n', 'r')
+            self.add_nbd_export('n', 'w', writable=True)
             with open_nbd('r') as h:
                 self.assertFalse(h.can_multi_conn())
             with open_nbd('w') as h:
                 self.assertFalse(h.can_multi_conn())

     def test_parallel_writes(self):
-        with self.run_server():
-            self.add_export('w', writable=True)
+        with self.run_nbd_server(nbd_sock):
+            self.add_nbd_export('n', 'w', writable=True)

             clients = [nbd.NBD() for _ in range(3)]
             for c in clients:
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 5/8] nbd/server: Reject rather than assert on non-compliant BLOCK_STATUS
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (3 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 4/8] iotests: Refactor python NBD server setup Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 6/8] iotests: Add new nbd-robustness test Eric Blake
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Feifan Qian, Haotian Jiang, qemu-stable, Daniel P. Berrangé,
	Stefan Hajnoczi, Vladimir Sementsov-Ogievskiy,
	open list:Network Block Dev...

The NBD spec is clear that a client must not send NBD_BLOCK_STATUS
requests to a server if it did not negotiate any metacontexts with
NBD_OPT_SET_META_CONTEXT for the current export.  However, if the
non-compliant client uses NBD_OPT_EXTENDED_HEADERS and then passes in
the relatively new NBD_CMD_FLAG_PAYLOAD_LEN flag alongside an
inappropriate block status request, this was enough to trigger an
assertion in qemu.  The assertion can also be tripped by initially
requesting the context for one export, then using NBD_OPT_GO on a
different export (nbd_check_meta_export() sets contexts.count to 0,
but did not wipe contexts.exp). Forcing the client command to be
rejected if contexts.count is 0 solves both triggers, and allows the
assertion to still operate cleanly at a later location.

If you do not use recommended practice to prevent unwanted clients
from connecting to QEMU's NBD server, then any client can cause a
denial of service with minimal effort.  However, with TLS and x509
certs in place, an attacker must first chain in an attack to convince
the client to send unwanted traffic, which is a factor in declaring
this a lower-risk denial of service.

Fixes: CVE-2026-124559
Fixes: 2dcbb11b ("nbd/server: Add FLAG_PAYLOAD support to CMD_BLOCK_STATUS", v8.2.0)
Reported-by: Feifan Qian <bea1e@proton.me>
Reported-by: https://gitlab.com/qemu-project/qemu/-/work_items/3796
CC: Haotian Jiang <jianghaotian.sunday@gmail.com>
CC: qemu-stable@nongnu.org
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-9-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 nbd/server.c | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/nbd/server.c b/nbd/server.c
index e6c47f8c2e3..8a01c27de07 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -2584,17 +2584,18 @@ nbd_co_block_status_payload_read(NBDClient *client, NBDRequest *request,
         return -EINVAL;
     }

-    assert(client->contexts.exp == client->exp);
     nr_bitmaps = client->exp->nr_export_bitmaps;
     request->contexts = g_new0(NBDMetaContexts, 1);
     request->contexts->exp = client->exp;

-    if (payload_len % sizeof(uint32_t) ||
+    if (!client->contexts.count ||
+        payload_len % sizeof(uint32_t) ||
         payload_len < sizeof(NBDBlockStatusPayload) ||
         payload_len > (sizeof(NBDBlockStatusPayload) +
                        sizeof(id) * client->contexts.count)) {
         goto skip;
     }
+    assert(client->contexts.exp == client->exp);

     buf = g_malloc(payload_len);
     if (nbd_read(client->ioc, buf, payload_len,
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 6/8] iotests: Add new nbd-robustness test
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (4 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 5/8] nbd/server: Reject rather than assert on non-compliant BLOCK_STATUS Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 7/8] nbd/server: Reject rather than assert on non-compliant NBD_OPT_SET_META_CONTEXT Eric Blake
                   ` (2 subsequent siblings)
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Stefan Hajnoczi, Vladimir Sementsov-Ogievskiy, Kevin Wolf,
	Hanna Reitz, open list:Network Block Dev...

Convert the proof of concept test by Sunday Jiang into an iotest. I
tried to get libnbd to trigger the situation, but forcing a block
status command when no status has been initiated is not something that
libnbd is willing to do. So this results in a hard-coded sequence of
bytes sent over a raw socket.  Swapping the order with the previous
patch shows the assertion failure that was previously reachable.

Based-on: https://gitlab.com/qemu-project/qemu/-/work_items/4091
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-10-eblake@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/qemu-iotests/tests/nbd-robustness     | 132 ++++++++++++++++++++
 tests/qemu-iotests/tests/nbd-robustness.out |   5 +
 2 files changed, 137 insertions(+)
 create mode 100755 tests/qemu-iotests/tests/nbd-robustness
 create mode 100644 tests/qemu-iotests/tests/nbd-robustness.out

diff --git a/tests/qemu-iotests/tests/nbd-robustness b/tests/qemu-iotests/tests/nbd-robustness
new file mode 100755
index 00000000000..fe61a839250
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-robustness
@@ -0,0 +1,132 @@
+#!/usr/bin/env python3
+# group: rw auto quick
+#
+# Test cases for NBD robustness against malicious clients
+#
+# Copyright (C) 2026 Red Hat, Inc.
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+import os
+import socket
+import struct
+
+import iotests
+from iotests import qemu_img_create, qemu_io
+
+# Selected NBD Protocol Constants
+NBD_MAGIC_INIT = 0x4e42444d41474943          # "NBDMAGIC"
+NBD_MAGIC_OPTS = 0x49484156454f5054          # "IHAVEOPT"
+NBD_MAGIC_REPLY = 0x0003e889045565a9         # Reply magic
+NBD_MAGIC_EXT_REPLY = 0x6e8a278c             # Structured reply magic
+NBD_FLAG_C_FIXED_NEWSTYLE = 0x1
+NBD_OPT_GO = 7
+NBD_OPT_SET_META_CONTEXT = 10
+NBD_OPT_EXTENDED_HEADERS = 11
+NBD_REP_ACK = 1
+NBD_REP_INFO = 3
+NBD_CMD_BLOCK_STATUS = 7
+
+disk = os.path.join(iotests.test_dir, 'disk')
+size = '4M'
+nbd_sock = os.path.join(iotests.sock_dir, 'nbd_sock')
+
+
+class TestNbdRobustness(iotests.QMPTestCase):
+    def setUp(self):
+        qemu_img_create('-f', iotests.imgfmt, disk, size)
+        qemu_io('-c', 'w -P 1 0 2M', '-c', 'w -P 2 2M 2M', disk)
+
+        self.vm = iotests.VM()
+        self.vm.launch()
+        self.vm.cmd('blockdev-add', {
+            'driver': iotests.imgfmt,
+            'node-name': 'n',
+            'file': {'driver': 'file', 'filename': disk}
+        })
+
+    def tearDown(self):
+        self.vm.shutdown()
+        os.remove(disk)
+        try:
+            os.remove(nbd_sock)
+        except OSError:
+            pass
+
+    def recv_exact(self, s: socket.socket, n: int) -> bytes:
+        buf = bytearray()
+        while len(buf) < n:
+            chunk = s.recv(n - len(buf))
+            if not chunk:
+                raise EOFError(f"Unexpected EOF while reading {n} bytes "
+                               f"(got {len(buf)})")
+            buf.extend(chunk)
+        return bytes(buf)
+
+    def test_invalid_block_status(self):
+        with self.run_nbd_server(nbd_sock):
+            self.add_nbd_export('n', 'r')
+
+            # Use raw socket traffic to intentionally violate NBD protocol
+            with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
+                s.connect(nbd_sock)
+                # Server sends NBDMAGIC(8) + IHAVEOPT(8) + flags(2)
+                data = self.recv_exact(s, 18)
+                magic, opt, flags = struct.unpack(">QQH", data)
+                self.assertEqual(magic, NBD_MAGIC_INIT)
+                self.assertEqual(opt, NBD_MAGIC_OPTS)
+                self.assertEqual(flags, 0x3)
+
+                # Reply with flags(4) - just fixed newstyle flag
+                s.sendall(struct.pack(">I", NBD_FLAG_C_FIXED_NEWSTYLE))
+
+                # Request NBD_OPT_EXTENDED_HEADERS, no extra payload
+                s.sendall(struct.pack(">QII", NBD_MAGIC_OPTS,
+                                      NBD_OPT_EXTENDED_HEADERS, 0))
+
+                # Expect successful reply:
+                # magic(8) + option(4) + reply(4) + length(4)
+                data = self.recv_exact(s, 20)
+                magic, opt, reply, length = struct.unpack(">QIII", data)
+                self.assertEqual(magic, NBD_MAGIC_REPLY)
+                self.assertEqual(opt, NBD_OPT_EXTENDED_HEADERS)
+                self.assertEqual(reply, NBD_REP_ACK)
+                self.assertEqual(length, 0)
+
+                # Request NBD_OPT_GO, name but no info as payload
+                payload = struct.pack(">I", 1) + b'r' + struct.pack(">H", 0)
+                s.sendall(struct.pack(">QII", NBD_MAGIC_OPTS, NBD_OPT_GO,
+                                      len(payload)) + payload)
+
+                # Expect (and ignore) zero or more info packets before ack
+                while True:
+                    data = self.recv_exact(s, 20)
+                    magic, opt, reply, length = struct.unpack(">QIII", data)
+                    self.assertEqual(magic, NBD_MAGIC_REPLY)
+                    self.assertEqual(opt, NBD_OPT_GO)
+                    payload = self.recv_exact(s, length)
+                    if reply != NBD_REP_INFO:
+                        break
+                self.assertEqual(reply, NBD_REP_ACK)
+
+                # Send invalid NBD_CMD_BLOCK_STATUS with payload flag. This
+                # violates the NBD protocol (since no context was negotiated),
+                # but should not crash the server.
+                payload = struct.pack(">QI", 0x1000, 0)
+                s.sendall(struct.pack(">IHHQQQ", 0x21e41c71, 0x20,
+                                      NBD_CMD_BLOCK_STATUS, 1, 0,
+                                      len(payload)) + payload)
+
+                # Expect a rejection.
+                data = self.recv_exact(s, 32)
+                magic, flags, rtype, cookie, _, length = \
+                    struct.unpack(">IHHQQQ", data)
+                self.assertEqual(magic, NBD_MAGIC_EXT_REPLY)
+                self.assertEqual(rtype & 0x8000, 0x8000)
+                self.assertEqual(cookie, 1)
+                self.recv_exact(s, length)
+
+            self.assertTrue(self.vm.is_running())
+
+if __name__ == '__main__':
+    iotests.main(supported_fmts=['qcow2'])
diff --git a/tests/qemu-iotests/tests/nbd-robustness.out b/tests/qemu-iotests/tests/nbd-robustness.out
new file mode 100644
index 00000000000..ae1213e6f86
--- /dev/null
+++ b/tests/qemu-iotests/tests/nbd-robustness.out
@@ -0,0 +1,5 @@
+.
+----------------------------------------------------------------------
+Ran 1 tests
+
+OK
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 7/8] nbd/server: Reject rather than assert on non-compliant NBD_OPT_SET_META_CONTEXT
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (5 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 6/8] iotests: Add new nbd-robustness test Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-11  3:51 ` [PULL 8/8] iotests: Add another nbd-robustness test Eric Blake
  2026-11-12 18:16 ` [PULL 0/8] NBD patches through 2026-11-10 Richard Henderson
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: qemu-stable, Daniel P. Berrangé, Stefan Hajnoczi,
	Vladimir Sementsov-Ogievskiy, open list:Network Block Dev...

The NBD protocol says that NBD_OPT_{LIST,SET}_META_CONTEXT sends
several lengths that must be consistent: an overall option reply
length, then a series of smaller lengths/data pairs representing the
export name and zero or more context names. If the smaller lengths
within the payload do not add up to the larger value, the client has
sent garbage bytes that will trigger the server's parse of the
client's next option to be out of sync.  However, QEMU was dying with
an assertion failure rather than diagnosing the issue, which a
malicious client can then exploit to mount a denial-of-service attack
against QEMU as the NBD server.

As with several other recent NBD protocol violations, the likelihood
of this attack can be reduced by using TLS and x509 certs to reject
unwanted clients (a "trusted" client can still trigger the problem,
but you first have to compromise the trusted client into sending
arbitrary traffic).

Fixes: CVE-2026-125683
Fixes: e7b1948d51 ("nbd: BLOCK_STATUS for standard get_block_status function: server part", v2.12.0)
Reported-by: TonySD via https://gitlab.com/qemu-project/qemu/-/work_items/4843
CC: qemu-stable@nongnu.org
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-11-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 nbd/server.c | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/nbd/server.c b/nbd/server.c
index 8a01c27de07..8c45f9fc43f 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -1095,6 +1095,11 @@ nbd_negotiate_meta_queries(NBDClient *client, Error **errp)
             }
         }
     }
+    if (client->optlen > 0) {
+        return nbd_opt_invalid(client, errp,
+                               "Inconsistent lengths in option %s",
+                               nbd_opt_lookup(client->opt));
+    }

     if (meta->base_allocation) {
         ret = nbd_negotiate_send_meta_context(client, "base:allocation",
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PULL 8/8] iotests: Add another nbd-robustness test
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (6 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 7/8] nbd/server: Reject rather than assert on non-compliant NBD_OPT_SET_META_CONTEXT Eric Blake
@ 2026-11-11  3:51 ` Eric Blake
  2026-11-12 18:16 ` [PULL 0/8] NBD patches through 2026-11-10 Richard Henderson
  8 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-11  3:51 UTC (permalink / raw)
  To: qemu-devel
  Cc: Stefan Hajnoczi, Vladimir Sementsov-Ogievskiy, Kevin Wolf,
	Hanna Reitz, open list:Network Block Dev...

Convert the proof of concept test by TonySD into another case of the
iotest. This is yet another instance where a hard-coded exchange is
easier than forcing libnbd into producing an exchange that violates
the protocol.  Swapping the order with the previous patch shows the
assertion failure that was previously reachable.

Based-on: https://gitlab.com/qemu-project/qemu/-/work_items/4843
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20261110155356.1520433-12-eblake@redhat.com>
Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com>
---
 tests/qemu-iotests/tests/nbd-robustness     | 73 +++++++++++++++++++++
 tests/qemu-iotests/tests/nbd-robustness.out |  4 +-
 2 files changed, 75 insertions(+), 2 deletions(-)

diff --git a/tests/qemu-iotests/tests/nbd-robustness b/tests/qemu-iotests/tests/nbd-robustness
index fe61a839250..410dfca04e3 100755
--- a/tests/qemu-iotests/tests/nbd-robustness
+++ b/tests/qemu-iotests/tests/nbd-robustness
@@ -21,6 +21,7 @@ NBD_MAGIC_REPLY = 0x0003e889045565a9         # Reply magic
 NBD_MAGIC_EXT_REPLY = 0x6e8a278c             # Structured reply magic
 NBD_FLAG_C_FIXED_NEWSTYLE = 0x1
 NBD_OPT_GO = 7
+NBD_OPT_STRUCTURED_REPLY = 8
 NBD_OPT_SET_META_CONTEXT = 10
 NBD_OPT_EXTENDED_HEADERS = 11
 NBD_REP_ACK = 1
@@ -128,5 +129,77 @@ class TestNbdRobustness(iotests.QMPTestCase):

             self.assertTrue(self.vm.is_running())

+    def test_invalid_set_meta_context(self):
+        with self.run_nbd_server(nbd_sock):
+            self.add_nbd_export('n', 'r')
+
+            # Use raw socket traffic to intentionally violate NBD protocol
+            with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
+                s.connect(nbd_sock)
+                # Server sends NBDMAGIC(8) + IHAVEOPT(8) + flags(2)
+                data = self.recv_exact(s, 18)
+                magic, opt, flags = struct.unpack(">QQH", data)
+                self.assertEqual(magic, NBD_MAGIC_INIT)
+                self.assertEqual(opt, NBD_MAGIC_OPTS)
+                self.assertEqual(flags, 0x3)
+
+                # Reply with flags(4) - just fixed newstyle flag
+                s.sendall(struct.pack(">I", NBD_FLAG_C_FIXED_NEWSTYLE))
+
+                # Request NBD_OPT_STRUCTURED_REPLY, no extra payload
+                s.sendall(struct.pack(">QII", NBD_MAGIC_OPTS,
+                                      NBD_OPT_STRUCTURED_REPLY, 0))
+
+                # Expect successful reply:
+                # magic(8) + option(4) + reply(4) + length(4)
+                data = self.recv_exact(s, 20)
+                magic, opt, reply, length = struct.unpack(">QIII", data)
+                self.assertEqual(magic, NBD_MAGIC_REPLY)
+                self.assertEqual(opt, NBD_OPT_STRUCTURED_REPLY)
+                self.assertEqual(reply, NBD_REP_ACK)
+                self.assertEqual(length, 0)
+
+                # Request NBD_OPT_SET_META_CONTEXT with invalid length
+                # namelen(4) + str + queries(4) + {querylen(4) + str)
+                payload = struct.pack(">I", 1) + b'r'
+                payload += struct.pack(">II", 1, 1) + b'X'
+                s.sendall(struct.pack(">QII", NBD_MAGIC_OPTS,
+                                      NBD_OPT_SET_META_CONTEXT,
+                                      len(payload) + 1) + payload)
+
+                # Request NBD_OPT_GO, name but no info as payload
+                # (request is early, but does not hurt here)
+                payload = struct.pack(">I", 1) + b'r' + struct.pack(">H", 0)
+                s.sendall(struct.pack(">QII", NBD_MAGIC_OPTS, NBD_OPT_GO,
+                                      len(payload)) + payload)
+
+                # Expect error reply to SET_META_CONTEXT:
+                # magic(8) + option(4) + reply(4) + length(4)
+                data = self.recv_exact(s, 20)
+                magic, opt, reply, length = struct.unpack(">QIII", data)
+                self.assertEqual(magic, NBD_MAGIC_REPLY)
+                self.assertEqual(opt, NBD_OPT_SET_META_CONTEXT)
+                self.assertEqual(reply & 0x80000000, 0x80000000)
+                self.recv_exact(s, length)
+
+                # Expect early disconnect for unaligned GO. Could show up as
+                # either b'' or ConnectionResetError
+                try:
+                    data = s.recv(1)
+                    self.assertEqual(data, b'')
+                except ConnectionResetError:
+                    pass
+
+                # Further writes must trigger an error (EPIPE / ECONNRESET)
+                with self.assertRaises((BrokenPipeError, ConnectionResetError,
+                                        OSError)):
+                    s.sendall(b'X')
+
+            # QEMU should still be up
+            self.assertIsNone(self.vm.exitcode())
+            self.assertTrue(self.vm.is_running())
+            res = self.vm.cmd('query-status')
+            self.assertEqual(res['status'], 'running')
+
 if __name__ == '__main__':
     iotests.main(supported_fmts=['qcow2'])
diff --git a/tests/qemu-iotests/tests/nbd-robustness.out b/tests/qemu-iotests/tests/nbd-robustness.out
index ae1213e6f86..fbc63e62f88 100644
--- a/tests/qemu-iotests/tests/nbd-robustness.out
+++ b/tests/qemu-iotests/tests/nbd-robustness.out
@@ -1,5 +1,5 @@
-.
+..
 ----------------------------------------------------------------------
-Ran 1 tests
+Ran 2 tests

 OK
-- 
2.56.0



^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PULL 0/8] NBD patches through 2026-11-10
  2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
                   ` (7 preceding siblings ...)
  2026-11-11  3:51 ` [PULL 8/8] iotests: Add another nbd-robustness test Eric Blake
@ 2026-11-12 18:16 ` Richard Henderson
  2026-11-12 19:27   ` Eric Blake
  8 siblings, 1 reply; 11+ messages in thread
From: Richard Henderson @ 2026-11-12 18:16 UTC (permalink / raw)
  To: Eric Blake, qemu-devel

On 11/10/26 19:51, Eric Blake wrote:
> The following changes since commit 723622bedb0b423de7d1c61ebbe6e7a71934949c:
> 
>    Merge tag 'for-upstream' ofhttps://repo.or.cz/qemu/kevin into staging (2026-11-09 11:58:07 -0800)
> 
> are available in the Git repository at:
> 
>    https://gitlab.com/ebblake/qemu.git tags/pull-nbd-2026-11-10
> 
> for you to fetch changes up to 1be14f69a8350349388763f83578f071fdbfee07:
> 
>    iotests: Add another nbd-robustness test (2026-11-10 11:20:24 -0600)
> 
> ----------------------------------------------------------------
> NBD patches through 2026-11-10
> 
> A few bug fixes:
> - Guillaume Vincke: improve connection retry if NBD client hangs up quickly
> - Eric Blake: fix two low-severity CVEs with non-compliant NBD clients

New failure:

https://gitlab.com/qemu-project/qemu/-/jobs/17979973534#L298

137/150 block-qcow2-optional+block-slow - 
qemu:io-qcow2-nbd-stale-connection          FAIL             2.37s 
exit status 1

{"error": {"class": "GenericError", "desc": "write failed: Input/output 
error"}}


r~


^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PULL 0/8] NBD patches through 2026-11-10
  2026-11-12 18:16 ` [PULL 0/8] NBD patches through 2026-11-10 Richard Henderson
@ 2026-11-12 19:27   ` Eric Blake
  0 siblings, 0 replies; 11+ messages in thread
From: Eric Blake @ 2026-11-12 19:27 UTC (permalink / raw)
  To: Richard Henderson, guillaume.vincke, armbru; +Cc: qemu-devel

On Thu, Nov 12, 2026 at 11:16:24AM -0700, Richard Henderson wrote:
> On 11/10/26 19:51, Eric Blake wrote:
> > The following changes since commit 723622bedb0b423de7d1c61ebbe6e7a71934949c:
> > 

> > A few bug fixes:
> > - Guillaume Vincke: improve connection retry if NBD client hangs up quickly
> > - Eric Blake: fix two low-severity CVEs with non-compliant NBD clients
> 
> New failure:
> 
> https://gitlab.com/qemu-project/qemu/-/jobs/17979973534#L298
> 
> 137/150 block-qcow2-optional+block-slow - qemu:io-qcow2-nbd-stale-connection
> FAIL             2.37s exit status 1
> 
> {"error": {"class": "GenericError", "desc": "write failed: Input/output
> error"}}

Shoot - looks like this hit the CI timeout despite our best efforts,
which is why Guillaume is working on a second series for making the
test more robust [1], although our initial take was that it is really
late in the 11.2 cycle to be adding a new x-debug- QMP command. I can
respin the pull request to drop the test and/or give it a longer
timeout so it is less likely to be flaky in CI.

[1] https://lists.gnu.org/archive/html/qemu-devel/2026-11/msg01941.html

Markus, what are your thoughts on a QMP addition this late into 11.2
so that the test in 2/8 can be stable? I'm torn on whether the NBD bug
in 1/8 is severe enough to need to go in 11.2.0 proper, or whether the
bug is weak enough that it is safer to wait until 11.2.1 via the
qemu-stable process.  Patches 3-8 fix CVEs, so they definitely belong
in 11.2.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.
Virtualization:  qemu.org | libguestfs.org



^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2026-11-12 19:28 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-11  3:51 [PULL 0/8] NBD patches through 2026-11-10 Eric Blake
2026-11-11  3:51 ` [PULL 1/8] nbd/client-connection: Fix client returning a closed channel Eric Blake
2026-11-11  3:51 ` [PULL 2/8] iotests: Add coverage for NBD connect thread start delay Eric Blake
2026-11-11  3:51 ` [PULL 3/8] docs: Mention security considerations for NBD Eric Blake
2026-11-11  3:51 ` [PULL 4/8] iotests: Refactor python NBD server setup Eric Blake
2026-11-11  3:51 ` [PULL 5/8] nbd/server: Reject rather than assert on non-compliant BLOCK_STATUS Eric Blake
2026-11-11  3:51 ` [PULL 6/8] iotests: Add new nbd-robustness test Eric Blake
2026-11-11  3:51 ` [PULL 7/8] nbd/server: Reject rather than assert on non-compliant NBD_OPT_SET_META_CONTEXT Eric Blake
2026-11-11  3:51 ` [PULL 8/8] iotests: Add another nbd-robustness test Eric Blake
2026-11-12 18:16 ` [PULL 0/8] NBD patches through 2026-11-10 Richard Henderson
2026-11-12 19:27   ` Eric Blake

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.