diff options
| -rwxr-xr-x | .gitlab-ci.d/check-patch.py | 6 | ||||
| -rw-r--r-- | crypto/block.c | 17 | ||||
| -rw-r--r-- | crypto/hmac-gcrypt.c | 12 | ||||
| -rw-r--r-- | crypto/hmac-glib.c | 10 | ||||
| -rw-r--r-- | crypto/ivgen-essiv.c | 15 | ||||
| -rw-r--r-- | crypto/ivgen.c | 6 | ||||
| -rw-r--r-- | crypto/secret_keyring.c | 5 | ||||
| -rw-r--r-- | crypto/x509-utils.c | 4 | ||||
| -rw-r--r-- | docs/about/deprecated.rst | 21 | ||||
| -rw-r--r-- | docs/system/security.rst | 10 | ||||
| -rw-r--r-- | io/channel-socket.c | 2 | ||||
| -rw-r--r-- | io/channel-websock.c | 10 | ||||
| -rw-r--r-- | meson.build | 6 | ||||
| -rw-r--r-- | tests/unit/meson.build | 1 | ||||
| -rw-r--r-- | tests/unit/test-io-channel-websock.c | 249 |
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 *)§or, 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(); +} |
