Some of these affect attempts to connect to older qemu-nbd versions, some of them were triggered by manual edits to qemu-nbd source code to provoke various other compliant (if uncommon) server behaviors. Eric Blake (4): starttls: Skip error payload if falling back to unencrypted states: Reject payload to NBD_REP_ACK meta-context: Skip error payload if server lacks meta_context states: Add NBD_OPT_EXPORT_NAME handling generator/Makefile.am | 1 + generator/generator | 53 ++++++++++++++ generator/states-newstyle-opt-export-name.c | 73 +++++++++++++++++++ generator/states-newstyle-opt-go.c | 13 +++- .../states-newstyle-opt-set-meta-context.c | 26 +++++-- generator/states-newstyle-opt-starttls.c | 27 ++++++- .../states-newstyle-opt-structured-reply.c | 6 ++ generator/states-newstyle.c | 11 ++- lib/internal.h | 1 + lib/nbd-protocol.h | 7 ++ 10 files changed, 200 insertions(+), 18 deletions(-) create mode 100644 generator/states-newstyle-opt-export-name.c -- 2.20.1
Eric Blake
2019-May-19 03:55 UTC
[Libguestfs] [libnbd PATCH 1/4] starttls: Skip error payload if falling back to unencrypted
If we tried but did not require TLS, and the NBD server rejected our
attempt at NBD_OPT_STARTTLS but included an error message, then we
MUST capture (or skip) that message off the wire before proceeding on
with attempting structured replies. This requires a new state, as well
as changing the rejection of payload length to only happen on
NBD_REP_ACK.
---
generator/generator | 7 ++++++
generator/states-newstyle-opt-starttls.c | 27 +++++++++++++++++++++---
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/generator/generator b/generator/generator
index d5b5f5f..3faa272 100755
--- a/generator/generator
+++ b/generator/generator
@@ -352,6 +352,13 @@ and newstyle_opt_starttls_state_machine = [
external_events = [ NotifyRead, "" ];
};
+ State {
+ default_state with
+ name = "SKIP_REPLY_PAYLOAD";
+ comment = "Skip newstyle NBD_OPT_STARTTLS reply payload";
+ external_events = [ NotifyRead, "" ];
+ };
+
State {
default_state with
name = "CHECK_REPLY";
diff --git a/generator/states-newstyle-opt-starttls.c
b/generator/states-newstyle-opt-starttls.c
index f44b654..f56553c 100644
--- a/generator/states-newstyle-opt-starttls.c
+++ b/generator/states-newstyle-opt-starttls.c
@@ -45,9 +45,23 @@
return 0;
NEWSTYLE.OPT_STARTTLS.RECV_REPLY:
+ uint32_t len;
+
switch (recv_into_rbuf (conn)) {
case -1: SET_NEXT_STATE (%.DEAD); return -1;
- case 0: SET_NEXT_STATE (%CHECK_REPLY);
+ case 0:
+ /* Discard the payload if there is one. */
+ len = be32toh (conn->sbuf.or.option_reply.replylen);
+ conn->rbuf = NULL;
+ conn->rlen = len;
+ SET_NEXT_STATE (%SKIP_REPLY_PAYLOAD);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_STARTTLS.SKIP_REPLY_PAYLOAD:
+ switch (recv_into_rbuf (conn)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return -1;
+ case 0: SET_NEXT_STATE (%CHECK_REPLY);
}
return 0;
@@ -62,13 +76,19 @@
option = be32toh (conn->sbuf.or.option_reply.option);
reply = be32toh (conn->sbuf.or.option_reply.reply);
len = be32toh (conn->sbuf.or.option_reply.replylen);
- if (magic != NBD_REP_MAGIC || option != NBD_OPT_STARTTLS || len != 0) {
+ if (magic != NBD_REP_MAGIC || option != NBD_OPT_STARTTLS) {
SET_NEXT_STATE (%.DEAD);
- set_error (0, "handshake: invalid option reply magic, option or
length");
+ set_error (0, "handshake: invalid option reply magic or option");
return -1;
}
switch (reply) {
case NBD_REP_ACK:
+ if (len != 0) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: invalid option reply length");
+ return -1;
+ }
+
new_sock = nbd_internal_crypto_create_session (conn, conn->sock);
if (new_sock == NULL) {
SET_NEXT_STATE (%.DEAD);
@@ -99,6 +119,7 @@
debug (conn->h,
"server refused TLS (%s), continuing with unencrypted
connection",
reply == NBD_REP_ERR_POLICY ? "policy" : "not
supported");
+ /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
SET_NEXT_STATE (%^OPT_STRUCTURED_REPLY.START);
return 0;
}
--
2.20.1
Eric Blake
2019-May-19 03:55 UTC
[Libguestfs] [libnbd PATCH 2/4] states: Reject payload to NBD_REP_ACK
The protocol says NBD_REP_ACK should be sent without payload. Enforce
this.
---
generator/states-newstyle-opt-go.c | 5 +++++
generator/states-newstyle-opt-set-meta-context.c | 5 +++++
generator/states-newstyle-opt-structured-reply.c | 6 ++++++
3 files changed, 16 insertions(+)
diff --git a/generator/states-newstyle-opt-go.c
b/generator/states-newstyle-opt-go.c
index 6a7b3af..200d16f 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -118,6 +118,11 @@
}
switch (reply) {
case NBD_REP_ACK:
+ if (len != 0) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: invalid option reply length");
+ return -1;
+ }
SET_NEXT_STATE (%.READY);
return 0;
case NBD_REP_INFO:
diff --git a/generator/states-newstyle-opt-set-meta-context.c
b/generator/states-newstyle-opt-set-meta-context.c
index fdc0500..5a445b2 100644
--- a/generator/states-newstyle-opt-set-meta-context.c
+++ b/generator/states-newstyle-opt-set-meta-context.c
@@ -133,6 +133,11 @@ const char base_allocation[] = "base:allocation";
}
switch (reply) {
case NBD_REP_ACK: /* End of list of replies. */
+ if (len != 0) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: invalid option reply length");
+ return -1;
+ }
SET_NEXT_STATE (%FINISH);
break;
case NBD_REP_META_CONTEXT: /* A context. */
diff --git a/generator/states-newstyle-opt-structured-reply.c
b/generator/states-newstyle-opt-structured-reply.c
index 56335fd..91acdcc 100644
--- a/generator/states-newstyle-opt-structured-reply.c
+++ b/generator/states-newstyle-opt-structured-reply.c
@@ -74,10 +74,16 @@
}
switch (reply) {
case NBD_REP_ACK:
+ if (conn->sbuf.or.option_reply.replylen != 0) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (0, "handshake: invalid option reply length");
+ return -1;
+ }
debug (conn->h, "negotiated structured replies on this
connection");
conn->structured_replies = true;
break;
default:
+ /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
debug (conn->h, "structured replies are not supported by this
server");
conn->structured_replies = false;
break;
--
2.20.1
Eric Blake
2019-May-19 03:55 UTC
[Libguestfs] [libnbd PATCH 3/4] meta-context: Skip error payload if server lacks meta_context
If we tried NBD_OPT_SET_META_CONTEXT, and the NBD server rejected our
attempt with an error message, then we should capture (or skip) that
message off the wire in order to go on without base:allocation, the
same as if the server had not sent a message. This requires a new
state.
---
generator/generator | 7 +++++++
.../states-newstyle-opt-set-meta-context.c | 21 ++++++++++++-------
2 files changed, 20 insertions(+), 8 deletions(-)
diff --git a/generator/generator b/generator/generator
index 3faa272..4d42185 100755
--- a/generator/generator
+++ b/generator/generator
@@ -491,6 +491,13 @@ and newstyle_opt_set_meta_context_state_machine = [
external_events = [ NotifyRead, "" ];
};
+ State {
+ default_state with
+ name = "RECV_SKIP_PAYLOAD";
+ comment = "Ignore newstyle NBD_OPT_SET_META_CONTEXT option reply
payload";
+ external_events = [ NotifyRead, "" ];
+ };
+
State {
default_state with
name = "FINISH";
diff --git a/generator/states-newstyle-opt-set-meta-context.c
b/generator/states-newstyle-opt-set-meta-context.c
index 5a445b2..b815701 100644
--- a/generator/states-newstyle-opt-set-meta-context.c
+++ b/generator/states-newstyle-opt-set-meta-context.c
@@ -150,14 +150,11 @@ const char base_allocation[] =
"base:allocation";
SET_NEXT_STATE (%RECV_REPLY_PAYLOAD);
break;
default:
- /* Anything else is an error, ignore it if the len == 0. */
- if (len != 0) {
- /* We could probably recover from this with some effort. */
- SET_NEXT_STATE (%.DEAD);
- set_error (0, "handshake: unknown option reply (%" PRIu32
")", reply);
- return -1;
- }
- SET_NEXT_STATE (%FINISH);
+ /* Anything else is an error, ignore it */
+ debug (conn->h, "base:allocation is not supported by this
server");
+ conn->rbuf = NULL;
+ conn->rlen = len;
+ SET_NEXT_STATE (%RECV_SKIP_PAYLOAD);
}
}
return 0;
@@ -181,6 +178,14 @@ const char base_allocation[] = "base:allocation";
}
return 0;
+ NEWSTYLE.OPT_SET_META_CONTEXT.RECV_SKIP_PAYLOAD:
+ switch (recv_into_rbuf (conn)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return -1;
+ case 0: SET_NEXT_STATE (%FINISH);
+ /* XXX: capture instead of skip server's payload to NBD_REP_ERR*? */
+ }
+ return 0;
+
NEWSTYLE.OPT_SET_META_CONTEXT.FINISH:
/* Jump to the next option. */
SET_NEXT_STATE (%^OPT_GO.START);
--
2.20.1
Eric Blake
2019-May-19 03:55 UTC
[Libguestfs] [libnbd PATCH 4/4] states: Add NBD_OPT_EXPORT_NAME handling
Used both for servers that are not fixed newstyle, and for servers
that don't understand NBD_OPT_GO.
---
generator/Makefile.am | 1 +
generator/generator | 39 +++++++++++
generator/states-newstyle-opt-export-name.c | 73 +++++++++++++++++++++
generator/states-newstyle-opt-go.c | 8 +--
generator/states-newstyle.c | 11 +++-
lib/internal.h | 1 +
lib/nbd-protocol.h | 7 ++
7 files changed, 133 insertions(+), 7 deletions(-)
create mode 100644 generator/states-newstyle-opt-export-name.c
diff --git a/generator/Makefile.am b/generator/Makefile.am
index 8262d7c..f4c127b 100644
--- a/generator/Makefile.am
+++ b/generator/Makefile.am
@@ -26,6 +26,7 @@ states_code = \
states-connect.c \
states-issue-command.c \
states-magic.c \
+ states-newstyle-opt-export-name.c \
states-newstyle-opt-go.c \
states-newstyle-opt-set-meta-context.c \
states-newstyle-opt-starttls.c \
diff --git a/generator/generator b/generator/generator
index 4d42185..60e4c55 100755
--- a/generator/generator
+++ b/generator/generator
@@ -327,6 +327,7 @@ and newstyle_state_machine = [
Group ("OPT_STRUCTURED_REPLY",
newstyle_opt_structured_reply_state_machine);
Group ("OPT_SET_META_CONTEXT",
newstyle_opt_set_meta_context_state_machine);
Group ("OPT_GO", newstyle_opt_go_state_machine);
+ Group ("OPT_EXPORT_NAME", newstyle_opt_export_name_state_machine);
]
(* Fixed newstyle NBD_OPT_STARTTLS option. *)
@@ -565,6 +566,44 @@ and newstyle_opt_go_state_machine = [
};
]
+(* Newstyle NBD_OPT_EXPORT_NAME option. *)
+and newstyle_opt_export_name_state_machine = [
+ State {
+ default_state with
+ name = "START";
+ comment = "Try to send newstyle NBD_OPT_EXPORT_NAME to end
handshake";
+ external_events = [];
+ };
+
+ State {
+ default_state with
+ name = "SEND";
+ comment = "Send newstyle NBD_OPT_EXPORT_NAME to end handshake";
+ external_events = [ NotifyWrite, "" ];
+ };
+
+ State {
+ default_state with
+ name = "SEND_EXPORT";
+ comment = "Send newstyle NBD_OPT_EXPORT_NAME export name";
+ external_events = [ NotifyWrite, "" ];
+ };
+
+ State {
+ default_state with
+ name = "RECV_REPLY";
+ comment = "Receive newstyle NBD_OPT_EXPORT_NAME reply";
+ external_events = [ NotifyRead, "" ];
+ };
+
+ State {
+ default_state with
+ name = "CHECK_REPLY";
+ comment = "Check newstyle NBD_OPT_EXPORT_NAME reply";
+ external_events = [];
+ };
+]
+
(* Sending a command to the server. *)
and issue_command_state_machine = [
State {
diff --git a/generator/states-newstyle-opt-export-name.c
b/generator/states-newstyle-opt-export-name.c
new file mode 100644
index 0000000..6c6bce4
--- /dev/null
+++ b/generator/states-newstyle-opt-export-name.c
@@ -0,0 +1,73 @@
+/* nbd client library in userspace: state machine
+ * Copyright (C) 2013-2019 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/* State machine for ending newstyle handshake with NBD_OPT_EXPORT_NAME. */
+
+/* STATE MACHINE */ {
+ NEWSTYLE.OPT_EXPORT_NAME.START:
+ conn->sbuf.option.version = htobe64 (NBD_NEW_VERSION);
+ conn->sbuf.option.option = htobe32 (NBD_OPT_EXPORT_NAME);
+ conn->sbuf.option.optlen = strlen (h->export_name);
+ conn->wbuf = &conn->sbuf;
+ conn->wlen = sizeof conn->sbuf.option;
+ SET_NEXT_STATE (%SEND);
+ return 0;
+
+ NEWSTYLE.OPT_EXPORT_NAME.SEND:
+ switch (send_from_wbuf (conn)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return -1;
+ case 0:
+ conn->wbuf = h->export_name;
+ conn->wlen = strlen (h->export_name);
+ SET_NEXT_STATE (%SEND_EXPORT);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXPORT_NAME.SEND_EXPORT:
+ switch (send_from_wbuf (conn)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return -1;
+ case 0:
+ conn->rbuf = &conn->sbuf;
+ conn->rlen = sizeof conn->sbuf.export_name_reply;
+ if ((conn->gflags & NBD_FLAG_NO_ZEROES) != 0)
+ conn->rlen -= sizeof conn->sbuf.export_name_reply.zeroes;
+ SET_NEXT_STATE (%RECV_REPLY);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXPORT_NAME.RECV_REPLY:
+ switch (recv_into_rbuf (conn)) {
+ case -1: SET_NEXT_STATE (%.DEAD); return -1;
+ case 0: SET_NEXT_STATE (%CHECK_REPLY);
+ }
+ return 0;
+
+ NEWSTYLE.OPT_EXPORT_NAME.CHECK_REPLY:
+ conn->h->exportsize = be64toh
(conn->sbuf.export_name_reply.exportsize);
+ conn->h->eflags = be16toh (conn->sbuf.export_name_reply.eflags);
+ debug (conn->h, "exportsize: %" PRIu64 " eflags: 0x%"
PRIx16,
+ conn->h->exportsize, conn->h->eflags);
+ if (conn->h->eflags == 0) {
+ SET_NEXT_STATE (%.DEAD);
+ set_error (EINVAL, "handshake: invalid eflags == 0 from server");
+ return -1;
+ }
+ SET_NEXT_STATE (%.READY);
+ return 0;
+
+} /* END STATE MACHINE */
diff --git a/generator/states-newstyle-opt-go.c
b/generator/states-newstyle-opt-go.c
index 200d16f..42bae8a 100644
--- a/generator/states-newstyle-opt-go.c
+++ b/generator/states-newstyle-opt-go.c
@@ -149,10 +149,10 @@
SET_NEXT_STATE (%RECV_REPLY);
return 0;
case NBD_REP_ERR_UNSUP:
- /* XXX fall back to NBD_OPT_EXPORT_NAME */
- SET_NEXT_STATE (%.DEAD);
- set_error (0, "handshake: server does not support NBD_OPT_GO");
- return -1;
+ debug (conn->h,
+ "server is confused by NBD_OPT_GO, continuing anyway");
+ SET_NEXT_STATE (%^OPT_EXPORT_NAME.START);
+ return 0;
default:
SET_NEXT_STATE (%.DEAD);
set_error (0, "handshake: unknown reply from NBD_OPT_GO: 0x%"
PRIx32,
diff --git a/generator/states-newstyle.c b/generator/states-newstyle.c
index f3a07f7..d108244 100644
--- a/generator/states-newstyle.c
+++ b/generator/states-newstyle.c
@@ -36,9 +36,11 @@
uint32_t cflags;
conn->gflags = be16toh (conn->gflags);
- if ((conn->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0) {
+ if ((conn->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0 &&
+ h->tls == 2) {
SET_NEXT_STATE (%.DEAD);
- set_error (0, "handshake: server is not a fixed newstyle NBD
server");
+ set_error (ENOTSUP, "handshake: server is not fixed newstyle, "
+ "but handle TLS setting is require (2)");
return -1;
}
@@ -54,7 +56,10 @@
case -1: SET_NEXT_STATE (%.DEAD); return -1;
case 0:
/* Start sending options. */
- SET_NEXT_STATE (%OPT_STARTTLS.START);
+ if ((conn->gflags & NBD_FLAG_FIXED_NEWSTYLE) == 0)
+ SET_NEXT_STATE (%OPT_EXPORT_NAME.START);
+ else
+ SET_NEXT_STATE (%OPT_STARTTLS.START);
}
return 0;
diff --git a/lib/internal.h b/lib/internal.h
index f154a7b..852158a 100644
--- a/lib/internal.h
+++ b/lib/internal.h
@@ -127,6 +127,7 @@ struct nbd_connection {
} __attribute__((packed)) context;
} payload;
} __attribute__((packed)) or;
+ struct nbd_export_name_option_reply export_name_reply;
struct nbd_request request;
struct nbd_simple_reply simple_reply;
struct {
diff --git a/lib/nbd-protocol.h b/lib/nbd-protocol.h
index 398403a..4bda056 100644
--- a/lib/nbd-protocol.h
+++ b/lib/nbd-protocol.h
@@ -74,6 +74,13 @@ struct nbd_new_option {
/* option data follows */
} __attribute__((packed));
+/* Newstyle handshake OPT_EXPORT_NAME reply message. */
+struct nbd_export_name_option_reply {
+ uint64_t exportsize; /* size of export */
+ uint16_t eflags; /* per-export flags */
+ char zeroes[124]; /* optional zeroes */
+} __attribute__((packed));;
+
/* Fixed newstyle handshake reply message. */
struct nbd_fixed_new_option_reply {
uint64_t magic; /* NBD_REP_MAGIC */
--
2.20.1
Richard W.M. Jones
2019-May-19 17:08 UTC
Re: [Libguestfs] [libnbd PATCH 4/4] states: Add NBD_OPT_EXPORT_NAME handling
Thanks - I have pushed this. I also added #line directives to lib/states.c which should hopefully make debugging a bit easier :-) Rich. -- Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones Read my programming and virtualization blog: http://rwmj.wordpress.com libguestfs lets you edit virtual machines. Supports shell scripting, bindings from many languages. http://libguestfs.org
Apparently Analagous Threads
- [libnbd PATCH 0/7] state machine refactoring
- [PATCH libnbd PROPOSAL] Add APIs for listing exports from an NBD server.
- [PATCH libnbd] api: Get rid of nbd_connection.
- [PATCH libnbd 0/3] Prevent some misuse of multi-conn.
- [libnbd PATCH 0/3] opt_list_meta_context