Fix assertion failure in blocksize-policy filter

Fix v2v conversion failure when minimum_io_size > 64K
resolves: RHEL-139390
This commit is contained in:
Richard W.M. Jones 2026-01-12 13:51:39 +00:00
parent ea09ddbfb4
commit 70c47311c0
6 changed files with 480 additions and 2 deletions

View File

@ -0,0 +1,27 @@
From 8dc9b3df1c2ff32a73b8dfd179b84b50de792d95 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Tue, 30 Dec 2025 22:01:04 +0000
Subject: [PATCH] docs/nbdkit_debug_hexdump.pod: Add a link back to
nbdkit-checkwrite-filter
Updates: commit 6a1c92f6a30941f8a891e704eeb95296e6d5669a
(cherry picked from commit 674f72a6553b97f86f8f8a276f86354c977d339f)
---
docs/nbdkit_debug_hexdump.pod | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/nbdkit_debug_hexdump.pod b/docs/nbdkit_debug_hexdump.pod
index 4c4cae19..1a428699 100644
--- a/docs/nbdkit_debug_hexdump.pod
+++ b/docs/nbdkit_debug_hexdump.pod
@@ -62,6 +62,7 @@ L<nbdkit(1)>,
L<nbdkit_debug(3)>,
L<nbdkit-plugin(3)>,
L<nbdkit-filter(3)>,
+L<nbdkit-checkwrite-filter(1)>,
L<nbdkit-evil-filter(1)>,
L<nbddump(1)>,
L<hexdump(1)>.
--
2.47.3

View File

