NBD patches for 2024-08-08

- plug CVE-2024-7409, a DoS attack exploiting nbd-server-stop
 -----BEGIN PGP SIGNATURE-----
 
 iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAma1PVEACgkQp6FrSiUn
 Q2qdHQf/dMydqNcPYnwEI238APyljpNvHNq6p9TYb0l5aVWisXHRlhFWM117hH7T
 Aq2KUgS5ppiEpw8mxa6/OaDa74VpMGyEPgn9w6o7T1xjVBVzpMxOKp5wFa8uICLj
 mFMYXtj9i0Rb+z0iZ+X+CqIV2Wy/FyV00Wr9T4HW94IV/9EK1sWvZvfyGWyxYyBZ
 XKTQV1Co3HYX8gfq7E88SgS064DnHjtRy2no4lwNFkBbVQCSbqwbK63TRPi7kEyC
 DmSLdHCdsD7Ev9kMZ6uNJS5T/9t7hjO5mWJckLt/cXOjHgL7GkoisLH8/nGjVkyc
 3SUGjMn4TlzqMU99STRP+a48TLCVhA==
 =kDut
 -----END PGP SIGNATURE-----

Merge tag 'pull-nbd-2024-08-08' of https://repo.or.cz/qemu/ericb into staging

NBD patches for 2024-08-08

- plug CVE-2024-7409, a DoS attack exploiting nbd-server-stop

# -----BEGIN PGP SIGNATURE-----
#
# iQEzBAABCAAdFiEEccLMIrHEYCkn0vOqp6FrSiUnQ2oFAma1PVEACgkQp6FrSiUn
# Q2qdHQf/dMydqNcPYnwEI238APyljpNvHNq6p9TYb0l5aVWisXHRlhFWM117hH7T
# Aq2KUgS5ppiEpw8mxa6/OaDa74VpMGyEPgn9w6o7T1xjVBVzpMxOKp5wFa8uICLj
# mFMYXtj9i0Rb+z0iZ+X+CqIV2Wy/FyV00Wr9T4HW94IV/9EK1sWvZvfyGWyxYyBZ
# XKTQV1Co3HYX8gfq7E88SgS064DnHjtRy2no4lwNFkBbVQCSbqwbK63TRPi7kEyC
# DmSLdHCdsD7Ev9kMZ6uNJS5T/9t7hjO5mWJckLt/cXOjHgL7GkoisLH8/nGjVkyc
# 3SUGjMn4TlzqMU99STRP+a48TLCVhA==
# =kDut
# -----END PGP SIGNATURE-----
# gpg: Signature made Fri 09 Aug 2024 07:49:05 AM AEST
# gpg:                using RSA key 71C2CC22B1C4602927D2F3AAA7A16B4A2527436A
# gpg: Good signature from "Eric Blake <eblake@redhat.com>" [full]
# gpg:                 aka "Eric Blake (Free Software Programmer) <ebb9@byu.net>" [full]
# gpg:                 aka "[jpeg image of size 6874]" [full]

* tag 'pull-nbd-2024-08-08' of https://repo.or.cz/qemu/ericb:
  nbd/server: CVE-2024-7409: Close stray clients at server-stop
  nbd/server: CVE-2024-7409: Drop non-negotiating clients
  nbd/server: CVE-2024-7409: Cap default max-connections to 100
  nbd/server: Plumb in new args to nbd_client_add()
  nbd: Minor style and typo fixes

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
Richard Henderson 2024-08-09 08:40:36 +10:00
commit 0f397dcfec
7 changed files with 116 additions and 12 deletions

View File

