Commit e682c0e570 for qemu.org

commit e682c0e570179bd7fa01ea896358a8cdad196a3b
Author: Eric Blake <eblake@redhat.com>
Date:   Tue Nov 10 09:54:02 2026 -0600

    iotests: Add another nbd-robustness test

    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>

diff --git a/tests/qemu-iotests/tests/nbd-robustness b/tests/qemu-iotests/tests/nbd-robustness
index fe61a83925..410dfca04e 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 ae1213e6f8..fbc63e62f8 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