Take advantage of the fact that we can now detect the type of client
during --tls=on in order to provide safe dummy content for plaintext
clients without having to rewrite plugins to do so.
Signed-off-by: Eric Blake <eblake(a)redhat.com>
---
docs/nbdkit-plugin.pod | 4 +-
docs/nbdkit-tls.pod | 5 +-
filters/tlsdummy/nbdkit-tlsdummy-filter.pod | 72 ++++++
configure.ac | 2 +
filters/tlsdummy/Makefile.am | 63 ++++++
filters/tlsdummy/tlsdummy.c | 235 ++++++++++++++++++++
6 files changed, 379 insertions(+), 2 deletions(-)
create mode 100644 filters/tlsdummy/nbdkit-tlsdummy-filter.pod
create mode 100644 filters/tlsdummy/Makefile.am
create mode 100644 filters/tlsdummy/tlsdummy.c
diff --git a/docs/nbdkit-plugin.pod b/docs/nbdkit-plugin.pod
index 6237b749..43b0f6f9 100644
--- a/docs/nbdkit-plugin.pod
+++ b/docs/nbdkit-plugin.pod
@@ -1478,7 +1478,9 @@ On error, C<nbdkit_error> is called and the call returns
C<NULL>.
A server may use C<nbdkit_is_tls> to limit which export names work
until after a client has completed TLS authentication. See
-L<nbdkit-tls(1)>.
+L<nbdkit-tls(1)>. It is also possible to use
+L<nbdkit-tlsdummy-filter(1)> to automatically ensure that the plugin
+is only used with authentication.
=head2 C<nbdkit_is_tls>
diff --git a/docs/nbdkit-tls.pod b/docs/nbdkit-tls.pod
index 450e0850..fd78c21b 100644
--- a/docs/nbdkit-tls.pod
+++ b/docs/nbdkit-tls.pod
@@ -25,7 +25,9 @@ I<--tls=on> means that the client may choose to connect either
with or
without TLS. In this mode, a plugin may wish to serve different
content depending on whether the client has authenticated; the
function C<nbdkit_is_tls()> can be used during the C<.open> callback
-for that purpose.
+for that purpose. Or, you can opt to use L<nbdkit-tlsdummy-filter(1)>
+to provide safe dummy content to plaintext connections, saving the
+plugin content only for secure connections.
Because I<--tls=on> is subject to downgrade attacks where a malicious
proxy pretends not to support TLS in order to force either the client
@@ -275,6 +277,7 @@ More information can be found in L<gnutls_priority_init(3)>.
=head1 SEE ALSO
L<nbdkit(1)>,
+L<nbdkit-tlsdummy-filter(1)>,
L<gnutls_priority_init(3)>,
L<psktool(1)>,
L<https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md>,
diff --git a/filters/tlsdummy/nbdkit-tlsdummy-filter.pod
b/filters/tlsdummy/nbdkit-tlsdummy-filter.pod
new file mode 100644
index 00000000..f8eef69f
--- /dev/null
+++ b/filters/tlsdummy/nbdkit-tlsdummy-filter.pod
@@ -0,0 +1,72 @@
+=head1 NAME
+
+nbdkit-tlsdummy-filter - nbdkit TLS protection filter
+
+=head1 SYNOPSIS
+
+ nbdkit --tls=on --filter=tlsdummy plugin [plugin-args...] [tlsreadme=MESSAGE]
+
+=head1 DESCRIPTION
+
+C<nbdkit-tlsdummy-filter> is designed to be used when offering a
+connection that allows but does not require TLS from clients, in order
+to offer safe alternative content to plaintext clients, only exposing
+the underlying plugin to authenticated users. This may provide a
+nicer failure mode for plaintext clients than the harsher C<nbdkit
+--tls=require>.
+
+When this filter detects a plaintext connection, it ignores the
+client's export name, and provides a single read-only export with 512
+bytes of data and content that defaults to the message "This NBD
+server requires TLS authentication before it will serve useful data."
+
+When using this filter, it is recommended to place this filter first
+in the command line, to reduce the chance that any work done by
+C<.open> in later filters or the plugin can be exploited by plaintext
+connections as a denial of service attack to starve further
+authenticated connections. Note that this plugin will fail to load if
+the plugin requests the C<SERIALIZE_CONNECTIONS> thread model, as a
+plaintext client holding its connection open indefinitely would be
+such a starvation.
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<tlsreadme=>MESSAGE
+
+This optional parameter can be used to use C<MESSAGE> as the contents
+of the dummy export exposed to plaintext clients, using trailing NUL
+bytes to round the size up to 512 bytes.
+
+=back
+
+=head1 FILES
+
+=over 4
+
+=item F<$filterdir/nbdkit-tlsdummy-filter.so>
+
+The filter.
+
+Use C<nbdkit --dump-config> to find the location of C<$filterdir>.
+
+=back
+
+=head1 VERSION
+
+C<nbdkit-tlsdummy-filter> first appeared in nbdkit 1.22.
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-tls(1)>,
+L<nbdkit-filter(3)>.
+
+=head1 AUTHORS
+
+Eric Blake
+
+=head1 COPYRIGHT
+
+Copyright (C) 2020 Red Hat Inc.
diff --git a/configure.ac b/configure.ac
index 28f4a3cd..34730000 100644
--- a/configure.ac
+++ b/configure.ac
@@ -124,6 +124,7 @@ filters="\
stats \
swab \
tar \
+ tlsdummy \
truncate \
xz \
"
@@ -1165,6 +1166,7 @@ AC_CONFIG_FILES([Makefile
filters/stats/Makefile
filters/swab/Makefile
filters/tar/Makefile
+ filters/tlsdummy/Makefile
filters/truncate/Makefile
filters/xz/Makefile
fuzzing/Makefile
diff --git a/filters/tlsdummy/Makefile.am b/filters/tlsdummy/Makefile.am
new file mode 100644
index 00000000..a1577511
--- /dev/null
+++ b/filters/tlsdummy/Makefile.am
@@ -0,0 +1,63 @@
+# nbdkit
+# Copyright (C) 2020 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.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-tlsdummy-filter.pod
+
+filter_LTLIBRARIES = nbdkit-tlsdummy-filter.la
+
+nbdkit_tlsdummy_filter_la_SOURCES = \
+ tlsdummy.c \
+ $(top_srcdir)/include/nbdkit-filter.h \
+ $(NULL)
+
+nbdkit_tlsdummy_filter_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include \
+ $(NULL)
+nbdkit_tlsdummy_filter_la_CFLAGS = $(WARNINGS_CFLAGS)
+nbdkit_tlsdummy_filter_la_LDFLAGS = \
+ -module -avoid-version -shared $(SHARED_LDFLAGS) \
+ -Wl,--version-script=$(top_srcdir)/filters/filters.syms \
+ $(NULL)
+
+if HAVE_POD
+
+man_MANS = nbdkit-tlsdummy-filter.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-tlsdummy-filter.1: nbdkit-tlsdummy-filter.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
diff --git a/filters/tlsdummy/tlsdummy.c b/filters/tlsdummy/tlsdummy.c
new file mode 100644
index 00000000..6e1a7414
--- /dev/null
+++ b/filters/tlsdummy/tlsdummy.c
@@ -0,0 +1,235 @@
+/* nbdkit
+ * Copyright (C) 2020 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.
+ */
+
+#include <config.h>
+
+#include <string.h>
+#include <assert.h>
+
+#include <nbdkit-filter.h>
+
+/* Needed to shut up newer gcc about use of strncpy on our message buffer */
+#if __GNUC__ >= 8
+#define NONSTRING __attribute__ ((nonstring))
+#else
+#define NONSTRING
+#endif
+
+static char message[512] NONSTRING = "This NBD server requires TLS "
+ "authentication before it will serve useful data.\n";
+
+/* Called for each key=value passed on the command line. */
+static int
+tlsdummy_config (nbdkit_next_config *next, void *nxdata,
+ const char *key, const char *value)
+{
+ if (strcmp (key, "tlsreadme") == 0) {
+ strncpy (message, value, sizeof message); /* Yes, we really mean strncpy */
+ return 0;
+ }
+ return next (nxdata, key, value);
+}
+
+#define tlsdummy_config_help \
+ "tlsreadme=<MESSAGE> Alternative contents for the plaintext dummy
export.\n"
+
+int
+tlsdummy_get_ready (nbdkit_next_get_ready *next, void *nxdata,
+ int thread_model)
+{
+ if (thread_model == NBDKIT_THREAD_MODEL_SERIALIZE_CONNECTIONS) {
+ nbdkit_error ("the tlsdummy filter requires parallel connection support");
+ return -1;
+ }
+ return next (nxdata);
+}
+
+/* TODO: init_exports needs is_tls parameter */
+
+/* Helper for determining if this connection is insecure. This works
+ * because we can treat all handles on a binary basis: secure or
+ * insecure, which lets .open get away without allocating a more
+ * complex handle.
+ */
+#define NOT_TLS (handle == &message)
+
+static void *
+tlsdummy_open (nbdkit_next_open *next, void *nxdata, int readonly,
+ const char *exportname, int is_tls)
+{
+ /* Bummer - we really do NOT want to call next() when insecure,
+ * because we don't know how long it will take. But that requires a
+ * fix to nbdkit; right now, it asserts if we skip this :(
+ */
+ if (next (nxdata, readonly, exportname) == -1)
+ return NULL;
+ if (!is_tls)
+ return &message; /* See NOT_TLS for this choice of handle */
+ return NBDKIT_HANDLE_NOT_NEEDED;
+}
+
+/* When insecure, override ALL plugin .can_FOO to avoid an information leak */
+
+static int64_t
+tlsdummy_get_size (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return sizeof message;
+ return next_ops->get_size (nxdata);
+}
+
+static int
+tlsdummy_can_write (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_write (nxdata);
+}
+
+static int
+tlsdummy_can_flush (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_flush (nxdata);
+}
+
+static int
+tlsdummy_is_rotational (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->is_rotational (nxdata);
+}
+
+static int
+tlsdummy_can_trim (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_trim (nxdata);
+}
+
+static int
+tlsdummy_can_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return NBDKIT_ZERO_NONE;
+ return next_ops->can_zero (nxdata);
+}
+
+static int
+tlsdummy_can_fast_zero (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_fast_zero (nxdata);
+}
+
+static int
+tlsdummy_can_extents (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_extents (nxdata);
+}
+
+static int
+tlsdummy_can_fua (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return NBDKIT_FUA_NONE;
+ return next_ops->can_fua (nxdata);
+}
+
+static int
+tlsdummy_can_multi_conn (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return 0;
+ return next_ops->can_multi_conn (nxdata);
+}
+
+static int
+tlsdummy_can_cache (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle)
+{
+ if (NOT_TLS)
+ return NBDKIT_CACHE_NONE;
+ return next_ops->can_cache (nxdata);
+}
+
+static int
+tlsdummy_pread (struct nbdkit_next_ops *next_ops, void *nxdata,
+ void *handle, void *b, uint32_t count, uint64_t offs,
+ uint32_t flags, int *err)
+{
+ if (NOT_TLS) {
+ memcpy (b, message + offs, count);
+ return 0;
+ }
+ return next_ops->pread (nxdata, b, count, offs, flags, err);
+}
+
+static struct nbdkit_filter filter = {
+ .name = "tlsdummy",
+ .longname = "nbdkit tlsdummy filter",
+ .config = tlsdummy_config,
+ .config_help = tlsdummy_config_help,
+ .get_ready = tlsdummy_get_ready,
+ /* XXX .init_exports needs is_tls parameter */
+ .open = tlsdummy_open,
+ .get_size = tlsdummy_get_size,
+ .can_write = tlsdummy_can_write,
+ .can_flush = tlsdummy_can_flush,
+ .is_rotational = tlsdummy_is_rotational,
+ .can_trim = tlsdummy_can_trim,
+ .can_zero = tlsdummy_can_zero,
+ .can_fast_zero = tlsdummy_can_fast_zero,
+ .can_extents = tlsdummy_can_extents,
+ .can_fua = tlsdummy_can_fua,
+ .can_multi_conn = tlsdummy_can_multi_conn,
+ .can_cache = tlsdummy_can_cache,
+ .pread = tlsdummy_pread,
+};
+
+NBDKIT_REGISTER_FILTER(filter)
--
2.28.0