On Wed, Apr 23, 2025 at 04:06:45PM -0500, Eric Blake wrote:
> There has been an off-by-one bug in the code for .extents since the
> introduction of that callback.  Remember, internally the code allows
> plugins to report on extents with 64-bit lengths, but the protocol
> only supports 32-bit block status calls (nbdkit will need to create
> plugin version 3 before it can support NBD's newer 64-bit block
> status).  As such, the server loop intentionally truncates a plugin's
> large extent to 2**32-1 bytes.  But in the process of checking whether
> the loop should exit early, or if any additional extents should be
> reported to the client, the server used 'pos > offset+count' instead
> of >=, which is one byte too far.  If the client has requested exactly
> 2**32-1 bytes, and the plugin's first extent has that same length, the
> code erroneously proceeds on to the plugin's second extent.  Worse, if
> the plugin's first extent has 2**32 bytes or more, it was truncated to
> 2**31-1 bytes, but not completely handled, and the failure to exit the
> loop early means that the server then fails the assertion:
> 
> nbdkit: ../../server/protocol.c:505: extents_to_block_descriptors:
> Assertion `e.length <= length' failed.
> 
> The single-byte fix addresses both symptoms, while the added test
> demonstrates both when run on older nbdkit (the protocol violation
> when the plugin returns 2**32-1 bytes in the first extent, and the
> assertion failure when the plugin returns 2**32 or more bytes in the
> first extent).
> 
> The problem can only be triggered by a client request for 2**32-1
> bytes; anything smaller is immune.  The problam also does not occur
> for plugins that do not return extents information beyond the client's
> request, or if the first extent is smaller than the client's request.
> 
> The ability to cause the server to die from an assertion failure can
> be used as a denial of service attack against other clients.
> Mitigations: if you require the use of TLS, then you can ensure that
> you only have trusted clients that won't trigger a block status call
> of length 2**32-1 bytes.  Also, you can use "--filter=blocksize-policy
> blocksize-minimum=512" to reject block status attempts from clients
> that are not sector-aligned.
> 
> Fixes: 26455d45 ('server: protocol: Implement Block Status 
> "base:allocation".', v1.11.10)
> Reported-by: Nikolay Ivanets <stena...@gmail.com>
> Signed-off-by: Eric Blake <ebl...@redhat.com>
> Acked-by: Richard W.M. Jones <rjo...@redhat.com>
> ---
>  tests/Makefile.am          |  2 ++
>  server/protocol.c          |  2 +-
>  tests/test-eval-extents.sh | 71 ++++++++++++++++++++++++++++++++++++++
>  3 files changed, 74 insertions(+), 1 deletion(-)
>  create mode 100755 tests/test-eval-extents.sh
> 
> diff --git a/tests/Makefile.am b/tests/Makefile.am
> index 13c0457c..ffe42c78 100644
> --- a/tests/Makefile.am
> +++ b/tests/Makefile.am
> @@ -881,6 +881,7 @@ TESTS += \
>       test-eval.sh \
>       test-eval-file.sh \
>       test-eval-exports.sh \
> +     test-eval-extents.sh \
>       test-eval-cache.sh \
>       test-eval-dump-plugin.sh \
>       test-eval-disconnect.sh \
> @@ -889,6 +890,7 @@ EXTRA_DIST += \
>       test-eval.sh \
>       test-eval-file.sh \
>       test-eval-exports.sh \
> +     test-eval-extents.sh \
>       test-eval-cache.sh \
>       test-eval-dump-plugin.sh \
>       test-eval-disconnect.sh \
> diff --git a/server/protocol.c b/server/protocol.c
> index d428bfc8..b4b1c162 100644
> --- a/server/protocol.c
> +++ b/server/protocol.c
> @@ -499,7 +499,7 @@ extents_to_block_descriptors (struct nbdkit_extents 
> *extents,
>        (*nr_blocks)++;
> 
>        pos += length;
> -      if (pos > offset + count) /* this must be the last block */
> +      if (pos >= offset + count) /* this must be the last block */
>          break;
> 
>        /* If we reach here then we must have consumed this whole
> diff --git a/tests/test-eval-extents.sh b/tests/test-eval-extents.sh
> new file mode 100755
> index 00000000..adc2b5b0
> --- /dev/null
> +++ b/tests/test-eval-extents.sh
> @@ -0,0 +1,71 @@
> +#!/usr/bin/env bash
> +# nbdkit
> +# Copyright Red Hat
> +#
> +# 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.
> +
> +source ./functions.sh
> +set -e
> +set -x
> +
> +requires_run
> +requires_plugin eval
> +requires_nbdsh_uri
> +requires nbdsh --base-allocation --version
> +
> +files="eval-extents.out"
> +rm -f $files
> +cleanup_fn rm -f $files
> +
> +# Trigger an off-by-one bug introduced in v1.11.10 and fixed in v1.43.6
> +export script='
> +def f(context, offset, extents, status):
> +  print(extents)
> +
> +# First, probe where the server should return 2 extents.
> +h.block_status(2**32-1, 2, f)
> +
> +# Next, probe where the server has exactly 2**32-1 bytes in its first extent.
> +h.block_status(2**32-1, 1, f)
> +
> +# Now, probe where the first extent has to be truncated.
> +h.block_status(2**32-1, 0, f)
> +'
> +nbdkit eval \
> +       get_size='echo 5G' \
> +       pread='dd if=/dev/zero count=$3 iflag=count_bytes' \
> +       extents='echo 0 4G 1; echo 4G 1G 2' \
> +       --run 'nbdsh --base-allocation --uri "$uri" -c "$script"' \
> +       > eval-extents.out
> +cat eval-extents.out
> +cat <<EOF |  diff -u - eval-extents.out
> +[4294967294, 1, 1073741824, 2]
> +[4294967295, 1]
> +[4294967295, 1]
> +EOF

^^ I really need to use this 'diff -' trick in a few other places ...

Reviewed-by: Richard W.M. Jones <rjo...@redhat.com>

-- 
Richard Jones, Virtualization Group, Red Hat http://people.redhat.com/~rjones
Read my programming and virtualization blog: http://rwmj.wordpress.com
nbdkit - Flexible, fast NBD server with plugins
https://gitlab.com/nbdkit/nbdkit
_______________________________________________
Libguestfs mailing list -- guestfs@lists.libguestfs.org
To unsubscribe send an email to guestfs-le...@lists.libguestfs.org

Reply via email to