mirror of
https://gitlab.com/qemu-project/qemu.git
synced 2024-09-10 15:56:45 +03:00
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:
commit
0f397dcfec
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
48
nbd/server.c
48
nbd/server.c
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user