@ -0,0 +1,253 @@
From 8d82d6228a25693bbf65c50ecd32953f48dcba9e Mon Sep 17 00:00:00 2001
From: Eric Blake <eblake@redhat.com>
Date: Mon, 5 Jan 2026 10:43:46 -0600
Subject: [PATCH] blocksize-policy: Fix assertion failure on unaligned block
status
The blocksize-policy filter had a minor denial-of-service security
flaw, where a client could trigger the server to die from an assertion
failure by sending an unaligned block status request in violation of
NBD protocol recommendations (see the updated test within the patch
for a sample trigger). Note that libnbd makes it difficult to
trigger, as by default an unaligned request won't be sent to the
server. Additionally, use of blocksize-error-policy=error is not
impacted; and although the blocksize-policy filter defaults to an
error policy of allow, it makes less sense to use the filter in
production without opting in to blocksize-error-policy=error.
Rather than complicating the blocksize-policy filter to manually munge
its extents requests to an aligned boundary, I opted to instead relax
the server's nbdkit_extents_aligned to support unaligned inputs by
first widening the request to alignment boundaries and then truncating
back to the original offset after at least one aligned extent is
learned. The function still stops at the first unaligned extent,
rather than trying harder to use all of the plugin's underlying
information; I have plans to add a parameter in a later patch to
optionally behave more like nbdkit_extents_full, but wanted this patch
to focus on merely the assertion failure.
An audit of all callers of nbdkit_extents_aligned shows that only
blocksize-policy was vulnerable; the blocksize and swab filters only
ever pass in aligned values. And while at it, I made the interface
accept a 64-bit count, which makes usage easier when a client widens a
request near the 4G boundary up to an alignment boundary.
Since the flaw is minor, I've gone ahead and made this patch public.
However, in parallel I am pursuing with Red Hat security on whether a
CVE needs to be assigned.
Fixes: 82b60fcd ("blocksize-policy: Round extents to match minimum alignment", v1.43.11)
Signed-off-by: Eric Blake <eblake@redhat.com>
(cherry picked from commit d4fe15c78295be3ec5f162bf41f9b9da8a669689)
---
docs/nbdkit-filter.pod | 7 ++--
docs/nbdkit-security.pod | 6 +++
include/nbdkit-filter.h | 2 +-
server/extents.c | 25 +++++++----
tests/test-blocksize-policy-extents.sh | 57 ++++++++++++++++++++++++++
5 files changed, 85 insertions(+), 12 deletions(-)
diff --git a/docs/nbdkit-filter.pod b/docs/nbdkit-filter.pod
index 022799f4..1bb01675 100644
--- a/docs/nbdkit-filter.pod
+++ b/docs/nbdkit-filter.pod
@@ -1043,7 +1043,7 @@ A convenience function is provided to filters only which makes it
easier to ensure that the client only encounters aligned extents.
int nbdkit_extents_aligned (nbdkit_next *next,
- uint32_t count, uint64_t offset,
+ uint64_t count, uint64_t offset,
uint32_t flags, uint32_t align,
struct nbdkit_extents *extents, int *err);
@@ -1052,8 +1052,9 @@ obtained, where C<align> is a power of 2. Anywhere the underlying
plugin returns differing extents within C<align> bytes, this function
treats that portion of the disk as a single extent with zero and
sparse status bits determined by the intersection of all underlying
-extents. It is an error to call this function with C<count> or
-C<offset> that is not already aligned.
+extents. This function supports unaligned C<offset> or C<count>, but
+the given C<extents> must begin at C<offset> and not have any extents
+added yet.
=head2 C<.cache>
diff --git a/docs/nbdkit-security.pod b/docs/nbdkit-security.pod
index 64435231..38887bc6 100644
--- a/docs/nbdkit-security.pod
+++ b/docs/nbdkit-security.pod
@@ -47,6 +47,12 @@ See the full announcement and links to mitigation, tests and fixes
here:
L<https://lists.libguestfs.org/archives/list/guestfs@lists.libguestfs.org/message/67E7AASHHADIY7VAD3FFW2I67LTWVWYF/>
+=head2 denial of service attack by client sending unaligned block status
+
+See the patch here:
+L<https://lists.libguestfs.org/archives/list/guestfs@lists.libguestfs.org/thread/EQTMAKGDP53WYOUBCEKWLCWAJHVUNSXX/>
+Full announcement and links to mitigation coming.
+
=head1 SEE ALSO
L<nbdkit(1)>.
diff --git a/include/nbdkit-filter.h b/include/nbdkit-filter.h
index ffa7fa5d..aa99af65 100644
--- a/include/nbdkit-filter.h
+++ b/include/nbdkit-filter.h
@@ -135,7 +135,7 @@ NBDKIT_EXTERN_DECL (struct nbdkit_extents *, nbdkit_extents_full,
NBDKIT_ATTRIBUTE_NONNULL ((1, 5)));
NBDKIT_EXTERN_DECL (int, nbdkit_extents_aligned,
(nbdkit_next *next,
- uint32_t count, uint64_t offset,
+ uint64_t count, uint64_t offset,
uint32_t flags, uint32_t align,
struct nbdkit_extents *extents, int *err)
NBDKIT_ATTRIBUTE_NONNULL ((1, 6, 7)));
diff --git a/server/extents.c b/server/extents.c
index e0a2b224..8e507a94 100644
--- a/server/extents.c
+++ b/server/extents.c
@@ -213,7 +213,7 @@ nbdkit_add_extent (struct nbdkit_extents *exts,
/* Compute aligned extents on behalf of a filter. */
NBDKIT_DLL_PUBLIC int
nbdkit_extents_aligned (struct context *next_c,
- uint32_t count, uint64_t offset,
+ uint64_t count, uint64_t offset,
uint32_t flags, uint32_t align,
struct nbdkit_extents *exts, int *err)
{
@@ -222,22 +222,25 @@ nbdkit_extents_aligned (struct context *next_c,
struct nbdkit_extent *e, *e2;
int64_t size;
+ assert (exts->extents.len == 0);
+ assert (exts->start == offset);
+
size = next->get_size (next_c);
if (size == -1) {
*err = EIO;
return -1;
}
- assert (IS_ALIGNED (offset, align));
- assert (IS_ALIGNED (count, align) || offset + count == size);
+ exts->start = ROUND_DOWN (offset, align);
+ count = MIN (ROUND_UP (offset + count, align), size) - exts->start;
/* Perform an initial query, then scan for the first unaligned extent. */
- if (next->extents (next_c, count, offset, flags, exts, err) == -1)
+ if (next->extents (next_c, count, exts->start, flags, exts, err) == -1)
return -1;
for (i = 0; i < exts->extents.len; ++i) {
e = &exts->extents.ptr[i];
if (!IS_ALIGNED (e->length, align)) {
/* If the unalignment is past align, just truncate and return early */
- if (e->offset + e->length > offset + align) {
+ if (e->offset + e->length > exts->start + align) {
e->length = ROUND_DOWN (e->length, align);
exts->extents.len = i + !!e->length;
exts->next = e->offset + e->length;
@@ -271,13 +274,13 @@ nbdkit_extents_aligned (struct context *next_c,
CLEANUP_EXTENTS_FREE struct nbdkit_extents *extents2 = NULL;
extents2 = nbdkit_extents_new (e->offset + e->length,
- offset + align);
+ exts->start + align);
if (extents2 == NULL) {
*err = errno;
return -1;
}
if (next->extents (next_c, align - e->length,
- offset + e->length,
+ exts->start + e->length,
flags & ~NBDKIT_FLAG_REQ_ONE,
extents2, err) == -1)
return -1;
@@ -298,7 +301,13 @@ nbdkit_extents_aligned (struct context *next_c,
break;
}
}
- /* Once we get here, all extents are aligned. */
+ /* Once we get here, all extents are aligned. Trim back to the
+ * original offset if it was unaligned.
+ */
+ e = &exts->extents.ptr[0];
+ e->length -= offset - exts->start;
+ e->offset += offset - exts->start;
+ exts->start = offset;
return 0;
}
diff --git a/tests/test-blocksize-policy-extents.sh b/tests/test-blocksize-policy-extents.sh
index 46f804bb..688161ba 100755
--- a/tests/test-blocksize-policy-extents.sh
+++ b/tests/test-blocksize-policy-extents.sh
@@ -40,6 +40,8 @@ set -u
requires_run
requires_plugin data
requires_nbdinfo
+requires nbdsh --base-allocation --version
+requires_nbdsh_uri
files="blocksize-policy-extents.out"
rm -f $files
@@ -69,3 +71,58 @@ diff -u - blocksize-policy-extents.out <<EOF
0 4294967296 3 hole,zero
4294967296 512 0 data
EOF
+
+# Check that unaligned requests are rejected when required
+define script <<\EOF
+def print_extents(context, offset, extents, err):
+ assert context == nbd.CONTEXT_BASE_ALLOCATION;
+ print(extents)
+
+h.set_strict_mode(0)
+try:
+ h.block_status(511, 512, print_extents)
+except nbd.Error:
+ print("detected misaligned count")
+try:
+ h.block_status(512, 511, print_extents)
+except nbd.Error:
+ print("detected misaligned offset")
+h.block_status(513, 32256, print_extents)
+h.block_status(1, 32768, print_extents)
+EOF
+export script
+nbdkit data "@32k 1" --filter=blocksize-policy \
+ blocksize-minimum=512 blocksize-error-policy=error \
+ --run 'nbdsh --base-allocation -u "$uri" -c "$script"' \
+ > blocksize-policy-extents.out
+diff -u - blocksize-policy-extents.out <<EOF
+detected misaligned count
+detected misaligned offset
+[512, 3]
+[1, 0]
+EOF
+
+# Check that unaligned requests still work when permitted (a user could trigger
+# an assertion failure prior to 1.48, as a minor security flaw)
+define script <<\EOF
+def print_extents(context, offset, extents, err):
+ assert context == nbd.CONTEXT_BASE_ALLOCATION;
+ print(extents)
+
+h.set_strict_mode(0)
+h.block_status(511, 512, print_extents)
+h.block_status(512, 511, print_extents)
+h.block_status(2, 32767, print_extents)
+h.block_status(1, 32768, print_extents)
+EOF
+export script
+nbdkit data "@32k 1" --filter=blocksize-policy \
+ blocksize-minimum=512 blocksize-error-policy=allow \
+ --run 'nbdsh --base-allocation -u "$uri" -c "$script"' \
+ > blocksize-policy-extents.out
+diff -u - blocksize-policy-extents.out <<EOF
+[32256, 3]
+[32257, 3]
+[1, 3]
+[1, 0]
+EOF
--
2.47.3

View File

@ -0,0 +1,46 @@
From 5db5982dc9b801fe37c0d4086724abc330656609 Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Wed, 7 Jan 2026 20:25:56 +0000
Subject: [PATCH] file: Don't advertise minimum_io_size > 64K (the max
supported by NBD)
If you create an LVM thin pool with chunk size of 128K then the kernel
will advertise /sys/block/dm-X/queue/minimum_io_size as 131072 (128K)
and the file plugin will set the minimum block size to 128K as well.
However the NBD protocol only supports a max of 64K, thus we get the
following failure:
nbdkit: file[2]: error: plugin must set minimum block size between 1 and 64K
This is not a hard limit and the kernel will split smaller requests,
so the simplest solution is to round down larger sizes to 64K.
Fixes: https://issues.redhat.com/browse/RHEL-139390
Reported-by: Nijin Ashok
Thanks: Eric Blake
See: https://github.com/NetworkBlockDevice/nbd/blob/master/doc/proto.md#size-constraints
(cherry picked from commit 42b72dc751030c1210bc2d50f6ff0c1c1a902afc)
---
plugins/file/file.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/plugins/file/file.c b/plugins/file/file.c
index 6a8e65c9..c69d837c 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -748,6 +748,12 @@ file_open (int readonly)
if (ioctl (h->fd, BLKIOMIN, &minimum_io_size) == -1)
nbdkit_debug ("cannot get BLKIOMIN: %s: %m", h->name);
+ /* This is the maximum that NBD supports. For Linux devices,
+ * minimum_io_size is only a hint and smaller operations work.
+ */
+ if (minimum_io_size > 65536)
+ minimum_io_size = 65536;
+
if (ioctl (h->fd, BLKIOOPT, &optimal_io_size) == -1)
nbdkit_debug ("cannot get BLKIOOPT: %s: %m", h->name);
else if (optimal_io_size == 0)
--
2.47.3

View File

@ -0,0 +1,33 @@
From 5d257a1ef7c742ac90f292c2dd5d74594e3d28ec Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 8 Jan 2026 21:05:23 +0000
Subject: [PATCH] todo: Add note about problems with file plugin block_size
(cherry picked from commit 2155ff48364a4d1a7368cf4f43ed78ce1ee47dd3)
---
TODO.md | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/TODO.md b/TODO.md
index 8488dd95..ff72ed25 100644
--- a/TODO.md
+++ b/TODO.md
@@ -199,6 +199,15 @@ nbdkit-file-plugin:
- For more, see comments in
https://gitlab.com/nbdkit/nbdkit/-/merge_requests/88
+* Block size constraints mapping between Linux hints and the
+ file_block_size callback could be improved. Our correspondent
+ writes:
+
+ > the kernel's minimum_io_size is a hint, while logical_block_size
+ is the hard limit. nbdkit should probably be setting "minimum" to
+ logical_block_size, and using the
+ max(minimum_io_size,optimal_io_size) for "preferred"
+
nbdkit-vram-plugin:
* Investigate why, on AMD Radeon, trim does not immediately free Video
--
2.47.3

View File

@ -0,0 +1,111 @@
From 07cbf8b7a1e94079946c36ab83c1ae75ac175d4e Mon Sep 17 00:00:00 2001
From: "Richard W.M. Jones" <rjones@redhat.com>
Date: Thu, 8 Jan 2026 21:38:36 +0000
Subject: [PATCH] file: Change calculations of block size hints for block
devices
Related: https://issues.redhat.com/browse/RHEL-139390
Related: commit 42b72dc751030c1210bc2d50f6ff0c1c1a902afc
Thanks: Eric Blake, Mikulas Patocka
(cherry picked from commit d0ee3c8a6dde446385c9b8e02f8d4623cb4d4184)
---
TODO.md | 9 ---------
plugins/file/file.c | 45 ++++++++++++++++++++++++++++-----------------
2 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/TODO.md b/TODO.md
index ff72ed25..8488dd95 100644
--- a/TODO.md
+++ b/TODO.md
@@ -199,15 +199,6 @@ nbdkit-file-plugin:
- For more, see comments in
https://gitlab.com/nbdkit/nbdkit/-/merge_requests/88
-* Block size constraints mapping between Linux hints and the
- file_block_size callback could be improved. Our correspondent
- writes:
-
- > the kernel's minimum_io_size is a hint, while logical_block_size
- is the hard limit. nbdkit should probably be setting "minimum" to
- logical_block_size, and using the
- max(minimum_io_size,optimal_io_size) for "preferred"
-
nbdkit-vram-plugin:
* Investigate why, on AMD Radeon, trim does not immediately free Video
diff --git a/plugins/file/file.c b/plugins/file/file.c
index c69d837c..abd78ffc 100644
--- a/plugins/file/file.c
+++ b/plugins/file/file.c
@@ -414,7 +414,7 @@ file_config_complete (void)
static void
file_dump_plugin (void)
{
-#if defined(BLKIOMIN) && defined(BLKIOOPT)
+#if defined(BLKSSZGET) && defined(BLKIOMIN) && defined(BLKIOOPT)
printf ("file_block_size=yes\n");
#endif
#ifdef BLKROTATIONAL
@@ -741,32 +741,43 @@ file_open (int readonly)
#endif
h->minimum = h->preferred = h->maximum = 0;
-#if defined(BLKIOMIN) && defined(BLKIOOPT)
+#if defined(BLKSSZGET) && defined(BLKIOMIN) && defined(BLKIOOPT)
if (h->is_block_device) {
- unsigned int minimum_io_size = 0, optimal_io_size = 0;
+ int logical_block_size = 0;
+ unsigned minimum_io_size = 0, optimal_io_size = 0;
+
+ if (ioctl (h->fd, BLKSSZGET, &logical_block_size) == -1)
+ nbdkit_debug ("ioctl: %s: %s: %m", "BLKSSZGET", h->name);
if (ioctl (h->fd, BLKIOMIN, &minimum_io_size) == -1)
- nbdkit_debug ("cannot get BLKIOMIN: %s: %m", h->name);
-
- /* This is the maximum that NBD supports. For Linux devices,
- * minimum_io_size is only a hint and smaller operations work.
- */
- if (minimum_io_size > 65536)
- minimum_io_size = 65536;
+ nbdkit_debug ("ioctl: %s: %s: %m", "BLKIOMIN", h->name);
if (ioctl (h->fd, BLKIOOPT, &optimal_io_size) == -1)
- nbdkit_debug ("cannot get BLKIOOPT: %s: %m", h->name);
+ nbdkit_debug ("ioctl: %s: %s: %m", "BLKIOOPT", h->name);
else if (optimal_io_size == 0)
/* All devices in the Linux kernel except for MD report optimal
- * as 0. In that case guess a good value.
+ * as 0. In that case use logical_block_size.
*/
- optimal_io_size = MAX (minimum_io_size, 4096);
+ optimal_io_size = logical_block_size;
/* Check the values are sane before using them. */
- if (minimum_io_size >= 512 && is_power_of_2 (minimum_io_size) &&
- optimal_io_size >= minimum_io_size && is_power_of_2 (optimal_io_size)) {
- h->minimum = minimum_io_size;
- h->preferred = optimal_io_size;
+ if (logical_block_size >= 512 && is_power_of_2 (logical_block_size) &&
+ minimum_io_size >= 512 && is_power_of_2 (minimum_io_size) &&
+ minimum_io_size >= logical_block_size &&
+ optimal_io_size >= 512 && is_power_of_2 (optimal_io_size) &&
+ optimal_io_size >= logical_block_size) {
+ /* The mapping from Linux kernel settings to NBD protocol block
+ * sizes is not obvious. logical_block_size is the sector size,
+ * and anything smaller than this will cause a RMW cycle. For
+ * the preferred size, we are advised to use the largest of
+ * minimum_io_size and optimal_io_size. For this plugin maximum
+ * can be the largest that NBD can handle.
+ */
+
+ /* 64K is the largest minimum that the NBD protocol supports. */
+ h->minimum = MIN (65536, logical_block_size);
+
+ h->preferred = MAX (minimum_io_size, optimal_io_size);
h->maximum = 0xffffffff;
}
}
--
2.47.3

View File

@ -55,7 +55,7 @@
Name: nbdkit
Version: 1.46.1
Release: 1%{?dist}
Release: 2%{?dist}
Summary: NBD server
License: BSD-3-Clause
@ -85,6 +85,11 @@ Patch0002: 0002-python-Sort-documentation-for-module-functions-in-or.patch
Patch0003: 0003-server-Add-nbdkit_debug_hexdiff-function.patch
Patch0004: 0004-checkwrite-Display-differences-if-D-checkwrite.showd.patch
Patch0005: 0005-docs-nbdkit_debug_hexdump.pod-Document-when-hexdiff-.patch
Patch0006: 0006-docs-nbdkit_debug_hexdump.pod-Add-a-link-back-to-nbd.patch
Patch0007: 0007-blocksize-policy-Fix-assertion-failure-on-unaligned-.patch
Patch0008: 0008-file-Don-t-advertise-minimum_io_size-64K-the-max-sup.patch
Patch0009: 0009-todo-Add-note-about-problems-with-file-plugin-block_.patch
Patch0010: 0010-file-Change-calculations-of-block-size-hints-for-blo.patch
# For automatic RPM Provides generation.
# See: https://rpm-software-management.github.io/rpm/manual/dependency_generators.html
@ -1592,13 +1597,16 @@ fi
%changelog
* Sun Jan 04 2026 Richard W.M. Jones <rjones@redhat.com> - 1.46.1-1
* Mon Jan 12 2026 Richard W.M. Jones <rjones@redhat.com> - 1.46.1-2
- Rebase to nbdkit 1.46.1
- Backport nbdkit_debug_hexdiff from nbdkit 1.47.
resolves: RHEL-111242
- Synchronize spec file with Fedora.
- vddk: Don't use FNM_PATHNAME when matching export parameter
resolves: RHEL-122755
- Fix assertion failure in blocksize-policy filter
- Fix v2v conversion failure when minimum_io_size > 64K
resolves: RHEL-139390
* Wed Jul 09 2025 Richard W.M. Jones <rjones@redhat.com> - 1.44.1-2
- Rebase to nbdkit 1.44.1