From 1d4c8ab44967d3c79e6d8712ead5dc7d01a8916d Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Fri, 13 Sep 2019 21:20:18 +0100 Subject: [PATCH] reflection: Enhance plugin to support client address mode. This adds nbdkit reflection mode=address which reflects the IP address and port number back to the client. Although this is reflecting client data back to the server we believe it is implemented securely. --- plugins/reflection/nbdkit-reflection-plugin.pod | 23 ++++- plugins/reflection/reflection.c | 109 +++++++++++++++++++++++- tests/Makefile.am | 2 + tests/test-reflection-address.sh | 63 ++++++++++++++ 4 files changed, 192 insertions(+), 5 deletions(-) create mode 100755 tests/test-reflection-address.sh diff --git a/plugins/reflection/nbdkit-reflection-plugin.pod b/plugins/reflection/nbdkit-reflection-plugin.pod index f091760a..06aebc9c 100644 --- a/plugins/reflection/nbdkit-reflection-plugin.pod +++ b/plugins/reflection/nbdkit-reflection-plugin.pod @@ -1,10 +1,10 @@ =head1 NAME -nbdkit-reflection-plugin - reflect export name back to the client +nbdkit-reflection-plugin - reflect client info back to the client =head1 SYNOPSIS - nbdkit reflection [mode=]exportname|base64exportname + nbdkit reflection [mode=]exportname|base64exportname|address =head1 DESCRIPTION @@ -19,6 +19,9 @@ below to make this clearer). Export names are limited to 4096 bytes by the NBD protocol, and nbdkit limits them to a few bytes smaller than this. +C creates a disk which contains the client's IP address +and port number as a string. + The plugin only supports read-only access. To make the disk writable, add L on top. @@ -59,10 +62,26 @@ qemu command line: AAAAAAAAAAAAAAAAAAAAVao= ' +Another use for the reflection plugin is to send back the client's IP +address: + + $ nbdkit reflection mode=address + $ nbdsh -u 'nbd://localhost' -c 'print(h.pread(h.get_size(), 0))' + +which will print something like: + + b'[::1]:58912' + =head1 PARAMETERS =over 4 +=item [B]B
+ +Reflect the client's IP address and client port number as a string in +the usual format. For Unix sockets this sets the disk to the string +C<"unix"> to avoid leaking host paths. + =item [B]B Reflect the export name passed by the client, assuming the client diff --git a/plugins/reflection/reflection.c b/plugins/reflection/reflection.c index 686ca06e..3a519ab7 100644 --- a/plugins/reflection/reflection.c +++ b/plugins/reflection/reflection.c @@ -35,6 +35,9 @@ #include #include #include +#include +#include +#include #if defined(HAVE_GNUTLS) && defined(HAVE_GNUTLS_BASE64_DECODE2) #include @@ -49,6 +52,7 @@ enum mode { MODE_EXPORTNAME, MODE_BASE64EXPORTNAME, + MODE_ADDRESS, }; static enum mode mode = MODE_EXPORTNAME; @@ -69,6 +73,9 @@ reflection_config (const char *key, const char *value) return -1; #endif } + else if (strcasecmp (value, "address") == 0) { + mode = MODE_ADDRESS; + } else { nbdkit_error ("unknown mode: '%s'", value); return -1; @@ -83,7 +90,7 @@ reflection_config (const char *key, const char *value) } #define reflection_config_help \ - "mode=exportname|base64exportname Plugin mode." + "mode=exportname|base64exportname|address Plugin mode." /* Provide a way to detect if the base64 feature is supported. */ static void @@ -139,6 +146,74 @@ decode_base64 (const char *data, size_t len, struct handle *ret) #endif } +static int +handle_address (struct sockaddr *sa, socklen_t addrlen, + struct handle *ret) +{ + struct sockaddr_in *addr = (struct sockaddr_in *) sa; + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *) sa; + union { + char straddr[INET_ADDRSTRLEN]; + char straddr6[INET6_ADDRSTRLEN]; + } u; + int r; + char *str; + + switch (addr->sin_family) { + case AF_INET: + if (inet_ntop (AF_INET, &addr->sin_addr, + u.straddr, sizeof u.straddr) == NULL) { + nbdkit_error ("inet_ntop: %m"); + return -1; + } + r = asprintf (&str, "%s:%d", u.straddr, ntohs (addr->sin_port)); + if (r == -1) { + nbdkit_error ("asprintf: %m"); + return -1; + } + ret->len = r; + ret->data = str; + return 0; + + case AF_INET6: + if (inet_ntop (AF_INET6, &addr6->sin6_addr, + u.straddr6, sizeof u.straddr6) == NULL) { + nbdkit_error ("inet_ntop: %m"); + return -1; + } + r = asprintf (&str, "[%s]:%d", u.straddr6, ntohs (addr6->sin6_port)); + if (r == -1) { + nbdkit_error ("asprintf: %m"); + return -1; + } + ret->len = r; + ret->data = str; + return 0; + + case AF_UNIX: + /* We don't want to expose the socket path because it's a host + * filesystem name. The client might not really be running on the + * same machine (eg. it is using a proxy). However it doesn't + * even matter because getpeername(2) on Linux returns a zero + * length sun_path in this case! + */ + str = strdup ("unix"); + if (str == NULL) { + nbdkit_error ("strdup: %m"); + return -1; + } + ret->len = strlen (str); + ret->data = str; + return 0; + + default: + nbdkit_debug ("unsupported socket family %d", addr->sin_family); + ret->data = NULL; + ret->len = 0; + return 0; + } +} + /* Create the per-connection handle. * * This is a rather unusual plugin because it has to parse data sent @@ -149,12 +224,16 @@ decode_base64 (const char *data, size_t len, struct handle *ret) * - Inputs that result in unbounded output. * * - Inputs that could hang, crash or exploit the server. + * + * - Leaking host information (eg. paths). */ static void * reflection_open (int readonly) { const char *export_name; size_t export_name_len; + struct sockaddr_storage addr; + socklen_t addrlen; struct handle *h; h = malloc (sizeof *h); @@ -191,6 +270,15 @@ reflection_open (int readonly) return h; } + case MODE_ADDRESS: + addrlen = sizeof addr; + if (nbdkit_peer_name ((struct sockaddr *) &addr, &addrlen) == -1 || + handle_address ((struct sockaddr *) &addr, addrlen, h) == -1) { + free (h); + return NULL; + } + return h; + default: abort (); } @@ -217,11 +305,26 @@ reflection_get_size (void *handle) return (int64_t) h->len; } -/* Read-only plugin so multi-conn is safe. */ static int reflection_can_multi_conn (void *handle) { - return 1; + switch (mode) { + /* Safe for exportname modes since clients should only request + * multi-conn with the same export name. + */ + case MODE_EXPORTNAME: + case MODE_BASE64EXPORTNAME: + return 1; + /* Unsafe for mode=address because all multi-conn connections + * won't necessarily originate from the same client address. + */ + case MODE_ADDRESS: + return 0; + + /* Keep GCC happy. */ + default: + abort (); + } } /* Cache. */ diff --git a/tests/Makefile.am b/tests/Makefile.am index 69d5d5e9..1b1e05b4 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -111,6 +111,7 @@ EXTRA_DIST = \ test-rate-dynamic.sh \ test.rb \ test-readahead-copy.sh \ + test-reflection-address.sh \ test-reflection-base64.sh \ test-reflection-raw.sh \ test-shutdown.sh \ @@ -606,6 +607,7 @@ test_random_LDADD = libtest.la $(LIBGUESTFS_LIBS) # reflection plugin test. TESTS += \ + test-reflection-address.sh \ test-reflection-base64.sh \ test-reflection-raw.sh \ $(NULL) diff --git a/tests/test-reflection-address.sh b/tests/test-reflection-address.sh new file mode 100755 index 00000000..e4289a73 --- /dev/null +++ b/tests/test-reflection-address.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# nbdkit +# Copyright (C) 2018-2019 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# * Neither the name of Red Hat nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF +# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT +# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +# SUCH DAMAGE. + +# Test the reflection plugin with mode=address. + +source ./functions.sh +set -e +set -x + +requires nbdsh --version + +sock=`mktemp -u` +files="reflection-address.out reflection-address.pid $sock" +rm -f $files +cleanup_fn rm -f $files + +# Run nbdkit. +start_nbdkit -P reflection-address.pid -U $sock \ + reflection mode=address + +export sock +nbdsh -c - <<'EOF' +import os +import re + +h.connect_unix (os.environ["sock"]) + +size = h.get_size () +assert size > 0 + +buf = h.pread (size, 0) +print ("buf = %r" % buf) +assert buf == b'unix' +EOF -- 2.11.4.GIT