CVE-2025-47711 denial of service attack by client sending maximum size block

status
CVE-2025-47712 denial of service attack by client sending large unaligned
size block status
resolves: RHEL-95814
This commit is contained in:
Richard W.M. Jones 2025-06-09 16:21:52 +01:00
parent 83a3fe53b0
commit d1cbcbdf54
3 changed files with 343 additions and 1 deletions

View File

@ -0,0 +1,170 @@
From 4d7da0f8b01f70ceddfd3d10345bf08284591938 Mon Sep 17 00:00:00 2001
From: Eric Blake <eblake@redhat.com>
Date: Tue, 22 Apr 2025 17:01:12 -0500
Subject: [PATCH] server: Fix off-by-one for maximum block_status length
[CVE-2025-47711]
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 problem 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 <stenavin@gmail.com>
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20250423211953.GR1450@redhat.com>
Reviewed-by: Richard W.M. Jones <rjones@redhat.com>
(cherry picked from commit e6f96bd1b77c0cc927ce6aeff650b52238304f39)
---
server/protocol.c | 2 +-
tests/Makefile.am | 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/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/Makefile.am b/tests/Makefile.am
index eed96d28..9f9885b4 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -824,6 +824,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 \
@@ -832,6 +833,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/tests/test-eval-extents.sh b/tests/test-eval-extents.sh
new file mode 100755
index 00000000..92b503e6
--- /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.7
+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
+diff -u - eval-extents.out <<EOF
+[4294967294, 1, 1073741824, 2]
+[4294967295, 1]
+[4294967295, 1]
+EOF
--
2.47.1

View File

