From patchwork Thu Jan 21 16:37:24 2016 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: =?utf-8?q?Daniel_P=2E_Berrang=C3=A9?= X-Patchwork-Id: 8083081 Return-Path: X-Original-To: patchwork-qemu-devel@patchwork.kernel.org Delivered-To: patchwork-parsemail@patchwork2.web.kernel.org Received: from mail.kernel.org (mail.kernel.org [198.145.29.136]) by patchwork2.web.kernel.org (Postfix) with ESMTP id A4B85BEEE5 for ; Thu, 21 Jan 2016 16:43:14 +0000 (UTC) Received: from mail.kernel.org (localhost [127.0.0.1]) by mail.kernel.org (Postfix) with ESMTP id 53850203F7 for ; Thu, 21 Jan 2016 16:43:13 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 3E5CB20390 for ; Thu, 21 Jan 2016 16:43:08 +0000 (UTC) Received: from localhost ([::1]:48697 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aMIKB-0006Ix-Ce for patchwork-qemu-devel@patchwork.kernel.org; Thu, 21 Jan 2016 11:43:07 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:41651) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aMIFC-00053A-1P for qemu-devel@nongnu.org; Thu, 21 Jan 2016 11:38:00 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1aMIF9-0004Hq-N6 for qemu-devel@nongnu.org; Thu, 21 Jan 2016 11:37:57 -0500 Received: from mx1.redhat.com ([209.132.183.28]:54429) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1aMIF4-0004EN-T5; Thu, 21 Jan 2016 11:37:51 -0500 Received: from int-mx09.intmail.prod.int.phx2.redhat.com (int-mx09.intmail.prod.int.phx2.redhat.com [10.5.11.22]) by mx1.redhat.com (Postfix) with ESMTPS id 8882A19F97D; Thu, 21 Jan 2016 16:37:50 +0000 (UTC) Received: from t530wlan.home.berrange.com.com (vpn1-6-170.ams2.redhat.com [10.36.6.170]) by int-mx09.intmail.prod.int.phx2.redhat.com (8.14.4/8.14.4) with ESMTP id u0LGbXn8001020; Thu, 21 Jan 2016 11:37:49 -0500 From: "Daniel P. Berrange" To: qemu-devel@nongnu.org Date: Thu, 21 Jan 2016 16:37:24 +0000 Message-Id: <1453394247-2267-12-git-send-email-berrange@redhat.com> In-Reply-To: <1453394247-2267-1-git-send-email-berrange@redhat.com> References: <1453394247-2267-1-git-send-email-berrange@redhat.com> X-Scanned-By: MIMEDefang 2.68 on 10.5.11.22 X-detected-operating-system: by eggs.gnu.org: GNU/Linux 3.x X-Received-From: 209.132.183.28 Cc: Paolo Bonzini , qemu-block@nongnu.org Subject: [Qemu-devel] [PATCH v4 11/14] nbd: implement TLS support in the protocol negotiation X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org Sender: qemu-devel-bounces+patchwork-qemu-devel=patchwork.kernel.org@nongnu.org X-Spam-Status: No, score=-6.9 required=5.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on mail.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP This extends the NBD protocol handling code so that it is capable of negotiating TLS support during the connection setup. This involves requesting the STARTTLS protocol option before any other NBD options. Signed-off-by: Daniel P. Berrange --- block/nbd-client.c | 12 +++-- blockdev-nbd.c | 2 +- include/block/nbd.h | 8 ++++ nbd/client.c | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++- nbd/common.c | 15 ++++++ nbd/nbd-internal.h | 14 ++++++ nbd/server.c | 118 ++++++++++++++++++++++++++++++++++++++++++--- qemu-nbd.c | 4 +- 8 files changed, 296 insertions(+), 13 deletions(-) diff --git a/block/nbd-client.c b/block/nbd-client.c index 75bb2d9..1c79e4b 100644 --- a/block/nbd-client.c +++ b/block/nbd-client.c @@ -405,7 +405,10 @@ int nbd_client_init(BlockDriverState *bs, QIOChannelSocket *sioc, qio_channel_set_blocking(QIO_CHANNEL(sioc), true, NULL); ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), export, - &client->nbdflags, &client->size, errp); + &client->nbdflags, + NULL, NULL, + &client->ioc, + &client->size, errp); if (ret < 0) { logout("Failed to negotiate with the NBD server\n"); return ret; @@ -415,8 +418,11 @@ int nbd_client_init(BlockDriverState *bs, QIOChannelSocket *sioc, qemu_co_mutex_init(&client->free_sema); client->sioc = sioc; object_ref(OBJECT(client->sioc)); - client->ioc = QIO_CHANNEL(sioc); - object_ref(OBJECT(client->ioc)); + + if (!client->ioc) { + client->ioc = QIO_CHANNEL(sioc); + object_ref(OBJECT(client->ioc)); + } /* Now that we're connected, set the socket to be non-blocking and * kick the reply mechanism. */ diff --git a/blockdev-nbd.c b/blockdev-nbd.c index 7d4c415..6af1684 100644 --- a/blockdev-nbd.c +++ b/blockdev-nbd.c @@ -33,7 +33,7 @@ static gboolean nbd_accept(QIOChannel *ioc, GIOCondition condition, return TRUE; } - nbd_client_new(NULL, cioc, nbd_client_put); + nbd_client_new(NULL, cioc, NULL, NULL, nbd_client_put); object_unref(OBJECT(cioc)); return TRUE; } diff --git a/include/block/nbd.h b/include/block/nbd.h index 1080ef8..b197adc 100644 --- a/include/block/nbd.h +++ b/include/block/nbd.h @@ -24,6 +24,7 @@ #include "qemu-common.h" #include "qemu/option.h" #include "io/channel-socket.h" +#include "crypto/tlscreds.h" struct nbd_request { uint32_t magic; @@ -56,7 +57,10 @@ struct nbd_reply { #define NBD_REP_ACK (1) /* Data sending finished. */ #define NBD_REP_SERVER (2) /* Export description. */ #define NBD_REP_ERR_UNSUP ((UINT32_C(1) << 31) | 1) /* Unknown option. */ +#define NBD_REP_ERR_POLICY ((UINT32_C(1) << 31) | 2) /* Server denied */ #define NBD_REP_ERR_INVALID ((UINT32_C(1) << 31) | 3) /* Invalid length. */ +#define NBD_REP_ERR_TLS_REQD ((UINT32_C(1) << 31) | 5) /* TLS required */ + #define NBD_CMD_MASK_COMMAND 0x0000ffff #define NBD_CMD_FLAG_FUA (1 << 16) @@ -81,6 +85,8 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc, size_t length, bool do_read); int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags, + QCryptoTLSCreds *tlscreds, const char *hostname, + QIOChannel **outioc, off_t *size, Error **errp); int nbd_init(int fd, QIOChannelSocket *sioc, uint32_t flags, off_t size); ssize_t nbd_send_request(QIOChannel *ioc, struct nbd_request *request); @@ -106,6 +112,8 @@ void nbd_export_close_all(void); void nbd_client_new(NBDExport *exp, QIOChannelSocket *sioc, + QCryptoTLSCreds *tlscreds, + const char *tlsaclname, void (*close)(NBDClient *)); void nbd_client_get(NBDClient *client); void nbd_client_put(NBDClient *client); diff --git a/nbd/client.c b/nbd/client.c index 89e172e..e37fe81 100644 --- a/nbd/client.c +++ b/nbd/client.c @@ -82,10 +82,18 @@ static int nbd_handle_reply_err(uint32_t opt, uint32_t type, Error **errp) error_setg(errp, "Unsupported option type %x", opt); break; + case NBD_REP_ERR_POLICY: + error_setg(errp, "Denied by server for option %x", opt); + break; + case NBD_REP_ERR_INVALID: error_setg(errp, "Invalid data length for option %x", opt); break; + case NBD_REP_ERR_TLS_REQD: + error_setg(errp, "TLS negotiation required before option %x", opt); + break; + default: error_setg(errp, "Unknown error code when asking for option %x", opt); break; @@ -241,17 +249,127 @@ static int nbd_receive_query_exports(QIOChannel *ioc, return 0; } +static QIOChannel *nbd_receive_starttls(QIOChannel *ioc, + QCryptoTLSCreds *tlscreds, + const char *hostname, Error **errp) +{ + uint64_t magic = cpu_to_be64(NBD_OPTS_MAGIC); + uint32_t opt = cpu_to_be32(NBD_OPT_STARTTLS); + uint32_t length = 0; + uint32_t type; + QIOChannelTLS *tioc; + struct NBDTLSHandshakeData data = { 0 }; + + TRACE("Requesting TLS from server"); + if (write_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) { + error_setg(errp, "Failed to send option magic"); + return NULL; + } + + if (write_sync(ioc, &opt, sizeof(opt)) != sizeof(opt)) { + error_setg(errp, "Failed to send option number"); + return NULL; + } + + if (write_sync(ioc, &length, sizeof(length)) != sizeof(length)) { + error_setg(errp, "Failed to send option length"); + return NULL; + } + + TRACE("Getting TLS reply from server1"); + if (read_sync(ioc, &magic, sizeof(magic)) != sizeof(magic)) { + error_setg(errp, "failed to read option magic"); + return NULL; + } + magic = be64_to_cpu(magic); + if (magic != NBD_REP_MAGIC) { + error_setg(errp, "Unexpected option magic"); + return NULL; + } + TRACE("Getting TLS reply from server2"); + if (read_sync(ioc, &opt, sizeof(opt)) != sizeof(opt)) { + error_setg(errp, "failed to read option"); + return NULL; + } + opt = be32_to_cpu(opt); + if (opt != NBD_OPT_STARTTLS) { + error_setg(errp, "Unexpected option type %x expected %x", + opt, NBD_OPT_STARTTLS); + return NULL; + } + + TRACE("Getting TLS reply from server"); + if (read_sync(ioc, &type, sizeof(type)) != sizeof(type)) { + error_setg(errp, "failed to read option type"); + return NULL; + } + type = be32_to_cpu(type); + if (type != NBD_REP_ACK) { + error_setg(errp, "Server rejected request to start TLS %x", + type); + return NULL; + } + + TRACE("Getting TLS reply from server"); + if (read_sync(ioc, &length, sizeof(length)) != sizeof(length)) { + error_setg(errp, "failed to read option length"); + return NULL; + } + length = be32_to_cpu(length); + if (length != 0) { + error_setg(errp, "Start TLS reponse was not zero %x", + length); + return NULL; + } + + TRACE("TLS request approved, setting up TLS"); + tioc = qio_channel_tls_new_client(ioc, tlscreds, hostname, errp); + if (!tioc) { + return NULL; + } + data.loop = g_main_loop_new(g_main_context_default(), FALSE); + TRACE("Starting TLS hanshake"); + qio_channel_tls_handshake(tioc, + nbd_tls_handshake, + &data, + NULL); + + if (!data.complete) { + g_main_loop_run(data.loop); + } + g_main_loop_unref(data.loop); + if (data.error) { + error_propagate(errp, data.error); + object_unref(OBJECT(tioc)); + return NULL; + } + + return QIO_CHANNEL(tioc); +} + + int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags, + QCryptoTLSCreds *tlscreds, const char *hostname, + QIOChannel **outioc, off_t *size, Error **errp) { char buf[256]; uint64_t magic, s; int rc; - TRACE("Receiving negotiation."); + TRACE("Receiving negotiation tlscreds=%p hostname=%s.", + tlscreds, hostname ? hostname : ""); rc = -EINVAL; + if (outioc) { + *outioc = NULL; + } + if (tlscreds && !outioc) { + error_setg(errp, "Output I/O channel required for TLS"); + goto fail; + } + if (read_sync(ioc, buf, 8) != 8) { error_setg(errp, "Failed to read data"); goto fail; @@ -313,6 +431,18 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags, error_setg(errp, "Failed to send clientflags field"); goto fail; } + if (tlscreds) { + if (fixedNewStyle) { + *outioc = nbd_receive_starttls(ioc, tlscreds, hostname, errp); + if (!*outioc) { + goto fail; + } + ioc = *outioc; + } else { + error_setg(errp, "Server does not support STARTTLS"); + goto fail; + } + } if (!name) { TRACE("Using default NBD export name \"\""); name = ""; @@ -370,6 +500,10 @@ int nbd_receive_negotiate(QIOChannel *ioc, const char *name, uint32_t *flags, error_setg(errp, "Server does not support export names"); goto fail; } + if (tlscreds) { + error_setg(errp, "Server does not support STARTTLS"); + goto fail; + } if (read_sync(ioc, &s, sizeof(s)) != sizeof(s)) { error_setg(errp, "Failed to read export length"); diff --git a/nbd/common.c b/nbd/common.c index ef97d0a..c85f98f 100644 --- a/nbd/common.c +++ b/nbd/common.c @@ -74,3 +74,18 @@ ssize_t nbd_wr_syncv(QIOChannel *ioc, g_free(local_iov_head); return done; } + + +void nbd_tls_handshake(Object *src, + Error *err, + void *opaque) +{ + struct NBDTLSHandshakeData *data = opaque; + + if (err) { + TRACE("TLS failed %s", error_get_pretty(err)); + data->error = error_copy(err); + } + data->complete = true; + g_main_loop_quit(data->loop); +} diff --git a/nbd/nbd-internal.h b/nbd/nbd-internal.h index 9960034..db6ab65 100644 --- a/nbd/nbd-internal.h +++ b/nbd/nbd-internal.h @@ -11,6 +11,7 @@ #define NBD_INTERNAL_H #include "block/nbd.h" #include "sysemu/block-backend.h" +#include "io/channel-tls.h" #include "qemu/coroutine.h" #include "qemu/iov.h" @@ -78,6 +79,8 @@ #define NBD_OPT_EXPORT_NAME (1) #define NBD_OPT_ABORT (2) #define NBD_OPT_LIST (3) +#define NBD_OPT_PEEK_EXPORT (4) +#define NBD_OPT_STARTTLS (5) /* NBD errors are based on errno numbers, so there is a 1:1 mapping, * but only a limited set of errno values is specified in the protocol. @@ -108,4 +111,15 @@ static inline ssize_t write_sync(QIOChannel *ioc, void *buffer, size_t size) return nbd_wr_syncv(ioc, &iov, 1, 0, size, false); } +struct NBDTLSHandshakeData { + GMainLoop *loop; + bool complete; + Error *error; +}; + + +void nbd_tls_handshake(Object *src, + Error *err, + void *opaque); + #endif diff --git a/nbd/server.c b/nbd/server.c index fb29451..b71b7bf 100644 --- a/nbd/server.c +++ b/nbd/server.c @@ -73,6 +73,8 @@ struct NBDClient { void (*close)(NBDClient *client); NBDExport *exp; + QCryptoTLSCreds *tlscreds; + char *tlsaclname; QIOChannelSocket *sioc; /* The underlying data channel */ QIOChannel *ioc; /* The current I/O channel which may differ (eg TLS) */ @@ -189,6 +191,8 @@ static int nbd_negotiate_send_rep(QIOChannel *ioc, uint32_t type, uint32_t opt) uint64_t magic; uint32_t len; + TRACE("Reply opt=%x type=%x", type, opt); + magic = cpu_to_be64(NBD_REP_MAGIC); if (nbd_negotiate_write(ioc, &magic, sizeof(magic)) != sizeof(magic)) { LOG("write failed (rep magic)"); @@ -307,6 +311,55 @@ fail: return rc; } + +static QIOChannel *nbd_negotiate_handle_starttls(NBDClient *client, + uint32_t length) +{ + QIOChannel *ioc; + QIOChannelTLS *tioc; + struct NBDTLSHandshakeData data = { 0 }; + + TRACE("Setting up TLS"); + ioc = client->ioc; + if (length) { + if (nbd_negotiate_drop_sync(ioc, length) != length) { + return NULL; + } + nbd_negotiate_send_rep(ioc, NBD_REP_ERR_INVALID, NBD_OPT_STARTTLS); + return NULL; + } + + nbd_negotiate_send_rep(client->ioc, NBD_REP_ACK, NBD_OPT_STARTTLS); + + tioc = qio_channel_tls_new_server(ioc, + client->tlscreds, + client->tlsaclname, + NULL); + if (!tioc) { + return NULL; + } + + TRACE("Starting TLS handshake"); + data.loop = g_main_loop_new(g_main_context_default(), FALSE); + qio_channel_tls_handshake(tioc, + nbd_tls_handshake, + &data, + NULL); + + if (!data.complete) { + g_main_loop_run(data.loop); + } + g_main_loop_unref(data.loop); + if (data.error) { + object_unref(OBJECT(tioc)); + error_free(data.error); + return NULL; + } + + return QIO_CHANNEL(tioc); +} + + static int nbd_negotiate_options(NBDClient *client) { uint32_t flags; @@ -374,7 +427,30 @@ static int nbd_negotiate_options(NBDClient *client) length = be32_to_cpu(length); TRACE("Checking option 0x%x", clientflags); - if (fixedNewstyle) { + if (client->tlscreds && + client->ioc == (QIOChannel *)client->sioc) { + QIOChannel *tioc; + if (!fixedNewstyle) { + TRACE("Unsupported option 0x%x", clientflags); + return -EINVAL; + } + switch (clientflags) { + case NBD_OPT_STARTTLS: + tioc = nbd_negotiate_handle_starttls(client, length); + if (!tioc) { + return -EIO; + } + object_unref(OBJECT(client->ioc)); + client->ioc = QIO_CHANNEL(tioc); + break; + + default: + TRACE("Option 0x%x not permitted before TLS", clientflags); + nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_TLS_REQD, + clientflags); + return -EINVAL; + } + } else if (fixedNewstyle) { switch (clientflags) { case NBD_OPT_LIST: ret = nbd_negotiate_handle_list(client, length); @@ -389,6 +465,17 @@ static int nbd_negotiate_options(NBDClient *client) case NBD_OPT_EXPORT_NAME: return nbd_negotiate_handle_export_name(client, length); + case NBD_OPT_STARTTLS: + if (client->tlscreds) { + TRACE("TLS already enabled"); + nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_INVALID, + clientflags); + } else { + TRACE("TLS not configured"); + nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_POLICY, + clientflags); + } + return -EINVAL; default: TRACE("Unsupported option 0x%x", clientflags); nbd_negotiate_send_rep(client->ioc, NBD_REP_ERR_UNSUP, @@ -424,8 +511,9 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) int rc; const int myflags = (NBD_FLAG_HAS_FLAGS | NBD_FLAG_SEND_TRIM | NBD_FLAG_SEND_FLUSH | NBD_FLAG_SEND_FUA); + bool oldStyle; - /* Negotiation header without options: + /* Old style negotiation header without options [ 0 .. 7] passwd ("NBDMAGIC") [ 8 .. 15] magic (NBD_CLIENT_MAGIC) [16 .. 23] size @@ -433,12 +521,11 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) [26 .. 27] export flags [28 .. 151] reserved (0) - Negotiation header with options, part 1: + New style negotiation header with options [ 0 .. 7] passwd ("NBDMAGIC") [ 8 .. 15] magic (NBD_OPTS_MAGIC) [16 .. 17] server flags (0) - - part 2 (after options are sent): + ....options sent.... [18 .. 25] size [26 .. 27] export flags [28 .. 151] reserved (0) @@ -450,7 +537,9 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) TRACE("Beginning negotiation."); memset(buf, 0, sizeof(buf)); memcpy(buf, "NBDMAGIC", 8); - if (client->exp) { + + oldStyle = client->exp != NULL && !client->tlscreds; + if (oldStyle) { assert ((client->exp->nbdflags & ~65535) == 0); cpu_to_be64w((uint64_t*)(buf + 8), NBD_CLIENT_MAGIC); cpu_to_be64w((uint64_t*)(buf + 16), client->exp->size); @@ -460,7 +549,11 @@ static coroutine_fn int nbd_negotiate(NBDClientNewData *data) cpu_to_be16w((uint16_t *)(buf + 16), NBD_FLAG_FIXED_NEWSTYLE); } - if (client->exp) { + if (oldStyle) { + if (client->tlscreds) { + TRACE("TLS cannot be enabled with oldstyle protocol"); + goto fail; + } if (nbd_negotiate_write(client->ioc, buf, sizeof(buf)) != sizeof(buf)) { LOG("write failed"); goto fail; @@ -599,6 +692,10 @@ void nbd_client_put(NBDClient *client) nbd_unset_handlers(client); object_unref(OBJECT(client->sioc)); object_unref(OBJECT(client->ioc)); + if (client->tlscreds) { + object_unref(OBJECT(client->tlscreds)); + } + g_free(client->tlsaclname); if (client->exp) { QTAILQ_REMOVE(&client->exp->clients, client, next); nbd_export_put(client->exp); @@ -1134,6 +1231,8 @@ out: void nbd_client_new(NBDExport *exp, QIOChannelSocket *sioc, + QCryptoTLSCreds *tlscreds, + const char *tlsaclname, void (*close_fn)(NBDClient *)) { NBDClient *client; @@ -1142,6 +1241,11 @@ void nbd_client_new(NBDExport *exp, client = g_malloc0(sizeof(NBDClient)); client->refcount = 1; client->exp = exp; + client->tlscreds = tlscreds; + if (tlscreds) { + object_ref(OBJECT(client->tlscreds)); + } + client->tlsaclname = g_strdup(tlsaclname); client->sioc = sioc; object_ref(OBJECT(client->sioc)); client->ioc = QIO_CHANNEL(sioc); diff --git a/qemu-nbd.c b/qemu-nbd.c index d494112..19fac3d 100644 --- a/qemu-nbd.c +++ b/qemu-nbd.c @@ -254,6 +254,7 @@ static void *nbd_client_thread(void *arg) } ret = nbd_receive_negotiate(QIO_CHANNEL(sioc), NULL, &nbdflags, + NULL, NULL, NULL, &size, &local_error); if (ret < 0) { if (local_error) { @@ -343,7 +344,8 @@ static gboolean nbd_accept(QIOChannel *ioc, GIOCondition cond, gpointer opaque) nb_fds++; nbd_update_server_watch(); - nbd_client_new(newproto ? NULL : exp, cioc, nbd_client_closed); + nbd_client_new(newproto ? NULL : exp, cioc, + NULL, NULL, nbd_client_closed); object_unref(OBJECT(cioc)); return TRUE;