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>
  • Loading branch information
ebblake committed Sep 19, 2019
1 parent fd2deeb commit a6b88b1
Show file tree
Hide file tree
Showing 6 changed files with 24 additions and 10 deletions.
13 changes: 13 additions & 0 deletions server/backend.c
Expand Up @@ -201,10 +201,23 @@ backend_prepare (struct backend *b, struct connection *conn)
return b->prepare (b, conn, h->can_write == 0);
}

void
backend_close (struct backend *b, struct connection *conn)
{
struct b_conn_handle *h = &conn->handles[b->i];

debug ("%s: close", b->name);

b->close (b, conn);
memset (h, -1, sizeof *h);
h->handle = NULL;
}

void
backend_set_handle (struct backend *b, struct connection *conn, void *handle)
{
assert (b->i < conn->nr_handles);
assert (conn->handles[b->i].handle == NULL);
conn->handles[b->i].handle = handle;
}

Expand Down
2 changes: 1 addition & 1 deletion server/connections.c
Expand Up @@ -369,7 +369,7 @@ free_connection (struct connection *conn)
*/
if (!quit && connection_get_handle (conn, 0)) {
lock_request (conn);
backend->close (backend, conn);
backend_close (backend, conn);
unlock_request (conn);
}

Expand Down
5 changes: 1 addition & 4 deletions server/filters.c
Expand Up @@ -225,12 +225,9 @@ filter_close (struct backend *b, struct connection *conn)
struct backend_filter *f = container_of (b, struct backend_filter, backend);
void *handle = connection_get_handle (conn, b->i);

debug ("%s: close", b->name);

if (handle && f->filter.close)
f->filter.close (handle);
backend_set_handle (b, conn, NULL);
b->next->close (b->next, conn);
backend_close (b->next, conn);
}

/* The next_functions structure contains pointers to backend
Expand Down
4 changes: 3 additions & 1 deletion server/internal.h
Expand Up @@ -334,9 +334,11 @@ extern int backend_open (struct backend *b, struct connection *conn,
__attribute__((__nonnull__ (1, 2)));
extern int backend_prepare (struct backend *b, struct connection *conn)
__attribute__((__nonnull__ (1, 2)));
extern void backend_close (struct backend *b, struct connection *conn)
__attribute__((__nonnull__ (1, 2)));
extern void backend_set_handle (struct backend *b, struct connection *conn,
void *handle)
__attribute__((__nonnull__ (1, 2 /* not 3 */)));
__attribute__((__nonnull__ (1, 2, 3)));
extern bool backend_valid_range (struct backend *b, struct connection *conn,
uint64_t offset, uint32_t count)
__attribute__((__nonnull__ (1, 2)));
Expand Down
4 changes: 0 additions & 4 deletions server/plugins.c
Expand Up @@ -273,12 +273,8 @@ plugin_close (struct backend *b, struct connection *conn)

assert (connection_get_handle (conn, 0));

debug ("close");

if (p->plugin.close)
p->plugin.close (connection_get_handle (conn, 0));

backend_set_handle (b, conn, NULL);
}

static int64_t
Expand Down
6 changes: 6 additions & 0 deletions server/protocol-handshake-newstyle.c
Expand Up @@ -469,6 +469,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 a6b88b1

Please sign in to comment.