summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2026-09-07 13:33:02 +0100
committerPeter Maydell <peter.maydell@linaro.org>2026-09-07 13:33:02 +0100
commit61e537b7ef2f8b899827c72126cada6161e5803d (patch)
tree42ace690a58e7704b60f6a703aab5dfa406c6324
parentcacd3462963a0a4f5bab4263ce79c2aa4b32692d (diff)
parent0766e83d2f94285338e6c642b4b003ffafb142f4 (diff)
downloadqemu-61e537b7ef2f8b899827c72126cada6161e5803d.tar.gz
qemu-61e537b7ef2f8b899827c72126cada6161e5803d.zip
Merge tag 'misc-fixes-pull-request' of https://gitlab.com/berrange/qemu into staging
Merge crypto, I/O and misc fixes * Deprecated the AF_ALG crypto backend * Improve checkpatch output in CI jobs * Document security policy for uninitialized stack variables * Fix multiple denial of service flaws in websockets * Fix error handling in some x509 APIs # -----BEGIN PGP SIGNATURE----- # # iQIzBAABCAAdFiEEOSEivHoFu8YQee2OpU5XPKwd8GwFAmqel3UACgkQpU5XPKwd # 8Gz2UA//S261uNlByPmh6kiDLWy0o1pNOu3Xfr2LD//WMG7Uota5MBl3C09Jy1in # pxTIPGp6xIhxDFFRVTSG3qkezsVFMEHQf5fp29nYlk4Q0q+JO7on558Xn7o10+99 # jdgSWNB7FMeVHkknSyQuei4h6iIdtpJjtWcH/jABF80z4DGo27PKWpUQJKsCh0wZ # FiOkgEqk/YQBre0ziZ+tDxgNyDl25JWdMAsV3tRaXnkNvzXOqYXzr0M97jJO45yZ # Fti0Krg96dAFwT7F8boxP+5Rn1rQcfPrZwuXQg5wC1RyQiQqnmh2bhC4DbMWujGc # s7bo40BSmp/z4rhLw5zPHMaVwokls28Nk4ocEsAUhqejL+MntxEs7xSa23EVKO4F # mvevSz1mtLPL7fyXCCfRSL5AFy4kMdfCflhpYUpk15cBuXBY9hnzBNiq9kFtf4lI # heCdnxJka6WUQrxWaHT9Bs3FDdaQ4Cw+yXLHn9XNK65J5sF2yOKkhyXbnuiy87KO # dAdCb/sL3Zg0wFA8j8YkgygBizS3FU+X1tq3Rj0WrNUNWrbUkSamnS+foi+uRjI0 # kotXIrr3pgFXq4CaS+XyWvn1Xk0veh/xw2FIDYRN9l1BBYP6f/svcq8L0h423KGg # GZzmUCkG89+uUJ56fgih62G5AgHFAfTB1BKGGW5jcoY8QOJ4MR4= # =BVbi # -----END PGP SIGNATURE----- # gpg: Signature made Mon Sep 7 11:52:37 2026 BST # gpg: using RSA key 392122BC7A05BBC61079ED8EA54E573CAC1DF06C # gpg: Good signature from "Daniel P. Berrange <dan@berrange.com>" [full] # gpg: aka "Daniel P. Berrange <berrange@redhat.com>" [full] # Primary key fingerprint: DAF3 A6FD B26B 6291 2D0E 8E3F BE86 EBB4 1510 4FDF # Subkey fingerprint: 3921 22BC 7A05 BBC6 1079 ED8E A54E 573C AC1D F06C * tag 'misc-fixes-pull-request' of https://gitlab.com/berrange/qemu: docs/system/security: exclude uninitialized stack variables as bugs gitlab: use --emacs --quiet for checkpatch.pl instead of --terse crypto: deprecate the AF_ALG crypto backend tests/unit: cover blocked IO during the websock handshake io/channel-websock: do not lose QIO_CHANNEL_ERR_BLOCK while reading tests/unit: add websock handshake test io/channel-websock: handle a blocked write during the handshake io/channel-websock: send an HTTP 400 when the greeting has no space io/channel-socket: do not treat a zero length write as an error crypto/x509-utils: propagate the error crypto/x509-utils: don't double set errp crypto: Use g_autofree Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-rwxr-xr-x.gitlab-ci.d/check-patch.py6
-rw-r--r--crypto/block.c17
-rw-r--r--crypto/hmac-gcrypt.c12
-rw-r--r--crypto/hmac-glib.c10
-rw-r--r--crypto/ivgen-essiv.c15
-rw-r--r--crypto/ivgen.c6
-rw-r--r--crypto/secret_keyring.c5
-rw-r--r--crypto/x509-utils.c4
-rw-r--r--docs/about/deprecated.rst21
-rw-r--r--docs/system/security.rst10
-rw-r--r--io/channel-socket.c2
-rw-r--r--io/channel-websock.c10
-rw-r--r--meson.build6
-rw-r--r--tests/unit/meson.build1
-rw-r--r--tests/unit/test-io-channel-websock.c249
15 files changed, 326 insertions, 48 deletions
diff --git a/.gitlab-ci.d/check-patch.py b/.gitlab-ci.d/check-patch.py
index be13e6f77d..45be77295d 100755
--- a/.gitlab-ci.d/check-patch.py
+++ b/.gitlab-ci.d/check-patch.py
@@ -46,7 +46,11 @@ errors = False
print("\nChecking all commits since %s...\n" % ancestor, flush=True)
-ret = subprocess.run(["scripts/checkpatch.pl", "--terse", ancestor + "..."])
+# We don't want "noise" for clean patches, but do want to see
+# the full commit hash for each violation, along with the
+# offending patch content
+ret = subprocess.run(["scripts/checkpatch.pl", "--emacs", "--quiet",
+ ancestor + "..."])
if ret.returncode != 0:
print(" ❌ FAIL one or more commits failed scripts/checkpatch.pl")
diff --git a/crypto/block.c b/crypto/block.c
index 96c83e60b9..42558f3caf 100644
--- a/crypto/block.c
+++ b/crypto/block.c
@@ -55,7 +55,7 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
unsigned int flags,
Error **errp)
{
- QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+ g_autofree QCryptoBlock *block = g_new0(QCryptoBlock, 1);
qemu_mutex_init(&block->mutex);
@@ -65,7 +65,6 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
!qcrypto_block_drivers[options->format]) {
error_setg(errp, "Unsupported block driver %s",
QCryptoBlockFormat_str(options->format));
- g_free(block);
return NULL;
}
@@ -74,11 +73,10 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options,
if (block->driver->open(block, options, optprefix,
readfunc, opaque, flags, errp) < 0)
{
- g_free(block);
return NULL;
}
- return block;
+ return g_steal_pointer(&block);
}
@@ -90,7 +88,7 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
unsigned int flags,
Error **errp)
{
- QCryptoBlock *block = g_new0(QCryptoBlock, 1);
+ g_autofree QCryptoBlock *block = g_new0(QCryptoBlock, 1);
qemu_mutex_init(&block->mutex);
@@ -100,7 +98,6 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
!qcrypto_block_drivers[options->format]) {
error_setg(errp, "Unsupported block driver %s",
QCryptoBlockFormat_str(options->format));
- g_free(block);
return NULL;
}
@@ -109,11 +106,10 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options,
if (block->driver->create(block, options, optprefix, initfunc,
writefunc, opaque, errp) < 0) {
- g_free(block);
return NULL;
}
- return block;
+ return g_steal_pointer(&block);
}
@@ -185,17 +181,16 @@ int qcrypto_block_amend_options(QCryptoBlock *block,
QCryptoBlockInfo *qcrypto_block_get_info(QCryptoBlock *block,
Error **errp)
{
- QCryptoBlockInfo *info = g_new0(QCryptoBlockInfo, 1);
+ g_autofree QCryptoBlockInfo *info = g_new0(QCryptoBlockInfo, 1);
info->format = block->format;
if (block->driver->get_info &&
block->driver->get_info(block, info, errp) < 0) {
- g_free(info);
return NULL;
}
- return info;
+ return g_steal_pointer(&info);
}
diff --git a/crypto/hmac-gcrypt.c b/crypto/hmac-gcrypt.c
index e428d17479..44631fb348 100644
--- a/crypto/hmac-gcrypt.c
+++ b/crypto/hmac-gcrypt.c
@@ -50,7 +50,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoHmacGcrypt *ctx;
+ g_autofree QCryptoHmacGcrypt *ctx = NULL;
gcry_error_t err;
if (!qcrypto_hmac_supports(alg)) {
@@ -66,7 +66,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
if (err != 0) {
error_setg(errp, "Cannot initialize hmac: %s",
gcry_strerror(err));
- goto error;
+ return NULL;
}
err = gcry_mac_setkey(ctx->handle, (const void *)key, nkey);
@@ -74,14 +74,10 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
error_setg(errp, "Cannot set key: %s",
gcry_strerror(err));
gcry_mac_close(ctx->handle);
- goto error;
+ return NULL;
}
- return ctx;
-
-error:
- g_free(ctx);
- return NULL;
+ return g_steal_pointer(&ctx);
}
static void
diff --git a/crypto/hmac-glib.c b/crypto/hmac-glib.c
index b845133a05..1f17769c1c 100644
--- a/crypto/hmac-glib.c
+++ b/crypto/hmac-glib.c
@@ -46,7 +46,7 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoHmacGlib *ctx;
+ g_autofree QCryptoHmacGlib *ctx = NULL;
if (!qcrypto_hmac_supports(alg)) {
error_setg(errp, "Unsupported hmac algorithm %s",
@@ -60,14 +60,10 @@ void *qcrypto_hmac_ctx_new(QCryptoHashAlgo alg,
(const uint8_t *)key, nkey);
if (!ctx->ghmac) {
error_setg(errp, "Cannot initialize hmac and set key");
- goto error;
+ return NULL;
}
- return ctx;
-
-error:
- g_free(ctx);
- return NULL;
+ return g_steal_pointer(&ctx);
}
static void
diff --git a/crypto/ivgen-essiv.c b/crypto/ivgen-essiv.c
index 3d5a188795..d5fa269888 100644
--- a/crypto/ivgen-essiv.c
+++ b/crypto/ivgen-essiv.c
@@ -31,10 +31,10 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
const uint8_t *key, size_t nkey,
Error **errp)
{
- uint8_t *salt;
+ g_autofree uint8_t *salt = NULL;
size_t nhash;
size_t nsalt;
- QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
+ g_autofree QCryptoIVGenESSIV *essiv = g_new0(QCryptoIVGenESSIV, 1);
/* Not necessarily the same as nkey */
nsalt = qcrypto_cipher_get_key_len(ivgen->cipher);
@@ -46,8 +46,6 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
if (qcrypto_hash_bytes(ivgen->hash, (const gchar *)key, nkey,
&salt, &nhash,
errp) < 0) {
- g_free(essiv);
- g_free(salt);
return -1;
}
@@ -57,13 +55,10 @@ static int qcrypto_ivgen_essiv_init(QCryptoIVGen *ivgen,
salt, MIN(nhash, nsalt),
errp);
if (!essiv->cipher) {
- g_free(essiv);
- g_free(salt);
return -1;
}
- g_free(salt);
- ivgen->private = essiv;
+ ivgen->private = g_steal_pointer(&essiv);
return 0;
}
@@ -75,7 +70,7 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
{
QCryptoIVGenESSIV *essiv = ivgen->private;
size_t ndata = qcrypto_cipher_get_block_len(ivgen->cipher);
- uint8_t *data = g_new(uint8_t, ndata);
+ g_autofree uint8_t *data = g_new(uint8_t, ndata);
sector = cpu_to_le64(sector);
memcpy(data, (uint8_t *)&sector, MIN(sizeof(sector), ndata));
@@ -88,7 +83,6 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
data,
ndata,
errp) < 0) {
- g_free(data);
return -1;
}
@@ -99,7 +93,6 @@ static int qcrypto_ivgen_essiv_calculate(QCryptoIVGen *ivgen,
if (ndata < niv) {
memset(iv + ndata, 0, niv - ndata);
}
- g_free(data);
return 0;
}
diff --git a/crypto/ivgen.c b/crypto/ivgen.c
index 6b7d24d889..9f1f7d7dca 100644
--- a/crypto/ivgen.c
+++ b/crypto/ivgen.c
@@ -33,7 +33,7 @@ QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgo alg,
const uint8_t *key, size_t nkey,
Error **errp)
{
- QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
+ g_autofree QCryptoIVGen *ivgen = g_new0(QCryptoIVGen, 1);
ivgen->algorithm = alg;
ivgen->cipher = cipheralg;
@@ -51,16 +51,14 @@ QCryptoIVGen *qcrypto_ivgen_new(QCryptoIVGenAlgo alg,
break;
default:
error_setg(errp, "Unknown block IV generator algorithm %d", alg);
- g_free(ivgen);
return NULL;
}
if (ivgen->driver->init(ivgen, key, nkey, errp) < 0) {
- g_free(ivgen);
return NULL;
}
- return ivgen;
+ return g_steal_pointer(&ivgen);
}
diff --git a/crypto/secret_keyring.c b/crypto/secret_keyring.c
index 78d7f09b3b..3b332276ef 100644
--- a/crypto/secret_keyring.c
+++ b/crypto/secret_keyring.c
@@ -41,7 +41,7 @@ qcrypto_secret_keyring_load_data(QCryptoSecretCommon *sec_common,
Error **errp)
{
QCryptoSecretKeyring *secret = QCRYPTO_SECRET_KEYRING(sec_common);
- uint8_t *buffer = NULL;
+ g_autofree uint8_t *buffer = NULL;
long retcode;
*output = NULL;
@@ -61,12 +61,11 @@ qcrypto_secret_keyring_load_data(QCryptoSecretCommon *sec_common,
retcode = keyctl_read(secret->serial, buffer, retcode);
if (retcode < 0) {
- g_free(buffer);
goto keyctl_error;
}
*outputlen = retcode;
- *output = buffer;
+ *output = g_steal_pointer(&buffer);
return;
keyctl_error:
diff --git a/crypto/x509-utils.c b/crypto/x509-utils.c
index e4767f9838..edcc44de80 100644
--- a/crypto/x509-utils.c
+++ b/crypto/x509-utils.c
@@ -319,13 +319,15 @@ int qcrypto_x509_check_ecc_curve_p521(uint8_t *cert, size_t size, Error **errp)
int curve_id;
algo = qcrypto_x509_get_pk_algorithm(cert, size, errp);
+ if (algo < 0) {
+ return -1;
+ }
if (algo != GNUTLS_PK_ECDSA) {
return 0;
}
curve_id = qcrypto_x509_get_ecc_curve(cert, size, errp);
if (curve_id == -1) {
- error_setg(errp, "Failed to get ECC curve");
return -1;
}
diff --git a/docs/about/deprecated.rst b/docs/about/deprecated.rst
index 05e4ce8cf1..98c32991c9 100644
--- a/docs/about/deprecated.rst
+++ b/docs/about/deprecated.rst
@@ -434,6 +434,27 @@ ABI is long-obsolete. We are therefore deprecating both OABI support
and NWFPE emulation, and they will be removed in a future QEMU
release.
+Build features
+--------------
+
+Crypto AF_ALG backend (since 11.2)
+----------------------------------
+
+The use of the AF_ALG backend for cryptography has been deprecated
+with no replacement.
+
+The AF_ALG interface is deprecated by Linux 7.2 and all support
+for hardware accelerators has been removed. It will thus always be
+slower than userspace crypto due to the overhead of copying data
+to kernel space. The GNUTLS, Nettle and GCrypt libraries supported
+by QEMU all include a variety of hardware optimized crypto
+implementations which should suffice for typical needs.
+
+For the virtio-crypto device, the 'cryptodev-backend-lkcf' backend
+can offload some operations to the kernel via the keyctl syscall,
+and the 'cryptodev-vhost-user' backend can offload the device
+backend to an external process which can integrate with crypto
+accelerators.
Backwards compatibility
-----------------------
diff --git a/docs/system/security.rst b/docs/system/security.rst
index af626a4230..8c42d1a6d8 100644
--- a/docs/system/security.rst
+++ b/docs/system/security.rst
@@ -143,6 +143,16 @@ an issue as a normal bug.
which case plain manipulation of the stream is not considered as
an attack vector.
+* **uninitialized stack variables**. If the bug scenario relies on
+ undefined behaviour from stack variables that lack explicit
+ initialization, it will not usually be considered a security flaw.
+ The build system adds '-ftrivial-auto-var-init=zero', which is
+ available in both the supported compilers (GCC and CLang) and
+ ensures all stack variables have implicit zero-initializers.
+ This eliminates undefined behaviour and usually gives the
+ correct desired initialization value, eliminating most of the
+ bug scenarios wrt uninitialized stack variables.
+
* **low severity impact**. As a catch all rule, issues which
are judged to have a "low" severity impact on the system will
usually not justify handling as security bugs, nor assignment
diff --git a/io/channel-socket.c b/io/channel-socket.c
index 12773b832c..7920cee639 100644
--- a/io/channel-socket.c
+++ b/io/channel-socket.c
@@ -667,7 +667,7 @@ static ssize_t qio_channel_socket_writev(QIOChannel *ioc,
retry:
ret = sendmsg(sioc->fd, &msg, sflags);
- if (ret <= 0) {
+ if (ret < 0) {
switch (errno) {
case EAGAIN:
return QIO_CHANNEL_ERR_BLOCK;
diff --git a/io/channel-websock.c b/io/channel-websock.c
index 1929abf56a..461abcae48 100644
--- a/io/channel-websock.c
+++ b/io/channel-websock.c
@@ -230,7 +230,7 @@ qio_channel_websock_extract_headers(QIOChannelWebsock *ioc,
tmp = strchr(buffer, ' ');
if (!tmp) {
error_setg(errp, "Missing HTTP path delimiter");
- return 0;
+ goto bad_request;
}
*tmp = '\0';
@@ -492,6 +492,9 @@ static int qio_channel_websock_handshake_read(QIOChannelWebsock *ioc,
buffer_reserve(&ioc->encinput, want);
ret = qio_channel_read(ioc->master,
(char *)buffer_end(&ioc->encinput), want, errp);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ return 0;
+ }
if (ret < 0) {
return -1;
}
@@ -562,6 +565,11 @@ static gboolean qio_channel_websock_handshake_send(QIOChannel *ioc,
wioc->encoutput.offset,
&err);
+ if (ret == QIO_CHANNEL_ERR_BLOCK) {
+ /* Socket buffer is full, the G_IO_OUT watch stays armed */
+ return TRUE;
+ }
+
if (ret < 0) {
trace_qio_channel_websock_handshake_fail(ioc, error_get_pretty(err));
qio_task_set_error(task, err);
diff --git a/meson.build b/meson.build
index 2a022c4d85..ec9b05414a 100644
--- a/meson.build
+++ b/meson.build
@@ -5059,3 +5059,9 @@ if not actually_reloc and (host_os == 'windows' or get_option('relocatable'))
message('QEMU will have to be installed under ' + get_option('prefix') + '.')
message('Use --disable-relocatable to remove this warning.')
endif
+
+if get_option('crypto_afalg').enabled()
+ warning('Use of the AF_ALG crypto backend is deprecated, ' +
+ 'since Linux 7.2 has deprecated the AF_ALG interface ' +
+ 'and removed its ability to use hardware accelerators.')
+endif
diff --git a/tests/unit/meson.build b/tests/unit/meson.build
index 3a9866c1f2..e47bc7225a 100644
--- a/tests/unit/meson.build
+++ b/tests/unit/meson.build
@@ -115,6 +115,7 @@ if have_block
endif
if host_os != 'windows'
tests += {
+ 'test-io-channel-websock': [io],
'test-image-locking': [testblock],
'test-nested-aio-poll': [],
}
diff --git a/tests/unit/test-io-channel-websock.c b/tests/unit/test-io-channel-websock.c
new file mode 100644
index 0000000000..88da24f993
--- /dev/null
+++ b/tests/unit/test-io-channel-websock.c
@@ -0,0 +1,249 @@
+/*
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * QEMU I/O channel websock test
+ *
+ * Copyright (c) 2026 Virtuozzo International GmbH
+ */
+
+#include "qemu/osdep.h"
+#include "io/channel-websock.h"
+#include "io/channel-socket.h"
+#include "qapi/error.h"
+#include "qemu/module.h"
+#include "qemu/sockets.h"
+#include "qom/object.h"
+
+#define TYPE_QIO_CHANNEL_STALL "qio-channel-stall"
+OBJECT_DECLARE_SIMPLE_TYPE(QIOChannelStall, QIO_CHANNEL_STALL)
+
+/*
+ * Reports QIO_CHANNEL_ERR_BLOCK for the first @rstalls reads and @wstalls
+ * writes, the way a TLS channel does when a record arrives split across TCP
+ * segments or the socket cannot take the whole reply at once.
+ */
+struct QIOChannelStall {
+ QIOChannel parent;
+ QIOChannel *master;
+ unsigned rstalls;
+ unsigned wstalls;
+};
+
+static ssize_t qio_channel_stall_readv(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ int **fds,
+ size_t *nfds,
+ int flags,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ if (sioc->rstalls) {
+ sioc->rstalls--;
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ return qio_channel_readv_full(sioc->master, iov, niov, fds, nfds,
+ flags, errp);
+}
+
+static ssize_t qio_channel_stall_writev(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ int *fds,
+ size_t nfds,
+ int flags,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ if (sioc->wstalls) {
+ sioc->wstalls--;
+ return QIO_CHANNEL_ERR_BLOCK;
+ }
+ return qio_channel_writev_full(sioc->master, iov, niov, fds, nfds,
+ flags, errp);
+}
+
+static int qio_channel_stall_set_blocking(QIOChannel *ioc, bool enabled,
+ Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_set_blocking(sioc->master, enabled, errp) ? 0 : -1;
+}
+
+static int qio_channel_stall_close(QIOChannel *ioc, Error **errp)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_close(sioc->master, errp);
+}
+
+static GSource *qio_channel_stall_create_watch(QIOChannel *ioc,
+ GIOCondition condition)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(ioc);
+
+ return qio_channel_create_watch(sioc->master, condition);
+}
+
+static void qio_channel_stall_finalize(Object *obj)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(obj);
+
+ object_unref(OBJECT(sioc->master));
+}
+
+static void qio_channel_stall_class_init(ObjectClass *klass,
+ const void *class_data G_GNUC_UNUSED)
+{
+ QIOChannelClass *ioc_klass = QIO_CHANNEL_CLASS(klass);
+
+ ioc_klass->io_writev = qio_channel_stall_writev;
+ ioc_klass->io_readv = qio_channel_stall_readv;
+ ioc_klass->io_set_blocking = qio_channel_stall_set_blocking;
+ ioc_klass->io_close = qio_channel_stall_close;
+ ioc_klass->io_create_watch = qio_channel_stall_create_watch;
+}
+
+static const TypeInfo qio_channel_stall_info = {
+ .parent = TYPE_QIO_CHANNEL,
+ .name = TYPE_QIO_CHANNEL_STALL,
+ .instance_size = sizeof(QIOChannelStall),
+ .instance_finalize = qio_channel_stall_finalize,
+ .class_init = qio_channel_stall_class_init,
+};
+
+static QIOChannelStall *qio_channel_stall_new(QIOChannel *master,
+ unsigned rstalls,
+ unsigned wstalls)
+{
+ QIOChannelStall *sioc = QIO_CHANNEL_STALL(
+ object_new(TYPE_QIO_CHANNEL_STALL));
+
+ object_ref(OBJECT(master));
+ sioc->master = master;
+ sioc->rstalls = rstalls;
+ sioc->wstalls = wstalls;
+
+ return sioc;
+}
+
+typedef struct {
+ bool finished;
+ bool failed;
+} QIOChannelWebsockHandshake;
+
+static void test_websock_handshake_done(QIOTask *task, gpointer opaque)
+{
+ QIOChannelWebsockHandshake *res = opaque;
+
+ res->finished = true;
+ res->failed = qio_task_propagate_error(task, NULL);
+}
+
+/*
+ * Drives a server-side handshake against @request and returns whatever
+ * the server wrote back, NUL terminated. The handshake is expected to
+ * fail; the point of the test is the HTTP response that goes with it.
+ */
+static char *test_websock_handshake_reply(const char *request,
+ unsigned rstalls, unsigned wstalls)
+{
+ QIOChannelWebsockHandshake res = { false, false };
+ QIOChannelSocket *cli, *srv;
+ QIOChannelStall *stall;
+ QIOChannelWebsock *wioc;
+ GMainContext *mainloop;
+ int channel[2];
+ char *reply;
+ ssize_t got;
+
+ g_assert(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, channel) == 0);
+
+ cli = qio_channel_socket_new_fd(channel[0], &error_abort);
+ srv = qio_channel_socket_new_fd(channel[1], &error_abort);
+ qio_channel_set_blocking(QIO_CHANNEL(srv), false, &error_abort);
+ qio_channel_set_blocking(QIO_CHANNEL(cli), false, &error_abort);
+
+ stall = qio_channel_stall_new(QIO_CHANNEL(srv), rstalls, wstalls);
+ wioc = qio_channel_websock_new_server(QIO_CHANNEL(stall));
+ qio_channel_websock_handshake(wioc, test_websock_handshake_done,
+ &res, NULL);
+
+ qio_channel_write_all(QIO_CHANNEL(cli), request, strlen(request),
+ &error_abort);
+
+ mainloop = g_main_context_default();
+ while (!res.finished) {
+ g_main_context_iteration(mainloop, TRUE);
+ }
+ g_assert(res.failed);
+
+ reply = g_malloc0(1024);
+ got = qio_channel_read(QIO_CHANNEL(cli), reply, 1023, &error_abort);
+ if (got > 0) {
+ reply[got] = '\0';
+ }
+
+ object_unref(OBJECT(wioc));
+ object_unref(OBJECT(stall));
+ object_unref(OBJECT(srv));
+ object_unref(OBJECT(cli));
+
+ return reply;
+}
+
+static void test_websock_bad_request(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 0, 0);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+static void test_websock_stalled_read(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 1, 0);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+static void test_websock_stalled_write(const void *opaque)
+{
+ const char *request = opaque;
+ g_autofree char *reply = test_websock_handshake_reply(request, 0, 1);
+
+ g_assert_true(g_str_has_prefix(reply, "HTTP/1.1 400 Bad Request\r\n"));
+}
+
+int main(int argc, char **argv)
+{
+ module_call_init(MODULE_INIT_QOM);
+ type_register_static(&qio_channel_stall_info);
+ g_test_init(&argc, &argv, NULL);
+
+#define TEST_BAD_REQUEST(name, request) \
+ g_test_add_data_func("/io/channel/websock/bad-request/" name, \
+ request, test_websock_bad_request)
+
+ /*
+ * A greeting with no space at all used to leave the response buffer
+ * empty, which drove the handshake into a zero length write.
+ */
+ TEST_BAD_REQUEST("no-space", "stats\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("method-only", "GET\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("no-version", "GET /\r\nx\r\n\r\n");
+ TEST_BAD_REQUEST("bad-method", "POST / HTTP/1.1\r\nx: y\r\n\r\n");
+ TEST_BAD_REQUEST("bad-version", "GET / HTTP/1.0\r\nx: y\r\n\r\n");
+
+ /* A read which blocks before any header arrives is not a fatal error. */
+ g_test_add_data_func("/io/channel/websock/stalled-read",
+ "stats\r\nx\r\n\r\n", test_websock_stalled_read);
+ g_test_add_data_func("/io/channel/websock/stalled-write",
+ "stats\r\nx\r\n\r\n", test_websock_stalled_write);
+
+ return g_test_run();
+}