* [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1
@ 2024-08-08 21:53 Eric Blake
2024-08-08 21:53 ` [PULL 1/5] nbd: Minor style and typo fixes Eric Blake
` (5 more replies)
0 siblings, 6 replies; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
The following changes since commit 75c7f574035622798e9361a942bdfbb0af930f0e:
Merge tag 'pull-hex-20240807' of https://github.com/quic/qemu into staging (2024-08-08 16:08:18 +1000)
are available in the Git repository at:
https://repo.or.cz/qemu/ericb.git tags/pull-nbd-2024-08-08
for you to fetch changes up to 3e7ef738c8462c45043a1d39f702a0990406a3b3:
nbd/server: CVE-2024-7409: Close stray clients at server-stop (2024-08-08 16:34:04 -0500)
----------------------------------------------------------------
NBD patches for 2024-08-08
- plug CVE-2024-7409, a DoS attack exploiting nbd-server-stop
----------------------------------------------------------------
Eric Blake (5):
nbd: Minor style and typo fixes
nbd/server: Plumb in new args to nbd_client_add()
nbd/server: CVE-2024-7409: Cap default max-connections to 100
nbd/server: CVE-2024-7409: Drop non-negotiating clients
nbd/server: CVE-2024-7409: Close stray clients at server-stop
qapi/block-export.json | 4 ++--
include/block/nbd.h | 18 +++++++++++++++-
block/monitor/block-hmp-cmds.c | 3 ++-
blockdev-nbd.c | 47 +++++++++++++++++++++++++++++++++++++++--
nbd/server.c | 48 ++++++++++++++++++++++++++++++++++++++----
qemu-nbd.c | 7 ++++--
nbd/trace-events | 1 +
7 files changed, 116 insertions(+), 12 deletions(-)
--
2.46.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PULL 1/5] nbd: Minor style and typo fixes
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
@ 2024-08-08 21:53 ` Eric Blake
2024-08-08 21:53 ` [PULL 2/5] nbd/server: Plumb in new args to nbd_client_add() Eric Blake
` (4 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
Cc: Daniel P. Berrangé, Vladimir Sementsov-Ogievskiy,
open list:Network Block Dev...
Touch up a comment with the wrong type name, and an over-long line,
both noticed while working on upcoming patches.
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20240807174943.771624-10-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
nbd/server.c | 2 +-
qemu-nbd.c | 3 ++-
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/nbd/server.c b/nbd/server.c
index 892797bb111..ecd9366ba64 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -1972,7 +1972,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
diff --git a/qemu-nbd.c b/qemu-nbd.c
index d7b3ccab21c..8e104ef22c3 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -588,7 +588,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;
--
2.46.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 2/5] nbd/server: Plumb in new args to nbd_client_add()
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
2024-08-08 21:53 ` [PULL 1/5] nbd: Minor style and typo fixes Eric Blake
@ 2024-08-08 21:53 ` Eric Blake
2024-08-08 21:53 ` [PULL 3/5] nbd/server: CVE-2024-7409: Cap default max-connections to 100 Eric Blake
` (3 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
Cc: Vladimir Sementsov-Ogievskiy, Daniel P. Berrangé, Kevin Wolf,
Hanna Reitz, open list:Network Block Dev...
Upcoming patches to fix a CVE need to track an opaque pointer passed
in by the owner of a client object, as well as request for a time
limit on how fast negotiation must complete. Prepare for that by
changing the signature of nbd_client_new() and adding an accessor to
get at the opaque pointer, although for now the two servers
(qemu-nbd.c and blockdev-nbd.c) do not change behavior even though
they pass in a new default timeout value.
Suggested-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20240807174943.771624-11-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
[eblake: s/LIMIT/MAX_SECS/ as suggested by Dan]
Signed-off-by: Eric Blake <eblake@redhat.com>
---
include/block/nbd.h | 11 ++++++++++-
blockdev-nbd.c | 6 ++++--
nbd/server.c | 20 +++++++++++++++++---
qemu-nbd.c | 4 +++-
4 files changed, 34 insertions(+), 7 deletions(-)
diff --git a/include/block/nbd.h b/include/block/nbd.h
index 4e7bd6342f9..1d4d65922d1 100644
--- a/include/block/nbd.h
+++ b/include/block/nbd.h
@@ -33,6 +33,12 @@ 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
+
/* Handshake phase structs - this struct is passed on the wire */
typedef struct NBDOption {
@@ -403,9 +409,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);
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index 213012435f4..267a1de903f 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -64,8 +64,10 @@ static void nbd_accept(QIONetListener *listener, QIOChannelSocket *cioc,
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, NULL);
}
static void nbd_update_server_watch(NBDServerData *s)
diff --git a/nbd/server.c b/nbd/server.c
index ecd9366ba64..fee04415d83 100644
--- a/nbd/server.c
+++ b/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) */
@@ -3191,6 +3193,7 @@ static coroutine_fn void nbd_co_client_start(void *opaque)
qemu_co_mutex_init(&client->send_lock);
+ /* TODO - utilize client->handshake_max_secs */
if (nbd_negotiate(client, &local_err)) {
if (local_err) {
error_report_err(local_err);
@@ -3205,14 +3208,17 @@ static coroutine_fn void nbd_co_client_start(void *opaque)
}
/*
- * 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 +3231,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;
+}
diff --git a/qemu-nbd.c b/qemu-nbd.c
index 8e104ef22c3..a186d2e1190 100644
--- a/qemu-nbd.c
+++ b/qemu-nbd.c
@@ -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)
--
2.46.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 3/5] nbd/server: CVE-2024-7409: Cap default max-connections to 100
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
2024-08-08 21:53 ` [PULL 1/5] nbd: Minor style and typo fixes Eric Blake
2024-08-08 21:53 ` [PULL 2/5] nbd/server: Plumb in new args to nbd_client_add() Eric Blake
@ 2024-08-08 21:53 ` Eric Blake
2024-08-08 21:53 ` [PULL 4/5] nbd/server: CVE-2024-7409: Drop non-negotiating clients Eric Blake
` (2 subsequent siblings)
5 siblings, 0 replies; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
Cc: Daniel P. Berrangé, Kevin Wolf, Hanna Reitz,
Vladimir Sementsov-Ogievskiy, Markus Armbruster,
open list:Block layer core
Allowing an unlimited number of clients to any web service is a recipe
for a rudimentary denial of service attack: the client merely needs to
open lots of sockets without closing them, until qemu no longer has
any more fds available to allocate.
For qemu-nbd, we default to allowing only 1 connection unless more are
explicitly asked for (-e or --shared); this was historically picked as
a nice default (without an explicit -t, a non-persistent qemu-nbd goes
away after a client disconnects, without needing any additional
follow-up commands), and we are not going to change that interface now
(besides, someday we want to point people towards qemu-storage-daemon
instead of qemu-nbd).
But for qemu proper, and the newer qemu-storage-daemon, the QMP
nbd-server-start command has historically had a default of unlimited
number of connections, in part because unlike qemu-nbd it is
inherently persistent until nbd-server-stop. Allowing multiple client
sockets is particularly useful for clients that can take advantage of
MULTI_CONN (creating parallel sockets to increase throughput),
although known clients that do so (such as libnbd's nbdcopy) typically
use only 8 or 16 connections (the benefits of scaling diminish once
more sockets are competing for kernel attention). Picking a number
large enough for typical use cases, but not unlimited, makes it
slightly harder for a malicious client to perform a denial of service
merely by opening lots of connections withot progressing through the
handshake.
This change does not eliminate CVE-2024-7409 on its own, but reduces
the chance for fd exhaustion or unlimited memory usage as an attack
surface. On the other hand, by itself, it makes it more obvious that
with a finite limit, we have the problem of an unauthenticated client
holding 100 fds opened as a way to block out a legitimate client from
being able to connect; thus, later patches will further add timeouts
to reject clients that are not making progress.
This is an INTENTIONAL change in behavior, and will break any client
of nbd-server-start that was not passing an explicit max-connections
parameter, yet expects more than 100 simultaneous connections. We are
not aware of any such client (as stated above, most clients aware of
MULTI_CONN get by just fine on 8 or 16 connections, and probably cope
with later connections failing by relying on the earlier connections;
libvirt has not yet been passing max-connections, but generally
creates NBD servers with the intent for a single client for the sake
of live storage migration; meanwhile, the KubeSAN project anticipates
a large cluster sharing multiple clients [up to 8 per node, and up to
100 nodes in a cluster], but it currently uses qemu-nbd with an
explicit --shared=0 rather than qemu-storage-daemon with
nbd-server-start).
We considered using a deprecation period (declare that omitting
max-parameters is deprecated, and make it mandatory in 3 releases -
then we don't need to pick an arbitrary default); that has zero risk
of breaking any apps that accidentally depended on more than 100
connections, and where such breakage might not be noticed under unit
testing but only under the larger loads of production usage. But it
does not close the denial-of-service hole until far into the future,
and requires all apps to change to add the parameter even if 100 was
good enough. It also has a drawback that any app (like libvirt) that
is accidentally relying on an unlimited default should seriously
consider their own CVE now, at which point they are going to change to
pass explicit max-connections sooner than waiting for 3 qemu releases.
Finally, if our changed default breaks an app, that app can always
pass in an explicit max-parameters with a larger value.
It is also intentional that the HMP interface to nbd-server-start is
not changed to expose max-connections (any client needing to fine-tune
things should be using QMP).
Suggested-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20240807174943.771624-12-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
[ericb: Expand commit message to summarize Dan's argument for why we
break corner-case back-compat behavior without a deprecation period]
Signed-off-by: Eric Blake <eblake@redhat.com>
---
qapi/block-export.json | 4 ++--
include/block/nbd.h | 7 +++++++
block/monitor/block-hmp-cmds.c | 3 ++-
blockdev-nbd.c | 8 ++++++++
4 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/qapi/block-export.json b/qapi/block-export.json
index 665d5fd0262..ce33fe378df 100644
--- a/qapi/block-export.json
+++ b/qapi/block-export.json
@@ -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
diff --git a/include/block/nbd.h b/include/block/nbd.h
index 1d4d65922d1..d4f8b21aecc 100644
--- a/include/block/nbd.h
+++ b/include/block/nbd.h
@@ -39,6 +39,13 @@ extern const BlockExportDriver blk_exp_nbd;
*/
#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 {
diff --git a/block/monitor/block-hmp-cmds.c b/block/monitor/block-hmp-cmds.c
index d954bec6f1e..bdf2eb50b68 100644
--- a/block/monitor/block-hmp-cmds.c
+++ b/block/monitor/block-hmp-cmds.c
@@ -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;
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index 267a1de903f..24ba5382db0 100644
--- a/blockdev-nbd.c
+++ b/blockdev-nbd.c
@@ -170,6 +170,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);
}
@@ -182,6 +186,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);
}
--
2.46.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 4/5] nbd/server: CVE-2024-7409: Drop non-negotiating clients
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
` (2 preceding siblings ...)
2024-08-08 21:53 ` [PULL 3/5] nbd/server: CVE-2024-7409: Cap default max-connections to 100 Eric Blake
@ 2024-08-08 21:53 ` Eric Blake
2024-08-08 21:53 ` [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop Eric Blake
2024-08-10 11:39 ` [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Richard Henderson
5 siblings, 0 replies; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
Cc: Daniel P. Berrangé, Vladimir Sementsov-Ogievskiy,
open list:Network Block Dev...
A client that opens a socket but does not negotiate is merely hogging
qemu's resources (an open fd and a small amount of memory); and a
malicious client that can access the port where NBD is listening can
attempt a denial of service attack by intentionally opening and
abandoning lots of unfinished connections. The previous patch put a
default bound on the number of such ongoing connections, but once that
limit is hit, no more clients can connect (including legitimate ones).
The solution is to insist that clients complete handshake within a
reasonable time limit, defaulting to 10 seconds. A client that has
not successfully completed NBD_OPT_GO by then (including the case of
where the client didn't know TLS credentials to even reach the point
of NBD_OPT_GO) is wasting our time and does not deserve to stay
connected. Later patches will allow fine-tuning the limit away from
the default value (including disabling it for doing integration
testing of the handshake process itself).
Note that this patch in isolation actually makes it more likely to see
qemu SEGV after nbd-server-stop, as any client socket still connected
when the server shuts down will now be closed after 10 seconds rather
than at the client's whims. That will be addressed in the next patch.
For a demo of this patch in action:
$ qemu-nbd -f raw -r -t -e 10 file &
$ nbdsh --opt-mode -c '
H = list()
for i in range(20):
print(i)
H.insert(i, nbd.NBD())
H[i].set_opt_mode(True)
H[i].connect_uri("nbd://localhost")
'
$ kill $!
where later connections get to start progressing once earlier ones are
forcefully dropped for taking too long, rather than hanging.
Suggested-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20240807174943.771624-13-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
[eblake: rebase to changes earlier in series, reduce scope of timer]
Signed-off-by: Eric Blake <eblake@redhat.com>
---
nbd/server.c | 28 +++++++++++++++++++++++++++-
nbd/trace-events | 1 +
2 files changed, 28 insertions(+), 1 deletion(-)
diff --git a/nbd/server.c b/nbd/server.c
index fee04415d83..c30e687fc8b 100644
--- a/nbd/server.c
+++ b/nbd/server.c
@@ -3186,22 +3186,48 @@ 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);
- /* TODO - utilize client->handshake_max_secs */
+ /*
+ * 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);
}
diff --git a/nbd/trace-events b/nbd/trace-events
index 00ae3216a11..cbd0a4ab7e4 100644
--- a/nbd/trace-events
+++ b/nbd/trace-events
@@ -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
--
2.46.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
` (3 preceding siblings ...)
2024-08-08 21:53 ` [PULL 4/5] nbd/server: CVE-2024-7409: Drop non-negotiating clients Eric Blake
@ 2024-08-08 21:53 ` Eric Blake
2024-08-11 8:02 ` Michael Tokarev
2024-08-10 11:39 ` [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Richard Henderson
5 siblings, 1 reply; 10+ messages in thread
From: Eric Blake @ 2024-08-08 21:53 UTC (permalink / raw)
To: qemu-devel
Cc: Alexander Ivanov, qemu-stable, Daniel P. Berrangé,
Vladimir Sementsov-Ogievskiy, Kevin Wolf, Hanna Reitz,
open list:Network Block Dev...
A malicious client can attempt to connect to an NBD server, and then
intentionally delay progress in the handshake, including if it does
not know the TLS secrets. Although the previous two patches reduce
this behavior by capping the default max-connections parameter and
killing slow clients, they did not eliminate the possibility of a
client waiting to close the socket until after the QMP nbd-server-stop
command is executed, at which point qemu would SEGV when trying to
dereference the NULL nbd_server global which is no longer present.
This amounts to a denial of service attack. Worse, if another NBD
server is started before the malicious client disconnects, I cannot
rule out additional adverse effects when the old client interferes
with the connection count of the new server (although the most likely
is a crash due to an assertion failure when checking
nbd_server->connections > 0).
For environments without this patch, the CVE can be mitigated by
ensuring (such as via a firewall) that only trusted clients can
connect to an NBD server. Note that using frameworks like libvirt
that ensure that TLS is used and that nbd-server-stop is not executed
while any trusted clients are still connected will only help if there
is also no possibility for an untrusted client to open a connection
but then stall on the NBD handshake.
Given the previous patches, it would be possible to guarantee that no
clients remain connected by having nbd-server-stop sleep for longer
than the default handshake deadline before finally freeing the global
nbd_server object, but that could make QMP non-responsive for a long
time. So intead, this patch fixes the problem by tracking all client
sockets opened while the server is running, and forcefully closing any
such sockets remaining without a completed handshake at the time of
nbd-server-stop, then waiting until the coroutines servicing those
sockets notice the state change. nbd-server-stop now has a second
AIO_WAIT_WHILE_UNLOCKED (the first is indirectly through the
blk_exp_close_all_type() that disconnects all clients that completed
handshakes), but forced socket shutdown is enough to progress the
coroutines and quickly tear down all clients before the server is
freed, thus finally fixing the CVE.
This patch relies heavily on the fact that nbd/server.c guarantees
that it only calls nbd_blockdev_client_closed() from the main loop
(see the assertion in nbd_client_put() and the hoops used in
nbd_client_put_nonzero() to achieve that); if we did not have that
guarantee, we would also need a mutex protecting our accesses of the
list of connections to survive re-entrancy from independent iothreads.
Although I did not actually try to test old builds, it looks like this
problem has existed since at least commit 862172f45c (v2.12.0, 2017) -
even back when that patch started using a QIONetListener to handle
listening on multiple sockets, nbd_server_free() was already unaware
that the nbd_blockdev_client_closed callback can be reached later by a
client thread that has not completed handshakes (and therefore the
client's socket never got added to the list closed in
nbd_export_close_all), despite that patch intentionally tearing down
the QIONetListener to prevent new clients.
Reported-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
Fixes: CVE-2024-7409
CC: qemu-stable@nongnu.org
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20240807174943.771624-14-eblake@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
---
blockdev-nbd.c | 35 ++++++++++++++++++++++++++++++++++-
1 file changed, 34 insertions(+), 1 deletion(-)
diff --git a/blockdev-nbd.c b/blockdev-nbd.c
index 24ba5382db0..f73409ae494 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;
+ 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,14 +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");
/* 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, NULL);
+ nbd_blockdev_client_closed, conn);
}
static void nbd_update_server_watch(NBDServerData *s)
@@ -81,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));
}
--
2.46.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
` (4 preceding siblings ...)
2024-08-08 21:53 ` [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop Eric Blake
@ 2024-08-10 11:39 ` Richard Henderson
5 siblings, 0 replies; 10+ messages in thread
From: Richard Henderson @ 2024-08-10 11:39 UTC (permalink / raw)
To: qemu-devel
On 8/9/24 07:53, Eric Blake wrote:
> The following changes since commit 75c7f574035622798e9361a942bdfbb0af930f0e:
>
> Merge tag 'pull-hex-20240807' ofhttps://github.com/quic/qemu into staging (2024-08-08 16:08:18 +1000)
>
> are available in the Git repository at:
>
> https://repo.or.cz/qemu/ericb.git tags/pull-nbd-2024-08-08
>
> for you to fetch changes up to 3e7ef738c8462c45043a1d39f702a0990406a3b3:
>
> nbd/server: CVE-2024-7409: Close stray clients at server-stop (2024-08-08 16:34:04 -0500)
>
> ----------------------------------------------------------------
> NBD patches for 2024-08-08
>
> - plug CVE-2024-7409, a DoS attack exploiting nbd-server-stop
Applied, thanks. Please update https://wiki.qemu.org/ChangeLog/9.1 as appropriate.
r~
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop
2024-08-08 21:53 ` [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop Eric Blake
@ 2024-08-11 8:02 ` Michael Tokarev
2024-08-12 14:44 ` Eric Blake
0 siblings, 1 reply; 10+ messages in thread
From: Michael Tokarev @ 2024-08-11 8:02 UTC (permalink / raw)
To: Eric Blake, qemu-devel
Cc: Alexander Ivanov, qemu-stable, Daniel P. Berrangé,
Vladimir Sementsov-Ogievskiy, Kevin Wolf, Hanna Reitz,
open list:Network Block Dev...
09.08.2024 00:53, Eric Blake wrote:
> A malicious client can attempt to connect to an NBD server, and then
> intentionally delay progress in the handshake, including if it does
> not know the TLS secrets. Although the previous two patches reduce
> this behavior by capping the default max-connections parameter and
> killing slow clients, they did not eliminate the possibility of a
> client waiting to close the socket until after the QMP nbd-server-stop
> command is executed, at which point qemu would SEGV when trying to
> dereference the NULL nbd_server global which is no longer present.
> This amounts to a denial of service attack. Worse, if another NBD
> server is started before the malicious client disconnects, I cannot
> rule out additional adverse effects when the old client interferes
> with the connection count of the new server (although the most likely
> is a crash due to an assertion failure when checking
> nbd_server->connections > 0).
>
> For environments without this patch, the CVE can be mitigated by
> ensuring (such as via a firewall) that only trusted clients can
> connect to an NBD server. Note that using frameworks like libvirt
> that ensure that TLS is used and that nbd-server-stop is not executed
> while any trusted clients are still connected will only help if there
> is also no possibility for an untrusted client to open a connection
> but then stall on the NBD handshake.
>
> Given the previous patches, it would be possible to guarantee that no
> clients remain connected by having nbd-server-stop sleep for longer
> than the default handshake deadline before finally freeing the global
> nbd_server object, but that could make QMP non-responsive for a long
> time. So intead, this patch fixes the problem by tracking all client
> sockets opened while the server is running, and forcefully closing any
> such sockets remaining without a completed handshake at the time of
> nbd-server-stop, then waiting until the coroutines servicing those
> sockets notice the state change. nbd-server-stop now has a second
> AIO_WAIT_WHILE_UNLOCKED (the first is indirectly through the
> blk_exp_close_all_type() that disconnects all clients that completed
> handshakes), but forced socket shutdown is enough to progress the
> coroutines and quickly tear down all clients before the server is
> freed, thus finally fixing the CVE.
>
> This patch relies heavily on the fact that nbd/server.c guarantees
> that it only calls nbd_blockdev_client_closed() from the main loop
> (see the assertion in nbd_client_put() and the hoops used in
> nbd_client_put_nonzero() to achieve that); if we did not have that
> guarantee, we would also need a mutex protecting our accesses of the
> list of connections to survive re-entrancy from independent iothreads.
>
> Although I did not actually try to test old builds, it looks like this
> problem has existed since at least commit 862172f45c (v2.12.0, 2017) -
> even back when that patch started using a QIONetListener to handle
> listening on multiple sockets, nbd_server_free() was already unaware
> that the nbd_blockdev_client_closed callback can be reached later by a
> client thread that has not completed handshakes (and therefore the
> client's socket never got added to the list closed in
> nbd_export_close_all), despite that patch intentionally tearing down
> the QIONetListener to prevent new clients.
Eric, from the 5-patch series, only this last patch is Cc'd for stable,
but it obviously does not work without all 4 previous patches. Do you
mean whole series should be applied to -stable?
I picked up patches 2-5 for 7.2 and 9.0.
Thanks,
/mjt
--
GPG Key transition (from rsa2048 to rsa4096) since 2024-04-24.
New key: rsa4096/61AD3D98ECDF2C8E 9D8B E14E 3F2A 9DD7 9199 28F1 61AD 3D98 ECDF 2C8E
Old key: rsa2048/457CE0A0804465C5 6EE1 95D1 886E 8FFB 810D 4324 457C E0A0 8044 65C5
Transition statement: http://www.corpit.ru/mjt/gpg-transition-2024.txt
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop
2024-08-11 8:02 ` Michael Tokarev
@ 2024-08-12 14:44 ` Eric Blake
2024-08-13 6:30 ` Michael Tokarev
0 siblings, 1 reply; 10+ messages in thread
From: Eric Blake @ 2024-08-12 14:44 UTC (permalink / raw)
To: Michael Tokarev
Cc: qemu-devel, Alexander Ivanov, qemu-stable,
Daniel P. Berrangé, Vladimir Sementsov-Ogievskiy, Kevin Wolf,
Hanna Reitz, open list:Network Block Dev...
On Sun, Aug 11, 2024 at 11:02:52AM GMT, Michael Tokarev wrote:
> 09.08.2024 00:53, Eric Blake wrote:
> > A malicious client can attempt to connect to an NBD server, and then
> > intentionally delay progress in the handshake, including if it does
> > not know the TLS secrets. Although the previous two patches reduce
>
> Eric, from the 5-patch series, only this last patch is Cc'd for stable,
> but it obviously does not work without all 4 previous patches. Do you
> mean whole series should be applied to -stable?
>
> I picked up patches 2-5 for 7.2 and 9.0.
You are correct that patch 5 in isolation won't work due to missing
pre-reqs, but also that 1 is fluff that doesn't need backporting; my
apologies for not more judiciously adding the cc to all 4 patches
worth the backport effort. I'm in the middle of efforts to backport
only 2-5 to various RHEL releases, so your choice to do the same for
7.2 and 9.0 matches what I'm doing downstream.
--
Eric Blake, Principal Software Engineer
Red Hat, Inc.
Virtualization: qemu.org | libguestfs.org
^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop
2024-08-12 14:44 ` Eric Blake
@ 2024-08-13 6:30 ` Michael Tokarev
0 siblings, 0 replies; 10+ messages in thread
From: Michael Tokarev @ 2024-08-13 6:30 UTC (permalink / raw)
To: Eric Blake; +Cc: qemu-devel, open list:Network Block Dev...
[Trim CC list]
12.08.2024 17:44, Eric Blake wrote:
> On Sun, Aug 11, 2024 at 11:02:52AM GMT, Michael Tokarev wrote:
..
>> Eric, from the 5-patch series, only this last patch is Cc'd for stable,
>> but it obviously does not work without all 4 previous patches. Do you
>> mean whole series should be applied to -stable?
>>
>> I picked up patches 2-5 for 7.2 and 9.0.
>
> You are correct that patch 5 in isolation won't work due to missing
> pre-reqs, but also that 1 is fluff that doesn't need backporting; my
> apologies for not more judiciously adding the cc to all 4 patches
> worth the backport effort. I'm in the middle of efforts to backport
> only 2-5 to various RHEL releases, so your choice to do the same for
> 7.2 and 9.0 matches what I'm doing downstream.
That's entirely okay, there's no reason to apologize, - I just wanted
to make sure I got it all correct, nothing more, - it's a purely working
moment.
Speaking of various RHEL etc releases, - maybe we should keep more stable
branches, sort of like I do with 7.2 which is used in Debian? Or is it
maybe a bad idea?
Thanks,
/mjt
^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2024-08-13 6:32 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2024-08-08 21:53 [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Eric Blake
2024-08-08 21:53 ` [PULL 1/5] nbd: Minor style and typo fixes Eric Blake
2024-08-08 21:53 ` [PULL 2/5] nbd/server: Plumb in new args to nbd_client_add() Eric Blake
2024-08-08 21:53 ` [PULL 3/5] nbd/server: CVE-2024-7409: Cap default max-connections to 100 Eric Blake
2024-08-08 21:53 ` [PULL 4/5] nbd/server: CVE-2024-7409: Drop non-negotiating clients Eric Blake
2024-08-08 21:53 ` [PULL 5/5] nbd/server: CVE-2024-7409: Close stray clients at server-stop Eric Blake
2024-08-11 8:02 ` Michael Tokarev
2024-08-12 14:44 ` Eric Blake
2024-08-13 6:30 ` Michael Tokarev
2024-08-10 11:39 ` [PULL 0/5] NBD: fix CVE-2024-7409 for 9.1 Richard Henderson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).