@ -0,0 +1,163 @@
From fbca97afb4139f4ae42113f1a91ba595d332d07c Mon Sep 17 00:00:00 2001
From: Eric Blake <eblake@redhat.com>
Date: Tue, 22 Apr 2025 19:53:39 -0500
Subject: [PATCH] blocksize: Fix 32-bit overflow in .extents [CVE-2025-47712]
If the original request is larger than 2**32 - minblock, then we were
calling nbdkit_extents_aligned() with a count that rounded up then
overflowed to 0 instead of the intended 4G because of overflowing a
32-bit type, which in turn causes an assertion failure:
nbdkit: ../../server/backend.c:814: backend_extents: Assertion `backend_valid_range (c, offset, count)' failed.
The fix is to force the rounding to be in a 64-bit type from the
get-go.
The ability for a well-behaved client 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 requrire the use of TLS, then you
can ensure that you only have trusted clients that won't trigger a
block status call that large. Also, the problem only occurs when
using the blocksize filter, although setting the filter's maxlen
parameter to a smaller value than its default of 2**32-1 does not
help.
Fixes: 2680be00 ('blocksize: Fix .extents when plugin changes type within minblock', v1.21.16)
Signed-off-by: Eric Blake <eblake@redhat.com>
Message-ID: <20250423210917.1784789-3-eblake@redhat.com>
Reviewed-by: Richard W.M. Jones <rjones@redhat.com>
(cherry picked from commit a486f88d1eea653ea88b0bf8804c4825dab25ec7)
---
filters/blocksize/blocksize.c | 5 +-
tests/Makefile.am | 2 +
tests/test-blocksize-extents-overflow.sh | 83 ++++++++++++++++++++++++
3 files changed, 88 insertions(+), 2 deletions(-)
create mode 100755 tests/test-blocksize-extents-overflow.sh
diff --git a/filters/blocksize/blocksize.c b/filters/blocksize/blocksize.c
index 09195cea..e5c8b744 100644
--- a/filters/blocksize/blocksize.c
+++ b/filters/blocksize/blocksize.c
@@ -482,8 +482,9 @@ blocksize_extents (nbdkit_next *next,
return -1;
}
- if (nbdkit_extents_aligned (next, MIN (ROUND_UP (count, h->minblock),
- h->maxlen),
+ if (nbdkit_extents_aligned (next,
+ MIN (ROUND_UP ((uint64_t) count, h->minblock),
+ h->maxlen),
ROUND_DOWN (offset, h->minblock), flags,
h->minblock, extents2, err) == -1)
return -1;
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 9f9885b4..428b65e2 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -1592,12 +1592,14 @@ test_layers_filter3_la_LIBADD = $(IMPORT_LIBRARY_ON_WINDOWS)
TESTS += \
test-blocksize.sh \
test-blocksize-extents.sh \
+ test-blocksize-extents-overflow.sh \
test-blocksize-default.sh \
test-blocksize-sharding.sh \
$(NULL)
EXTRA_DIST += \
test-blocksize.sh \
test-blocksize-extents.sh \
+ test-blocksize-extents-overflow.sh \
test-blocksize-default.sh \
test-blocksize-sharding.sh \
$(NULL)
diff --git a/tests/test-blocksize-extents-overflow.sh b/tests/test-blocksize-extents-overflow.sh
new file mode 100755
index 00000000..844c3999
--- /dev/null
+++ b/tests/test-blocksize-extents-overflow.sh
@@ -0,0 +1,83 @@
+#!/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.
+
+# Demonstrate a fix for a bug where blocksize overflowed 32 bits
+
+source ./functions.sh
+set -e
+set -x
+
+requires_run
+requires_plugin eval
+requires_nbdsh_uri
+requires nbdsh --base-allocation --version
+
+# Script a sparse server that requires 512-byte aligned requests.
+exts='
+if test $(( ($3|$4) & 511 )) != 0; then
+ echo "EINVAL request unaligned" 2>&1
+ exit 1
+fi
+echo 0 5G 0
+'
+
+# We also need an nbdsh script to parse all extents, coalescing adjacent
+# types for simplicity.
+# FIXME: Once nbdkit plugin version 3 allows 64-bit block extents, run
+# this test twice, once for each bit size (32-bit needs 2 extents, 64-bit
+# will get the same result with only 1 extent).
+export script='
+size = h.get_size()
+offs = 0
+entries = []
+def f(metacontext, offset, e, err):
+ global entries
+ global offs
+ assert offs == offset
+ for length, flags in zip(*[iter(e)] * 2):
+ if entries and flags == entries[-1][1]:
+ entries[-1] = (entries[-1][0] + length, flags)
+ else:
+ entries.append((length, flags))
+ offs = offs + length
+
+# Test a loop over the entire device
+while offs < size:
+ len = min(size - offs, 2**32-1)
+ h.block_status(len, offs, f)
+assert entries == [(5 * 2**30, 0)]
+'
+
+# Now run everything
+nbdkit --filter=blocksize eval minblock=512 \
+ get_size='echo 5G' pread='exit 1' extents="$exts" \
+ --run 'nbdsh --base-allocation -u "$uri" -c "$script"'
--
2.47.1

View File

@ -56,7 +56,7 @@
Name: nbdkit
Version: 1.38.5
Release: 9%{?dist}
Release: 10%{?dist}
Summary: NBD server
License: BSD-3-Clause
@ -121,6 +121,8 @@ Patch0037: 0037-vddk-stats-Use-us-instead-of-Unicode-s-for-microseco.patch
Patch0038: 0038-vddk-stats-Line-up-the-columns-correctly.patch
Patch0039: 0039-vddk-stats-Record-the-byte-count-of-each-QueryAlloca.patch
Patch0040: 0040-vddk-stats-Collect-elapsed-time-for-ReadAsync-and-Wr.patch
Patch0041: 0041-server-Fix-off-by-one-for-maximum-block_status-lengt.patch
Patch0042: 0042-blocksize-Fix-32-bit-overflow-in-.extents-CVE-2025-4.patch
# For automatic RPM Provides generation.
# See: https://rpm-software-management.github.io/rpm/manual/dependency_generators.html
@ -1539,6 +1541,13 @@ fi
%changelog
* Mon Jun 09 2025 Richard W.M. Jones <rjones@redhat.com> - 1.38.5-10
- CVE-2025-47711 denial of service attack by client sending maximum size block
status
- CVE-2025-47712 denial of service attack by client sending large unaligned
size block status
resolves: RHEL-95814
* Sun Jun 08 2025 Richard W.M. Jones <rjones@redhat.com> - 1.38.5-9
- vddk: Improve statistics
related: RHEL-94823