Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
server: Fix regression for NBD_OPT_INFO before NBD_OPT_GO
Most known NBD clients do not bother with NBD_OPT_INFO (except for
clients like 'qemu-nbd --list' that don't ever intend to connect), but
go straight to NBD_OPT_GO.  However, it's not too hard to hack up qemu
to add in an extra client step (whether info on the same name, or more
interestingly, info on a different name), as a patch against qemu
commit 6f214b30445:

| diff --git i/nbd/client.c w/nbd/client.c
| index f6733962b49b..425292ac5ea9 100644
| --- i/nbd/client.c
| +++ w/nbd/client.c
| @@ -1038,6 +1038,14 @@ int nbd_receive_negotiate(AioContext *aio_context, QIOChannel *ioc,
|           * TLS).  If it is not available, fall back to
|           * NBD_OPT_LIST for nicer error messages about a missing
|           * export, then use NBD_OPT_EXPORT_NAME.  */
| +        if (getenv ("HACK"))
| +            info->name[0]++;
| +        result = nbd_opt_info_or_go(ioc, NBD_OPT_INFO, info, errp);
| +        if (getenv ("HACK"))
| +            info->name[0]--;
| +        if (result < 0) {
| +            return -EINVAL;
| +        }
|          result = nbd_opt_info_or_go(ioc, NBD_OPT_GO, info, errp);
|          if (result < 0) {
|              return -EINVAL;

This works just fine in 1.14.0, where we call .open only once (so the
INFO and GO repeat calls into the same plugin handle), but in 1.14.1
it regressed into causing an assertion failure: we are now calling
.open a second time on a connection that is already opened:

$ nbdkit -rfv null &
$ hacked-qemu-io -f raw -r nbd://localhost -c quit
...
nbdkit: null[1]: debug: null: open readonly=1
nbdkit: backend.c:179: backend_open: Assertion `h->handle == NULL' failed.

Worse, on the mainline development, we have recently made it possible
for plugins to actively report different information for different
export names; for example, a plugin may choose to report different
answers for .can_write on export A than for export B; but if we share
cached handles, then an NBD_OPT_INFO on one export prevents correct
answers for NBD_OPT_GO on the second export name.  (The HACK envvar in
my qemu modifications can be used to demonstrate cross-name requests,
which are even less likely in a real client).

The solution is to call .close after NBD_OPT_INFO, coupled with enough
glue logic to reset cached connection handles back to the state
expected by .open.  This in turn means factoring out another backend_*
function, but also gives us an opportunity to change
backend_set_handle to no longer accept NULL.

The assertion failure is, to some extent, a possible denial of service
attack (one client can force nbdkit to exit by merely sending OPT_INFO
before OPT_GO, preventing the next client from connecting), although
this is mitigated by using TLS to weed out untrusted clients.  Still,
the fact that we introduced a potential DoS attack while trying to fix
a traffic amplification security bug is not very nice.

Sadly, as there are no known clients that easily trigger this mode of
operation (OPT_INFO before OPT_GO), there is no easy way to cover this
via a testsuite addition.  I may end up hacking something into libnbd.

Fixes: c05686f
Signed-off-by: Eric Blake <eblake@redhat.com>
(cherry picked from commit a6b88b1)

Conflicts:
	server/backend.c
	server/connections.c
	server/filters.c
	server/internal.h
	server/plugins.c

No backend.c in the stable branch, and less things to reset, so instead
ode that logic into filter_close.
Signed-off-by: Eric Blake <eblake@redhat.com>
  • Loading branch information
ebblake committed Sep 19, 2019
1 parent 4e12bbd commit bf0d618
Show file tree
Hide file tree
Showing 2 changed files with 7 additions and 0 deletions.
1 change: 1 addition & 0 deletions server/filters.c
Expand Up @@ -269,6 +269,7 @@ filter_close (struct backend *b, struct connection *conn)
if (handle && f->filter.close)
f->filter.close (handle);
f->backend.next->close (f->backend.next, conn);
connection_set_handle (conn, f->backend.i, NULL);
}

/* The next_functions structure contains pointers to backend
Expand Down
6 changes: 6 additions & 0 deletions server/protocol-handshake-newstyle.c
Expand Up @@ -453,6 +453,12 @@ negotiate_handshake_newstyle_options (struct connection *conn)
if (send_newstyle_option_reply (conn, option, NBD_REP_ACK) == -1)
return -1;

if (option == NBD_OPT_INFO) {
if (backend->finalize (backend, conn) == -1)
return -1;
backend->close (backend, conn);
}

break;

case NBD_OPT_STRUCTURED_REPLY:
Expand Down

0 comments on commit bf0d618

Please sign in to comment.