A malicious client can attempt to connect to an NBD server, and then
intentionally not progress in the handshake. This can form a resource
leak for as long as the client wants, when the client does not catch
our attention by failing to provide TLS credentials; although it can
be bounded by max-connections per NBD server that does not use the
default of unlimited. Better is to forcefully disconnect any
remaining client sockets when the NBD server is shut down. While less
severe than the issue fixed in the previous patch, this can still be
considered defense against a potential denial of service attack, so it
is still categorized under the same CVE.
Signed-off-by: Eric Blake <eblake@redhat.com>
---
I do not know if I need to worry about multi-threaded access (is it
possible that more than one client trying to connect simultaneously
means that I need to access nbd_server->conns atomically)?
blockdev-nbd.c | 22 ++++++++++++++++++++++
1 file changed, 22 insertions(+)
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index 1ddcbd7b247..9bbd86ebc31 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -21,12 +21,18 @@
#include "io/channel-socket.h"
#include "io/net-listener.h"
+typedef struct NBDConn {
+ QIOChannelSocket *cioc;
+ QSLIST_ENTRY(NBDConn) next;
+} NBDConn;
+
typedef struct NBDServerData {
QIONetListener *listener;
QCryptoTLSCreds *tlscreds;
char *tlsauthz;
uint32_t max_connections;
uint32_t connections;
+ QSLIST_HEAD(, NBDConn) conns;
} NBDServerData;
static NBDServerData *nbd_server;
@@ -65,8 +71,13 @@ static void nbd_blockdev_client_closed(NBDClient *client, uint32_t cookie,
static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
gpointer opaque)
{
+ NBDConn *conn = g_new0(NBDConn, 1);
+
assert(nbd_server);
nbd_server->connections++;
+ object_ref(OBJECT(cioc));
+ conn->cioc = cioc;
+ QSLIST_INSERT_HEAD(&nbd_server->conns, conn, next);
nbd_update_server_watch(nbd_server);
qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
@@ -89,8 +100,21 @@ static void nbd_server_free(NBDServerData *server)
return;
}
+ /*
+ * Forcefully close the listener socket, and any clients that have
+ * not yet disconnected on their own.
+ */
qio_net_listener_disconnect(server->listener);
object_unref(OBJECT(server->listener));
+ while (!QSLIST_EMPTY(&server->conns)) {
+ NBDConn *conn = QSLIST_FIRST(&server->conns);
+
+ qio_channel_close(QIO_CHANNEL(conn->cioc), NULL);
+ object_unref(OBJECT(conn->cioc));
+ QSLIST_REMOVE_HEAD(&server->conns, next);
+ g_free(conn);
+ }
if (server->tlscreds) {
object_unref(OBJECT(server->tlscreds));
}
--
2.45.2