From 397c38dd4e25d741d1581f5e98c52b6875b6acf2 Mon Sep 17 00:00:00 2001 From: James Antill Date: Mon, 8 Aug 2022 14:10:37 -0400 Subject: [PATCH] Import rpm: 6aa05d4d2c64ef12985ee75997e84e76559029c8 --- .gitignore | 2 + 0001-Add-nbddump-tool.patch | 1223 +++++++++++++++++ ...-sparse-file.sh-Skip-test-unless-nbd.patch | 30 + ...sually-separate-columns-0-7-and-8-15.patch | 153 +++ ...nerator-Refactor-CONNECT.START-state.patch | 57 + 0003-dump-Fix-build-on-i686.patch | 38 + ...a-better-error-message-if-connect-2-.patch | 48 + 0004-dump-Fix-tests-on-Debian-10.patch | 41 + ...t_go-Tolerate-unplanned-server-death.patch | 59 + ...mp-data.sh-Test-requires-nbdkit-1.22.patch | 31 + ...ocument-assignment-of-CVE-2021-20286.patch | 40 + ...my-variable-rather-than-errno-to-cal.patch | 163 +++ ...referred-block-size-in-the-operation.patch | 163 +++ ...85-Fail-nbdcopy-if-NBD-read-or-write.patch | 318 +++++ ...Use-preferred-block-size-for-copying.patch | 457 ++++++ ...mp-Add-another-example-to-the-manual.patch | 29 + ...to-Use-GNUTLS_NO_SIGNAL-if-available.patch | 93 ++ ...ore-TLS-premature-termination-after-.patch | 100 ++ copy-patches.sh | 55 + gating.yaml | 6 + libguestfs.keyring | Bin 0 -> 82814 bytes libnbd-1.6.0.tar.gz.sig | 17 + libnbd.spec | 427 ++++++ sources | 2 + tests/basic-test.sh | 15 + tests/tests.yml | 12 + 26 files changed, 3579 insertions(+) create mode 100644 .gitignore create mode 100644 0001-Add-nbddump-tool.patch create mode 100644 0001-copy-copy-nbd-to-sparse-file.sh-Skip-test-unless-nbd.patch create mode 100644 0002-dump-Visually-separate-columns-0-7-and-8-15.patch create mode 100644 0002-generator-Refactor-CONNECT.START-state.patch create mode 100644 0003-dump-Fix-build-on-i686.patch create mode 100644 0003-generator-Print-a-better-error-message-if-connect-2-.patch create mode 100644 0004-dump-Fix-tests-on-Debian-10.patch create mode 100644 0004-opt_go-Tolerate-unplanned-server-death.patch create mode 100644 0005-dump-dump-data.sh-Test-requires-nbdkit-1.22.patch create mode 100644 0005-security-Document-assignment-of-CVE-2021-20286.patch create mode 100644 0006-copy-Pass-in-dummy-variable-rather-than-errno-to-cal.patch create mode 100644 0006-copy-Store-the-preferred-block-size-in-the-operation.patch create mode 100644 0007-copy-CVE-2022-0485-Fail-nbdcopy-if-NBD-read-or-write.patch create mode 100644 0007-copy-Use-preferred-block-size-for-copying.patch create mode 100644 0008-dump-Add-another-example-to-the-manual.patch create mode 100644 0009-lib-crypto-Use-GNUTLS_NO_SIGNAL-if-available.patch create mode 100644 0010-lib-crypto.c-Ignore-TLS-premature-termination-after-.patch create mode 100755 copy-patches.sh create mode 100755 gating.yaml create mode 100644 libguestfs.keyring create mode 100644 libnbd-1.6.0.tar.gz.sig create mode 100644 libnbd.spec create mode 100644 sources create mode 100755 tests/basic-test.sh create mode 100755 tests/tests.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2f4c71e --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +SOURCES/libguestfs.keyring +SOURCES/libnbd-1.6.0.tar.gz diff --git a/0001-Add-nbddump-tool.patch b/0001-Add-nbddump-tool.patch new file mode 100644 index 0000000..5600ba9 --- /dev/null +++ b/0001-Add-nbddump-tool.patch @@ -0,0 +1,1223 @@ +From 90fd39da16256407b9229cd17a830739b03629d6 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 30 Jun 2022 09:07:27 +0100 +Subject: [PATCH] Add nbddump tool + +You can already do this operation using: + + nbdcopy -- $uri - | hexdump -C + +but that is slow, especially for large, sparse disks. This tool uses +sparseness information to skip zero sections of the disk, and it has +nice colourized output. + +(cherry picked from commit c4107b9a40d6451630dcccf1bf6596c8e56420be) +--- + .gitignore | 3 + + Makefile.am | 1 + + README | 2 + + bash-completion/Makefile.am | 9 +- + bash-completion/nbdsh | 6 + + configure.ac | 1 + + copy/nbdcopy.pod | 3 +- + docs/libnbd.pod | 1 + + dump/Makefile.am | 79 ++++++ + dump/dump-data.sh | 57 +++++ + dump/dump-empty-qcow2.sh | 46 ++++ + dump/dump-pattern.sh | 56 +++++ + dump/dump.c | 464 ++++++++++++++++++++++++++++++++++++ + dump/nbddump.pod | 116 +++++++++ + dump/test-long-options.sh | 35 +++ + dump/test-short-options.sh | 35 +++ + dump/test-version.sh | 33 +++ + fuse/nbdfuse.pod | 1 + + info/nbdinfo.pod | 1 + + run.in | 1 + + sh/nbdsh.pod | 1 + + 21 files changed, 947 insertions(+), 4 deletions(-) + create mode 100644 dump/Makefile.am + create mode 100755 dump/dump-data.sh + create mode 100755 dump/dump-empty-qcow2.sh + create mode 100755 dump/dump-pattern.sh + create mode 100644 dump/dump.c + create mode 100644 dump/nbddump.pod + create mode 100755 dump/test-long-options.sh + create mode 100755 dump/test-short-options.sh + create mode 100755 dump/test-version.sh + +diff --git a/.gitignore b/.gitignore +index 498eabc..3771655 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -29,6 +29,7 @@ Makefile.in + /aclocal.m4 + /autom4te.cache + /bash-completion/nbdcopy ++/bash-completion/nbddump + /bash-completion/nbdfuse + /bash-completion/nbdinfo + /common/include/test-array-size +@@ -57,6 +58,8 @@ Makefile.in + !/docs/nbd_close.3 + !/docs/nbd_create.pod + !/docs/nbd_get_err??.3 ++/dump/nbddump ++/dump/nbddump.1 + /examples/aio-connect-read + /examples/batched-read-write + /examples/connect-command +diff --git a/Makefile.am b/Makefile.am +index 303b95c..9e7a281 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -46,6 +46,7 @@ SUBDIRS = \ + sh \ + info \ + copy \ ++ dump \ + fuse \ + ocaml \ + ocaml/examples \ +diff --git a/README b/README +index e50c19d..4f9298e 100644 +--- a/README ++++ b/README +@@ -23,6 +23,8 @@ The key features are: + + * Copying tool (nbdcopy) for high performance copying and streaming. + ++ * Hexdump tool (nbddump) to print NBD content. ++ + * Query tool (nbdinfo) to query NBD servers. + + * FUSE support (nbdfuse) to mount NBD in the local filesystem. +diff --git a/bash-completion/Makefile.am b/bash-completion/Makefile.am +index 41d7b13..cab8ffb 100644 +--- a/bash-completion/Makefile.am ++++ b/bash-completion/Makefile.am +@@ -24,17 +24,20 @@ EXTRA_DIST = \ + + if HAVE_BASH_COMPLETION + +-bashcomp_DATA = nbdfuse nbdsh ++bashcomp_DATA = nbddump nbdfuse nbdsh + + if HAVE_LIBXML2 + bashcomp_DATA += nbdcopy nbdinfo + endif HAVE_LIBXML2 + +- + nbdcopy: nbdsh + rm -f $@ + $(LN_S) $(srcdir)/nbdsh $@ + ++nbddump: nbdsh ++ rm -f $@ ++ $(LN_S) $(srcdir)/nbdsh $@ ++ + nbdfuse: nbdsh + rm -f $@ + $(LN_S) $(srcdir)/nbdsh $@ +@@ -43,6 +46,6 @@ nbdinfo: nbdsh + rm -f $@ + $(LN_S) $(srcdir)/nbdsh $@ + +-CLEANFILES += nbdcopy nbdfuse nbdinfo ++CLEANFILES += nbdcopy nbddump nbdfuse nbdinfo + + endif +diff --git a/bash-completion/nbdsh b/bash-completion/nbdsh +index a740be9..a342003 100644 +--- a/bash-completion/nbdsh ++++ b/bash-completion/nbdsh +@@ -47,6 +47,11 @@ _nbdcopy () + _libnbd_command nbdcopy + } + ++_nbddump () ++{ ++ _libnbd_command nbddump ++} ++ + _nbdfuse () + { + _libnbd_command nbdfuse +@@ -64,6 +69,7 @@ _nbdsh () + + # Install the handler function. + complete -o default -F _nbdcopy nbdcopy ++complete -o default -F _nbddump nbddump + complete -o default -F _nbdfuse nbdfuse + complete -o default -F _nbdinfo nbdinfo + complete -o default -F _nbdsh nbdsh +diff --git a/configure.ac b/configure.ac +index b1bfaac..49ca8ab 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -574,6 +574,7 @@ AC_CONFIG_FILES([Makefile + common/utils/Makefile + copy/Makefile + docs/Makefile ++ dump/Makefile + examples/Makefile + fuse/Makefile + fuzzing/Makefile +diff --git a/copy/nbdcopy.pod b/copy/nbdcopy.pod +index 7fe3fd1..fd10f7c 100644 +--- a/copy/nbdcopy.pod ++++ b/copy/nbdcopy.pod +@@ -285,7 +285,7 @@ Some examples follow. + In this example, L is run as a subprocess. The + subprocess opens F and exposes it as NBD to nbdcopy. + nbdcopy streams this to stdout (C<->) into the pipe which is read by +-L. ++L. (See also L) + + =head2 nbdcopy -- [ qemu-nbd -f qcow2 disk.qcow2 ] [ nbdkit memory 1G ] + +@@ -299,6 +299,7 @@ so this command has no overall effect, but is useful for testing. + =head1 SEE ALSO + + L, ++L, + L, + L, + L, +diff --git a/docs/libnbd.pod b/docs/libnbd.pod +index 13facc6..076cafb 100644 +--- a/docs/libnbd.pod ++++ b/docs/libnbd.pod +@@ -1044,6 +1044,7 @@ L. + + L, + L, ++L, + L, + L, + L, +diff --git a/dump/Makefile.am b/dump/Makefile.am +new file mode 100644 +index 0000000..9fd4fed +--- /dev/null ++++ b/dump/Makefile.am +@@ -0,0 +1,79 @@ ++# nbd client library in userspace ++# Copyright (C) 2020-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++include $(top_srcdir)/subdir-rules.mk ++ ++EXTRA_DIST = \ ++ dump-data.sh \ ++ dump-empty-qcow2.sh \ ++ dump-pattern.sh \ ++ nbddump.pod \ ++ test-long-options.sh \ ++ test-short-options.sh \ ++ test-version.sh \ ++ $(NULL) ++ ++bin_PROGRAMS = nbddump ++ ++nbddump_SOURCES = \ ++ dump.c \ ++ $(NULL) ++nbddump_CPPFLAGS = \ ++ -I$(top_srcdir)/include \ ++ -I$(top_srcdir)/common/include \ ++ -I$(top_srcdir)/common/utils \ ++ $(NULL) ++nbddump_CFLAGS = \ ++ $(WARNINGS_CFLAGS) \ ++ $(NULL) ++nbddump_LDADD = \ ++ $(top_builddir)/common/utils/libutils.la \ ++ $(top_builddir)/lib/libnbd.la \ ++ $(NULL) ++ ++if HAVE_POD ++ ++man_MANS = \ ++ nbddump.1 \ ++ $(NULL) ++ ++nbddump.1: nbddump.pod $(top_builddir)/podwrapper.pl ++ $(PODWRAPPER) --section=1 --man $@ \ ++ --html $(top_builddir)/html/$@.html \ ++ $< ++ ++endif HAVE_POD ++ ++TESTS_ENVIRONMENT = \ ++ LIBNBD_DEBUG=1 \ ++ $(MALLOC_CHECKS) \ ++ EXPECTED_VERSION=$(VERSION) \ ++ QEMU_NBD=$(QEMU_NBD) \ ++ $(NULL) ++LOG_COMPILER = $(top_builddir)/run ++ ++TESTS = \ ++ dump-data.sh \ ++ dump-empty-qcow2.sh \ ++ dump-pattern.sh \ ++ test-long-options.sh \ ++ test-short-options.sh \ ++ test-version.sh \ ++ $(NULL) ++ ++check-valgrind: ++ LIBNBD_VALGRIND=1 $(MAKE) check +diff --git a/dump/dump-data.sh b/dump/dump-data.sh +new file mode 100755 +index 0000000..23d09da +--- /dev/null ++++ b/dump/dump-data.sh +@@ -0,0 +1,57 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2020-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++. ../tests/functions.sh ++ ++set -e ++set -x ++ ++requires nbdkit --version ++requires nbdkit data --dump-plugin ++ ++output=dump-data.out ++rm -f $output ++cleanup_fn rm -f $output ++ ++nbdkit -U - data data=' ++ @32768 1 ++ @65535 "hello, world!" ++ @17825790 "spanning buffer boundary" ++ @20000000 0 ++' --run 'nbddump "$uri"' > $output ++ ++cat $output ++ ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++0000008000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++0000008010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++000000fff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 |...............h| ++0000010000: 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 00 |ello, world!....| ++0000010010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++00010ffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 73 70 |..............sp| ++0001100000: 61 6e 6e 69 6e 67 20 62 75 66 66 65 72 20 62 6f |anning buffer bo| ++0001100010: 75 6e 64 61 72 79 00 00 00 00 00 00 00 00 00 00 |undary..........| ++0001100020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++0001312d00: 00 |. |' ]; then ++ echo "$0: unexpected output from nbddump command" ++ exit 1 ++fi +diff --git a/dump/dump-empty-qcow2.sh b/dump/dump-empty-qcow2.sh +new file mode 100755 +index 0000000..c9e583b +--- /dev/null ++++ b/dump/dump-empty-qcow2.sh +@@ -0,0 +1,46 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2020-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++. ../tests/functions.sh ++ ++set -e ++set -x ++ ++requires $QEMU_NBD --version ++requires qemu-img --version ++ ++file=dump-empty-qcow2.qcow2 ++output=dump-empty-qcow2.out ++rm -f $file $output ++cleanup_fn rm -f $file $output ++ ++size=1G ++ ++# Create a large, empty qcow2 file. ++qemu-img create -f qcow2 $file $size ++ ++# Dump it and check the output. ++nbddump -- [ $QEMU_NBD -r -f qcow2 $file ] > $output ++cat $output ++ ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++003ffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|' ]; then ++ echo "$0: unexpected output from nbddump command" ++ exit 1 ++fi +diff --git a/dump/dump-pattern.sh b/dump/dump-pattern.sh +new file mode 100755 +index 0000000..e4016a8 +--- /dev/null ++++ b/dump/dump-pattern.sh +@@ -0,0 +1,56 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2020-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++. ../tests/functions.sh ++ ++set -e ++set -x ++ ++requires nbdkit --version ++requires nbdkit pattern --dump-plugin ++ ++output=dump-pattern.out ++rm -f $output ++cleanup_fn rm -f $output ++ ++nbdkit -U - pattern size=299 --run 'nbddump "$uri"' > $output ++ ++cat $output ++ ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 |................| ++0000000010: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 18 |................| ++0000000020: 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 28 |....... .......(| ++0000000030: 00 00 00 00 00 00 00 30 00 00 00 00 00 00 00 38 |.......0.......8| ++0000000040: 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 |.......@.......H| ++0000000050: 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 58 |.......P.......X| ++0000000060: 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 68 |.......`.......h| ++0000000070: 00 00 00 00 00 00 00 70 00 00 00 00 00 00 00 78 |.......p.......x| ++0000000080: 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 88 |................| ++0000000090: 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 98 |................| ++00000000a0: 00 00 00 00 00 00 00 a0 00 00 00 00 00 00 00 a8 |................| ++00000000b0: 00 00 00 00 00 00 00 b0 00 00 00 00 00 00 00 b8 |................| ++00000000c0: 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 00 c8 |................| ++00000000d0: 00 00 00 00 00 00 00 d0 00 00 00 00 00 00 00 d8 |................| ++00000000e0: 00 00 00 00 00 00 00 e0 00 00 00 00 00 00 00 e8 |................| ++00000000f0: 00 00 00 00 00 00 00 f0 00 00 00 00 00 00 00 f8 |................| ++0000000100: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 08 |................| ++0000000110: 00 00 00 00 00 00 01 10 00 00 00 00 00 00 01 18 |................| ++0000000120: 00 00 00 00 00 00 01 20 00 00 00 |....... ... |' ]; then ++ echo "$0: unexpected output from nbddump command" ++ exit 1 ++fi +diff --git a/dump/dump.c b/dump/dump.c +new file mode 100644 +index 0000000..76af04c +--- /dev/null ++++ b/dump/dump.c +@@ -0,0 +1,464 @@ ++/* NBD client library in userspace ++ * Copyright (C) 2020-2022 Red Hat Inc. ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "minmax.h" ++#include "rounding.h" ++#include "version.h" ++#include "vector.h" ++ ++DEFINE_VECTOR_TYPE (uint32_vector, uint32_t) ++ ++static const char *progname; ++static struct nbd_handle *nbd; ++static bool colour; ++static uint64_t limit = UINT64_MAX; /* --length (unlimited by default) */ ++static int64_t size; /* actual size */ ++static bool can_meta_context; /* did we get extent data? */ ++ ++/* See do_connect () */ ++static enum { MODE_URI = 1, MODE_SQUARE_BRACKET } mode; ++static char **args; ++ ++/* Read buffer. */ ++static unsigned char buffer[16*1024*1024]; ++ ++static void do_connect (void); ++static void do_dump (void); ++static void catch_signal (int); ++ ++static void __attribute__((noreturn)) ++usage (FILE *fp, int exitcode) ++{ ++ fprintf (fp, ++"\n" ++"Hexdump the content of a disk over NBD:\n" ++"\n" ++" nbddump NBD-URI | [ CMD ARGS ... ]\n" ++"\n" ++"Other options:\n" ++"\n" ++" nbddump --help\n" ++" nbddump --version\n" ++"\n" ++"Examples:\n" ++"\n" ++" nbddump nbd://localhost\n" ++" nbddump -- [ qemu-nbd -r -f qcow2 file.qcow2 ]\n" ++"\n" ++"Please read the nbddump(1) manual page for full usage.\n" ++"\n" ++); ++ exit (exitcode); ++} ++ ++int ++main (int argc, char *argv[]) ++{ ++ enum { ++ HELP_OPTION = CHAR_MAX + 1, ++ LONG_OPTIONS, ++ SHORT_OPTIONS, ++ COLOUR_OPTION, ++ NO_COLOUR_OPTION, ++ }; ++ const char *short_options = "n:V"; ++ const struct option long_options[] = { ++ { "help", no_argument, NULL, HELP_OPTION }, ++ { "long-options", no_argument, NULL, LONG_OPTIONS }, ++ { "short-options", no_argument, NULL, SHORT_OPTIONS }, ++ { "version", no_argument, NULL, 'V' }, ++ ++ { "color", no_argument, NULL, COLOUR_OPTION }, ++ { "colors", no_argument, NULL, COLOUR_OPTION }, ++ { "colour", no_argument, NULL, COLOUR_OPTION }, ++ { "colours", no_argument, NULL, COLOUR_OPTION }, ++ { "no-color", no_argument, NULL, NO_COLOUR_OPTION }, ++ { "no-colors", no_argument, NULL, NO_COLOUR_OPTION }, ++ { "no-colour", no_argument, NULL, NO_COLOUR_OPTION }, ++ { "no-colours", no_argument, NULL, NO_COLOUR_OPTION }, ++ { "length", required_argument, NULL, 'n' }, ++ { "limit", required_argument, NULL, 'n' }, ++ { NULL } ++ }; ++ int c; ++ size_t i; ++ ++ progname = argv[0]; ++ colour = isatty (STDOUT_FILENO); ++ ++ for (;;) { ++ c = getopt_long (argc, argv, short_options, long_options, NULL); ++ if (c == -1) ++ break; ++ ++ switch (c) { ++ case HELP_OPTION: ++ usage (stdout, EXIT_SUCCESS); ++ ++ case LONG_OPTIONS: ++ for (i = 0; long_options[i].name != NULL; ++i) { ++ if (strcmp (long_options[i].name, "long-options") != 0 && ++ strcmp (long_options[i].name, "short-options") != 0) ++ printf ("--%s\n", long_options[i].name); ++ } ++ exit (EXIT_SUCCESS); ++ ++ case SHORT_OPTIONS: ++ for (i = 0; short_options[i]; ++i) { ++ if (short_options[i] != ':' && short_options[i] != '+') ++ printf ("-%c\n", short_options[i]); ++ } ++ exit (EXIT_SUCCESS); ++ ++ case COLOUR_OPTION: ++ colour = true; ++ break; ++ ++ case NO_COLOUR_OPTION: ++ colour = false; ++ break; ++ ++ case 'n': ++ /* XXX Allow human sizes here. */ ++ if (sscanf (optarg, "%" SCNu64, &limit) != 1) { ++ fprintf (stderr, "%s: could not parse --length option: %s\n", ++ progname, optarg); ++ exit (EXIT_FAILURE); ++ } ++ break; ++ ++ case 'V': ++ display_version ("nbddump"); ++ exit (EXIT_SUCCESS); ++ ++ default: ++ usage (stderr, EXIT_FAILURE); ++ } ++ } ++ ++ /* Is it a URI or subprocess? */ ++ if (argc - optind >= 3 && ++ strcmp (argv[optind], "[") == 0 && ++ strcmp (argv[argc-1], "]") == 0) { ++ mode = MODE_SQUARE_BRACKET; ++ argv[argc-1] = NULL; ++ args = &argv[optind+1]; ++ } ++ else if (argc - optind == 1) { ++ mode = MODE_URI; ++ args = &argv[optind]; ++ } ++ else { ++ usage (stderr, EXIT_FAILURE); ++ } ++ ++ /* Open the NBD side. */ ++ nbd = nbd_create (); ++ if (nbd == NULL) { ++ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++ nbd_set_uri_allow_local_file (nbd, true); /* Allow ?tls-psk-file. */ ++ nbd_add_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION); ++ ++ /* Connect to the server. */ ++ do_connect (); ++ can_meta_context = ++ nbd_can_meta_context (nbd, LIBNBD_CONTEXT_BASE_ALLOCATION) > 0; ++ ++ /* Get the size. */ ++ size = nbd_get_size (nbd); ++ if (size == -1) { ++ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++ ++ /* Before dumping, make sure we restore the terminal on ^C etc. */ ++ signal (SIGINT, catch_signal); ++ signal (SIGQUIT, catch_signal); ++ signal (SIGTERM, catch_signal); ++ signal (SIGHUP, catch_signal); ++ ++ /* Dump the content. */ ++ do_dump (); ++ ++ nbd_shutdown (nbd, 0); ++ nbd_close (nbd); ++ ++ exit (EXIT_SUCCESS); ++} ++ ++/* Connect the handle to the server. */ ++static void ++do_connect (void) ++{ ++ int r; ++ ++ switch (mode) { ++ case MODE_URI: /* NBD-URI */ ++ r = nbd_connect_uri (nbd, args[0]); ++ break; ++ ++ case MODE_SQUARE_BRACKET: /* [ CMD ARGS ... ] */ ++ r = nbd_connect_systemd_socket_activation (nbd, args); ++ break; ++ ++ default: ++ abort (); ++ } ++ ++ if (r == -1) { ++ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++} ++ ++/* Various ANSI colours, suppressed if --no-colour / not tty output. */ ++static void ++ansi_restore (void) ++{ ++ if (colour) ++ fputs ("\033[0m", stdout); ++} ++ ++static void ++ansi_blue (void) ++{ ++ if (colour) ++ fputs ("\033[1;34m", stdout); ++} ++ ++static void ++ansi_green (void) ++{ ++ if (colour) ++ fputs ("\033[0;32m", stdout); ++} ++ ++static void ++ansi_magenta (void) ++{ ++ if (colour) ++ fputs ("\033[1;35m", stdout); ++} ++ ++static void ++ansi_red (void) ++{ ++ if (colour) ++ fputs ("\033[1;31m", stdout); ++} ++ ++static void ++ansi_grey (void) ++{ ++ if (colour) ++ fputs ("\033[0;90m", stdout); ++} ++ ++static void ++catch_signal (int sig) ++{ ++ printf ("\n"); ++ ansi_restore (); ++ fflush (stdout); ++ _exit (EXIT_FAILURE); ++} ++ ++/* Read the extent map for the next block and return true if it is all ++ * zeroes. This is conservative and returns false if we did not get ++ * the full extent map from the server, or if the server doesn't ++ * support base:allocation at all. ++ */ ++static int ++extent_callback (void *user_data, const char *metacontext, ++ uint64_t offset, ++ uint32_t *entries, size_t nr_entries, ++ int *error) ++{ ++ uint32_vector *list = user_data; ++ size_t i; ++ ++ if (strcmp (metacontext, LIBNBD_CONTEXT_BASE_ALLOCATION) != 0) ++ return 0; ++ ++ /* Just append the entries we got to the list. */ ++ for (i = 0; i < nr_entries; ++i) { ++ if (uint32_vector_append (list, entries[i]) == -1) { ++ perror ("realloc"); ++ exit (EXIT_FAILURE); ++ } ++ } ++ return 0; ++} ++ ++static bool ++test_all_zeroes (uint64_t offset, size_t count) ++{ ++ uint32_vector entries = empty_vector; ++ size_t i; ++ uint64_t count_read; ++ ++ if (!can_meta_context) ++ return false; ++ ++ /* Get the extent map for the block. Note the server doesn't need ++ * to return all requested data here. If it does not then we return ++ * false, causing the main code to do a full read. We could be ++ * smarter and keep asking the server (XXX). ++ */ ++ if (nbd_block_status (nbd, count, offset, ++ (nbd_extent_callback) { ++ .callback = extent_callback, ++ .user_data = &entries }, ++ 0) == -1) { ++ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++ ++ count_read = 0; ++ for (i = 0; i < entries.len; i += 2) { ++ uint32_t len = entries.ptr[i]; ++ uint32_t type = entries.ptr[i+1]; ++ ++ count_read += len; ++ if (!(type & 2)) /* not zero */ ++ return false; ++ } ++ ++ /* Did we read at least the whole range wanted? */ ++ if (count_read < count) ++ return false; ++ ++ /* If we got here, we read the whole range and it was all zeroes. */ ++ return true; ++} ++ ++/* Hexdump the NBD data. ++ * ++ * XXX In future we could do this all asynch (including writing to ++ * stdout) which could make it very efficient. ++ */ ++static void ++do_dump (void) ++{ ++ /* If --no-colour, don't use unicode in the output. */ ++ const char *splat = colour ? "☆" : "*"; ++ const char *pipe = colour ? "│" : "|"; ++ const char *dot = colour ? "·" : "."; ++ uint64_t offset = 0; ++ uint64_t count = size > limit ? limit : size; ++ size_t i, j, n; ++ char last[16]; ++ bool printed_splat = false, same; ++ ++ while (count) { ++ n = MIN (count, sizeof buffer); ++ ++ if (! test_all_zeroes (offset, n)) { ++ if (nbd_pread (nbd, buffer, n, offset, 0) == -1) { ++ fprintf (stderr, "%s: %s\n", progname, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++ } ++ else { ++ memset (buffer, 0, n); ++ } ++ ++ /* Make sure a multiple of 16 bytes gets written to the buffer. */ ++ if (n & 15) ++ memset (&buffer[n], 0, 16 - (n & 15)); ++ ++ for (i = 0; i < n; i += 16) { ++ /* Is this line the same as the last line? (Squashing) */ ++ same = ++ offset + i > 0 && /* first line is never squashed */ ++ offset + i + 16 < size && /* last line is never squashed */ ++ memcmp (&buffer[i], last, 16) == 0; ++ if (same) { ++ if (!printed_splat) { ++ printf ("%s\n", splat); ++ printed_splat = true; ++ } ++ continue; ++ } ++ printed_splat = false; ++ memcpy (last, &buffer[i], 16); /* Save the current line. */ ++ ++ /* Print the offset. */ ++ ansi_green (); ++ printf ("%010zx", offset + i); ++ ansi_grey (); ++ printf (": "); ++ ++ /* Print the hex codes. */ ++ for (j = i; j < MIN (i+16, n); ++j) { ++ if (buffer[j]) ++ ansi_blue (); ++ else ++ ansi_grey (); ++ printf ("%02x ", buffer[j]); ++ } ++ ansi_grey (); ++ for (; j < i+16; ++j) ++ printf (" "); ++ ++ /* Print the ASCII codes. */ ++ printf ("%s", pipe); ++ for (j = i; j < MIN (i+16, n); ++j) { ++ char c = (char) buffer[j]; ++ if (isalnum (c)) { ++ ansi_red (); ++ printf ("%c", c); ++ } ++ else if (isprint (c)) { ++ ansi_magenta (); ++ printf ("%c", c); ++ } ++ else { ++ ansi_grey (); ++ printf ("%s", dot); ++ } ++ } ++ ansi_grey (); ++ for (; j < i+16; ++j) ++ printf (" "); ++ printf ("%s\n", pipe); ++ ansi_restore (); ++ } ++ ++ offset += n; ++ count -= n; ++ } ++} +diff --git a/dump/nbddump.pod b/dump/nbddump.pod +new file mode 100644 +index 0000000..5d7864d +--- /dev/null ++++ b/dump/nbddump.pod +@@ -0,0 +1,116 @@ ++=head1 NAME ++ ++nbddump - hexdump the content of a disk over NBD ++ ++=head1 SYNOPSIS ++ ++ nbddump NBD ++ ++C is an NBD URI or subprocess: ++ ++ NBD := nbd://... | nbd+unix:// (or other URI formats) ++ | [ CMD ARGS ... ] ++ ++=for paragraph ++ ++ nbddump --help ++ ++=for paragraph ++ ++ nbddump --version ++ ++=head1 DESCRIPTION ++ ++nbddump prints the content of a disk from an NBD server using the ++usual hexdump format: ++ ++ $ nbddump nbd://localhost ++ 0000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│ ++ 0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│ ++ ☆ ++ 0100: 68 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 │hello, world!···│ ++ 0110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │················│ ++ ☆ ++ 1000: 00 00 00 21 │···! │ ++ ++=head2 Output format ++ ++The first field (before the C<:>) is the offset within the file, in ++hexadecimal. ++ ++The second field shows the hex codes of bytes read from the file. ++ ++The third field shows the ASCII equivalent characters (if printable). ++ ++A splat character (C<☆>) indicates lines of repeated output which have ++been squashed. (Note this is not just for lines of zero bytes, but ++any case where the next line shown would be the same as the previous ++line.) ++ ++=head2 Subprocess ++ ++nbddump can also run an NBD server as a subprocess. This requires an ++NBD server which understands systemd socket activation, such as ++L or L. ++ ++For example, to dump out a qcow2 file as raw data: ++ ++ nbddump -- [ qemu-nbd -r -f qcow2 file.qcow2 ] ++ ++Note that S> are separate parameters, and must be ++surrounded by spaces. C<--> separates nbddump parameters from ++subprocess parameters. ++ ++=head1 OPTIONS ++ ++=over 4 ++ ++=item B<--help> ++ ++Display brief command line help and exit. ++ ++=item B<--color> ++ ++=item B<--colour> ++ ++=item B<--no-color> ++ ++=item B<--no-colour> ++ ++Enable or disable ANSI colours in output. By default we use colours ++if the output seems to be a terminal, and disable them if not. ++ ++=item B<--length=>N ++ ++=item B<-n> N ++ ++Dump up to I bytes and then stop. ++ ++=item B<-V> ++ ++=item B<--version> ++ ++Display the package name and version and exit. ++ ++=back ++ ++=head1 SEE ALSO ++ ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L. ++ ++=head1 AUTHORS ++ ++Richard W.M. Jones ++ ++=head1 COPYRIGHT ++ ++Copyright (C) 2022 Red Hat Inc. +diff --git a/dump/test-long-options.sh b/dump/test-long-options.sh +new file mode 100755 +index 0000000..924c8f5 +--- /dev/null ++++ b/dump/test-long-options.sh +@@ -0,0 +1,35 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2019-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test that nbddump --long-options looks sane. ++ ++. ../tests/functions.sh ++set -e ++set -x ++ ++output=test-long-options.out ++cleanup_fn rm -f $output ++ ++$VG nbddump --long-options > $output ++if [ $? != 0 ]; then ++ echo "$0: unexpected exit status" ++ fail=1 ++fi ++cat $output ++grep -- --length $output ++grep -- --version $output +diff --git a/dump/test-short-options.sh b/dump/test-short-options.sh +new file mode 100755 +index 0000000..325f7df +--- /dev/null ++++ b/dump/test-short-options.sh +@@ -0,0 +1,35 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2019-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test that nbddump --short-options looks sane. ++ ++. ../tests/functions.sh ++set -e ++set -x ++ ++output=test-short-options.out ++cleanup_fn rm -f $output ++ ++$VG nbddump --short-options > $output ++if [ $? != 0 ]; then ++ echo "$0: unexpected exit status" ++ fail=1 ++fi ++cat $output ++grep -- -n $output ++grep -- -V $output +diff --git a/dump/test-version.sh b/dump/test-version.sh +new file mode 100755 +index 0000000..fce4ed1 +--- /dev/null ++++ b/dump/test-version.sh +@@ -0,0 +1,33 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2019 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Test that nbddump --version looks sane. ++ ++fail=0 ++output=$($VG nbddump --version) ++if [ $? != 0 ]; then ++ echo "$0: unexpected exit status" ++ fail=1 ++fi ++if [ "$output" != "nbddump $EXPECTED_VERSION ++libnbd $EXPECTED_VERSION" ]; then ++ echo "$0: unexpected output" ++ fail=1 ++fi ++echo "$output" ++exit $fail +diff --git a/fuse/nbdfuse.pod b/fuse/nbdfuse.pod +index 7c1c817..daa79c1 100644 +--- a/fuse/nbdfuse.pod ++++ b/fuse/nbdfuse.pod +@@ -412,6 +412,7 @@ The differences from nbdfuse are similar to the list above. + + L, + L, ++L, + L, + L, + L, +diff --git a/info/nbdinfo.pod b/info/nbdinfo.pod +index 649cf1c..4733ecd 100644 +--- a/info/nbdinfo.pod ++++ b/info/nbdinfo.pod +@@ -407,6 +407,7 @@ Display the package name and version and exit. + + L, + L, ++L, + L, + L, + L, +diff --git a/run.in b/run.in +index 8a21906..2a171e5 100755 +--- a/run.in ++++ b/run.in +@@ -58,6 +58,7 @@ b="$(cd @abs_builddir@ && pwd)" + + # Set the PATH to contain all libnbd binaries. + prepend PATH "$b/copy" ++prepend PATH "$b/dump" + prepend PATH "$b/fuse" + prepend PATH "$b/info" + prepend PATH "$b/sh" +diff --git a/sh/nbdsh.pod b/sh/nbdsh.pod +index ca5d6af..c9dac4a 100644 +--- a/sh/nbdsh.pod ++++ b/sh/nbdsh.pod +@@ -147,6 +147,7 @@ L. + L, + L, + L, ++L, + L, + L, + L. +-- +2.31.1 + diff --git a/0001-copy-copy-nbd-to-sparse-file.sh-Skip-test-unless-nbd.patch b/0001-copy-copy-nbd-to-sparse-file.sh-Skip-test-unless-nbd.patch new file mode 100644 index 0000000..173aae4 --- /dev/null +++ b/0001-copy-copy-nbd-to-sparse-file.sh-Skip-test-unless-nbd.patch @@ -0,0 +1,30 @@ +From 486799e853aa9df034366303230a1785087a507a Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Fri, 8 Jan 2021 12:14:18 +0000 +Subject: [PATCH] copy/copy-nbd-to-sparse-file.sh: Skip test unless nbdkit + available. + +This test used nbdkit without checking it is available, which broke +the test on RHEL 8 i686. + +Fixes: commit 28fe8d9d8d1ecb491070d20f22e2f34bb147f19f +(cherry picked from commit 781cb44b63a87f2d5f40590ab8c446ad2e7b6702) +--- + copy/copy-nbd-to-sparse-file.sh | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/copy/copy-nbd-to-sparse-file.sh b/copy/copy-nbd-to-sparse-file.sh +index aa2cb1b..47ff09a 100755 +--- a/copy/copy-nbd-to-sparse-file.sh ++++ b/copy/copy-nbd-to-sparse-file.sh +@@ -24,6 +24,7 @@ set -x + requires cmp --version + requires dd --version + requires dd oflag=seek_bytes +Date: Thu, 30 Jun 2022 21:09:39 +0100 +Subject: [PATCH] dump: Visually separate columns 0-7 and 8-15 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Before: + +0000090000: 68 65 72 65 20 77 65 20 61 72 65 00 68 65 72 65 │... +0000090010: 20 77 65 20 61 72 65 00 68 65 72 65 20 77 65 20 │... +0000090020: 61 72 65 00 68 65 72 65 20 77 65 20 61 72 65 00 │... + +After: + +0000090000: 68 65 72 65 20 77 65 20 61 72 65 00 68 65 72 65 │... +0000090010: 20 77 65 20 61 72 65 00 68 65 72 65 20 77 65 20 │... +0000090020: 61 72 65 00 68 65 72 65 20 77 65 20 61 72 65 00 │... + +Updates: commit c4107b9a40d6451630dcccf1bf6596c8e56420be +(cherry picked from commit 315a637d3eae003c1d84eb1b88a7b47b534f1e80) +--- + dump/dump-data.sh | 22 +++++++++++----------- + dump/dump-empty-qcow2.sh | 4 ++-- + dump/dump-pattern.sh | 38 +++++++++++++++++++------------------- + dump/dump.c | 5 ++++- + 4 files changed, 36 insertions(+), 33 deletions(-) + +diff --git a/dump/dump-data.sh b/dump/dump-data.sh +index 23d09da..955cd3b 100755 +--- a/dump/dump-data.sh ++++ b/dump/dump-data.sh +@@ -37,21 +37,21 @@ nbdkit -U - data data=' + + cat $output + +-if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-0000008000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +-0000008010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++0000008000: 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++0000008010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-000000fff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 |...............h| +-0000010000: 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 00 |ello, world!....| +-0000010010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++000000fff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 68 |...............h| ++0000010000: 65 6c 6c 6f 2c 20 77 6f 72 6c 64 21 00 00 00 00 |ello, world!....| ++0000010010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-00010ffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 73 70 |..............sp| +-0001100000: 61 6e 6e 69 6e 67 20 62 75 66 66 65 72 20 62 6f |anning buffer bo| +-0001100010: 75 6e 64 61 72 79 00 00 00 00 00 00 00 00 00 00 |undary..........| +-0001100020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++00010ffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 73 70 |..............sp| ++0001100000: 61 6e 6e 69 6e 67 20 62 75 66 66 65 72 20 62 6f |anning buffer bo| ++0001100010: 75 6e 64 61 72 79 00 00 00 00 00 00 00 00 00 00 |undary..........| ++0001100020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-0001312d00: 00 |. |' ]; then ++0001312d00: 00 |. |' ]; then + echo "$0: unexpected output from nbddump command" + exit 1 + fi +diff --git a/dump/dump-empty-qcow2.sh b/dump/dump-empty-qcow2.sh +index c9e583b..472b6eb 100755 +--- a/dump/dump-empty-qcow2.sh ++++ b/dump/dump-empty-qcow2.sh +@@ -38,9 +38,9 @@ qemu-img create -f qcow2 $file $size + nbddump -- [ $QEMU_NBD -r -f qcow2 $file ] > $output + cat $output + +-if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-003ffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|' ]; then ++003ffffff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|' ]; then + echo "$0: unexpected output from nbddump command" + exit 1 + fi +diff --git a/dump/dump-pattern.sh b/dump/dump-pattern.sh +index e4016a8..d512b77 100755 +--- a/dump/dump-pattern.sh ++++ b/dump/dump-pattern.sh +@@ -32,25 +32,25 @@ nbdkit -U - pattern size=299 --run 'nbddump "$uri"' > $output + + cat $output + +-if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 |................| +-0000000010: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 18 |................| +-0000000020: 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 28 |....... .......(| +-0000000030: 00 00 00 00 00 00 00 30 00 00 00 00 00 00 00 38 |.......0.......8| +-0000000040: 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 |.......@.......H| +-0000000050: 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 58 |.......P.......X| +-0000000060: 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 68 |.......`.......h| +-0000000070: 00 00 00 00 00 00 00 70 00 00 00 00 00 00 00 78 |.......p.......x| +-0000000080: 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 88 |................| +-0000000090: 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 98 |................| +-00000000a0: 00 00 00 00 00 00 00 a0 00 00 00 00 00 00 00 a8 |................| +-00000000b0: 00 00 00 00 00 00 00 b0 00 00 00 00 00 00 00 b8 |................| +-00000000c0: 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 00 c8 |................| +-00000000d0: 00 00 00 00 00 00 00 d0 00 00 00 00 00 00 00 d8 |................| +-00000000e0: 00 00 00 00 00 00 00 e0 00 00 00 00 00 00 00 e8 |................| +-00000000f0: 00 00 00 00 00 00 00 f0 00 00 00 00 00 00 00 f8 |................| +-0000000100: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 08 |................| +-0000000110: 00 00 00 00 00 00 01 10 00 00 00 00 00 00 01 18 |................| +-0000000120: 00 00 00 00 00 00 01 20 00 00 00 |....... ... |' ]; then ++if [ "$(cat $output)" != '0000000000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 08 |................| ++0000000010: 00 00 00 00 00 00 00 10 00 00 00 00 00 00 00 18 |................| ++0000000020: 00 00 00 00 00 00 00 20 00 00 00 00 00 00 00 28 |....... .......(| ++0000000030: 00 00 00 00 00 00 00 30 00 00 00 00 00 00 00 38 |.......0.......8| ++0000000040: 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00 48 |.......@.......H| ++0000000050: 00 00 00 00 00 00 00 50 00 00 00 00 00 00 00 58 |.......P.......X| ++0000000060: 00 00 00 00 00 00 00 60 00 00 00 00 00 00 00 68 |.......`.......h| ++0000000070: 00 00 00 00 00 00 00 70 00 00 00 00 00 00 00 78 |.......p.......x| ++0000000080: 00 00 00 00 00 00 00 80 00 00 00 00 00 00 00 88 |................| ++0000000090: 00 00 00 00 00 00 00 90 00 00 00 00 00 00 00 98 |................| ++00000000a0: 00 00 00 00 00 00 00 a0 00 00 00 00 00 00 00 a8 |................| ++00000000b0: 00 00 00 00 00 00 00 b0 00 00 00 00 00 00 00 b8 |................| ++00000000c0: 00 00 00 00 00 00 00 c0 00 00 00 00 00 00 00 c8 |................| ++00000000d0: 00 00 00 00 00 00 00 d0 00 00 00 00 00 00 00 d8 |................| ++00000000e0: 00 00 00 00 00 00 00 e0 00 00 00 00 00 00 00 e8 |................| ++00000000f0: 00 00 00 00 00 00 00 f0 00 00 00 00 00 00 00 f8 |................| ++0000000100: 00 00 00 00 00 00 01 00 00 00 00 00 00 00 01 08 |................| ++0000000110: 00 00 00 00 00 00 01 10 00 00 00 00 00 00 01 18 |................| ++0000000120: 00 00 00 00 00 00 01 20 00 00 00 |....... ... |' ]; then + echo "$0: unexpected output from nbddump command" + exit 1 + fi +diff --git a/dump/dump.c b/dump/dump.c +index 76af04c..7818f1f 100644 +--- a/dump/dump.c ++++ b/dump/dump.c +@@ -429,10 +429,13 @@ do_dump (void) + else + ansi_grey (); + printf ("%02x ", buffer[j]); ++ if ((j - i) == 7) printf (" "); + } + ansi_grey (); +- for (; j < i+16; ++j) ++ for (; j < i+16; ++j) { + printf (" "); ++ if ((j - i) == 7) printf (" "); ++ } + + /* Print the ASCII codes. */ + printf ("%s", pipe); +-- +2.31.1 + diff --git a/0002-generator-Refactor-CONNECT.START-state.patch b/0002-generator-Refactor-CONNECT.START-state.patch new file mode 100644 index 0000000..ca013dc --- /dev/null +++ b/0002-generator-Refactor-CONNECT.START-state.patch @@ -0,0 +1,57 @@ +From 5dc2d2261224c9533d2b5ec4df6ed822de4cfc3b Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 4 Feb 2021 17:57:06 +0000 +Subject: [PATCH] generator: Refactor CONNECT.START state. + +Small, neutral refactoring to the CONNECT.START to make the subsequent +commit easier. + +(cherry picked from commit cd231fd94bbfaacdd9b89e7d355ba2bbc83c2aeb) +--- + generator/states-connect.c | 21 ++++++++++----------- + 1 file changed, 10 insertions(+), 11 deletions(-) + +diff --git a/generator/states-connect.c b/generator/states-connect.c +index 392879d..03b34c7 100644 +--- a/generator/states-connect.c ++++ b/generator/states-connect.c +@@ -47,11 +47,12 @@ disable_nagle (int sock) + + STATE_MACHINE { + CONNECT.START: +- int fd; ++ sa_family_t family; ++ int fd, r; + + assert (!h->sock); +- fd = socket (h->connaddr.ss_family, +- SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); ++ family = h->connaddr.ss_family; ++ fd = socket (family, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0); + if (fd == -1) { + SET_NEXT_STATE (%.DEAD); + set_error (errno, "socket"); +@@ -65,14 +66,12 @@ STATE_MACHINE { + + disable_nagle (fd); + +- if (connect (fd, (struct sockaddr *) &h->connaddr, +- h->connaddrlen) == -1) { +- if (errno != EINPROGRESS) { +- SET_NEXT_STATE (%.DEAD); +- set_error (errno, "connect"); +- return 0; +- } +- } ++ r = connect (fd, (struct sockaddr *) &h->connaddr, h->connaddrlen); ++ if (r == 0 || (r == -1 && errno == EINPROGRESS)) ++ return 0; ++ assert (r == -1); ++ SET_NEXT_STATE (%.DEAD); ++ set_error (errno, "connect"); + return 0; + + CONNECT.CONNECTING: +-- +2.31.1 + diff --git a/0003-dump-Fix-build-on-i686.patch b/0003-dump-Fix-build-on-i686.patch new file mode 100644 index 0000000..7ec277e --- /dev/null +++ b/0003-dump-Fix-build-on-i686.patch @@ -0,0 +1,38 @@ +From 590e3a010d2c840314702883e44ec9841e3383c6 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 30 Jun 2022 22:27:43 +0100 +Subject: [PATCH] dump: Fix build on i686 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Because we used the wrong printf format, the build would fail on +32 bit architectures but succeed on 64 bit: + +dump.c: In function ‘do_dump’: +dump.c:421:21: error: format ‘%zx’ expects argument of type ‘size_t’, but argument 2 has type ‘uint64_t’ {aka ‘long long unsigned int’} [-Werror=format=] + printf ("%010zx", offset + i); + ~~~~~^ ~~~~~~~~~~ + %010llx + +(cherry picked from commit ce004c329c7fcd6c60d11673b7a5c5ce3414413b) +--- + dump/dump.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/dump/dump.c b/dump/dump.c +index 7818f1f..8bf62f9 100644 +--- a/dump/dump.c ++++ b/dump/dump.c +@@ -418,7 +418,7 @@ do_dump (void) + + /* Print the offset. */ + ansi_green (); +- printf ("%010zx", offset + i); ++ printf ("%010" PRIx64, offset + i); + ansi_grey (); + printf (": "); + +-- +2.31.1 + diff --git a/0003-generator-Print-a-better-error-message-if-connect-2-.patch b/0003-generator-Print-a-better-error-message-if-connect-2-.patch new file mode 100644 index 0000000..6ac2a69 --- /dev/null +++ b/0003-generator-Print-a-better-error-message-if-connect-2-.patch @@ -0,0 +1,48 @@ +From f094472efcf34cea8bf1f02a1c5c9442ffc4ca53 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 4 Feb 2021 18:02:46 +0000 +Subject: [PATCH] generator: Print a better error message if connect(2) returns + EAGAIN. + +The new error message is: + +nbd_connect_unix: connect: server backlog overflowed, see https://bugzilla.redhat.com/1925045: Resource temporarily unavailable + +Fixes: https://bugzilla.redhat.com/1925045 +Thanks: Xin Long, Lukas Doktor, Eric Blake +Reviewed-by: Martin Kletzander +(cherry picked from commit 85ed74960a658a82d7b61b0be07f43d1b2dcede9) +--- + generator/states-connect.c | 16 ++++++++++++++++ + 1 file changed, 16 insertions(+) + +diff --git a/generator/states-connect.c b/generator/states-connect.c +index 03b34c7..98c26e5 100644 +--- a/generator/states-connect.c ++++ b/generator/states-connect.c +@@ -70,6 +70,22 @@ STATE_MACHINE { + if (r == 0 || (r == -1 && errno == EINPROGRESS)) + return 0; + assert (r == -1); ++#ifdef __linux__ ++ if (errno == EAGAIN && family == AF_UNIX) { ++ /* This can happen on Linux when connecting to a Unix domain ++ * socket, if the server's backlog is full. Unfortunately there ++ * is nothing good we can do on the client side when this happens ++ * since any solution would involve sleeping or busy-waiting. The ++ * only solution is on the server side, increasing the backlog. ++ * But at least improve the error message. ++ * https://bugzilla.redhat.com/1925045 ++ */ ++ SET_NEXT_STATE (%.DEAD); ++ set_error (errno, "connect: server backlog overflowed, " ++ "see https://bugzilla.redhat.com/1925045"); ++ return 0; ++ } ++#endif + SET_NEXT_STATE (%.DEAD); + set_error (errno, "connect"); + return 0; +-- +2.31.1 + diff --git a/0004-dump-Fix-tests-on-Debian-10.patch b/0004-dump-Fix-tests-on-Debian-10.patch new file mode 100644 index 0000000..cdb908a --- /dev/null +++ b/0004-dump-Fix-tests-on-Debian-10.patch @@ -0,0 +1,41 @@ +From e7a2815412891d5c13b5b5f0e9aa61882880c87f Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 30 Jun 2022 22:31:00 +0100 +Subject: [PATCH] dump: Fix tests on Debian 10 + +The version of nbdkit on Debian 10 does not set $uri. Check for this +or skip the test. + +(cherry picked from commit 083b1ca30fb5e6e0dc0e4b0eea9ebe8474d3f864) +--- + dump/dump-data.sh | 1 + + dump/dump-pattern.sh | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/dump/dump-data.sh b/dump/dump-data.sh +index 955cd3b..46e4d1e 100755 +--- a/dump/dump-data.sh ++++ b/dump/dump-data.sh +@@ -23,6 +23,7 @@ set -x + + requires nbdkit --version + requires nbdkit data --dump-plugin ++requires nbdkit -U - null --run 'test "$uri" != ""' + + output=dump-data.out + rm -f $output +diff --git a/dump/dump-pattern.sh b/dump/dump-pattern.sh +index d512b77..e2188ac 100755 +--- a/dump/dump-pattern.sh ++++ b/dump/dump-pattern.sh +@@ -23,6 +23,7 @@ set -x + + requires nbdkit --version + requires nbdkit pattern --dump-plugin ++requires nbdkit -U - null --run 'test "$uri" != ""' + + output=dump-pattern.out + rm -f $output +-- +2.31.1 + diff --git a/0004-opt_go-Tolerate-unplanned-server-death.patch b/0004-opt_go-Tolerate-unplanned-server-death.patch new file mode 100644 index 0000000..9080ea6 --- /dev/null +++ b/0004-opt_go-Tolerate-unplanned-server-death.patch @@ -0,0 +1,59 @@ +From ffe8f0a994c1f2656aa011353b386663d32db69e Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Mon, 1 Mar 2021 15:25:31 -0600 +Subject: [PATCH] opt_go: Tolerate unplanned server death + +While debugging some experimental nbdkit code that was triggering an +assertion failure in nbdkit, I noticed a secondary failure of nbdsh +also dying from an assertion: + +libnbd: debug: nbdsh: nbd_opt_go: transition: NEWSTYLE.OPT_GO.SEND -> DEAD +libnbd: debug: nbdsh: nbd_opt_go: option queued, ignoring state machine failure +nbdsh: opt.c:86: nbd_unlocked_opt_go: Assertion `nbd_internal_is_state_negotiating (get_next_state (h))' failed. + +Although my trigger was from non-production nbdkit code, libnbd should +never die from an assertion failure merely because a server +disappeared at the wrong moment during an incomplete reply to +NBD_OPT_GO or NBD_OPT_INFO. If this is assigned a CVE, a followup +patch will add mention of it in docs/libnbd-security.pod. + +Fixes: bbf1c51392 (api: Give aio_opt_go a completion callback) +(cherry picked from commit fb4440de9cc76e9c14bd3ddf3333e78621f40ad0) +--- + lib/opt.c | 8 +++++--- + 1 file changed, 5 insertions(+), 3 deletions(-) + +diff --git a/lib/opt.c b/lib/opt.c +index 2317b72..e5802f4 100644 +--- a/lib/opt.c ++++ b/lib/opt.c +@@ -1,5 +1,5 @@ + /* NBD client library in userspace +- * Copyright (C) 2020 Red Hat Inc. ++ * Copyright (C) 2020-2021 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -83,7 +83,8 @@ nbd_unlocked_opt_go (struct nbd_handle *h) + + r = wait_for_option (h); + if (r == 0 && err) { +- assert (nbd_internal_is_state_negotiating (get_next_state (h))); ++ assert (nbd_internal_is_state_negotiating (get_next_state (h)) || ++ nbd_internal_is_state_dead (get_next_state (h))); + set_error (err, "server replied with error to opt_go request"); + return -1; + } +@@ -105,7 +106,8 @@ nbd_unlocked_opt_info (struct nbd_handle *h) + + r = wait_for_option (h); + if (r == 0 && err) { +- assert (nbd_internal_is_state_negotiating (get_next_state (h))); ++ assert (nbd_internal_is_state_negotiating (get_next_state (h)) || ++ nbd_internal_is_state_dead (get_next_state (h))); + set_error (err, "server replied with error to opt_info request"); + return -1; + } +-- +2.31.1 + diff --git a/0005-dump-dump-data.sh-Test-requires-nbdkit-1.22.patch b/0005-dump-dump-data.sh-Test-requires-nbdkit-1.22.patch new file mode 100644 index 0000000..d868281 --- /dev/null +++ b/0005-dump-dump-data.sh-Test-requires-nbdkit-1.22.patch @@ -0,0 +1,31 @@ +From 7c669783b1b3fab902ce34d7914b62617ed8b263 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 30 Jun 2022 22:35:05 +0100 +Subject: [PATCH] dump/dump-data.sh: Test requires nbdkit 1.22 + +Ubuntu 20.04 has nbdkit 1.16 which lacks support for strings. These +were added in nbdkit 1.22. + +(cherry picked from commit a8fa05ffb8b85f41276ffb52498e4528c08e5f21) +--- + dump/dump-data.sh | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/dump/dump-data.sh b/dump/dump-data.sh +index 46e4d1e..11145b0 100755 +--- a/dump/dump-data.sh ++++ b/dump/dump-data.sh +@@ -25,6 +25,10 @@ requires nbdkit --version + requires nbdkit data --dump-plugin + requires nbdkit -U - null --run 'test "$uri" != ""' + ++# This test requires nbdkit >= 1.22. ++minor=$( nbdkit --dump-config | grep ^version_minor | cut -d= -f2 ) ++requires test $minor -ge 22 ++ + output=dump-data.out + rm -f $output + cleanup_fn rm -f $output +-- +2.31.1 + diff --git a/0005-security-Document-assignment-of-CVE-2021-20286.patch b/0005-security-Document-assignment-of-CVE-2021-20286.patch new file mode 100644 index 0000000..8732515 --- /dev/null +++ b/0005-security-Document-assignment-of-CVE-2021-20286.patch @@ -0,0 +1,40 @@ +From 171ffdde8be590f784086a021a7e6f36c4ecdb4b Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Fri, 12 Mar 2021 17:00:58 -0600 +Subject: [PATCH] security: Document assignment of CVE-2021-20286 + +Now that we finally have a CVE number, it's time to document +the problem (it's low severity, but still a denial of service). + +Fixes: fb4440de9cc7 (opt_go: Tolerate unplanned server death) +(cherry picked from commit 40308a005eaa6b2e8f98da8952d0c0cacc51efde) +--- + docs/libnbd-security.pod | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/docs/libnbd-security.pod b/docs/libnbd-security.pod +index d8ead87..0cae846 100644 +--- a/docs/libnbd-security.pod ++++ b/docs/libnbd-security.pod +@@ -22,6 +22,12 @@ L + See the full announcement here: + L + ++=head2 CVE-2021-20286 ++denial of service when using L ++ ++See the full announcement here: ++L ++ + =head1 SEE ALSO + + L. +@@ -34,4 +40,4 @@ Richard W.M. Jones + + =head1 COPYRIGHT + +-Copyright (C) 2019 Red Hat Inc. ++Copyright (C) 2019-2021 Red Hat Inc. +-- +2.31.1 + diff --git a/0006-copy-Pass-in-dummy-variable-rather-than-errno-to-cal.patch b/0006-copy-Pass-in-dummy-variable-rather-than-errno-to-cal.patch new file mode 100644 index 0000000..896876f --- /dev/null +++ b/0006-copy-Pass-in-dummy-variable-rather-than-errno-to-cal.patch @@ -0,0 +1,163 @@ +From 22572f8ac13e2e8daf91d227eac2f384303fb5b4 Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Thu, 3 Feb 2022 14:25:57 -0600 +Subject: [PATCH] copy: Pass in dummy variable rather than &errno to callback + +In several places where asynch handlers manually call the provided +nbd_completion_callback, the value of errno is indeterminate (for +example, in file-ops.c:file_asynch_read(), the previous call to +file_synch_read() already triggered exit() on error, but does not +guarantee what is left in errno on success). As the callback should +be paying attention to the value of *error (to be fixed in the next +patch), we are better off ensuring that we pass in a pointer to a +known-zero value. Besides, passing in &errno carries a risk that if +the callback uses any other library function that alters errno prior +to dereferncing *error, it will no longer see the value we passed in. +Thus, it is easier to use a dummy variable on the stack than to mess +around with errno and it's magic macro expansion into a thread-local +storage location. + +Note that several callsites then check if the callback returned -1, +and if so assume that the callback has caused errno to now have a sane +value to pass on to perror. In theory, the fact that we are no longer +passing in &errno means that if the callback assigns into *error but +did not otherwise affect errno (a tenuous assumption, given our +argument above that we could not even guarantee that the callback does +not accidentally alter errno prior to reading *error), our perror call +would no longer reflect the intended error value from the callback. +But in practice, since the callback never actually returned -1, nor +even assigned into *error, the call to perror is dead code; although I +have chosen to defer that additional cleanup to the next patch. + +Message-Id: <20220203202558.203013-5-eblake@redhat.com> +Acked-by: Richard W.M. Jones +Acked-by: Nir Soffer +Reviewed-by: Laszlo Ersek +(cherry picked from commit 794c8ce06e995ebd282e8f2b9465a06140572112) +Conflicts: + copy/file-ops.c - no backport of d5f65e56 ("copy: Do not use trim + for zeroing"), so asynch_trim needed same treatment + copy/multi-thread-copying.c - context due to missing refactoring + copy/null-ops.c - no backport of 0b16205e "copy: Implement "null:" + destination." +(cherry picked from commit 26e3dcf80815fe2db320d3046aabc2580c2f7a0d) +--- + copy/file-ops.c | 22 +++++++++++++--------- + copy/multi-thread-copying.c | 8 +++++--- + 2 files changed, 18 insertions(+), 12 deletions(-) + +diff --git a/copy/file-ops.c b/copy/file-ops.c +index 086348a..cc312b4 100644 +--- a/copy/file-ops.c ++++ b/copy/file-ops.c +@@ -1,5 +1,5 @@ + /* NBD client library in userspace. +- * Copyright (C) 2020 Red Hat Inc. ++ * Copyright (C) 2020-2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -158,10 +158,11 @@ file_asynch_read (struct rw *rw, + struct command *command, + nbd_completion_callback cb) + { ++ int dummy = 0; ++ + file_synch_read (rw, slice_ptr (command->slice), + command->slice.len, command->offset); +- errno = 0; +- if (cb.callback (cb.user_data, &errno) == -1) { ++ if (cb.callback (cb.user_data, &dummy) == -1) { + perror (rw->name); + exit (EXIT_FAILURE); + } +@@ -172,10 +173,11 @@ file_asynch_write (struct rw *rw, + struct command *command, + nbd_completion_callback cb) + { ++ int dummy = 0; ++ + file_synch_write (rw, slice_ptr (command->slice), + command->slice.len, command->offset); +- errno = 0; +- if (cb.callback (cb.user_data, &errno) == -1) { ++ if (cb.callback (cb.user_data, &dummy) == -1) { + perror (rw->name); + exit (EXIT_FAILURE); + } +@@ -185,10 +187,11 @@ static bool + file_asynch_trim (struct rw *rw, struct command *command, + nbd_completion_callback cb) + { ++ int dummy = 0; ++ + if (!file_synch_trim (rw, command->offset, command->slice.len)) + return false; +- errno = 0; +- if (cb.callback (cb.user_data, &errno) == -1) { ++ if (cb.callback (cb.user_data, &dummy) == -1) { + perror (rw->name); + exit (EXIT_FAILURE); + } +@@ -199,10 +202,11 @@ static bool + file_asynch_zero (struct rw *rw, struct command *command, + nbd_completion_callback cb) + { ++ int dummy = 0; ++ + if (!file_synch_zero (rw, command->offset, command->slice.len)) + return false; +- errno = 0; +- if (cb.callback (cb.user_data, &errno) == -1) { ++ if (cb.callback (cb.user_data, &dummy) == -1) { + perror (rw->name); + exit (EXIT_FAILURE); + } +diff --git a/copy/multi-thread-copying.c b/copy/multi-thread-copying.c +index a7aaa7d..2593ff7 100644 +--- a/copy/multi-thread-copying.c ++++ b/copy/multi-thread-copying.c +@@ -1,5 +1,5 @@ + /* NBD client library in userspace. +- * Copyright (C) 2020 Red Hat Inc. ++ * Copyright (C) 2020-2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -391,6 +391,7 @@ finished_read (void *vp, int *error) + bool last_is_hole = false; + uint64_t i; + struct command *newcommand; ++ int dummy = 0; + + /* Iterate over whole blocks in the command, starting on a block + * boundary. +@@ -473,7 +474,7 @@ finished_read (void *vp, int *error) + /* Free the original command since it has been split into + * subcommands and the original is no longer needed. + */ +- free_command (command, &errno); ++ free_command (command, &dummy); + } + + return 1; /* auto-retires the command */ +@@ -498,6 +499,7 @@ static void + fill_dst_range_with_zeroes (struct command *command) + { + char *data; ++ int dummy = 0; + + if (destination_is_zero) + goto free_and_return; +@@ -541,7 +543,7 @@ fill_dst_range_with_zeroes (struct command *command) + free (data); + + free_and_return: +- free_command (command, &errno); ++ free_command (command, &dummy); + } + + static int +-- +2.31.1 + diff --git a/0006-copy-Store-the-preferred-block-size-in-the-operation.patch b/0006-copy-Store-the-preferred-block-size-in-the-operation.patch new file mode 100644 index 0000000..893a026 --- /dev/null +++ b/0006-copy-Store-the-preferred-block-size-in-the-operation.patch @@ -0,0 +1,163 @@ +From 8dce43a3ea7a529bc37cbe5607a8d52186cc8169 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Jun 2022 18:27:58 +0100 +Subject: [PATCH] copy: Store the preferred block size in the operations struct + +This will be used in a subsequent commit. At the moment the preferred +block size for all sources / destinations is simply calculated and +stored. + +(cherry picked from commit e6c42f8b2d447bbcc659d6dd33be67335834b2e5) +--- + copy/file-ops.c | 4 +++- + copy/main.c | 29 +++++++++++++++++++++++------ + copy/nbd-ops.c | 10 ++++++++++ + copy/nbdcopy.h | 4 +++- + copy/null-ops.c | 1 + + copy/pipe-ops.c | 1 + + 6 files changed, 41 insertions(+), 8 deletions(-) + +diff --git a/copy/file-ops.c b/copy/file-ops.c +index ab37875..34f08e5 100644 +--- a/copy/file-ops.c ++++ b/copy/file-ops.c +@@ -241,13 +241,15 @@ seek_hole_supported (int fd) + + struct rw * + file_create (const char *name, int fd, +- off_t st_size, bool is_block, direction d) ++ off_t st_size, uint64_t preferred, ++ bool is_block, direction d) + { + struct rw_file *rwf = calloc (1, sizeof *rwf); + if (rwf == NULL) { perror ("calloc"); exit (EXIT_FAILURE); } + + rwf->rw.ops = &file_ops; + rwf->rw.name = name; ++ rwf->rw.preferred = preferred; + rwf->fd = fd; + rwf->is_block = is_block; + +diff --git a/copy/main.c b/copy/main.c +index cc379e9..19ec384 100644 +--- a/copy/main.c ++++ b/copy/main.c +@@ -512,10 +512,26 @@ open_local (const char *filename, direction d) + fprintf (stderr, "%s: %s: %m\n", prog, filename); + exit (EXIT_FAILURE); + } +- if (S_ISBLK (stat.st_mode) || S_ISREG (stat.st_mode)) +- return file_create (filename, fd, stat.st_size, S_ISBLK (stat.st_mode), d); +- else { +- /* Probably stdin/stdout, a pipe or a socket. */ ++ if (S_ISREG (stat.st_mode)) /* Regular file. */ ++ return file_create (filename, fd, ++ stat.st_size, (uint64_t) stat.st_blksize, false, d); ++ else if (S_ISBLK (stat.st_mode)) { /* Block device. */ ++ unsigned int blkioopt; ++ ++#ifdef BLKIOOPT ++ if (ioctl (fd, BLKIOOPT, &blkioopt) == -1) { ++ fprintf (stderr, "warning: cannot get optimal I/O size: %s: %m", ++ filename); ++ blkioopt = 4096; ++ } ++#else ++ blkioopt = 4096; ++#endif ++ ++ return file_create (filename, fd, ++ stat.st_size, (uint64_t) blkioopt, true, d); ++ } ++ else { /* Probably stdin/stdout, a pipe or a socket. */ + synchronous = true; /* Force synchronous mode for pipes. */ + return pipe_create (filename, fd); + } +@@ -528,8 +544,9 @@ print_rw (struct rw *rw, const char *prefix, FILE *fp) + char buf[HUMAN_SIZE_LONGEST]; + + fprintf (fp, "%s: %s \"%s\"\n", prefix, rw->ops->ops_name, rw->name); +- fprintf (fp, "%s: size=%" PRIi64 " (%s)\n", +- prefix, rw->size, human_size (buf, rw->size, NULL)); ++ fprintf (fp, "%s: size=%" PRIi64 " (%s), preferred block size=%" PRIu64 "\n", ++ prefix, rw->size, human_size (buf, rw->size, NULL), ++ rw->preferred); + } + + /* Default implementation of rw->ops->get_extents for backends which +diff --git a/copy/nbd-ops.c b/copy/nbd-ops.c +index 3bc26ba..0988634 100644 +--- a/copy/nbd-ops.c ++++ b/copy/nbd-ops.c +@@ -112,12 +112,22 @@ open_one_nbd_handle (struct rw_nbd *rwn) + * the same way. + */ + if (rwn->handles.len == 0) { ++ int64_t block_size; ++ + rwn->can_zero = nbd_can_zero (nbd) > 0; ++ + rwn->rw.size = nbd_get_size (nbd); + if (rwn->rw.size == -1) { + fprintf (stderr, "%s: %s: %s\n", prog, rwn->rw.name, nbd_get_error ()); + exit (EXIT_FAILURE); + } ++ ++ block_size = nbd_get_block_size (nbd, LIBNBD_SIZE_PREFERRED); ++ if (block_size == -1) { ++ fprintf (stderr, "%s: %s: %s\n", prog, rwn->rw.name, nbd_get_error ()); ++ exit (EXIT_FAILURE); ++ } ++ rwn->rw.preferred = block_size == 0 ? 4096 : block_size; + } + + if (handles_append (&rwn->handles, nbd) == -1) { +diff --git a/copy/nbdcopy.h b/copy/nbdcopy.h +index 19797df..9438cce 100644 +--- a/copy/nbdcopy.h ++++ b/copy/nbdcopy.h +@@ -43,6 +43,7 @@ struct rw { + struct rw_ops *ops; /* Operations. */ + const char *name; /* Printable name, for error messages etc. */ + int64_t size; /* May be -1 for streams. */ ++ uint64_t preferred; /* Preferred block size. */ + /* Followed by private data for the particular subtype. */ + }; + +@@ -53,7 +54,8 @@ typedef enum { READING, WRITING } direction; + + /* Create subtypes. */ + extern struct rw *file_create (const char *name, int fd, +- off_t st_size, bool is_block, direction d); ++ off_t st_size, uint64_t preferred, ++ bool is_block, direction d); + extern struct rw *nbd_rw_create_uri (const char *name, + const char *uri, direction d); + extern struct rw *nbd_rw_create_subprocess (const char **argv, size_t argc, +diff --git a/copy/null-ops.c b/copy/null-ops.c +index 1218a62..99cc9a7 100644 +--- a/copy/null-ops.c ++++ b/copy/null-ops.c +@@ -45,6 +45,7 @@ null_create (const char *name) + rw->rw.ops = &null_ops; + rw->rw.name = name; + rw->rw.size = INT64_MAX; ++ rw->rw.preferred = 4096; + return &rw->rw; + } + +diff --git a/copy/pipe-ops.c b/copy/pipe-ops.c +index 3c8b6c2..3815f82 100644 +--- a/copy/pipe-ops.c ++++ b/copy/pipe-ops.c +@@ -43,6 +43,7 @@ pipe_create (const char *name, int fd) + rwp->rw.ops = &pipe_ops; + rwp->rw.name = name; + rwp->rw.size = -1; ++ rwp->rw.preferred = 4096; + rwp->fd = fd; + return &rwp->rw; + } +-- +2.31.1 + diff --git a/0007-copy-CVE-2022-0485-Fail-nbdcopy-if-NBD-read-or-write.patch b/0007-copy-CVE-2022-0485-Fail-nbdcopy-if-NBD-read-or-write.patch new file mode 100644 index 0000000..b191e8b --- /dev/null +++ b/0007-copy-CVE-2022-0485-Fail-nbdcopy-if-NBD-read-or-write.patch @@ -0,0 +1,318 @@ +From 1b0b732e6a9b4979fccf6a09eb6704264edf675d Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Thu, 3 Feb 2022 14:25:58 -0600 +Subject: [PATCH] copy: CVE-2022-0485: Fail nbdcopy if NBD read or write fails + +nbdcopy has a nasty bug when performing multi-threaded copies using +asynchronous nbd calls - it was blindly treating the completion of an +asynchronous command as successful, rather than checking the *error +parameter. This can result in the silent creation of a corrupted +image in two different ways: when a read fails, we blindly wrote +garbage to the destination; when a write fails, we did not flag that +the destination was not written. + +Since nbdcopy already calls exit() on a synchronous read or write +failure to a file, doing the same for an asynchronous op to an NBD +server is the simplest solution. A nicer solution, but more invasive +to code and thus not done here, might be to allow up to N retries of +the transaction (in case the read or write failure was transient), or +even having a mode where as much data is copied as possible (portions +of the copy that failed would be logged on stderr, and nbdcopy would +still fail with a non-zero exit status, but this would copy more than +just stopping at the first error, as can be done with rsync or +ddrescue). + +Note that since we rely on auto-retiring and do NOT call +nbd_aio_command_completed, our completion callbacks must always return +1 (if they do not exit() first), even when acting on *error, so as not +leave the command allocated until nbd_close. As such, there is no +sane way to return an error to a manual caller of the callback, and +therefore we can drop dead code that calls perror() and exit() if the +callback "failed". It is also worth documenting the contract on when +we must manually call the callback during the asynch_zero callback, so +that we do not leak or double-free the command; thankfully, all the +existing code paths were correct. + +The added testsuite script demonstrates several scenarios, some of +which fail without the rest of this patch in place, and others which +showcase ways in which sparse images can bypass errors. + +Once backports are complete, a followup patch on the main branch will +edit docs/libnbd-security.pod with the mailing list announcement of +the stable branch commit ids and release versions that incorporate +this fix. + +Reported-by: Nir Soffer +Fixes: bc896eec4d ("copy: Implement multi-conn, multiple threads, multiple requests in flight.", v1.5.6) +Fixes: https://bugzilla.redhat.com/2046194 +Message-Id: <20220203202558.203013-6-eblake@redhat.com> +Acked-by: Richard W.M. Jones +Acked-by: Nir Soffer +[eblake: fix error message per Nir, tweak requires lines in unit test per Rich] +Reviewed-by: Laszlo Ersek + +(cherry picked from commit 8d444b41d09a700c7ee6f9182a649f3f2d325abb) +Conflicts: + copy/nbdcopy.h - copyright context + copy/null-ops.c - no backport of 0b16205e "copy: Implement "null:" + destination." + copy/copy-nbd-error.sh - no backport of d5f65e56 ("copy: Do not use + trim for zeroing"), so one test needed an additional error-trim-rate; + no backport of 4ff9e62d (copy: Add --request-size option") and friends, so + this version uses larger transactions, so change error rate of 0.5 to 1; + no backport of 0b16205e "copy: Implement "null:" destination.", so use + nbdkit null instead +Note that while the use of NBD_CMD_TRIM can create data corruption, it is +not as severe as what this patch fixes, since trim corruption will only +expose what had previously been on the disk, compared to this patch fixing +a potential leak of nbdcopy heap contents into the destination. +(cherry picked from commit 6c8f2f859926b82094fb5e85c446ea099700fa10) +--- + TODO | 1 + + copy/Makefile.am | 4 +- + copy/copy-nbd-error.sh | 81 +++++++++++++++++++++++++++++++++++++ + copy/file-ops.c | 17 +++----- + copy/multi-thread-copying.c | 13 ++++++ + copy/nbdcopy.h | 7 ++-- + 6 files changed, 107 insertions(+), 16 deletions(-) + create mode 100755 copy/copy-nbd-error.sh + +diff --git a/TODO b/TODO +index 510c219..19c21d4 100644 +--- a/TODO ++++ b/TODO +@@ -35,6 +35,7 @@ nbdcopy: + - Better page cache usage, see nbdkit-file-plugin options + fadvise=sequential cache=none. + - Consider io_uring if there are performance bottlenecks. ++ - Configurable retries in response to read or write failures. + + nbdfuse: + - If you write beyond the end of the virtual file, it returns EIO. +diff --git a/copy/Makefile.am b/copy/Makefile.am +index d318388..3406cd8 100644 +--- a/copy/Makefile.am ++++ b/copy/Makefile.am +@@ -1,5 +1,5 @@ + # nbd client library in userspace +-# Copyright (C) 2020 Red Hat Inc. ++# Copyright (C) 2020-2022 Red Hat Inc. + # + # This library is free software; you can redistribute it and/or + # modify it under the terms of the GNU Lesser General Public +@@ -30,6 +30,7 @@ EXTRA_DIST = \ + copy-nbd-to-small-nbd-error.sh \ + copy-nbd-to-sparse-file.sh \ + copy-nbd-to-stdout.sh \ ++ copy-nbd-error.sh \ + copy-progress-bar.sh \ + copy-sparse.sh \ + copy-sparse-allocated.sh \ +@@ -105,6 +106,7 @@ TESTS += \ + copy-nbd-to-sparse-file.sh \ + copy-stdin-to-nbd.sh \ + copy-nbd-to-stdout.sh \ ++ copy-nbd-error.sh \ + copy-progress-bar.sh \ + copy-sparse.sh \ + copy-sparse-allocated.sh \ +diff --git a/copy/copy-nbd-error.sh b/copy/copy-nbd-error.sh +new file mode 100755 +index 0000000..bba71db +--- /dev/null ++++ b/copy/copy-nbd-error.sh +@@ -0,0 +1,81 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++# Tests several scenarios of handling NBD server errors ++# Serves as a regression test for the CVE-2022-0485 fix. ++ ++. ../tests/functions.sh ++ ++set -e ++set -x ++ ++requires nbdkit --exit-with-parent --version ++requires nbdkit --filter=noextents null --version ++requires nbdkit --filter=error pattern --version ++requires nbdkit --filter=nozero memory --version ++ ++fail=0 ++ ++# Failure to get block status should not be fatal, but merely downgrade to ++# reading the entire image as if data ++echo "Testing extents failures on source" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v --filter=error pattern 5M \ ++ error-extents-rate=1 ] [ nbdkit --exit-with-parent -v null 5M ] || fail=1 ++ ++# Failure to read should be fatal ++echo "Testing read failures on non-sparse source" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v --filter=error pattern 5M \ ++ error-pread-rate=1 ] [ nbdkit --exit-with-parent -v null 5M ] && fail=1 ++ ++# However, reliable block status on a sparse image can avoid the need to read ++echo "Testing read failures on sparse source" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v --filter=error null 5M \ ++ error-pread-rate=1 ] [ nbdkit --exit-with-parent -v null 5M ] || fail=1 ++ ++# Failure to write data should be fatal ++echo "Testing write data failures on arbitrary destination" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v pattern 5M ] \ ++ [ nbdkit --exit-with-parent -v --filter=error --filter=noextents \ ++ memory 5M error-pwrite-rate=1 ] && fail=1 ++ ++# However, writing zeroes can bypass the need for normal writes ++echo "Testing write data failures from sparse source" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v null 5M ] \ ++ [ nbdkit --exit-with-parent -v --filter=error --filter=noextents \ ++ memory 5M error-pwrite-rate=1 ] || fail=1 ++ ++# Failure to write zeroes should be fatal ++echo "Testing write zero failures on arbitrary destination" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v null 5M ] \ ++ [ nbdkit --exit-with-parent -v --filter=error memory 5M \ ++ error-trim-rate=1 error-zero-rate=1 ] && fail=1 ++ ++# However, assuming/learning destination is zero can skip need to write ++echo "Testing write failures on pre-zeroed destination" ++$VG nbdcopy --destination-is-zero -- \ ++ [ nbdkit --exit-with-parent -v null 5M ] \ ++ [ nbdkit --exit-with-parent -v --filter=error memory 5M \ ++ error-pwrite-rate=1 error-zero-rate=1 ] || fail=1 ++ ++# Likewise, when write zero is not advertised, fallback to normal write works ++echo "Testing write zeroes to destination without zero support" ++$VG nbdcopy -- [ nbdkit --exit-with-parent -v null 5M ] \ ++ [ nbdkit --exit-with-parent -v --filter=nozero --filter=error memory 5M \ ++ error-zero-rate=1 ] || fail=1 ++ ++exit $fail +diff --git a/copy/file-ops.c b/copy/file-ops.c +index cc312b4..b19af04 100644 +--- a/copy/file-ops.c ++++ b/copy/file-ops.c +@@ -162,10 +162,8 @@ file_asynch_read (struct rw *rw, + + file_synch_read (rw, slice_ptr (command->slice), + command->slice.len, command->offset); +- if (cb.callback (cb.user_data, &dummy) == -1) { +- perror (rw->name); +- exit (EXIT_FAILURE); +- } ++ /* file_synch_read called exit() on error */ ++ cb.callback (cb.user_data, &dummy); + } + + static void +@@ -177,10 +175,8 @@ file_asynch_write (struct rw *rw, + + file_synch_write (rw, slice_ptr (command->slice), + command->slice.len, command->offset); +- if (cb.callback (cb.user_data, &dummy) == -1) { +- perror (rw->name); +- exit (EXIT_FAILURE); +- } ++ /* file_synch_write called exit() on error */ ++ cb.callback (cb.user_data, &dummy); + } + + static bool +@@ -206,10 +202,7 @@ file_asynch_zero (struct rw *rw, struct command *command, + + if (!file_synch_zero (rw, command->offset, command->slice.len)) + return false; +- if (cb.callback (cb.user_data, &dummy) == -1) { +- perror (rw->name); +- exit (EXIT_FAILURE); +- } ++ cb.callback (cb.user_data, &dummy); + return true; + } + +diff --git a/copy/multi-thread-copying.c b/copy/multi-thread-copying.c +index 2593ff7..28749ae 100644 +--- a/copy/multi-thread-copying.c ++++ b/copy/multi-thread-copying.c +@@ -28,6 +28,7 @@ + #include + #include + #include ++#include + + #include + +@@ -374,6 +375,12 @@ finished_read (void *vp, int *error) + { + struct command *command = vp; + ++ if (*error) { ++ fprintf (stderr, "read at offset %" PRId64 " failed: %s\n", ++ command->offset, strerror (*error)); ++ exit (EXIT_FAILURE); ++ } ++ + if (allocated || sparse_size == 0) { + /* If sparseness detection (see below) is turned off then we write + * the whole command. +@@ -552,6 +559,12 @@ free_command (void *vp, int *error) + struct command *command = vp; + struct buffer *buffer = command->slice.buffer; + ++ if (*error) { ++ fprintf (stderr, "write at offset %" PRId64 " failed: %s\n", ++ command->offset, strerror (*error)); ++ exit (EXIT_FAILURE); ++ } ++ + if (buffer != NULL) { + if (--buffer->refs == 0) { + free (buffer->data); +diff --git a/copy/nbdcopy.h b/copy/nbdcopy.h +index 3dcc6df..9626a52 100644 +--- a/copy/nbdcopy.h ++++ b/copy/nbdcopy.h +@@ -1,5 +1,5 @@ + /* NBD client library in userspace. +- * Copyright (C) 2020 Red Hat Inc. ++ * Copyright (C) 2020-2022 Red Hat Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public +@@ -134,7 +134,8 @@ struct rw_ops { + bool (*synch_zero) (struct rw *rw, uint64_t offset, uint64_t count); + + /* Asynchronous I/O operations. These start the operation and call +- * 'cb' on completion. ++ * 'cb' on completion. 'cb' will return 1, for auto-retiring with ++ * asynchronous libnbd calls. + * + * The file_ops versions are actually implemented synchronously, but + * still call 'cb'. +@@ -156,7 +157,7 @@ struct rw_ops { + nbd_completion_callback cb); + + /* Asynchronously zero. command->slice.buffer is not used. If not possible, +- * returns false. ++ * returns false. 'cb' must be called only if returning true. + */ + bool (*asynch_zero) (struct rw *rw, struct command *command, + nbd_completion_callback cb); +-- +2.31.1 + diff --git a/0007-copy-Use-preferred-block-size-for-copying.patch b/0007-copy-Use-preferred-block-size-for-copying.patch new file mode 100644 index 0000000..577f8f1 --- /dev/null +++ b/0007-copy-Use-preferred-block-size-for-copying.patch @@ -0,0 +1,457 @@ +From c8626acc63c4ae1c6cf5d1505e0209ac10f44e81 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 28 Jun 2022 21:58:55 +0100 +Subject: [PATCH] copy: Use preferred block size for copying + +You're not supposed to read or write NBD servers at a granularity less +than the advertised minimum block size. nbdcopy has ignored this +requirement, and this is usually fine because the NBD servers we care +about support 512-byte sector granularity, and never advertise sizes / +extents less granular than sectors (even if it's a bit suboptimal in a +few cases). + +However there is one new case where we do care: When writing to a +compressed qcow2 file, qemu advertises a minimum and preferred block +size of 64K, and it really means it. You cannot write blocks smaller +than this because of the way qcow2 compression is implemented. + +This commit attempts to do the least work possible to fix this. + +The previous multi-thread-copying loop was driven by the extent map +received from the source. I have modified the loop so that it +iterates over request_size blocks. request_size is set from the +command line (--request-size) but will be adjusted upwards if either +the source or destination preferred block size is larger. So this +will always copy blocks which are at least the preferred block size +(except for the very last block of the disk). + +While copying these blocks we consult the source extent map. If it +contains only zero regions covering the whole block (only_zeroes +function) then we can skip straight to zeroing the target +(fill_dst_range_with_zeroes), else we do read + write as before. + +I only modified the multi-thread-copying loop, not the synchronous +loop. That should be updated in the same way later. + +One side effect of this change is it always makes larger requests, +even for regions we know are sparse. This is clear in the +copy-sparse.sh and copy-sparse-allocated.sh tests which were +previously driven by the 32K sparse map granularity of the source. +Without changing these tests, they would make make 256K reads & writes +(and also read from areas of the disk even though we know they are +sparse). I adjusted these tests to use --request-size=32768 to force +the existing behaviour. + +Note this doesn't attempt to limit the maximum block size when reading +or writing. That is for future work. + +This is a partial fix for https://bugzilla.redhat.com/2047660. +Further changes will be required in virt-v2v. + +Link: https://lists.gnu.org/archive/html/qemu-block/2022-01/threads.html#00729 +Link: https://bugzilla.redhat.com/show_bug.cgi?id=2047660 +(cherry picked from commit 4058fe1ff03fb41156b67302ba1006b9d06b0218) +--- + TODO | 4 +- + copy/Makefile.am | 6 +- + copy/copy-file-to-qcow2-compressed.sh | 64 +++++++++++ + copy/copy-sparse-allocated.sh | 4 +- + copy/copy-sparse.sh | 7 +- + copy/main.c | 13 +++ + copy/multi-thread-copying.c | 149 +++++++++++++++++++------- + copy/nbdcopy.pod | 5 +- + 8 files changed, 202 insertions(+), 50 deletions(-) + create mode 100755 copy/copy-file-to-qcow2-compressed.sh + +diff --git a/TODO b/TODO +index 7c9c15e..bc38d70 100644 +--- a/TODO ++++ b/TODO +@@ -28,7 +28,9 @@ Performance: Chart it over various buffer sizes and threads, as that + Examine other fuzzers: https://gitlab.com/akihe/radamsa + + nbdcopy: +- - Minimum/preferred/maximum block size. ++ - Enforce maximum block size. ++ - Synchronous loop should be adjusted to take into account ++ the NBD preferred block size, as was done for multi-thread loop. + - Benchmark. + - Better page cache usage, see nbdkit-file-plugin options + fadvise=sequential cache=none. +diff --git a/copy/Makefile.am b/copy/Makefile.am +index e729f86..25f75c5 100644 +--- a/copy/Makefile.am ++++ b/copy/Makefile.am +@@ -23,6 +23,7 @@ EXTRA_DIST = \ + copy-file-to-nbd.sh \ + copy-file-to-null.sh \ + copy-file-to-qcow2.sh \ ++ copy-file-to-qcow2-compressed.sh \ + copy-nbd-to-block.sh \ + copy-nbd-to-file.sh \ + copy-nbd-to-hexdump.sh \ +@@ -142,7 +143,10 @@ TESTS += \ + $(NULL) + + if HAVE_QEMU_NBD +-TESTS += copy-file-to-qcow2.sh ++TESTS += \ ++ copy-file-to-qcow2.sh \ ++ copy-file-to-qcow2-compressed.sh \ ++ $(NULL) + endif + + if HAVE_GNUTLS +diff --git a/copy/copy-file-to-qcow2-compressed.sh b/copy/copy-file-to-qcow2-compressed.sh +new file mode 100755 +index 0000000..dfe4fa5 +--- /dev/null ++++ b/copy/copy-file-to-qcow2-compressed.sh +@@ -0,0 +1,64 @@ ++#!/usr/bin/env bash ++# nbd client library in userspace ++# Copyright (C) 2020-2022 Red Hat Inc. ++# ++# This library is free software; you can redistribute it and/or ++# modify it under the terms of the GNU Lesser General Public ++# License as published by the Free Software Foundation; either ++# version 2 of the License, or (at your option) any later version. ++# ++# This library is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# Lesser General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public ++# License along with this library; if not, write to the Free Software ++# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ ++. ../tests/functions.sh ++ ++set -e ++set -x ++ ++requires $QEMU_NBD --version ++requires nbdkit --exit-with-parent --version ++requires nbdkit sparse-random --dump-plugin ++requires qemu-img --version ++requires stat --version ++ ++file1=copy-file-to-qcow2-compressed.file1 ++file2=copy-file-to-qcow2-compressed.file2 ++rm -f $file1 $file2 ++cleanup_fn rm -f $file1 $file2 ++ ++size=1G ++seed=$RANDOM ++ ++# Create a compressed qcow2 file1. ++# ++# sparse-random files should compress easily because by default each ++# block uses repeated bytes. ++qemu-img create -f qcow2 $file1 $size ++nbdcopy -- [ nbdkit --exit-with-parent sparse-random $size seed=$seed ] \ ++ [ $QEMU_NBD --image-opts driver=compress,file.driver=qcow2,file.file.driver=file,file.file.filename=$file1 ] ++ ++ls -l $file1 ++ ++# Create an uncompressed qcow2 file2 with the same data. ++qemu-img create -f qcow2 $file2 $size ++nbdcopy -- [ nbdkit --exit-with-parent sparse-random $size seed=$seed ] \ ++ [ $QEMU_NBD --image-opts driver=qcow2,file.driver=file,file.filename=$file2 ] ++ ++ls -l $file2 ++ ++# file1 < file2 (shows the compression is having some effect). ++size1="$( stat -c %s $file1 )" ++size2="$( stat -c %s $file2 )" ++if [ $size1 -ge $size2 ]; then ++ echo "$0: qcow2 compression did not make the file smaller" ++ exit 1 ++fi ++ ++# Logical content of the files should be identical. ++qemu-img compare -f qcow2 $file1 -F qcow2 $file2 +diff --git a/copy/copy-sparse-allocated.sh b/copy/copy-sparse-allocated.sh +index 203c3b9..465e347 100755 +--- a/copy/copy-sparse-allocated.sh ++++ b/copy/copy-sparse-allocated.sh +@@ -17,8 +17,6 @@ + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + # Adapted from copy-sparse.sh. +-# +-# This test depends on the nbdkit default sparse block size (32K). + + . ../tests/functions.sh + +@@ -33,7 +31,7 @@ requires nbdkit eval --version + out=copy-sparse-allocated.out + cleanup_fn rm -f $out + +-$VG nbdcopy --allocated -- \ ++$VG nbdcopy --allocated --request-size=32768 -- \ + [ nbdkit --exit-with-parent data data=' + 1 + @1073741823 1 +diff --git a/copy/copy-sparse.sh b/copy/copy-sparse.sh +index 1a6da86..7912a21 100755 +--- a/copy/copy-sparse.sh ++++ b/copy/copy-sparse.sh +@@ -16,8 +16,6 @@ + # License along with this library; if not, write to the Free Software + # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +-# This test depends on the nbdkit default sparse block size (32K). +- + . ../tests/functions.sh + + set -e +@@ -34,8 +32,9 @@ cleanup_fn rm -f $out + # Copy from a sparse data disk to an nbdkit-eval-plugin instance which + # is logging everything. This allows us to see exactly what nbdcopy + # is writing, to ensure it is writing and zeroing the target as +-# expected. +-$VG nbdcopy -S 0 -- \ ++# expected. Force request size to match nbdkit default sparse ++# allocator block size (32K). ++$VG nbdcopy -S 0 --request-size=32768 -- \ + [ nbdkit --exit-with-parent data data=' + 1 + @1073741823 1 +diff --git a/copy/main.c b/copy/main.c +index 19ec384..0e27db8 100644 +--- a/copy/main.c ++++ b/copy/main.c +@@ -40,6 +40,7 @@ + + #include "ispowerof2.h" + #include "human-size.h" ++#include "minmax.h" + #include "version.h" + #include "nbdcopy.h" + +@@ -379,10 +380,22 @@ main (int argc, char *argv[]) + if (threads < connections) + connections = threads; + ++ /* request_size must always be at least as large as the preferred ++ * size of source & destination. ++ */ ++ request_size = MAX (request_size, src->preferred); ++ request_size = MAX (request_size, dst->preferred); ++ + /* Adapt queue to size to request size if needed. */ + if (request_size > queue_size) + queue_size = request_size; + ++ /* Sparse size (if using) must not be smaller than the destination ++ * preferred size, otherwise we end up creating too small requests. ++ */ ++ if (sparse_size > 0 && sparse_size < dst->preferred) ++ sparse_size = dst->preferred; ++ + /* Truncate the destination to the same size as the source. Only + * has an effect on regular files. + */ +diff --git a/copy/multi-thread-copying.c b/copy/multi-thread-copying.c +index 06cdb8e..9267545 100644 +--- a/copy/multi-thread-copying.c ++++ b/copy/multi-thread-copying.c +@@ -166,6 +166,62 @@ decrease_queue_size (struct worker *worker, size_t len) + worker->queue_size -= len; + } + ++/* Using the extents map 'exts', check if the region ++ * [offset..offset+len-1] intersects only with zero extents. ++ * ++ * The invariant for '*i' is always an extent which starts before or ++ * equal to the current offset. ++ */ ++static bool ++only_zeroes (const extent_list exts, size_t *i, ++ uint64_t offset, unsigned len) ++{ ++ size_t j; ++ ++ /* Invariant. */ ++ assert (*i < exts.len); ++ assert (exts.ptr[*i].offset <= offset); ++ ++ /* Update the invariant. Search for the last possible extent in the ++ * list which is <= offset. ++ */ ++ for (j = *i + 1; j < exts.len; ++j) { ++ if (exts.ptr[j].offset <= offset) ++ *i = j; ++ else ++ break; ++ } ++ ++ /* Check invariant again. */ ++ assert (*i < exts.len); ++ assert (exts.ptr[*i].offset <= offset); ++ ++ /* If *i is not the last extent, then the next extent starts ++ * strictly beyond our current offset. ++ */ ++ assert (*i == exts.len - 1 || exts.ptr[*i + 1].offset > offset); ++ ++ /* Search forward, look for any non-zero extents overlapping the region. */ ++ for (j = *i; j < exts.len; ++j) { ++ uint64_t start, end; ++ ++ /* [start..end-1] is the current extent. */ ++ start = exts.ptr[j].offset; ++ end = exts.ptr[j].offset + exts.ptr[j].length; ++ ++ assert (end > offset); ++ ++ if (start >= offset + len) ++ break; ++ ++ /* Non-zero extent covering this region => test failed. */ ++ if (!exts.ptr[j].zero) ++ return false; ++ } ++ ++ return true; ++} ++ + /* There are 'threads' worker threads, each copying work ranges from + * src to dst until there are no more work ranges. + */ +@@ -177,7 +233,10 @@ worker_thread (void *wp) + extent_list exts = empty_vector; + + while (get_next_offset (&offset, &count)) { +- size_t i; ++ struct command *command; ++ size_t extent_index; ++ bool is_zeroing = false; ++ uint64_t zeroing_start = 0; /* initialized to avoid bogus GCC warning */ + + assert (0 < count && count <= THREAD_WORK_SIZE); + if (extents) +@@ -185,52 +244,64 @@ worker_thread (void *wp) + else + default_get_extents (src, w->index, offset, count, &exts); + +- for (i = 0; i < exts.len; ++i) { +- struct command *command; +- size_t len; ++ extent_index = 0; // index into extents array used to optimize only_zeroes ++ while (count) { ++ const size_t len = MIN (count, request_size); + +- if (exts.ptr[i].zero) { ++ if (only_zeroes (exts, &extent_index, offset, len)) { + /* The source is zero so we can proceed directly to skipping, +- * fast zeroing, or writing zeroes at the destination. ++ * fast zeroing, or writing zeroes at the destination. Defer ++ * zeroing so we can send it as a single large command. + */ +- command = create_command (exts.ptr[i].offset, exts.ptr[i].length, +- true, w); +- fill_dst_range_with_zeroes (command); ++ if (!is_zeroing) { ++ is_zeroing = true; ++ zeroing_start = offset; ++ } + } +- + else /* data */ { +- /* As the extent might be larger than permitted for a single +- * command, we may have to split this into multiple read +- * requests. +- */ +- while (exts.ptr[i].length > 0) { +- len = exts.ptr[i].length; +- if (len > request_size) +- len = request_size; +- +- command = create_command (exts.ptr[i].offset, len, +- false, w); +- +- wait_for_request_slots (w); +- +- /* NOTE: Must increase the queue size after waiting. */ +- increase_queue_size (w, len); +- +- /* Begin the asynch read operation. */ +- src->ops->asynch_read (src, command, +- (nbd_completion_callback) { +- .callback = finished_read, +- .user_data = command, +- }); +- +- exts.ptr[i].offset += len; +- exts.ptr[i].length -= len; ++ /* If we were in the middle of deferred zeroing, do it now. */ ++ if (is_zeroing) { ++ /* Note that offset-zeroing_start can never exceed ++ * THREAD_WORK_SIZE, so there is no danger of overflowing ++ * size_t. ++ */ ++ command = create_command (zeroing_start, offset-zeroing_start, ++ true, w); ++ fill_dst_range_with_zeroes (command); ++ is_zeroing = false; + } ++ ++ /* Issue the asynchronous read command. */ ++ command = create_command (offset, len, false, w); ++ ++ wait_for_request_slots (w); ++ ++ /* NOTE: Must increase the queue size after waiting. */ ++ increase_queue_size (w, len); ++ ++ /* Begin the asynch read operation. */ ++ src->ops->asynch_read (src, command, ++ (nbd_completion_callback) { ++ .callback = finished_read, ++ .user_data = command, ++ }); + } + +- offset += count; +- count = 0; +- } /* for extents */ ++ offset += len; ++ count -= len; ++ } /* while (count) */ ++ ++ /* If we were in the middle of deferred zeroing, do it now. */ ++ if (is_zeroing) { ++ /* Note that offset-zeroing_start can never exceed ++ * THREAD_WORK_SIZE, so there is no danger of overflowing ++ * size_t. ++ */ ++ command = create_command (zeroing_start, offset - zeroing_start, ++ true, w); ++ fill_dst_range_with_zeroes (command); ++ is_zeroing = false; ++ } + } + + /* Wait for in flight NBD requests to finish. */ +diff --git a/copy/nbdcopy.pod b/copy/nbdcopy.pod +index fd10f7c..f06d112 100644 +--- a/copy/nbdcopy.pod ++++ b/copy/nbdcopy.pod +@@ -182,8 +182,9 @@ Set the maximum number of requests in flight per NBD connection. + =item B<--sparse=>N + + Detect all zero blocks of size N (bytes) and make them sparse on the +-output. You can also turn off sparse detection using S>. +-The default is 4096 bytes. ++output. You can also turn off sparse detection using S>. The ++default is 4096 bytes, or the destination preferred block size, ++whichever is larger. + + =item B<--synchronous> + +-- +2.31.1 + diff --git a/0008-dump-Add-another-example-to-the-manual.patch b/0008-dump-Add-another-example-to-the-manual.patch new file mode 100644 index 0000000..1a4eb5b --- /dev/null +++ b/0008-dump-Add-another-example-to-the-manual.patch @@ -0,0 +1,29 @@ +From 5d21b00dbdd1e1a04317bf16afb8f4d2ceaa470f Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 2 Jul 2022 17:12:46 +0100 +Subject: [PATCH] dump: Add another example to the manual + +(cherry picked from commit be3768b077c9542aba34eb821016c36f31d234af) +--- + dump/nbddump.pod | 5 +++++ + 1 file changed, 5 insertions(+) + +diff --git a/dump/nbddump.pod b/dump/nbddump.pod +index 5d7864d..656a965 100644 +--- a/dump/nbddump.pod ++++ b/dump/nbddump.pod +@@ -57,6 +57,11 @@ For example, to dump out a qcow2 file as raw data: + + nbddump -- [ qemu-nbd -r -f qcow2 file.qcow2 ] + ++To dump out an empty floppy disk created by L: ++ ++ mkdir /var/tmp/empty ++ nbddump -- [ nbdkit floppy /var/tmp/empty ] ++ + Note that S> are separate parameters, and must be + surrounded by spaces. C<--> separates nbddump parameters from + subprocess parameters. +-- +2.31.1 + diff --git a/0009-lib-crypto-Use-GNUTLS_NO_SIGNAL-if-available.patch b/0009-lib-crypto-Use-GNUTLS_NO_SIGNAL-if-available.patch new file mode 100644 index 0000000..e33ee16 --- /dev/null +++ b/0009-lib-crypto-Use-GNUTLS_NO_SIGNAL-if-available.patch @@ -0,0 +1,93 @@ +From a432e773e0cdc24cb27ccdda4111744ea2c3b819 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Wed, 27 Jul 2022 17:08:14 +0100 +Subject: [PATCH] lib/crypto: Use GNUTLS_NO_SIGNAL if available + +libnbd has long used MSG_NOSIGNAL to avoid receiving SIGPIPE if we +accidentally write on a closed socket, which is a nice alternative to +using a SIGPIPE signal handler. However with TLS connections, gnutls +did not use this flag and so programs using libnbd + TLS would receive +SIGPIPE in some situations, notably if the server closed the +connection abruptly while we were trying to write something. + +GnuTLS 3.4.2 introduces GNUTLS_NO_SIGNAL which does the same thing. +Use this flag if available. + +RHEL 7 has an older gnutls which lacks this flag. To avoid qemu-nbd +interop tests failing (rarely, but more often with a forthcoming +change to TLS shutdown behaviour), register a SIGPIPE signal handler +in the test if the flag is missing. +--- + configure.ac | 15 +++++++++++++++ + interop/interop.c | 10 ++++++++++ + lib/crypto.c | 7 ++++++- + 3 files changed, 31 insertions(+), 1 deletion(-) + +diff --git a/configure.ac b/configure.ac +index 49ca8ab..6bd9e1b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -179,6 +179,21 @@ AS_IF([test "$GNUTLS_LIBS" != ""],[ + gnutls_session_set_verify_cert \ + gnutls_transport_is_ktls_enabled \ + ]) ++ AC_MSG_CHECKING([if gnutls has GNUTLS_NO_SIGNAL]) ++ AC_COMPILE_IFELSE( ++ [AC_LANG_PROGRAM([ ++ #include ++ gnutls_session_t session; ++ ], [ ++ gnutls_init(&session, GNUTLS_CLIENT|GNUTLS_NO_SIGNAL); ++ ]) ++ ], [ ++ AC_MSG_RESULT([yes]) ++ AC_DEFINE([HAVE_GNUTLS_NO_SIGNAL], [1], ++ [GNUTLS_NO_SIGNAL found at compile time]) ++ ], [ ++ AC_MSG_RESULT([no]) ++ ]) + LIBS="$old_LIBS" + ]) + +diff --git a/interop/interop.c b/interop/interop.c +index b41f3ca..036545b 100644 +--- a/interop/interop.c ++++ b/interop/interop.c +@@ -84,6 +84,16 @@ main (int argc, char *argv[]) + REQUIRES + #endif + ++ /* Ignore SIGPIPE. We only need this for GnuTLS < 3.4.2, since ++ * newer GnuTLS has the GNUTLS_NO_SIGNAL flag which adds ++ * MSG_NOSIGNAL to each write call. ++ */ ++#if !HAVE_GNUTLS_NO_SIGNAL ++#if TLS ++ signal (SIGPIPE, SIG_IGN); ++#endif ++#endif ++ + /* Create a large sparse temporary file. */ + #ifdef NEEDS_TMPFILE + int fd = mkstemp (TMPFILE); +diff --git a/lib/crypto.c b/lib/crypto.c +index 1272888..ca9520e 100644 +--- a/lib/crypto.c ++++ b/lib/crypto.c +@@ -588,7 +588,12 @@ nbd_internal_crypto_create_session (struct nbd_handle *h, + gnutls_psk_client_credentials_t pskcreds = NULL; + gnutls_certificate_credentials_t xcreds = NULL; + +- err = gnutls_init (&session, GNUTLS_CLIENT|GNUTLS_NONBLOCK); ++ err = gnutls_init (&session, ++ GNUTLS_CLIENT | GNUTLS_NONBLOCK ++#if HAVE_GNUTLS_NO_SIGNAL ++ | GNUTLS_NO_SIGNAL ++#endif ++ ); + if (err < 0) { + set_error (errno, "gnutls_init: %s", gnutls_strerror (err)); + return NULL; +-- +2.31.1 + diff --git a/0010-lib-crypto.c-Ignore-TLS-premature-termination-after-.patch b/0010-lib-crypto.c-Ignore-TLS-premature-termination-after-.patch new file mode 100644 index 0000000..cc91332 --- /dev/null +++ b/0010-lib-crypto.c-Ignore-TLS-premature-termination-after-.patch @@ -0,0 +1,100 @@ +From 8bbee9c0ff052cf8ab5ba81fd1b67e3c45e7012a Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Wed, 27 Jul 2022 16:07:37 +0100 +Subject: [PATCH] lib/crypto.c: Ignore TLS premature termination after write + shutdown + +qemu-nbd doesn't call gnutls_bye to cleanly shut down the connection +after we send NBD_CMD_DISC. When copying from a qemu-nbd server (or +any operation which calls nbd_shutdown) you will see errors like this: + + $ nbdcopy nbds://foo?tls-certificates=/var/tmp/pki null: + nbds://foo?tls-certificates=/var/tmp/pki: nbd_shutdown: gnutls_record_recv: The TLS connection was non-properly terminated. + +Relatedly you may also see: + + nbd_shutdown: gnutls_record_recv: Error in the pull function. + +This commit suppresses the error in the case where we know that we +have shut down writes (which happens after NBD_CMD_DISC has been sent +on the wire). +--- + interop/interop.c | 9 --------- + lib/crypto.c | 17 +++++++++++++++++ + lib/internal.h | 1 + + 3 files changed, 18 insertions(+), 9 deletions(-) + +diff --git a/interop/interop.c b/interop/interop.c +index 036545b..cce9407 100644 +--- a/interop/interop.c ++++ b/interop/interop.c +@@ -226,19 +226,10 @@ main (int argc, char *argv[]) + + /* XXX In future test more operations here. */ + +-#if !TLS +- /* XXX qemu doesn't shut down the connection nicely (using +- * gnutls_bye) and because of this the following call will fail +- * with: +- * +- * nbd_shutdown: gnutls_record_recv: The TLS connection was +- * non-properly terminated. +- */ + if (nbd_shutdown (nbd, 0) == -1) { + fprintf (stderr, "%s\n", nbd_get_error ()); + exit (EXIT_FAILURE); + } +-#endif + + nbd_close (nbd); + +diff --git a/lib/crypto.c b/lib/crypto.c +index ca9520e..aa5d820 100644 +--- a/lib/crypto.c ++++ b/lib/crypto.c +@@ -187,6 +187,22 @@ tls_recv (struct nbd_handle *h, struct socket *sock, void *buf, size_t len) + errno = EAGAIN; + return -1; + } ++ if (h->tls_shut_writes && ++ (r == GNUTLS_E_PULL_ERROR || r == GNUTLS_E_PREMATURE_TERMINATION)) { ++ /* qemu-nbd doesn't call gnutls_bye to cleanly shut down the ++ * connection after we send NBD_CMD_DISC, instead it simply ++ * closes the connection. On the client side we see ++ * "gnutls_record_recv: The TLS connection was non-properly ++ * terminated" or "gnutls_record_recv: Error in the pull ++ * function.". ++ * ++ * If we see these errors after we shut down the write side ++ * (h->tls_shut_writes), which happens after we have sent ++ * NBD_CMD_DISC on the wire, downgrade them to a debug message. ++ */ ++ debug (h, "gnutls_record_recv: %s", gnutls_strerror (r)); ++ return 0; /* EOF */ ++ } + set_error (0, "gnutls_record_recv: %s", gnutls_strerror (r)); + errno = EIO; + return -1; +@@ -234,6 +250,7 @@ tls_shut_writes (struct nbd_handle *h, struct socket *sock) + return false; + if (r != 0) + debug (h, "ignoring gnutls_bye failure: %s", gnutls_strerror (r)); ++ h->tls_shut_writes = true; + return sock->u.tls.oldsock->ops->shut_writes (h, sock->u.tls.oldsock); + } + +diff --git a/lib/internal.h b/lib/internal.h +index 6aaced3..f1b4c63 100644 +--- a/lib/internal.h ++++ b/lib/internal.h +@@ -307,6 +307,7 @@ struct nbd_handle { + struct command *reply_cmd; + + bool disconnect_request; /* True if we've queued NBD_CMD_DISC */ ++ bool tls_shut_writes; /* Used by lib/crypto.c to track disconnect. */ + }; + + struct meta_context { +-- +2.31.1 + diff --git a/copy-patches.sh b/copy-patches.sh new file mode 100755 index 0000000..e00af4e --- /dev/null +++ b/copy-patches.sh @@ -0,0 +1,55 @@ +#!/bin/bash - + +set -e + +# Maintainer script to copy patches from the git repo to the current +# directory. Use it like this: +# ./copy-patches.sh + +rhel_version=8.6 + +# Check we're in the right directory. +if [ ! -f libnbd.spec ]; then + echo "$0: run this from the directory containing 'libnbd.spec'" + exit 1 +fi + +git_checkout=$HOME/d/libnbd-rhel-$rhel_version +if [ ! -d $git_checkout ]; then + echo "$0: $git_checkout does not exist" + echo "This script is only for use by the maintainer when preparing a" + echo "libnbd release on RHEL." + exit 1 +fi + +# Get the base version of libnbd. +version=`grep '^Version:' libnbd.spec | awk '{print $2}'` +tag="v$version" + +# Remove any existing patches. +git rm -f [0-9]*.patch ||: +rm -f [0-9]*.patch + +# Get the patches. +(cd $git_checkout; rm -f [0-9]*.patch; git format-patch -N $tag) +mv $git_checkout/[0-9]*.patch . + +# Remove any not to be applied. +rm -f *NOT-FOR-RPM*.patch + +# Add the patches. +git add [0-9]*.patch + +# Print out the patch lines. +echo +echo "--- Copy the following text into libnbd.spec file" +echo + +echo "# Patches." +for f in [0-9]*.patch; do + n=`echo $f | awk -F- '{print $1}'` + echo "Patch$n: $f" +done + +echo +echo "--- End of text" diff --git a/gating.yaml b/gating.yaml new file mode 100755 index 0000000..648918d --- /dev/null +++ b/gating.yaml @@ -0,0 +1,6 @@ +--- !Policy +product_versions: + - rhel-9 +decision_context: osci_compose_gate +rules: + - !PassingTestCaseRule {test_case_name: osci.brew-build.tier0.functional} diff --git a/libguestfs.keyring b/libguestfs.keyring new file mode 100644 index 0000000000000000000000000000000000000000..f0508c8d7008b4290b09df40ececc5dd721b5bf9 GIT binary patch literal 82814 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 TLIWWL0(`FYGeIr>4*35AXm|6* literal 0 HcmV?d00001 diff --git a/libnbd-1.6.0.tar.gz.sig b/libnbd-1.6.0.tar.gz.sig new file mode 100644 index 0000000..fa6006d --- /dev/null +++ b/libnbd-1.6.0.tar.gz.sig @@ -0,0 +1,17 @@ +-----BEGIN PGP SIGNATURE----- + +iQJFBAABCAAvFiEE93dPsa0HSn6Mh2fqkXOPc+G3aKAFAl/3RFQRHHJpY2hAYW5u +ZXhpYS5vcmcACgkQkXOPc+G3aKD9aw/+Pfg3owjJmhTcCyFvuH2lgiiBb+qL2An+ +hsoax6dM5JxzV6x1Ikgn3C8z2+dLRMowo2FrRgpzTwfaS+ngLDipSC04hKl9MhFN +7OPLCm+L7wcP7KUk4cC0qTSHpHkApo2SP3/bD7vVBYZMYSjgUVFcRoqZlRl3N9RF +7XNsxA2YG9bV4Ln3KbB+k2uxIKNUZIVjmEpretVbb+NTKW9C23ZHicSHYB+Eok1M +iTN6j66rYFn0Xb+L2v7jty19tSdYOMbkdSn0KpniURAWevjjVWGqcojMqW4YuAZ5 +h2MpRfyKFyusbsbtX5bjICTu6+AgFFUALKH7ReDs1RY1cEph9XdBLVulXTggxY05 +E3I1Nns1YmjRlV6ky2Abl2e+Doc44mycINRlwL2q8+Q3TqlVVPFXoVTWxIJ6/Uae +tqnEwWIa2wGv3KU1KLNbWTn1z6I8NM/Nj+7pMKDNnxJzFmHEjL94tmG+iNmHsF34 +vWBZ1q7h9EezxHLOPFYDjlpS+IxeuXakbpuTX2jXvi3zSAbr5WmRR1uO8dAiwu9b +RwOHRmVQOFLAAICYTZDmxl42DpWs5Z2aP7eRwpe8/MOSRiAVepjhUD/bsdaFwmBR +8Z7CGNzyTtt+sy5l7cPBYZ+4RdxWgFEBceBbHs06zdlD/Pui288UQVB/0e9AXYOc +wluyWT1v7sA= +=BaN1 +-----END PGP SIGNATURE----- diff --git a/libnbd.spec b/libnbd.spec new file mode 100644 index 0000000..0020064 --- /dev/null +++ b/libnbd.spec @@ -0,0 +1,427 @@ +# If we should verify tarball signature with GPGv2. +%global verify_tarball_signature 1 + +# If there are patches which touch autotools files, set this to 1. +%global patches_touch_autotools 1 + +# The source directory. +%global source_directory 1.6-stable + +Name: libnbd +Version: 1.6.0 +Release: 5%{?dist} +Summary: NBD client library in userspace + +License: LGPLv2+ +URL: https://github.com/libguestfs/libnbd + +Source0: http://libguestfs.org/download/libnbd/%{source_directory}/%{name}-%{version}.tar.gz +Source1: http://libguestfs.org/download/libnbd/%{source_directory}/%{name}-%{version}.tar.gz.sig +# Keyring used to verify tarball signature. This contains the single +# key from here: +# https://pgp.key-server.io/pks/lookup?search=rjones%40redhat.com&fingerprint=on&op=vindex +Source2: libguestfs.keyring + +# Maintainer script which helps with handling patches. +Source3: copy-patches.sh + +# Patches come from this upstream branch: +# https://github.com/libguestfs/libnbd/tree/rhel-8.6 + +# Patches. +Patch0001: 0001-copy-copy-nbd-to-sparse-file.sh-Skip-test-unless-nbd.patch +Patch0002: 0002-generator-Refactor-CONNECT.START-state.patch +Patch0003: 0003-generator-Print-a-better-error-message-if-connect-2-.patch +Patch0004: 0004-opt_go-Tolerate-unplanned-server-death.patch +Patch0005: 0005-security-Document-assignment-of-CVE-2021-20286.patch +Patch0006: 0006-copy-Pass-in-dummy-variable-rather-than-errno-to-cal.patch +Patch0007: 0007-copy-CVE-2022-0485-Fail-nbdcopy-if-NBD-read-or-write.patch + +%if 0%{patches_touch_autotools} +BuildRequires: autoconf, automake, libtool +%endif + +%if 0%{verify_tarball_signature} +BuildRequires: gnupg2 +%endif + +# For the core library. +BuildRequires: gcc +BuildRequires: /usr/bin/pod2man +BuildRequires: gnutls-devel +BuildRequires: libxml2-devel + +# For nbdfuse. +BuildRequires: fuse, fuse-devel + +# For the Python 3 bindings. +BuildRequires: python3-devel + +# For the OCaml bindings. +BuildRequires: ocaml +BuildRequires: ocaml-findlib-devel +BuildRequires: ocaml-ocamldoc + +# Only for building the examples. +BuildRequires: glib2-devel + +# For bash-completion. +BuildRequires: bash-completion + +# Only for running the test suite. +BuildRequires: coreutils +BuildRequires: gcc-c++ +BuildRequires: gnutls-utils +#BuildRequires: jq +%ifnarch %{ix86} +BuildRequires: nbdkit +BuildRequires: nbdkit-data-plugin +#BuildRequires: nbdkit-eval-plugin +BuildRequires: nbdkit-memory-plugin +BuildRequires: nbdkit-null-plugin +BuildRequires: nbdkit-pattern-plugin +BuildRequires: nbdkit-sh-plugin +#BuildRequires: nbdkit-sparse-random-plugin +#BuildRequires: nbd +BuildRequires: qemu-img +%endif +BuildRequires: util-linux + + +%description +NBD — Network Block Device — is a protocol for accessing Block Devices +(hard disks and disk-like things) over a Network. + +This is the NBD client library in userspace, a simple library for +writing NBD clients. + +The key features are: + + * Synchronous and asynchronous APIs, both for ease of use and for + writing non-blocking, multithreaded clients. + + * High performance. + + * Minimal dependencies for the basic library. + + * Well-documented, stable API. + + * Bindings in several programming languages. + + +%package devel +Summary: Development headers for %{name} +License: LGPLv2+ and BSD +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description devel +This package contains development headers for %{name}. + + +%package -n ocaml-%{name} +Summary: OCaml language bindings for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description -n ocaml-%{name} +This package contains OCaml language bindings for %{name}. + + +%package -n ocaml-%{name}-devel +Summary: OCaml language development package for %{name} +Requires: ocaml-%{name}%{?_isa} = %{version}-%{release} + + +%description -n ocaml-%{name}-devel +This package contains OCaml language development package for +%{name}. Install this if you want to compile OCaml software which +uses %{name}. + + +%package -n python3-%{name} +Summary: Python 3 bindings for %{name} +Requires: %{name}%{?_isa} = %{version}-%{release} +%{?python_provide:%python_provide python3-%{name}} + +# The Python module happens to be called lib*.so. Don't scan it and +# have a bogus "Provides: libnbdmod.*". +%global __provides_exclude_from ^%{python3_sitearch}/lib.*\\.so + + +%description -n python3-%{name} +python3-%{name} contains Python 3 bindings for %{name}. + + +%package -n nbdfuse +Summary: FUSE support for %{name} +License: LGPLv2+ and BSD +Requires: %{name}%{?_isa} = %{version}-%{release} + + +%description -n nbdfuse +This package contains FUSE support for %{name}. + + +%package bash-completion +Summary: Bash tab-completion for %{name} +BuildArch: noarch +Requires: bash-completion >= 2.0 +# Don't use _isa here because it's a noarch package. This dependency +# is just to ensure that the subpackage is updated along with libnbd. +Requires: %{name} = %{version}-%{release} + + +%description bash-completion +Install this package if you want intelligent bash tab-completion +for %{name}. + + +%prep +%if 0%{verify_tarball_signature} +tmphome="$(mktemp -d)" +gpgv2 --homedir "$tmphome" --keyring %{SOURCE2} %{SOURCE1} %{SOURCE0} +%endif +%autosetup -p1 +%if 0%{patches_touch_autotools} +autoreconf -i +%endif + + +%build +%configure \ + --disable-static \ + --with-tls-priority=@LIBNBD,SYSTEM \ + PYTHON=%{__python3} \ + --enable-python \ + --enable-ocaml \ + --enable-fuse \ + --disable-golang + +make %{?_smp_mflags} + + +%install +%make_install + +# Delete libtool crap. +find $RPM_BUILD_ROOT -name '*.la' -delete + +# Delete the golang man page since we're not distributing the bindings. +rm $RPM_BUILD_ROOT%{_mandir}/man3/libnbd-golang.3* + + +%check +# interop/structured-read.sh fails with the old qemu-nbd in Fedora 29, +# so disable it there. +%if 0%{?fedora} <= 29 +rm interop/structured-read.sh +touch interop/structured-read.sh +chmod +x interop/structured-read.sh +%endif + +# All fuse tests fail in Koji with: +# fusermount: entry for fuse/test-*.d not found in /etc/mtab +# for unknown reasons but probably related to the Koji environment. +for f in fuse/test-*.sh; do + rm $f + touch $f + chmod +x $f +done + +# info/info-map-base-allocation-json.sh fails because of a bug in +# jq 1.5 in RHEL 8 (fixed in later versions). +rm info/info-map-base-allocation-json.sh +touch info/info-map-base-allocation-json.sh +chmod +x info/info-map-base-allocation-json.sh + +make %{?_smp_mflags} check || { + for f in $(find -name test-suite.log); do + echo + echo "==== $f ====" + cat $f + done + exit 1 + } + + +%files +%doc README +%license COPYING.LIB +%{_bindir}/nbdcopy +%{_bindir}/nbdinfo +%{_libdir}/libnbd.so.* +%{_mandir}/man1/nbdcopy.1* +%{_mandir}/man1/nbdinfo.1* + + +%files devel +%doc TODO examples/*.c +%license examples/LICENSE-FOR-EXAMPLES +%{_includedir}/libnbd.h +%{_libdir}/libnbd.so +%{_libdir}/pkgconfig/libnbd.pc +%{_mandir}/man3/libnbd.3* +%{_mandir}/man1/libnbd-release-notes-1.*.1* +%{_mandir}/man3/libnbd-security.3* +%{_mandir}/man3/nbd_*.3* + + +%files -n ocaml-%{name} +%{_libdir}/ocaml/nbd +%exclude %{_libdir}/ocaml/nbd/*.a +%exclude %{_libdir}/ocaml/nbd/*.cmxa +%exclude %{_libdir}/ocaml/nbd/*.cmx +%exclude %{_libdir}/ocaml/nbd/*.mli +%{_libdir}/ocaml/stublibs/dllmlnbd.so +%{_libdir}/ocaml/stublibs/dllmlnbd.so.owner + + +%files -n ocaml-%{name}-devel +%doc ocaml/examples/*.ml +%license ocaml/examples/LICENSE-FOR-EXAMPLES +%{_libdir}/ocaml/nbd/*.a +%{_libdir}/ocaml/nbd/*.cmxa +%{_libdir}/ocaml/nbd/*.cmx +%{_libdir}/ocaml/nbd/*.mli +%{_mandir}/man3/libnbd-ocaml.3* +%{_mandir}/man3/NBD.3* +%{_mandir}/man3/NBD.*.3* + + +%files -n python3-%{name} +%{python3_sitearch}/libnbdmod*.so +%{python3_sitearch}/nbd.py +%{python3_sitearch}/nbdsh.py +%{python3_sitearch}/__pycache__/nbd*.py* +%{_bindir}/nbdsh +%{_mandir}/man1/nbdsh.1* + + +%files -n nbdfuse +%{_bindir}/nbdfuse +%{_mandir}/man1/nbdfuse.1* + + +%files bash-completion +%dir %{_datadir}/bash-completion/completions +%{_datadir}/bash-completion/completions/nbdcopy +%{_datadir}/bash-completion/completions/nbdfuse +%{_datadir}/bash-completion/completions/nbdinfo +%{_datadir}/bash-completion/completions/nbdsh + + +%changelog +* Mon Feb 7 2022 Richard W.M. Jones - 1.6.0-5.el8 +- Fix CVE-2022-0485: Fail nbdcopy if NBD read or write fails + resolves: rhbz#2045718 + +* Thu Sep 2 2021 Danilo C. L. de Paula - 1.6.0-4.el8 +- Resolves: bz#2000225 + (Rebase virt:rhel module:stream based on AV-8.6) + +* Mon Jul 13 2020 Danilo C. L. de Paula - 1.2.2 +- Resolves: bz#1844296 +(Upgrade components in virt:rhel module:stream for RHEL-8.3 release) + +* Wed Feb 5 2020 Richard W.M. Jones - 1.2.2-1 +- New stable release 1.2.2. + +* Tue Dec 3 2019 Richard W.M. Jones - 1.2.1-1 +- New stable release 1.2.1. + +* Thu Nov 14 2019 Richard W.M. Jones - 1.2.0-1 +- New stable release 1.2.0. + +* Wed Oct 9 2019 Richard W.M. Jones - 1.0.3-1 +- New upstream version 1.0.3. +- Contains fix for remote code execution vulnerability. +- Add new libnbd-security(3) man page. + +* Tue Sep 17 2019 Richard W.M. Jones - 1.0.2-1 +- New upstream version 1.0.2. +- Remove patches which are upstream. +- Contains fix for NBD Protocol Downgrade Attack (CVE-2019-14842). +- Fix previous commit message. + +* Thu Sep 12 2019 Richard W.M. Jones - 1.0.1-2 +- Add upstream patch to fix nbdsh (for nbdkit tests). +- Fix interop tests on slow machines. + +* Sun Sep 08 2019 Richard W.M. Jones - 1.0.1-1 +- New stable version 1.0.1. + +* Wed Aug 28 2019 Richard W.M. Jones - 1.0.0-1 +- New upstream version 1.0.0. + +* Wed Aug 21 2019 Miro Hrončok - 0.9.9-2 +- Rebuilt for Python 3.8 + +* Wed Aug 21 2019 Richard W.M. Jones - 0.9.9-1 +- New upstream version 0.9.9. + +* Wed Aug 21 2019 Richard W.M. Jones - 0.9.8-4 +- Fix nbdkit dependencies so we're actually running the tests. +- Add glib2-devel BR so we build the glib main loop example. +- Add upstream patch to fix test error: + nbd_connect_unix: getlogin: No such device or address +- Fix test failure on 32 bit. + +* Tue Aug 20 2019 Richard W.M. Jones - 0.9.8-3 +- Bump and rebuild to fix releng brokenness. + https://lists.fedoraproject.org/archives/list/devel@lists.fedoraproject.org/message/2LIDI33G3IEIPYSCCIP6WWKNHY7XZJGQ/ + +* Mon Aug 19 2019 Miro Hrončok - 0.9.8-2 +- Rebuilt for Python 3.8 + +* Thu Aug 15 2019 Richard W.M. Jones - 0.9.8-1 +- New upstream version 0.9.8. +- Package the new nbd_*(3) man pages. + +* Mon Aug 5 2019 Richard W.M. Jones - 0.9.7-1 +- New upstream version 0.9.7. +- Add libnbd-ocaml(3) man page. + +* Sat Aug 3 2019 Richard W.M. Jones - 0.9.6-2 +- Add all upstream patches since 0.9.6 was released. +- Package the ocaml bindings into a subpackage. + +* Tue Jul 30 2019 Richard W.M. Jones - 0.9.6-1 +- New upstream verison 0.9.6. + +* Fri Jul 26 2019 Richard W.M. Jones - 0.1.9-1 +- New upstream version 0.1.9. + +* Thu Jul 25 2019 Fedora Release Engineering - 0.1.8-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_31_Mass_Rebuild + +* Wed Jul 17 2019 Richard W.M. Jones - 0.1.8-1 +- New upstream version 0.1.8. + +* Tue Jul 16 2019 Richard W.M. Jones - 0.1.7-1 +- New upstream version 0.1.7. + +* Wed Jul 3 2019 Richard W.M. Jones - 0.1.6-1 +- New upstream version 0.1.6. + +* Thu Jun 27 2019 Richard W.M. Jones - 0.1.5-1 +- New upstream version 0.1.5. + +* Sun Jun 09 2019 Richard W.M. Jones - 0.1.4-1 +- New upstream version 0.1.4. + +* Sun Jun 2 2019 Richard W.M. Jones - 0.1.2-2 +- Enable libxml2 for NBD URI support. + +* Thu May 30 2019 Richard W.M. Jones - 0.1.2-1 +- New upstream version 0.1.2. + +* Tue May 28 2019 Richard W.M. Jones - 0.1.1-1 +- Fix license in man pages and examples. +- Add nbdsh(1) man page. +- Include the signature and keyring even if validation is disabled. +- Update devel subpackage license. +- Fix old FSF address in Python tests. +- Filter Python provides. +- Remove executable permission on the tar.gz.sig file. +- Initial release. diff --git a/sources b/sources new file mode 100644 index 0000000..072f87e --- /dev/null +++ b/sources @@ -0,0 +1,2 @@ +SHA1 (libguestfs.keyring) = 1bbc40f501a7fef9eef2a39b701a71aee2fea7c4 +SHA1 (libnbd-1.6.0.tar.gz) = b14ac9349d324df71d26cf3de9fb606c56f18cb0 diff --git a/tests/basic-test.sh b/tests/basic-test.sh new file mode 100755 index 0000000..f514ef0 --- /dev/null +++ b/tests/basic-test.sh @@ -0,0 +1,15 @@ +#!/bin/bash - +set -e +set -x + +# Enable libnbd debugging. +export LIBNBD_DEBUG=1 + +# Connect to nbdkit. +nbdsh -c - <