@ -402,7 +402,8 @@ void hmp_nbd_server_start(Monitor *mon, const QDict *qdict)
goto exit;
}
nbd_server_start(addr, NULL, NULL, 0, &local_err);
nbd_server_start(addr, NULL, NULL, NBD_DEFAULT_MAX_CONNECTIONS,
&local_err);
qapi_free_SocketAddress(addr);
if (local_err != NULL) {
goto exit;

View File

@ -21,12 +21,18 @@
#include "io/channel-socket.h"
#include "io/net-listener.h"
typedef struct NBDConn {
QIOChannelSocket *cioc;
QLIST_ENTRY(NBDConn) next;
} NBDConn;
typedef struct NBDServerData {
QIONetListener *listener;
QCryptoTLSCreds *tlscreds;
char *tlsauthz;
uint32_t max_connections;
uint32_t connections;
QLIST_HEAD(, NBDConn) conns;
} NBDServerData;
static NBDServerData *nbd_server;
@ -51,6 +57,14 @@ int nbd_server_max_connections(void)
static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
{
NBDConn *conn = nbd_client_owner(client);
assert(qemu_in_main_thread() && nbd_server);
object_unref(OBJECT(conn->cioc));
QLIST_REMOVE(conn, next);
g_free(conn);
nbd_client_put(client);
assert(nbd_server->connections > 0);
nbd_server->connections--;
@ -60,12 +74,20 @@ static void nbd_blockdev_client_closed(NBDClient *client, bool ignored)
static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
gpointer opaque)
{
NBDConn *conn = g_new0(NBDConn, 1);
assert(qemu_in_main_thread() && nbd_server);
nbd_server->connections++;
object_ref(OBJECT(cioc));
conn->cioc = cioc;
QLIST_INSERT_HEAD(&nbd_server->conns, conn, next);
nbd_update_server_watch(nbd_server);
qio_channel_set_name(QIO_CHANNEL(cioc), "nbd-server");
nbd_client_new(cioc, nbd_server->tlscreds, nbd_server->tlsauthz,
nbd_blockdev_client_closed);
/* TODO - expose handshake timeout as QMP option */
nbd_client_new(cioc, NBD_DEFAULT_HANDSHAKE_MAX_SECS,
nbd_server->tlscreds, nbd_server->tlsauthz,
nbd_blockdev_client_closed, conn);
}
static void nbd_update_server_watch(NBDServerData *s)
@ -79,12 +101,25 @@ static void nbd_update_server_watch(NBDServerData *s)
static void nbd_server_free(NBDServerData *server)
{
NBDConn *conn, *tmp;
if (!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));
QLIST_FOREACH_SAFE(conn, &server->conns, next, tmp) {
qio_channel_shutdown(QIO_CHANNEL(conn->cioc), QIO_CHANNEL_SHUTDOWN_BOTH,
NULL);
}
AIO_WAIT_WHILE_UNLOCKED(NULL, server->connections > 0);
if (server->tlscreds) {
object_unref(OBJECT(server->tlscreds));
}
@ -168,6 +203,10 @@ void nbd_server_start(SocketAddress *addr, const char *tls_creds,
void nbd_server_start_options(NbdServerOptions *arg, Error **errp)
{
if (!arg->has_max_connections) {
arg->max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
}
nbd_server_start(arg->addr, arg->tls_creds, arg->tls_authz,
arg->max_connections, errp);
}
@ -180,6 +219,10 @@ void qmp_nbd_server_start(SocketAddressLegacy *addr,
{
SocketAddress *addr_flat = socket_address_flatten(addr);
if (!has_max_connections) {
max_connections = NBD_DEFAULT_MAX_CONNECTIONS;
}
nbd_server_start(addr_flat, tls_creds, tls_authz, max_connections, errp);
qapi_free_SocketAddress(addr_flat);
}

View File

@ -33,6 +33,19 @@ typedef struct NBDMetaContexts NBDMetaContexts;
extern const BlockExportDriver blk_exp_nbd;
/*
* NBD_DEFAULT_HANDSHAKE_MAX_SECS: Number of seconds in which client must
* succeed at NBD_OPT_GO before being forcefully dropped as too slow.
*/
#define NBD_DEFAULT_HANDSHAKE_MAX_SECS 10
/*
* NBD_DEFAULT_MAX_CONNECTIONS: Number of client sockets to allow at
* once; must be large enough to allow a MULTI_CONN-aware client like
* nbdcopy to create its typical number of 8-16 sockets.
*/
#define NBD_DEFAULT_MAX_CONNECTIONS 100
/* Handshake phase structs - this struct is passed on the wire */
typedef struct NBDOption {
@ -403,9 +416,12 @@ AioContext *nbd_export_aio_context(NBDExport *exp);
NBDExport *nbd_export_find(const char *name);
void nbd_client_new(QIOChannelSocket *sioc,
uint32_t handshake_max_secs,
QCryptoTLSCreds *tlscreds,
const char *tlsauthz,
void (*close_fn)(NBDClient *, bool));
void (*close_fn)(NBDClient *, bool),
void *owner);
void *nbd_client_owner(NBDClient *client);
void nbd_client_get(NBDClient *client);
void nbd_client_put(NBDClient *client);

View File

@ -124,12 +124,14 @@ struct NBDMetaContexts {
struct NBDClient {
int refcount; /* atomic */
void (*close_fn)(NBDClient *client, bool negotiated);
void *owner;
QemuMutex lock;
NBDExport *exp;
QCryptoTLSCreds *tlscreds;
char *tlsauthz;
uint32_t handshake_max_secs;
QIOChannelSocket *sioc; /* The underlying data channel */
QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */
@ -1972,7 +1974,7 @@ static void nbd_export_request_shutdown(BlockExport *blk_exp)
blk_exp_ref(&exp->common);
/*
* TODO: Should we expand QMP NbdServerRemoveNode enum to allow a
* TODO: Should we expand QMP BlockExportRemoveMode enum to allow a
* close mode that stops advertising the export to new clients but
* still permits existing clients to run to completion? Because of
* that possibility, nbd_export_close() can be called more than
@ -3184,35 +3186,65 @@ static void nbd_client_receive_next_request(NBDClient *client)
}
}
static void nbd_handshake_timer_cb(void *opaque)
{
QIOChannel *ioc = opaque;
trace_nbd_handshake_timer_cb();
qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
}
static coroutine_fn void nbd_co_client_start(void *opaque)
{
NBDClient *client = opaque;
Error *local_err = NULL;
QEMUTimer *handshake_timer = NULL;
qemu_co_mutex_init(&client->send_lock);
/*
* Create a timer to bound the time spent in negotiation. If the
* timer expires, it is likely nbd_negotiate will fail because the
* socket was shutdown.
*/
if (client->handshake_max_secs > 0) {
handshake_timer = aio_timer_new(qemu_get_aio_context(),
QEMU_CLOCK_REALTIME,
SCALE_NS,
nbd_handshake_timer_cb,
client->sioc);
timer_mod(handshake_timer,
qemu_clock_get_ns(QEMU_CLOCK_REALTIME) +
client->handshake_max_secs * NANOSECONDS_PER_SECOND);
}
if (nbd_negotiate(client, &local_err)) {
if (local_err) {
error_report_err(local_err);
}
timer_free(handshake_timer);
client_close(client, false);
return;
}
timer_free(handshake_timer);
WITH_QEMU_LOCK_GUARD(&client->lock) {
nbd_client_receive_next_request(client);
}
}
/*
* Create a new client listener using the given channel @sioc.
* Create a new client listener using the given channel @sioc and @owner.
* Begin servicing it in a coroutine. When the connection closes, call
* @close_fn with an indication of whether the client completed negotiation.
* @close_fn with an indication of whether the client completed negotiation
* within @handshake_max_secs seconds (0 for unbounded).
*/
void nbd_client_new(QIOChannelSocket *sioc,
uint32_t handshake_max_secs,
QCryptoTLSCreds *tlscreds,
const char *tlsauthz,
void (*close_fn)(NBDClient *, bool))
void (*close_fn)(NBDClient *, bool),
void *owner)
{
NBDClient *client;
Coroutine *co;
@ -3225,13 +3257,21 @@ void nbd_client_new(QIOChannelSocket *sioc,
object_ref(OBJECT(client->tlscreds));
}
client->tlsauthz = g_strdup(tlsauthz);
client->handshake_max_secs = handshake_max_secs;
client->sioc = sioc;
qio_channel_set_delay(QIO_CHANNEL(sioc), false);
object_ref(OBJECT(client->sioc));
client->ioc = QIO_CHANNEL(sioc);
object_ref(OBJECT(client->ioc));
client->close_fn = close_fn;
client->owner = owner;
co = qemu_coroutine_create(nbd_co_client_start, client);
qemu_coroutine_enter(co);
}
void *
nbd_client_owner(NBDClient *client)
{
return client->owner;
}

View File

@ -76,6 +76,7 @@ nbd_co_receive_request_payload_received(uint64_t cookie, uint64_t len) "Payload
nbd_co_receive_ext_payload_compliance(uint64_t from, uint64_t len) "client sent non-compliant write without payload flag: from=0x%" PRIx64 ", len=0x%" PRIx64
nbd_co_receive_align_compliance(const char *op, uint64_t from, uint64_t len, uint32_t align) "client sent non-compliant unaligned %s request: from=0x%" PRIx64 ", len=0x%" PRIx64 ", align=0x%" PRIx32
nbd_trip(void) "Reading request"
nbd_handshake_timer_cb(void) "client took too long to negotiate"
# client-connection.c
nbd_connect_thread_sleep(uint64_t timeout) "timeout %" PRIu64

View File

@ -28,7 +28,7 @@
# @max-connections: The maximum number of connections to allow at the
# same time, 0 for unlimited. Setting this to 1 also stops the
# server from advertising multiple client support (since 5.2;
# default: 0)
# default: 100)
#
# Since: 4.2
##
@ -63,7 +63,7 @@
# @max-connections: The maximum number of connections to allow at the
# same time, 0 for unlimited. Setting this to 1 also stops the
# server from advertising multiple client support (since 5.2;
# default: 0).
# default: 100).
#
# Errors:
# - if the server is already running

View File

@ -390,7 +390,9 @@ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
nb_fds++;
nbd_update_server_watch();
nbd_client_new(cioc, tlscreds, tlsauthz, nbd_client_closed);
/* TODO - expose handshake timeout as command line option */
nbd_client_new(cioc, NBD_DEFAULT_HANDSHAKE_MAX_SECS,
tlscreds, tlsauthz, nbd_client_closed, NULL);
}
static void nbd_update_server_watch(void)
@ -588,7 +590,8 @@ int main(int argc, char **argv)
pthread_t client_thread;
const char *fmt = NULL;
Error *local_err = NULL;
BlockdevDetectZeroesOptions detect_zeroes = BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
BlockdevDetectZeroesOptions detect_zeroes =
BLOCKDEV_DETECT_ZEROES_OPTIONS_OFF;
QDict *options = NULL;
const char *export_name = NULL; /* defaults to "" later for server mode */
const char *export_description = NULL;