From dd7a8c39f594c5976f32862fb1e41f5f4f8f979c Mon Sep 17 00:00:00 2001 From: James Antill Date: Mon, 8 Aug 2022 14:10:51 -0400 Subject: [PATCH] Import rpm: 64a15868f7a8724e8cee61f214e98c8ea3fa59b4 --- .gitignore | 2 + ...ta-corruption-in-zero-and-trim-on-un.patch | 82 + ...-Allow-the-remote-file-to-be-created.patch | 293 +++ ...e-this-filter-so-it-prefetches-using.patch | 794 +++++++ ...3716-reset-structured-replies-on-sta.patch | 94 + 0003-readahead-Fix-test.patch | 120 + ...set-meta-context-replies-on-starttls.patch | 40 + 0004-New-filter-luks.patch | 1816 ++++++++++++++ ...mu-6.1-which-requires-backing-format.patch | 59 + ...-filter-with-old-GnuTLS-in-Debian-10.patch | 95 + 0006-luks-Various-fixes-for-Clang.patch | 71 + ...-luks-Link-with-libcompat-on-Windows.patch | 43 + 0008-luks-Refactor-the-filter.patch | 2096 +++++++++++++++++ ...Reduce-time-taken-to-run-these-tests.patch | 101 + ...dd-nbdkit.parse_size-Python-function.patch | 112 + ...ss-reference-nbdkit-readahead-filter.patch | 34 + ...ocument-curl-plugin-readahead-filter.patch | 48 + 0013-New-filter-scan.patch | 1021 ++++++++ 0014-scan-Remove-condition-variable.patch | 67 + ...an-Small-typographical-fix-in-manual.patch | 57 + ...nce-readahead-or-scan-filters-from-t.patch | 34 + ...o-we-don-t-try-to-prefetch-beyond-en.patch | 56 + ...gression-test-for-LUKS-zeroing-crash.patch | 110 + ...te-Allow-burstiness-to-be-controlled.patch | 121 + ...rn-values-from-malloc-more-carefully.patch | 104 + ...tial-overflow-when-computing-key-mat.patch | 57 + ...luks-Avoid-memory-leak-on-error-path.patch | 36 + ...-EXTRA_DIST-out-of-automake-conditio.patch | 48 + copy-patches.sh | 55 + gating.yaml | 6 + libguestfs.keyring | Bin 0 -> 82814 bytes nbdkit-1.24.0.tar.gz.sig | 17 + nbdkit-find-provides | 23 + nbdkit.attr | 3 + nbdkit.spec | 1569 ++++++++++++ sources | 2 + tests/basic-test.sh | 6 + tests/tests.yml | 12 + 38 files changed, 9304 insertions(+) create mode 100644 .gitignore create mode 100644 0001-cache-cow-Fix-data-corruption-in-zero-and-trim-on-un.patch create mode 100644 0001-ssh-Allow-the-remote-file-to-be-created.patch create mode 100644 0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch create mode 100644 0002-server-CVE-2021-3716-reset-structured-replies-on-sta.patch create mode 100644 0003-readahead-Fix-test.patch create mode 100644 0003-server-reset-meta-context-replies-on-starttls.patch create mode 100644 0004-New-filter-luks.patch create mode 100644 0004-cow-Fix-for-qemu-6.1-which-requires-backing-format.patch create mode 100644 0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch create mode 100644 0006-luks-Various-fixes-for-Clang.patch create mode 100644 0007-luks-Link-with-libcompat-on-Windows.patch create mode 100644 0008-luks-Refactor-the-filter.patch create mode 100644 0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch create mode 100644 0010-Add-nbdkit.parse_size-Python-function.patch create mode 100644 0011-cache-Fix-cross-reference-nbdkit-readahead-filter.patch create mode 100644 0012-curl-Don-t-document-curl-plugin-readahead-filter.patch create mode 100644 0013-New-filter-scan.patch create mode 100644 0014-scan-Remove-condition-variable.patch create mode 100644 0015-scan-Small-typographical-fix-in-manual.patch create mode 100644 0016-ssh-Don-t-reference-readahead-or-scan-filters-from-t.patch create mode 100644 0017-scan-Fix-bound-so-we-don-t-try-to-prefetch-beyond-en.patch create mode 100644 0018-tests-Add-a-regression-test-for-LUKS-zeroing-crash.patch create mode 100644 0019-rate-Allow-burstiness-to-be-controlled.patch create mode 100644 0020-luks-Check-return-values-from-malloc-more-carefully.patch create mode 100644 0021-luks-Avoid-potential-overflow-when-computing-key-mat.patch create mode 100644 0022-luks-Avoid-memory-leak-on-error-path.patch create mode 100644 0023-tests-Hoist-some-EXTRA_DIST-out-of-automake-conditio.patch create mode 100755 copy-patches.sh create mode 100755 gating.yaml create mode 100644 libguestfs.keyring create mode 100644 nbdkit-1.24.0.tar.gz.sig create mode 100755 nbdkit-find-provides create mode 100644 nbdkit.attr create mode 100644 nbdkit.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..e138377 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +SOURCES/libguestfs.keyring +SOURCES/nbdkit-1.24.0.tar.gz diff --git a/0001-cache-cow-Fix-data-corruption-in-zero-and-trim-on-un.patch b/0001-cache-cow-Fix-data-corruption-in-zero-and-trim-on-un.patch new file mode 100644 index 0000000..151fc17 --- /dev/null +++ b/0001-cache-cow-Fix-data-corruption-in-zero-and-trim-on-un.patch @@ -0,0 +1,82 @@ +From 99788909d9ec36e3210cf85976fe5b18da690ddd Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Wed, 4 Aug 2021 20:24:59 +0100 +Subject: [PATCH] cache, cow: Fix data corruption in zero and trim on unaligned + tail + +Commit eb6009b092 ("cache, cow: Reduce use of bounce-buffer") first +introduced in nbdkit 1.14 added an optimization of the +read-modify-write mechanism used for unaligned heads and tails when +zeroing in the cache layer. + +Unfortunately the part applied to the tail contained a mistake: It +zeroes the end of the buffer rather than the beginning. This causes +data corruption when you use the zero or trim function with an offset +and count which is not aligned to the block size. + +Although the bug has been around for years, a recent change made it +more likely to happen. Commit c1905b0a28 ("cache, cow: Use a 64K +block size by default") increased the default block size from 4K to +64K. Most filesystems use a 4K block size so operations like fstrim +will make 4K-aligned requests, and with a 4K block size also in the +cache or cow filter the unaligned case would never have been hit +before. + +We can demonstrate the bug simply by filling a buffer with data +(100000 bytes in the example), and then trimming that data, which +ought to zero it out. + +Before this commit there is data visible after the trim: + +$ nbdkit --filter=cow data "0x21 * 100000" --run 'nbdsh -u $uri -c "h.trim(100000, 0)" ; nbdcopy $uri - | hexdump -C' +00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +00018000 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 21 |!!!!!!!!!!!!!!!!| +* +000186a0 + +After this commit the trim completely clears the data: + +$ nbdkit --filter=cow data "0x21 * 100000" --run 'nbdsh -u $uri -c "h.trim(100000, 0)" ; nbdcopy $uri - | hexdump -C' +00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| +* +000186a0 + +Thanks: Ming Xie for finding the bug +Fixes: commit eb6009b092ae642ed25f133d487dd40ef7bf70f8 +(cherry picked from commit a0ae7b2158598ce48ac31706319007f716d01c87) +(cherry picked from commit c0b15574647672cb5c48178333acdd07424692ef) +--- + filters/cache/cache.c | 2 +- + filters/cow/cow.c | 2 +- + 2 files changed, 2 insertions(+), 2 deletions(-) + +diff --git a/filters/cache/cache.c b/filters/cache/cache.c +index 91dcc43d..0616cc7b 100644 +--- a/filters/cache/cache.c ++++ b/filters/cache/cache.c +@@ -493,7 +493,7 @@ cache_zero (struct nbdkit_next_ops *next_ops, void *nxdata, + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + r = blk_read (next_ops, nxdata, blknum, block, err); + if (r != -1) { +- memset (&block[count], 0, blksize - count); ++ memset (block, 0, count); + r = blk_write (next_ops, nxdata, blknum, block, flags, err); + } + if (r == -1) +diff --git a/filters/cow/cow.c b/filters/cow/cow.c +index 51ca64a4..1cfcc4e7 100644 +--- a/filters/cow/cow.c ++++ b/filters/cow/cow.c +@@ -419,7 +419,7 @@ cow_zero (struct nbdkit_next_ops *next_ops, void *nxdata, + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); + r = blk_read (next_ops, nxdata, blknum, block, err); + if (r != -1) { +- memset (&block[count], 0, BLKSIZE - count); ++ memset (block, 0, count); + r = blk_write (blknum, block, err); + } + if (r == -1) +-- +2.31.1 + diff --git a/0001-ssh-Allow-the-remote-file-to-be-created.patch b/0001-ssh-Allow-the-remote-file-to-be-created.patch new file mode 100644 index 0000000..f3e07e4 --- /dev/null +++ b/0001-ssh-Allow-the-remote-file-to-be-created.patch @@ -0,0 +1,293 @@ +From 6a2b0aac8be655524ea223e32cac0395fcc9f975 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Fri, 15 Apr 2022 12:08:37 +0100 +Subject: [PATCH] ssh: Allow the remote file to be created + +This adds new parameters, create=(true|false), create-size=SIZE and +create-mode=MODE to create and truncate the remote file. + +Reviewed-by: Laszlo Ersek +(cherry picked from commit 0793f30b1071753532362b2ebf9cb8156a88c3c3) +--- + plugins/ssh/nbdkit-ssh-plugin.pod | 34 ++++++++- + plugins/ssh/ssh.c | 112 +++++++++++++++++++++++++++--- + tests/test-ssh.sh | 13 +++- + 3 files changed, 146 insertions(+), 13 deletions(-) + +diff --git a/plugins/ssh/nbdkit-ssh-plugin.pod b/plugins/ssh/nbdkit-ssh-plugin.pod +index 3f401c15..2bc2c4a7 100644 +--- a/plugins/ssh/nbdkit-ssh-plugin.pod ++++ b/plugins/ssh/nbdkit-ssh-plugin.pod +@@ -5,8 +5,10 @@ nbdkit-ssh-plugin - access disk images over the SSH protocol + =head1 SYNOPSIS + + nbdkit ssh host=HOST [path=]PATH +- [compression=true] [config=CONFIG_FILE] [identity=FILENAME] +- [known-hosts=FILENAME] [password=PASSWORD|-|+FILENAME] ++ [compression=true] [config=CONFIG_FILE] ++ [create=true] [create-mode=MODE] [create-size=SIZE] ++ [identity=FILENAME] [known-hosts=FILENAME] ++ [password=PASSWORD|-|+FILENAME] + [port=PORT] [timeout=SECS] [user=USER] + [verify-remote-host=false] + +@@ -62,6 +64,34 @@ The C parameter is optional. If it is I specified at all + then F<~/.ssh/config> and F are both read. + Missing or unreadable files are ignored. + ++=item B ++ ++(nbdkit E 1.32) ++ ++If set, the remote file will be created. The remote file is created ++on the first NBD connection to nbdkit, not when nbdkit starts up. If ++the file already exists, it will be replaced and any existing content ++lost. ++ ++If using this option, you must use C. C can ++be used to control the permissions of the new file. ++ ++=item BMODE ++ ++(nbdkit E 1.32) ++ ++If using C specify the default permissions of the new ++remote file. You can use octal modes like C or ++C. The default is C<0600>, ie. only readable and ++writable by the remote user. ++ ++=item BSIZE ++ ++(nbdkit E 1.32) ++ ++If using C, specify the virtual size of the new disk. ++C can use modifiers like C<100M> etc. ++ + =item BHOST + + Specify the name or IP address of the remote host. +diff --git a/plugins/ssh/ssh.c b/plugins/ssh/ssh.c +index 39d77e44..5e314cd7 100644 +--- a/plugins/ssh/ssh.c ++++ b/plugins/ssh/ssh.c +@@ -44,6 +44,8 @@ + #include + #include + ++#include ++ + #include + #include + #include +@@ -51,6 +53,7 @@ + #include + + #include "array-size.h" ++#include "cleanup.h" + #include "const-string-vector.h" + #include "minmax.h" + +@@ -64,6 +67,9 @@ static const char *known_hosts = NULL; + static const_string_vector identities = empty_vector; + static uint32_t timeout = 0; + static bool compression = false; ++static bool create = false; ++static int64_t create_size = -1; ++static unsigned create_mode = S_IRUSR | S_IWUSR /* 0600 */; + + /* config can be: + * NULL => parse options from default file +@@ -167,6 +173,27 @@ ssh_config (const char *key, const char *value) + return -1; + compression = r; + } ++ else if (strcmp (key, "create") == 0) { ++ r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ create = r; ++ } ++ else if (strcmp (key, "create-size") == 0) { ++ create_size = nbdkit_parse_size (value); ++ if (create_size == -1) ++ return -1; ++ } ++ else if (strcmp (key, "create-mode") == 0) { ++ r = nbdkit_parse_unsigned (key, value, &create_mode); ++ if (r == -1) ++ return -1; ++ /* OpenSSH checks this too. */ ++ if (create_mode > 0777) { ++ nbdkit_error ("create-mode must be <= 0777"); ++ return -1; ++ } ++ } + + else { + nbdkit_error ("unknown parameter '%s'", key); +@@ -186,6 +213,13 @@ ssh_config_complete (void) + return -1; + } + ++ /* If create=true, create-size must be supplied. */ ++ if (create && create_size == -1) { ++ nbdkit_error ("if using create=true, you must specify the size " ++ "of the new remote file using create-size=SIZE"); ++ return -1; ++ } ++ + return 0; + } + +@@ -200,7 +234,10 @@ ssh_config_complete (void) + "identity= Prepend private key (identity) file.\n" \ + "timeout=SECS Set SSH connection timeout.\n" \ + "verify-remote-host=false Ignore known_hosts.\n" \ +- "compression=true Enable compression." ++ "compression=true Enable compression.\n" \ ++ "create=true Create the remote file.\n" \ ++ "create-mode=MODE Set the permissions of the remote file.\n" \ ++ "create-size=SIZE Set the size of the remote file." + + /* Since we must simulate atomic pread and pwrite using seek + + * read/write, calls on each handle must be serialized. +@@ -329,6 +366,65 @@ authenticate (struct ssh_handle *h) + return -1; + } + ++/* This function opens or creates the remote file (depending on ++ * create=false|true). Parallel connections might call this function ++ * at the same time, and so we must hold a lock to ensure that the ++ * file is created at most once. ++ */ ++static pthread_mutex_t create_lock = PTHREAD_MUTEX_INITIALIZER; ++ ++static sftp_file ++open_or_create_path (ssh_session session, sftp_session sftp, int readonly) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&create_lock); ++ int access_type; ++ int r; ++ sftp_file file; ++ ++ access_type = readonly ? O_RDONLY : O_RDWR; ++ if (create) access_type |= O_CREAT | O_TRUNC; ++ ++ file = sftp_open (sftp, path, access_type, S_IRWXU); ++ if (!file) { ++ nbdkit_error ("cannot %s file for %s: %s", ++ create ? "create" : "open", ++ readonly ? "reading" : "writing", ++ ssh_get_error (session)); ++ return NULL; ++ } ++ ++ if (create) { ++ /* There's no sftp_truncate call. However OpenSSH lets you call ++ * SSH_FXP_SETSTAT + SSH_FILEXFER_ATTR_SIZE which invokes ++ * truncate(2) on the server. Libssh doesn't provide a binding ++ * for SSH_FXP_FSETSTAT so we have to pass the session + path. ++ */ ++ struct sftp_attributes_struct attrs = { ++ .flags = SSH_FILEXFER_ATTR_SIZE | ++ SSH_FILEXFER_ATTR_PERMISSIONS, ++ .size = create_size, ++ .permissions = create_mode, ++ }; ++ ++ r = sftp_setstat (sftp, path, &attrs); ++ if (r != SSH_OK) { ++ nbdkit_error ("setstat failed: %s", ssh_get_error (session)); ++ ++ /* Best-effort attempt to delete the remote file on failure. */ ++ r = sftp_unlink (sftp, path); ++ if (r != SSH_OK) ++ nbdkit_debug ("unlink failed: %s", ssh_get_error (session)); ++ ++ return NULL; ++ } ++ } ++ ++ /* On the next connection, don't create or truncate the file. */ ++ create = false; ++ ++ return file; ++} ++ + /* Create the per-connection handle. */ + static void * + ssh_open (int readonly) +@@ -337,7 +433,6 @@ ssh_open (int readonly) + const int set = 1; + size_t i; + int r; +- int access_type; + + h = calloc (1, sizeof *h); + if (h == NULL) { +@@ -471,7 +566,7 @@ ssh_open (int readonly) + if (authenticate (h) == -1) + goto err; + +- /* Open the SFTP connection and file. */ ++ /* Open the SFTP connection. */ + h->sftp = sftp_new (h->session); + if (!h->sftp) { + nbdkit_error ("failed to allocate sftp session: %s", +@@ -484,14 +579,11 @@ ssh_open (int readonly) + ssh_get_error (h->session)); + goto err; + } +- access_type = readonly ? O_RDONLY : O_RDWR; +- h->file = sftp_open (h->sftp, path, access_type, S_IRWXU); +- if (!h->file) { +- nbdkit_error ("cannot open file for %s: %s", +- readonly ? "reading" : "writing", +- ssh_get_error (h->session)); ++ ++ /* Open or create the remote file. */ ++ h->file = open_or_create_path (h->session, h->sftp, readonly); ++ if (!h->file) + goto err; +- } + + nbdkit_debug ("opened libssh handle"); + +diff --git a/tests/test-ssh.sh b/tests/test-ssh.sh +index 6c0ce410..f04b4488 100755 +--- a/tests/test-ssh.sh ++++ b/tests/test-ssh.sh +@@ -36,6 +36,7 @@ set -x + + requires test -f disk + requires nbdcopy --version ++requires stat --version + + # Check that ssh to localhost will work without any passwords or phrases. + # +@@ -48,7 +49,7 @@ then + exit 77 + fi + +-files="ssh.img" ++files="ssh.img ssh2.img" + rm -f $files + cleanup_fn rm -f $files + +@@ -59,3 +60,13 @@ nbdkit -v -D ssh.log=2 -U - \ + + # The output should be identical. + cmp disk ssh.img ++ ++# Copy local file 'ssh.img' to newly created "remote" 'ssh2.img' ++size="$(stat -c %s disk)" ++nbdkit -v -D ssh.log=2 -U - \ ++ ssh host=localhost $PWD/ssh2.img \ ++ create=true create-size=$size \ ++ --run 'nbdcopy ssh.img "$uri"' ++ ++# The output should be identical. ++cmp disk ssh2.img +-- +2.31.1 + diff --git a/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch b/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch new file mode 100644 index 0000000..69d5efa --- /dev/null +++ b/0002-readahead-Rewrite-this-filter-so-it-prefetches-using.patch @@ -0,0 +1,794 @@ +From ac40ae11bc9983e11185749b23e793568cb366cc Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 16 Apr 2022 18:39:13 +0100 +Subject: [PATCH] readahead: Rewrite this filter so it prefetches using .cache + +The previous readahead filter did not work well and we have stopped +using it in virt-v2v. However the concept is a good one if we can +make it work. This commit completely rethinks the filter. + +Now, in parallel with the ordinary pread command, we issue a prefetch +(ie. .cache) to the underlying plugin for the data immediately +following the pread. For example a simple sequence of operations: + + t=1 pread (offset=0, count=65536) + t=2 pread (offset=65536, count=65536) + t=3 pread (offset=131072, count=65536) + +would become: + + t=1 pread (offset=0, count=65536) <--\ issued + cache (offset=65536, count=65536) <--/ in parallel + t=2 pread (offset=65536, count=65536) + cache (offset=131072, count=65536) + t=3 pread (offset=131072, count=65536) + cache (offset=196608, count=65536) + +This requires that the underlying filter(s) and plugin chain can +actually do something with the .cache request. If this is not the +case then the filter does nothing (but it will print a warning). For +plugins which don't have native support for prefetching, it is +sufficient to insert nbdkit-cache-filter after this filter. +(nbdkit-cow-filter can also be used with cow-on-cache=true, but that +is more useful for advanced users who are already using the cow +filter). + +The implementation creates a background thread per connection to issue +the parallel .cache requests. This is safer than the alternative (one +background thread in total) since we don't have to deal with the +problem of cache requests being issued with the wrong export name or +stale next pointer. The background thread is controlled by a queue of +commands, with the only possible commands being "cache" or "quit". + +Because the background thread issues parallel requests on the same +connection, the underlying plugin must support the parallel thread +model, otherwise we would be violating the plugin's thread model. It +may be possible in future to open a new connection from the background +thread (but with the same exportname), which would lift this +restriction to at least serialize_requests. Because of the current +limitation, nbdkit-curl-plugin cannot use prefetch. + +(cherry picked from commit 2ff548d66ad3eae87868402ec5b3319edd12090f) +--- + TODO | 22 +- + filters/readahead/Makefile.am | 2 + + filters/readahead/bgthread.c | 76 ++++ + filters/readahead/nbdkit-readahead-filter.pod | 70 +++- + filters/readahead/readahead.c | 338 +++++++++--------- + filters/readahead/readahead.h | 60 ++++ + tests/test-readahead.sh | 2 +- + 7 files changed, 367 insertions(+), 203 deletions(-) + create mode 100644 filters/readahead/bgthread.c + create mode 100644 filters/readahead/readahead.h + +diff --git a/TODO b/TODO +index 5ae21db5..4d2a9796 100644 +--- a/TODO ++++ b/TODO +@@ -62,16 +62,6 @@ General ideas for improvements + continue to keep their non-standard handshake while utilizing nbdkit + to prototype new behaviors in serving the kernel. + +-* Background thread for filters. Some filters (readahead, cache and +- proposed scan filter - see below) could be more effective if they +- were able to defer work to a background thread. We finally have +- nbdkit_next_context_open and friends for allowing a background +- thread to have access into the plugin, but still need to worry about +- thread-safety (how much must the filter do vs. nbdkit, to avoid +- calling into the plugin too many times at once) and cleanup +- (spawning the thread during .after_fork is viable, but cleaning it +- up during .unload is too late). +- + * "nbdkit.so": nbdkit as a loadable shared library. The aim of nbdkit + is to make it reusable from other programs (see nbdkit-captive(1)). + If it was a loadable shared library it would be even more reusable. +@@ -228,6 +218,8 @@ Suggestions for filters + * nbdkit-cache-filter should handle ENOSPC errors automatically by + reclaiming blocks from the cache + ++* nbdkit-cache-filter could use a background thread for reclaiming. ++ + * zstd filter was requested as a way to do what we currently do with + xz but saving many hours on compression (at the cost of hundreds of + MBs of extra data) +@@ -240,6 +232,16 @@ Suggestions for filters + could inject a flush after pausing. However this requires that + filter background threads have access to the plugin (see above). + ++nbdkit-readahead-filter: ++ ++* The filter should open a new connection to the plugin per background ++ thread so it is able to work with plugins that use the ++ serialize_requests thread model (like curl). At the moment it makes ++ requests on the same connection, so it requires plugins to use the ++ parallel thread model. ++ ++* It should combine (or avoid) overlapping cache requests. ++ + nbdkit-rate-filter: + + * allow other kinds of traffic shaping such as VBR +diff --git a/filters/readahead/Makefile.am b/filters/readahead/Makefile.am +index ee5bb3fb..187993ae 100644 +--- a/filters/readahead/Makefile.am ++++ b/filters/readahead/Makefile.am +@@ -37,6 +37,8 @@ filter_LTLIBRARIES = nbdkit-readahead-filter.la + + nbdkit_readahead_filter_la_SOURCES = \ + readahead.c \ ++ readahead.h \ ++ bgthread.c \ + $(top_srcdir)/include/nbdkit-filter.h \ + $(NULL) + +diff --git a/filters/readahead/bgthread.c b/filters/readahead/bgthread.c +new file mode 100644 +index 00000000..5894bb5f +--- /dev/null ++++ b/filters/readahead/bgthread.c +@@ -0,0 +1,76 @@ ++/* nbdkit ++ * Copyright (C) 2019-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "readahead.h" ++ ++#include "cleanup.h" ++ ++void * ++readahead_thread (void *vp) ++{ ++ struct bgthread_ctrl *ctrl = vp; ++ ++ for (;;) { ++ struct command cmd; ++ ++ /* Wait until we are sent at least one command. */ ++ { ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&ctrl->lock); ++ while (ctrl->cmds.len == 0) ++ pthread_cond_wait (&ctrl->cond, &ctrl->lock); ++ cmd = ctrl->cmds.ptr[0]; ++ command_queue_remove (&ctrl->cmds, 0); ++ } ++ ++ switch (cmd.type) { ++ case CMD_QUIT: ++ /* Finish processing and exit the thread. */ ++ return NULL; ++ ++ case CMD_CACHE: ++ /* Issue .cache (readahead) to underlying plugin. We ignore any ++ * errors because there's no way to communicate that back to the ++ * client, and readahead is only advisory. ++ */ ++ cmd.next->cache (cmd.next, cmd.count, cmd.offset, 0, NULL); ++ } ++ } ++} +diff --git a/filters/readahead/nbdkit-readahead-filter.pod b/filters/readahead/nbdkit-readahead-filter.pod +index c220d379..630e5924 100644 +--- a/filters/readahead/nbdkit-readahead-filter.pod ++++ b/filters/readahead/nbdkit-readahead-filter.pod +@@ -1,28 +1,66 @@ + =head1 NAME + +-nbdkit-readahead-filter - prefetch data when reading sequentially ++nbdkit-readahead-filter - prefetch data ahead of sequential reads + + =head1 SYNOPSIS + +- nbdkit --filter=readahead plugin ++ nbdkit --filter=readahead PLUGIN ++ ++ nbdkit --filter=readahead --filter=cache PLUGIN ++ ++ nbdkit --filter=readahead --filter=cow PLUGIN cow-on-cache=true + + =head1 DESCRIPTION + + C is a filter that prefetches data when the +-client is reading sequentially. ++client is reading. + +-A common use for this filter is to accelerate sequential copy +-operations (like S>) when plugin requests have a +-high overhead (like L). For example: +- +- nbdkit -U - --filter=readahead curl https://example.com/disk.img \ +- --run 'qemu-img convert $nbd disk.img' ++When the client issues a read, this filter issues a parallel prefetch ++(C<.cache>) for subsequent data. Plugins which support this command ++will prefetch the data, making sequential reads faster. For plugins ++which do not support this command, you can inject ++L below (after) this filter, giving ++approximately the same effect. L can be used ++instead of nbdkit-cache-filter, if you add the C ++option. + + The filter uses a simple adaptive algorithm which accelerates +-sequential reads, but has a small penalty if the client does random +-reads. If the client mixes reads with writes or write-like operations +-(trimming, zeroing) then it will work but there can be a large +-performance penalty. ++sequential reads and requires no further configuration. ++ ++=head2 Limitations ++ ++In a number of significant cases this filter will do nothing. The ++filter will print a warning message if this happens. ++ ++=over 4 ++ ++=item Thread model must be parallel ++ ++For example L only supports ++C, and so this filter cannot perform prefetches in ++parallel with the read requests. ++ ++We may be able to lift this restriction in future. ++ ++=item Underlying filters or plugin must support C<.cache> (prefetch) ++ ++Very many plugins do not have the concept of prefetching and/or ++do not implement the C<.cache> callback, and so there is no ++way for this filter to issue prefetches. ++ ++You can usually get around this by adding I<--filter=cache> after this ++filter as explained above. It may be necessary to limit the total ++size of the cache (see L). ++ ++=item Clients and kernels may do readahead already ++ ++It may be the case that NBD clients are already issuing ++C (NBD prefetch) commands. It may also be the case ++that your plugin is using local file functions where the kernel is ++doing readahead. In such cases this filter is not necessary and may ++be pessimal. ++ ++=back + + =head1 PARAMETERS + +@@ -50,9 +88,9 @@ C first appeared in nbdkit 1.12. + + L, + L, +-L, ++L, ++L, + L, +-L, + L, + L, + L, +@@ -64,4 +102,4 @@ Richard W.M. Jones + + =head1 COPYRIGHT + +-Copyright (C) 2019 Red Hat Inc. ++Copyright (C) 2019-2022 Red Hat Inc. +diff --git a/filters/readahead/readahead.c b/filters/readahead/readahead.c +index f5552d4c..1d7ae111 100644 +--- a/filters/readahead/readahead.c ++++ b/filters/readahead/readahead.c +@@ -1,5 +1,5 @@ + /* nbdkit +- * Copyright (C) 2019-2021 Red Hat Inc. ++ * Copyright (C) 2019-2022 Red Hat Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are +@@ -34,232 +34,218 @@ + + #include + #include ++#include + #include + #include + #include +- + #include + + #include + ++#include "readahead.h" ++ + #include "cleanup.h" + #include "minmax.h" +- +-/* Copied from server/plugins.c. */ +-#define MAX_REQUEST_SIZE (64 * 1024 * 1024) ++#include "vector.h" + + /* These could be made configurable in future. */ +-#define READAHEAD_MIN 65536 +-#define READAHEAD_MAX MAX_REQUEST_SIZE +- +-/* This lock protects the global state. */ +-static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; +- +-/* The real size of the underlying plugin. */ +-static uint64_t size; ++#define READAHEAD_MIN 32768 ++#define READAHEAD_MAX (4*1024*1024) + + /* Size of the readahead window. */ ++static pthread_mutex_t window_lock = PTHREAD_MUTEX_INITIALIZER; + static uint64_t window = READAHEAD_MIN; ++static uint64_t last_offset = 0, last_readahead = 0; + +-/* The single prefetch buffer shared by all threads, and its virtual +- * location in the virtual disk. The prefetch buffer grows +- * dynamically as required, but never shrinks. ++static int thread_model = -1; /* Thread model of the underlying plugin. */ ++ ++/* Per-connection data. */ ++struct readahead_handle { ++ int can_cache; /* Can the underlying plugin cache? */ ++ pthread_t thread; /* The background thread, one per connection. */ ++ struct bgthread_ctrl ctrl; ++}; ++ ++/* We have various requirements of the underlying filter(s) + plugin: ++ * - They must support NBDKIT_CACHE_NATIVE (otherwise our requests ++ * would not do anything useful). ++ * - They must use the PARALLEL thread model (otherwise we could ++ * violate their thread model). ++ */ ++static bool ++filter_working (struct readahead_handle *h) ++{ ++ return ++ h->can_cache == NBDKIT_CACHE_NATIVE && ++ thread_model == NBDKIT_THREAD_MODEL_PARALLEL; ++} ++ ++static bool ++suggest_cache_filter (struct readahead_handle *h) ++{ ++ return ++ h->can_cache != NBDKIT_CACHE_NATIVE && ++ thread_model == NBDKIT_THREAD_MODEL_PARALLEL; ++} ++ ++/* We need to hook into .get_ready() so we can read the final thread ++ * model (of the whole server). + */ +-static char *buffer = NULL; +-static size_t bufsize = 0; +-static uint64_t position; +-static uint32_t length = 0; ++static int ++readahead_get_ready (int final_thread_model) ++{ ++ thread_model = final_thread_model; ++ return 0; ++} ++ ++static int ++send_command_to_background_thread (struct bgthread_ctrl *ctrl, ++ const struct command cmd) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&ctrl->lock); ++ if (command_queue_append (&ctrl->cmds, cmd) == -1) ++ return -1; ++ /* Signal the thread if it could be sleeping on an empty queue. */ ++ if (ctrl->cmds.len == 1) ++ pthread_cond_signal (&ctrl->cond); ++ return 0; ++} ++ ++static void * ++readahead_open (nbdkit_next_open *next, nbdkit_context *nxdata, ++ int readonly, const char *exportname, int is_tls) ++{ ++ struct readahead_handle *h; ++ int err; ++ ++ if (next (nxdata, readonly, exportname) == -1) ++ return NULL; ++ ++ h = malloc (sizeof *h); ++ if (h == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return NULL; ++ } ++ ++ h->ctrl.cmds = (command_queue) empty_vector; ++ pthread_mutex_init (&h->ctrl.lock, NULL); ++ pthread_cond_init (&h->ctrl.cond, NULL); ++ ++ /* Create the background thread. */ ++ err = pthread_create (&h->thread, NULL, readahead_thread, &h->ctrl); ++ if (err != 0) { ++ errno = err; ++ nbdkit_error ("pthread_create: %m"); ++ pthread_cond_destroy (&h->ctrl.cond); ++ pthread_mutex_destroy (&h->ctrl.lock); ++ free (h); ++ return NULL; ++ } ++ ++ return h; ++} + + static void +-readahead_unload (void) ++readahead_close (void *handle) + { +- free (buffer); ++ struct readahead_handle *h = handle; ++ const struct command quit_cmd = { .type = CMD_QUIT }; ++ ++ send_command_to_background_thread (&h->ctrl, quit_cmd); ++ pthread_join (h->thread, NULL); ++ pthread_cond_destroy (&h->ctrl.cond); ++ pthread_mutex_destroy (&h->ctrl.lock); ++ command_queue_reset (&h->ctrl.cmds); ++ free (h); + } + +-static int64_t readahead_get_size (nbdkit_next *next, void *handle); +- +-/* In prepare, force a call to get_size which sets the size global. */ + static int +-readahead_prepare (nbdkit_next *next, void *handle, int readonly) ++readahead_can_cache (nbdkit_next *next, void *handle) + { +- int64_t r; ++ struct readahead_handle *h = handle; ++ int r; + +- r = readahead_get_size (next, handle); +- return r >= 0 ? 0 : -1; +-} +- +-/* Get the size. */ +-static int64_t +-readahead_get_size (nbdkit_next *next, void *handle) +-{ +- int64_t r; +- +- r = next->get_size (next); ++ /* Call next->can_cache to read the underlying 'can_cache'. */ ++ r = next->can_cache (next); + if (r == -1) + return -1; ++ h->can_cache = r; + +- ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); +- size = r; ++ if (!filter_working (h)) { ++ nbdkit_error ("readahead: warning: underlying plugin does not support " ++ "NBD_CMD_CACHE or PARALLEL thread model, so the filter " ++ "won't do anything"); ++ if (suggest_cache_filter (h)) ++ nbdkit_error ("readahead: try adding --filter=cache " ++ "after this filter"); ++ /* This is an error, but that's just to ensure that the warning ++ * above is seen. We don't need to return -1 here. ++ */ ++ } + + return r; + } + +-/* Cache */ +-static int +-readahead_can_cache (nbdkit_next *next, void *handle) +-{ +- /* We are already operating as a cache regardless of the plugin's +- * underlying .can_cache, but it's easiest to just rely on nbdkit's +- * behavior of calling .pread for caching. +- */ +- return NBDKIT_CACHE_EMULATE; +-} +- + /* Read data. */ +- +-static int +-fill_readahead (nbdkit_next *next, +- uint32_t count, uint64_t offset, uint32_t flags, int *err) +-{ +- position = offset; +- +- /* Read at least window bytes, but if count is larger read that. +- * Note that the count cannot be bigger than the buffer size. +- */ +- length = MAX (count, window); +- +- /* Don't go beyond the end of the underlying file. */ +- length = MIN (length, size - position); +- +- /* Grow the buffer if necessary. */ +- if (bufsize < length) { +- char *new_buffer = realloc (buffer, length); +- if (new_buffer == NULL) { +- *err = errno; +- nbdkit_error ("realloc: %m"); +- return -1; +- } +- buffer = new_buffer; +- bufsize = length; +- } +- +- if (next->pread (next, buffer, length, offset, flags, err) == -1) { +- length = 0; /* failed to fill the prefetch buffer */ +- return -1; +- } +- +- return 0; +-} +- + static int + readahead_pread (nbdkit_next *next, + void *handle, void *buf, uint32_t count, uint64_t offset, + uint32_t flags, int *err) + { +- ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); ++ struct readahead_handle *h = handle; + +- while (count > 0) { +- if (length == 0) { +- /* We don't have a prefetch buffer at all. This could be the +- * first request or reset after a miss. +- */ +- window = READAHEAD_MIN; +- if (fill_readahead (next, count, offset, flags, err) == -1) +- return -1; +- } ++ /* If the underlying plugin doesn't support caching then skip that ++ * step completely. The filter will do nothing. ++ */ ++ if (filter_working (h)) { ++ struct command ra_cmd = { .type = CMD_CACHE, .next = NULL }; ++ int64_t size; + +- /* Can we satisfy this request partly or entirely from the prefetch +- * buffer? +- */ +- else if (position <= offset && offset < position + length) { +- uint32_t n = MIN (position - offset + length, count); +- memcpy (buf, &buffer[offset-position], n); +- buf += n; +- offset += n; +- count -= n; +- } ++ size = next->get_size (next); ++ if (size >= 0) { ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&window_lock); + +- /* Does the request start immediately after the prefetch buffer? +- * This is a “hit” allowing us to double the window size. +- */ +- else if (offset == position + length) { +- window = MIN (window * 2, READAHEAD_MAX); +- if (fill_readahead (next, count, offset, flags, err) == -1) +- return -1; ++ /* Generate the asynchronous (background) cache command for ++ * the readahead window. ++ */ ++ ra_cmd.offset = offset + count; ++ if (ra_cmd.offset < size) { ++ ra_cmd.count = MIN (window, size - ra_cmd.offset); ++ ra_cmd.next = next; /* If .next is non-NULL, we'll send it below. */ ++ } ++ ++ /* Should we change the window size? ++ * If the last readahead < current offset, double the window. ++ * If not, but we're still making forward progress, keep the window. ++ * If we're not making forward progress, reduce the window to minimum. ++ */ ++ if (last_readahead < offset) ++ window = MIN (window * 2, READAHEAD_MAX); ++ else if (last_offset < offset) ++ /* leave window unchanged */ ; ++ else ++ window = READAHEAD_MIN; ++ last_offset = offset; ++ last_readahead = ra_cmd.offset + ra_cmd.count; + } + +- /* Else it's a “miss”. Reset everything and start again. */ +- else +- length = 0; ++ if (ra_cmd.next && ++ send_command_to_background_thread (&h->ctrl, ra_cmd) == -1) ++ return -1; + } + +- return 0; +-} +- +-/* Any writes or write-like operations kill the prefetch buffer. +- * +- * We could do better here, but for the current use case of this +- * filter it doesn't matter. XXX +- */ +- +-static void +-kill_readahead (void) +-{ +- ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&lock); +- window = READAHEAD_MIN; +- length = 0; +-} +- +-static int +-readahead_pwrite (nbdkit_next *next, +- void *handle, +- const void *buf, uint32_t count, uint64_t offset, +- uint32_t flags, int *err) +-{ +- kill_readahead (); +- return next->pwrite (next, buf, count, offset, flags, err); +-} +- +-static int +-readahead_trim (nbdkit_next *next, +- void *handle, +- uint32_t count, uint64_t offset, uint32_t flags, +- int *err) +-{ +- kill_readahead (); +- return next->trim (next, count, offset, flags, err); +-} +- +-static int +-readahead_zero (nbdkit_next *next, +- void *handle, +- uint32_t count, uint64_t offset, uint32_t flags, +- int *err) +-{ +- kill_readahead (); +- return next->zero (next, count, offset, flags, err); +-} +- +-static int +-readahead_flush (nbdkit_next *next, +- void *handle, uint32_t flags, int *err) +-{ +- kill_readahead (); +- return next->flush (next, flags, err); ++ /* Issue the synchronous read. */ ++ return next->pread (next, buf, count, offset, flags, err); + } + + static struct nbdkit_filter filter = { + .name = "readahead", + .longname = "nbdkit readahead filter", +- .unload = readahead_unload, +- .prepare = readahead_prepare, +- .get_size = readahead_get_size, ++ .get_ready = readahead_get_ready, ++ .open = readahead_open, ++ .close = readahead_close, + .can_cache = readahead_can_cache, + .pread = readahead_pread, +- .pwrite = readahead_pwrite, +- .trim = readahead_trim, +- .zero = readahead_zero, +- .flush = readahead_flush, + }; + + NBDKIT_REGISTER_FILTER(filter) +diff --git a/filters/readahead/readahead.h b/filters/readahead/readahead.h +new file mode 100644 +index 00000000..a68204d5 +--- /dev/null ++++ b/filters/readahead/readahead.h +@@ -0,0 +1,60 @@ ++/* nbdkit ++ * Copyright (C) 2019-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#ifndef NBDKIT_READAHEAD_H ++#define NBDKIT_READAHEAD_H ++ ++#include ++ ++#include ++ ++#include "vector.h" ++ ++/* List of commands issued to the background thread. */ ++struct command { ++ enum { CMD_QUIT, CMD_CACHE } type; ++ nbdkit_next *next; ++ uint64_t offset; ++ uint32_t count; ++}; ++DEFINE_VECTOR_TYPE(command_queue, struct command); ++ ++struct bgthread_ctrl { ++ command_queue cmds; /* Command queue. */ ++ pthread_mutex_t lock; /* Lock for queue. */ ++ pthread_cond_t cond; /* Condition queue size 0 -> 1. */ ++}; ++ ++/* Start background thread (one per connection). */ ++extern void *readahead_thread (void *vp); ++ ++#endif /* NBDKIT_READAHEAD_H */ +diff --git a/tests/test-readahead.sh b/tests/test-readahead.sh +index 7ec7f8e9..17126e5a 100755 +--- a/tests/test-readahead.sh ++++ b/tests/test-readahead.sh +@@ -59,7 +59,7 @@ for i in range(0, 512*10, 512): + echo $((end_t - start_t)) + } + +-t1=$(test --filter=readahead) ++t1=$(test --filter=readahead --filter=cache) + t2=$(test) + + # In the t1 case we should make only 1 request into the plugin, +-- +2.31.1 + diff --git a/0002-server-CVE-2021-3716-reset-structured-replies-on-sta.patch b/0002-server-CVE-2021-3716-reset-structured-replies-on-sta.patch new file mode 100644 index 0000000..ce6938e --- /dev/null +++ b/0002-server-CVE-2021-3716-reset-structured-replies-on-sta.patch @@ -0,0 +1,94 @@ +From 6b9d4380df9bd0be91f49aad8c4f47b4e672adde Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Mon, 16 Aug 2021 13:43:29 -0500 +Subject: [PATCH] server: CVE-2021-3716 reset structured replies on starttls + +https://nostarttls.secvuln.info/ pointed out a series of CVEs in +common implementation flaw in various SMTP and IMAP clients and +servers, all with a common thread of improperly caching plaintext +state across the STARTTLS encryption boundary; and recommended that +other protocols with a STARTTLS operation perform a similar audit. + +It turns out that nbdkit has the same vulnerability in regards to the +NBD protocol: when nbdkit is run in opportunistic TLS mode, an +attacker is able to inject a plaintext NBD_OPT_STRUCTURED_REPLY before +proxying everything else a client sends to the server; if the server +then acts on that plaintext request (as nbdkit did before this patch), +then the server ends up sending structured replies to at least +NBD_CMD_READ, even though the client was assuming that the transition +to TLS has ruled out a MitM attack. + +On the bright side, nbdkit's behavior on a second +NBD_OPT_STRUCTURED_REPLY was to still reply with success, so a client +that always requests structured replies after starting TLS sees no +difference in behavior (that is, qemu 2.12 and later are immune) (had +nbdkit given an error to the second request, that may have caused +confusion to more clients). And there is always the mitigation of +using --tls=require, which lets nbdkit reject the MitM message +pre-encryption. However, nbd-client 3.15 to the present do not +understand structured replies, and I have confirmed that a MitM +attacker can thus cause a denial-of-service attack that does not +trigger until the client does its first encrypted NBD_CMD_READ. + +The NBD spec has been recently tightened to declare the nbdkit +behavior to be a security hole: +https://github.com/NetworkBlockDevice/nbd/commit/77e55378096aa +Fixes: eaa4c6e9a2c4bd (server: Minimal implementation of NBD Structured Replies.) + +(cherry picked from commit 09a13dafb7bb3a38ab52eb5501cba786365ba7fd) +(cherry picked from commit 6185b15a81e6915734d678f0781e31d45a7941a1) +--- + docs/nbdkit-security.pod | 11 +++++++++-- + server/protocol-handshake-newstyle.c | 3 ++- + 2 files changed, 11 insertions(+), 3 deletions(-) + +diff --git a/docs/nbdkit-security.pod b/docs/nbdkit-security.pod +index 3a28e54d..5a4e6da8 100644 +--- a/docs/nbdkit-security.pod ++++ b/docs/nbdkit-security.pod +@@ -10,7 +10,7 @@ For how to report new security issues, see the C file in the + top level source directory, also available online here: + L + +-=head2 CVE-2019-14850 ++=head2 CVE-2019-14850 + denial of service due to premature opening of back-end connection + + See the full announcement and links to mitigation, tests and fixes +@@ -26,6 +26,13 @@ See the full announcement and links to mitigation, tests and fixes + here: + https://www.redhat.com/archives/libguestfs/2019-September/msg00272.html + ++=head2 CVE-2021-3716 ++structured read denial of service attack against starttls ++ ++See the full announcement and links to mitigation, tests and fixes ++here: ++https://www.redhat.com/archives/libguestfs/2021-August/msg00083.html ++ + =head1 SEE ALSO + + L. +@@ -38,4 +45,4 @@ Richard W.M. Jones + + =head1 COPYRIGHT + +-Copyright (C) 2013-2020 Red Hat Inc. ++Copyright (C) 2013-2021 Red Hat Inc. +diff --git a/server/protocol-handshake-newstyle.c b/server/protocol-handshake-newstyle.c +index 0a76a814..b94950e2 100644 +--- a/server/protocol-handshake-newstyle.c ++++ b/server/protocol-handshake-newstyle.c +@@ -495,7 +495,8 @@ negotiate_handshake_newstyle_options (void) + return -1; + conn->using_tls = true; + debug ("using TLS on this connection"); +- /* Wipe out any cached default export name. */ ++ /* Wipe out any cached state. */ ++ conn->structured_replies = false; + for_each_backend (b) { + struct handle *h = get_handle (conn, b->i); + free (h->default_exportname); +-- +2.31.1 + diff --git a/0003-readahead-Fix-test.patch b/0003-readahead-Fix-test.patch new file mode 100644 index 0000000..52962b7 --- /dev/null +++ b/0003-readahead-Fix-test.patch @@ -0,0 +1,120 @@ +From b41b7d7ddf6d3fba23ac7978c8b272f2ff84265d Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Thu, 21 Apr 2022 16:14:46 +0100 +Subject: [PATCH] readahead: Fix test + +The previous test turned out to be pretty bad at testing the new +filter. A specific problem is that the filter starts a background +thread which issues .cache requests, while on the main connection +.pread requests are being passed through. The test used +--filter=readahead --filter=cache with the cache filter only caching +on .cache requests (since cache-on-read defaults to false), so only +caching requests made by the background thread. + + main thread + client ---- .pread ----- delay-filter -------> plugin + \ + \ background thread + .cache --- cache-filter + +Under very high load, the background thread could be starved. This +means no requests were being cached at all, and all requests were +passing through the delay filter. It would appear that readahead was +failing (which it was, in a way). + +It's not very easy to fix this since readahead is best-effort, but we +can go back to using a simpler plugin that logs reads and caches and +check that they look valid. + +Update: commit 2ff548d66ad3eae87868402ec5b3319edd12090f +(cherry picked from commit db1e3311727c6ecab3264a1811d33db1aa45a4d0) +--- + tests/test-readahead.sh | 61 +++++++++++++++++++++++------------------ + 1 file changed, 35 insertions(+), 26 deletions(-) + +diff --git a/tests/test-readahead.sh b/tests/test-readahead.sh +index 17126e5a..37f4a06f 100755 +--- a/tests/test-readahead.sh ++++ b/tests/test-readahead.sh +@@ -30,43 +30,52 @@ + # OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + # SUCH DAMAGE. + +-# Is the readahead filter faster? Copy a blank disk with a custom +-# plugin that sleeps on every request. Because the readahead filter +-# should result in fewer requests it should run faster. +- + source ./functions.sh + set -e + set -x + +-requires_filter delay ++requires_plugin sh + requires nbdsh --version + requires dd iflag=count_bytes > readahead.out ++ ;; ++ pread) ++ echo "$@" >> readahead.out ++ dd if=/dev/zero count=$3 iflag=count_bytes ++ ;; ++ *) ++ exit 2 ++ ;; ++esac ++EOF + +- end_t=$SECONDS +- echo $((end_t - start_t)) +-} ++cat readahead.out + +-t1=$(test --filter=readahead --filter=cache) +-t2=$(test) +- +-# In the t1 case we should make only 1 request into the plugin, +-# resulting in around 1 sleep period (5 seconds). In the t2 case we +-# make 10 requests so sleep for around 50 seconds. t1 should be < t2 +-# is every reasonable scenario. +-if [ $t1 -ge $t2 ]; then +- echo "$0: readahead filter took longer, should be shorter" +- exit 1 +-fi ++# We should see the pread requests, and additional cache requests for ++# the 32K region following each pread request. ++for i in `seq 0 512 $((512*10 - 512))` ; do ++ grep "pread 512 $i" readahead.out ++ grep "cache 32768 $((i+512))" readahead.out ++done +-- +2.31.1 + diff --git a/0003-server-reset-meta-context-replies-on-starttls.patch b/0003-server-reset-meta-context-replies-on-starttls.patch new file mode 100644 index 0000000..4ab0de0 --- /dev/null +++ b/0003-server-reset-meta-context-replies-on-starttls.patch @@ -0,0 +1,40 @@ +From add9b794b9dc697a1b52115c997fcfb6e06bf64c Mon Sep 17 00:00:00 2001 +From: Eric Blake +Date: Mon, 16 Aug 2021 13:43:29 -0500 +Subject: [PATCH] server: reset meta context replies on starttls + +Related to CVE-2021-3716, but not as severe. No compliant client will +send NBD_CMD_BLOCK_STATUS unless it first negotiates +NBD_OPT_SET_META_CONTEXT. If an attacker injects a premature +SET_META_CONTEXT, either the client will never notice (because it +never uses BLOCK_STATUS), or the client will overwrite the attacker's +attempt with the client's own SET_META_CONTEXT request after +encryption is enabled. So I don't class this as having the potential +to trigger denial-of-service due to any protocol mismatch between +compliant client and server (I don't care what happens with +non-compliant clients). + +Fixes: 26455d45 (server: protocol: Implement Block Status "base:allocation".) +(cherry picked from commit 6c5faac6a37077cf2366388a80862bb00616d0d8) +(cherry picked from commit 814d8103fb4b581dc01dfd25d2cd81596576f211) +--- + server/protocol-handshake-newstyle.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/server/protocol-handshake-newstyle.c b/server/protocol-handshake-newstyle.c +index b94950e2..eb0f3961 100644 +--- a/server/protocol-handshake-newstyle.c ++++ b/server/protocol-handshake-newstyle.c +@@ -497,6 +497,9 @@ negotiate_handshake_newstyle_options (void) + debug ("using TLS on this connection"); + /* Wipe out any cached state. */ + conn->structured_replies = false; ++ free (conn->exportname_from_set_meta_context); ++ conn->exportname_from_set_meta_context = NULL; ++ conn->meta_context_base_allocation = false; + for_each_backend (b) { + struct handle *h = get_handle (conn, b->i); + free (h->default_exportname); +-- +2.31.1 + diff --git a/0004-New-filter-luks.patch b/0004-New-filter-luks.patch new file mode 100644 index 0000000..5d14e25 --- /dev/null +++ b/0004-New-filter-luks.patch @@ -0,0 +1,1816 @@ +From c19936170cf8b385687cf40f5a9507d87ae08267 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 30 Apr 2022 12:35:07 +0100 +Subject: [PATCH] New filter: luks + +This filter allows you to open, read and write LUKSv1 disk images, +compatible with the ones used by dm-crypt and qemu. + +(cherry picked from commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c) +--- + TODO | 11 +- + configure.ac | 6 +- + docs/nbdkit-tls.pod | 1 + + filters/luks/Makefile.am | 77 ++ + filters/luks/luks.c | 1263 +++++++++++++++++++++++++++ + filters/luks/nbdkit-luks-filter.pod | 120 +++ + plugins/file/nbdkit-file-plugin.pod | 1 + + tests/Makefile.am | 12 + + tests/test-luks-copy.sh | 125 +++ + tests/test-luks-info.sh | 56 ++ + 10 files changed, 1668 insertions(+), 4 deletions(-) + create mode 100644 filters/luks/Makefile.am + create mode 100644 filters/luks/luks.c + create mode 100644 filters/luks/nbdkit-luks-filter.pod + create mode 100755 tests/test-luks-copy.sh + create mode 100755 tests/test-luks-info.sh + +diff --git a/TODO b/TODO +index 4d2a9796..0f5dc41d 100644 +--- a/TODO ++++ b/TODO +@@ -195,9 +195,6 @@ Suggestions for filters + connections. This may even allow a filter to offer a more parallel + threading model than the underlying plugin. + +-* LUKS encrypt/decrypt filter, bonus points if compatible with qemu +- LUKS-encrypted disk images +- + * CBT filter to track dirty blocks. See these links for inspiration: + https://www.cloudandheat.com/block-level-data-tracking-using-davice-mappers-dm-era/ + https://github.com/qemu/qemu/blob/master/docs/interop/bitmaps.rst +@@ -232,6 +229,14 @@ Suggestions for filters + could inject a flush after pausing. However this requires that + filter background threads have access to the plugin (see above). + ++nbdkit-luks-filter: ++ ++* This filter should also support LUKSv2 (and so should qemu). ++ ++* There are some missing features: ESSIV, more ciphers. ++ ++* Implement trim and zero if possible. ++ + nbdkit-readahead-filter: + + * The filter should open a new connection to the plugin per background +diff --git a/configure.ac b/configure.ac +index a402921b..de85b4da 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -127,6 +127,7 @@ filters="\ + ip \ + limit \ + log \ ++ luks \ + multi-conn \ + nocache \ + noextents \ +@@ -614,8 +615,9 @@ PKG_CHECK_MODULES([GNUTLS], [gnutls >= 3.3.0], [ + ], [ + AC_MSG_WARN([gnutls not found or < 3.3.0, TLS support will be disabled.]) + ]) ++AM_CONDITIONAL([HAVE_GNUTLS], [test "x$GNUTLS_LIBS" != "x"]) + +-AS_IF([test "$GNUTLS_LIBS" != ""],[ ++AS_IF([test "x$GNUTLS_LIBS" != "x"],[ + AC_MSG_CHECKING([for default TLS session priority string]) + AC_ARG_WITH([tls-priority], + [AS_HELP_STRING([--with-tls-priority=...], +@@ -1383,6 +1385,7 @@ AC_CONFIG_FILES([Makefile + filters/ip/Makefile + filters/limit/Makefile + filters/log/Makefile ++ filters/luks/Makefile + filters/multi-conn/Makefile + filters/nocache/Makefile + filters/noextents/Makefile +@@ -1481,6 +1484,7 @@ echo "Optional filters:" + echo + feature "ext2" test "x$HAVE_EXT2_TRUE" = "x" + feature "gzip" test "x$HAVE_ZLIB_TRUE" = "x" ++feature "LUKS" test "x$HAVE_GNUTLS_TRUE" != "x" + feature "xz" test "x$HAVE_LIBLZMA_TRUE" = "x" + + echo +diff --git a/docs/nbdkit-tls.pod b/docs/nbdkit-tls.pod +index 86f5f984..4d0dc14c 100644 +--- a/docs/nbdkit-tls.pod ++++ b/docs/nbdkit-tls.pod +@@ -364,6 +364,7 @@ More information can be found in L. + =head1 SEE ALSO + + L, ++L, + L, + L, + L, +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +new file mode 100644 +index 00000000..30089621 +--- /dev/null ++++ b/filters/luks/Makefile.am +@@ -0,0 +1,77 @@ ++# nbdkit ++# Copyright (C) 2019-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++include $(top_srcdir)/common-rules.mk ++ ++EXTRA_DIST = nbdkit-luks-filter.pod ++ ++if HAVE_GNUTLS ++ ++filter_LTLIBRARIES = nbdkit-luks-filter.la ++ ++nbdkit_luks_filter_la_SOURCES = \ ++ luks.c \ ++ $(top_srcdir)/include/nbdkit-filter.h \ ++ $(NULL) ++ ++nbdkit_luks_filter_la_CPPFLAGS = \ ++ -I$(top_srcdir)/include \ ++ -I$(top_srcdir)/common/include \ ++ -I$(top_srcdir)/common/utils \ ++ $(NULL) ++nbdkit_luks_filter_la_CFLAGS = \ ++ $(WARNINGS_CFLAGS) \ ++ $(GNUTLS_CFLAGS) \ ++ $(NULL) ++nbdkit_luks_filter_la_LIBADD = \ ++ $(top_builddir)/common/utils/libutils.la \ ++ $(IMPORT_LIBRARY_ON_WINDOWS) \ ++ $(GNUTLS_LIBS) \ ++ $(NULL) ++nbdkit_luks_filter_la_LDFLAGS = \ ++ -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \ ++ -Wl,--version-script=$(top_srcdir)/filters/filters.syms \ ++ $(NULL) ++ ++if HAVE_POD ++ ++man_MANS = nbdkit-luks-filter.1 ++CLEANFILES += $(man_MANS) ++ ++nbdkit-luks-filter.1: nbdkit-luks-filter.pod \ ++ $(top_builddir)/podwrapper.pl ++ $(PODWRAPPER) --section=1 --man $@ \ ++ --html $(top_builddir)/html/$@.html \ ++ $< ++ ++endif HAVE_POD ++ ++endif +diff --git a/filters/luks/luks.c b/filters/luks/luks.c +new file mode 100644 +index 00000000..706a9bd2 +--- /dev/null ++++ b/filters/luks/luks.c +@@ -0,0 +1,1263 @@ ++/* nbdkit ++ * Copyright (C) 2018-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "byte-swapping.h" ++#include "cleanup.h" ++#include "isaligned.h" ++#include "minmax.h" ++#include "rounding.h" ++ ++/* LUKSv1 constants. */ ++#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE } ++#define LUKS_MAGIC_LEN 6 ++#define LUKS_DIGESTSIZE 20 ++#define LUKS_SALTSIZE 32 ++#define LUKS_NUMKEYS 8 ++#define LUKS_KEY_DISABLED 0x0000DEAD ++#define LUKS_KEY_ENABLED 0x00AC71F3 ++#define LUKS_STRIPES 4000 ++#define LUKS_ALIGN_KEYSLOTS 4096 ++#define LUKS_SECTOR_SIZE 512 ++ ++/* Key slot. */ ++struct luks_keyslot { ++ uint32_t active; /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */ ++ uint32_t password_iterations; ++ char password_salt[LUKS_SALTSIZE]; ++ uint32_t key_material_offset; ++ uint32_t stripes; ++} __attribute__((__packed__)); ++ ++/* LUKS superblock. */ ++struct luks_phdr { ++ char magic[LUKS_MAGIC_LEN]; /* LUKS_MAGIC */ ++ uint16_t version; /* Only 1 is supported. */ ++ char cipher_name[32]; ++ char cipher_mode[32]; ++ char hash_spec[32]; ++ uint32_t payload_offset; ++ uint32_t master_key_len; ++ uint8_t master_key_digest[LUKS_DIGESTSIZE]; ++ uint8_t master_key_salt[LUKS_SALTSIZE]; ++ uint32_t master_key_digest_iterations; ++ uint8_t uuid[40]; ++ ++ struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */ ++} __attribute__((__packed__)); ++ ++static char *passphrase = NULL; ++ ++static void ++luks_unload (void) ++{ ++ /* XXX We should really store the passphrase (and master key) ++ * in mlock-ed memory. ++ */ ++ if (passphrase) { ++ memset (passphrase, 0, strlen (passphrase)); ++ free (passphrase); ++ } ++} ++ ++static int ++luks_thread_model (void) ++{ ++ return NBDKIT_THREAD_MODEL_PARALLEL; ++} ++ ++static int ++luks_config (nbdkit_next_config *next, nbdkit_backend *nxdata, ++ const char *key, const char *value) ++{ ++ if (strcmp (key, "passphrase") == 0) { ++ if (nbdkit_read_password (value, &passphrase) == -1) ++ return -1; ++ return 0; ++ } ++ ++ return next (nxdata, key, value); ++} ++ ++static int ++luks_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata) ++{ ++ if (passphrase == NULL) { ++ nbdkit_error ("LUKS \"passphrase\" parameter is missing"); ++ return -1; ++ } ++ return next (nxdata); ++} ++ ++#define luks_config_help \ ++ "passphrase= Secret passphrase." ++ ++enum cipher_mode { ++ CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR, ++}; ++ ++static enum cipher_mode ++lookup_cipher_mode (const char *str) ++{ ++ if (strcmp (str, "ecb") == 0) ++ return CIPHER_MODE_ECB; ++ if (strcmp (str, "cbc") == 0) ++ return CIPHER_MODE_CBC; ++ if (strcmp (str, "xts") == 0) ++ return CIPHER_MODE_XTS; ++ if (strcmp (str, "ctr") == 0) ++ return CIPHER_MODE_CTR; ++ nbdkit_error ("unknown cipher mode: %s " ++ "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str); ++ return -1; ++} ++ ++static const char * ++cipher_mode_to_string (enum cipher_mode v) ++{ ++ switch (v) { ++ case CIPHER_MODE_ECB: return "ecb"; ++ case CIPHER_MODE_CBC: return "cbc"; ++ case CIPHER_MODE_XTS: return "xts"; ++ case CIPHER_MODE_CTR: return "ctr"; ++ default: abort (); ++ } ++} ++ ++enum ivgen { ++ IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */ ++}; ++ ++static enum ivgen ++lookup_ivgen (const char *str) ++{ ++ if (strcmp (str, "plain") == 0) ++ return IVGEN_PLAIN; ++ if (strcmp (str, "plain64") == 0) ++ return IVGEN_PLAIN64; ++/* ++ if (strcmp (str, "essiv") == 0) ++ return IVGEN_ESSIV; ++*/ ++ nbdkit_error ("unknown IV generation algorithm: %s " ++ "(expecting \"plain\", \"plain64\" etc)", str); ++ return -1; ++} ++ ++static const char * ++ivgen_to_string (enum ivgen v) ++{ ++ switch (v) { ++ case IVGEN_PLAIN: return "plain"; ++ case IVGEN_PLAIN64: return "plain64"; ++ /*case IVGEN_ESSIV: return "essiv";*/ ++ default: abort (); ++ } ++} ++ ++static void ++calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector) ++{ ++ size_t prefixlen; ++ uint32_t sector32; ++ ++ switch (v) { ++ case IVGEN_PLAIN: ++ prefixlen = 4; /* 32 bits */ ++ if (prefixlen > ivlen) ++ prefixlen = ivlen; ++ sector32 = (uint32_t) sector; /* truncate to only lower bits */ ++ sector32 = htole32 (sector32); ++ memcpy (iv, §or32, prefixlen); ++ memset (iv + prefixlen, 0, ivlen - prefixlen); ++ break; ++ ++ case IVGEN_PLAIN64: ++ prefixlen = 8; /* 64 bits */ ++ if (prefixlen > ivlen) ++ prefixlen = ivlen; ++ sector = htole64 (sector); ++ memcpy (iv, §or, prefixlen); ++ memset (iv + prefixlen, 0, ivlen - prefixlen); ++ break; ++ ++ /*case IVGEN_ESSIV:*/ ++ default: abort (); ++ } ++} ++ ++enum cipher_alg { ++ CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256, ++}; ++ ++static enum cipher_alg ++lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes) ++{ ++ if (mode == CIPHER_MODE_XTS) ++ key_bytes /= 2; ++ ++ if (strcmp (str, "aes") == 0) { ++ if (key_bytes == 16) ++ return CIPHER_ALG_AES_128; ++ if (key_bytes == 24) ++ return CIPHER_ALG_AES_192; ++ if (key_bytes == 32) ++ return CIPHER_ALG_AES_256; ++ } ++ nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str); ++ return -1; ++} ++ ++static const char * ++cipher_alg_to_string (enum cipher_alg v) ++{ ++ switch (v) { ++ case CIPHER_ALG_AES_128: return "aes-128"; ++ case CIPHER_ALG_AES_192: return "aes-192"; ++ case CIPHER_ALG_AES_256: return "aes-256"; ++ default: abort (); ++ } ++} ++ ++#if 0 ++static int ++cipher_alg_key_bytes (enum cipher_alg v) ++{ ++ switch (v) { ++ case CIPHER_ALG_AES_128: return 16; ++ case CIPHER_ALG_AES_192: return 24; ++ case CIPHER_ALG_AES_256: return 32; ++ default: abort (); ++ } ++} ++#endif ++ ++static int ++cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode) ++{ ++ if (CIPHER_MODE_ECB) ++ return 0; /* Don't need an IV in this mode. */ ++ ++ switch (v) { ++ case CIPHER_ALG_AES_128: ++ case CIPHER_ALG_AES_192: ++ case CIPHER_ALG_AES_256: ++ return 16; ++ default: abort (); ++ } ++} ++ ++static gnutls_digest_algorithm_t ++lookup_hash (const char *str) ++{ ++ if (strcmp (str, "md5") == 0) ++ return GNUTLS_DIG_MD5; ++ if (strcmp (str, "sha1") == 0) ++ return GNUTLS_DIG_SHA1; ++ if (strcmp (str, "sha224") == 0) ++ return GNUTLS_DIG_SHA224; ++ if (strcmp (str, "sha256") == 0) ++ return GNUTLS_DIG_SHA256; ++ if (strcmp (str, "sha384") == 0) ++ return GNUTLS_DIG_SHA384; ++ if (strcmp (str, "sha512") == 0) ++ return GNUTLS_DIG_SHA512; ++ if (strcmp (str, "ripemd160") == 0) ++ return GNUTLS_DIG_RMD160; ++ nbdkit_error ("unknown hash algorithm: %s " ++ "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str); ++ return -1; ++} ++ ++static const char * ++hash_to_string (gnutls_digest_algorithm_t v) ++{ ++ switch (v) { ++ case GNUTLS_DIG_UNKNOWN: return "unknown"; ++ case GNUTLS_DIG_MD5: return "md5"; ++ case GNUTLS_DIG_SHA1: return "sha1"; ++ case GNUTLS_DIG_SHA224: return "sha224"; ++ case GNUTLS_DIG_SHA256: return "sha256"; ++ case GNUTLS_DIG_SHA384: return "sha384"; ++ case GNUTLS_DIG_SHA512: return "sha512"; ++ case GNUTLS_DIG_RMD160: return "ripemd160"; ++ default: abort (); ++ } ++} ++ ++#if 0 ++/* See qemu & dm-crypt implementations for an explanation of what's ++ * going on here. ++ */ ++static enum cipher_alg ++lookup_essiv_cipher (enum cipher_alg cipher_alg, ++ gnutls_digest_algorithm_t ivgen_hash_alg) ++{ ++ int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg); ++ int key_bytes = cipher_alg_key_bytes (cipher_alg); ++ ++ if (digest_bytes == key_bytes) ++ return cipher_alg; ++ ++ switch (cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ case CIPHER_ALG_AES_192: ++ case CIPHER_ALG_AES_256: ++ if (digest_bytes == 16) return CIPHER_ALG_AES_128; ++ if (digest_bytes == 24) return CIPHER_ALG_AES_192; ++ if (digest_bytes == 32) return CIPHER_ALG_AES_256; ++ nbdkit_error ("no %s cipher available with key size %d", ++ "AES", digest_bytes); ++ return -1; ++ default: ++ nbdkit_error ("ESSIV does not support cipher %s", ++ cipher_alg_to_string (cipher_alg)); ++ return -1; ++ } ++} ++#endif ++ ++/* Per-connection handle. */ ++struct handle { ++ /* LUKS header, if necessary byte-swapped into host order. */ ++ struct luks_phdr phdr; ++ ++ /* Decoded algorithm etc. */ ++ enum cipher_alg cipher_alg; ++ enum cipher_mode cipher_mode; ++ gnutls_digest_algorithm_t hash_alg; ++ enum ivgen ivgen_alg; ++ gnutls_digest_algorithm_t ivgen_hash_alg; ++ enum cipher_alg ivgen_cipher_alg; ++ ++ /* GnuTLS algorithm. */ ++ gnutls_cipher_algorithm_t gnutls_cipher; ++ ++ /* If we managed to decrypt one of the keyslots using the passphrase ++ * then this contains the master key, otherwise NULL. ++ */ ++ uint8_t *masterkey; ++}; ++ ++static void * ++luks_open (nbdkit_next_open *next, nbdkit_context *nxdata, ++ int readonly, const char *exportname, int is_tls) ++{ ++ struct handle *h; ++ ++ if (next (nxdata, readonly, exportname) == -1) ++ return NULL; ++ ++ h = calloc (1, sizeof *h); ++ if (h == NULL) { ++ nbdkit_error ("calloc: %m"); ++ return NULL; ++ } ++ ++ return h; ++} ++ ++static void ++luks_close (void *handle) ++{ ++ struct handle *h = handle; ++ ++ if (h->masterkey) { ++ memset (h->masterkey, 0, h->phdr.master_key_len); ++ free (h->masterkey); ++ } ++ free (h); ++} ++ ++/* Perform decryption of a block of data in memory. */ ++static int ++do_decrypt (struct handle *h, gnutls_cipher_hd_t cipher, ++ uint64_t offset, uint8_t *buf, size_t len) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ uint64_t sector = offset / LUKS_SECTOR_SIZE; ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE)); ++ assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE)); ++ ++ while (len) { ++ calculate_iv (h->ivgen_alg, iv, ivlen, sector); ++ gnutls_cipher_set_iv (cipher, iv, ivlen); ++ r = gnutls_cipher_decrypt2 (cipher, ++ buf, LUKS_SECTOR_SIZE, /* ciphertext */ ++ buf, LUKS_SECTOR_SIZE /* plaintext */); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ buf += LUKS_SECTOR_SIZE; ++ offset += LUKS_SECTOR_SIZE; ++ len -= LUKS_SECTOR_SIZE; ++ sector++; ++ } ++ ++ return 0; ++} ++ ++/* Perform encryption of a block of data in memory. */ ++static int ++do_encrypt (struct handle *h, gnutls_cipher_hd_t cipher, ++ uint64_t offset, uint8_t *buf, size_t len) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ uint64_t sector = offset / LUKS_SECTOR_SIZE; ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE)); ++ assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE)); ++ ++ while (len) { ++ calculate_iv (h->ivgen_alg, iv, ivlen, sector); ++ gnutls_cipher_set_iv (cipher, iv, ivlen); ++ r = gnutls_cipher_encrypt2 (cipher, ++ buf, LUKS_SECTOR_SIZE, /* plaintext */ ++ buf, LUKS_SECTOR_SIZE /* ciphertext */); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ buf += LUKS_SECTOR_SIZE; ++ offset += LUKS_SECTOR_SIZE; ++ len -= LUKS_SECTOR_SIZE; ++ sector++; ++ } ++ ++ return 0; ++} ++ ++/* Parse the header fields containing cipher algorithm, mode, etc. */ ++static int ++parse_cipher_strings (struct handle *h) ++{ ++ char cipher_name[33], cipher_mode[33], hash_spec[33]; ++ char *ivgen, *ivhash; ++ ++ /* Copy the header fields locally and ensure they are \0 terminated. */ ++ memcpy (cipher_name, h->phdr.cipher_name, 32); ++ cipher_name[32] = 0; ++ memcpy (cipher_mode, h->phdr.cipher_mode, 32); ++ cipher_mode[32] = 0; ++ memcpy (hash_spec, h->phdr.hash_spec, 32); ++ hash_spec[32] = 0; ++ ++ nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s " ++ "master key: %" PRIu32 " bits", ++ h->phdr.version, cipher_name, cipher_mode, hash_spec, ++ h->phdr.master_key_len * 8); ++ ++ /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]" ++ * QEmu writes: "xts-plain64" ++ */ ++ ivgen = strchr (cipher_mode, '-'); ++ if (!ivgen) { ++ nbdkit_error ("incorrect cipher_mode header, " ++ "expecting mode-ivgenerator but got \"%s\"", cipher_mode); ++ return -1; ++ } ++ *ivgen = '\0'; ++ ivgen++; ++ ++ ivhash = strchr (ivgen, ':'); ++ if (!ivhash) ++ h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN; ++ else { ++ *ivhash = '\0'; ++ ivhash++; ++ ++ h->ivgen_hash_alg = lookup_hash (ivhash); ++ if (h->ivgen_hash_alg == -1) ++ return -1; ++ } ++ ++ h->cipher_mode = lookup_cipher_mode (cipher_mode); ++ if (h->cipher_mode == -1) ++ return -1; ++ ++ h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode, ++ h->phdr.master_key_len); ++ if (h->cipher_alg == -1) ++ return -1; ++ ++ h->hash_alg = lookup_hash (hash_spec); ++ if (h->hash_alg == -1) ++ return -1; ++ ++ h->ivgen_alg = lookup_ivgen (ivgen); ++ if (h->ivgen_alg == -1) ++ return -1; ++ ++#if 0 ++ if (h->ivgen_alg == IVGEN_ESSIV) { ++ if (!ivhash) { ++ nbdkit_error ("incorrect IV generator hash specification"); ++ return -1; ++ } ++ h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg, ++ h->ivgen_hash_alg); ++ if (h->ivgen_cipher_alg == -1) ++ return -1; ++ } ++ else ++#endif ++ h->ivgen_cipher_alg = h->cipher_alg; ++ ++ nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s", ++ cipher_alg_to_string (h->cipher_alg), ++ cipher_mode_to_string (h->cipher_mode), ++ hash_to_string (h->hash_alg), ++ ivgen_to_string (h->ivgen_alg), ++ hash_to_string (h->ivgen_hash_alg), ++ cipher_alg_to_string (h->ivgen_cipher_alg)); ++ ++ /* GnuTLS combines cipher and block mode into a single value. Not ++ * all possible combinations are available in GnuTLS. See: ++ * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html ++ */ ++ h->gnutls_cipher = GNUTLS_CIPHER_NULL; ++ switch (h->cipher_mode) { ++ case CIPHER_MODE_XTS: ++ switch (h->cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS; ++ break; ++ case CIPHER_ALG_AES_256: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS; ++ break; ++ default: break; ++ } ++ break; ++ case CIPHER_MODE_CBC: ++ switch (h->cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC; ++ break; ++ case CIPHER_ALG_AES_192: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC; ++ break; ++ case CIPHER_ALG_AES_256: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC; ++ break; ++ default: break; ++ } ++ default: break; ++ } ++ if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) { ++ nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS", ++ cipher_alg_to_string (h->cipher_alg), ++ cipher_mode_to_string (h->cipher_mode)); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* Anti-Forensic merge operation. */ ++static void ++xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len) ++{ ++ size_t i; ++ ++ for (i = 0; i < len; ++i) ++ out[i] = in1[i] ^ in2[i]; ++} ++ ++static int ++af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len) ++{ ++ size_t digest_bytes = gnutls_hash_get_len (hash_alg); ++ size_t nr_blocks, last_block_len; ++ size_t i; ++ CLEANUP_FREE uint8_t *temp = malloc (digest_bytes); ++ int r; ++ gnutls_hash_hd_t hash; ++ ++ nr_blocks = len / digest_bytes; ++ last_block_len = len % digest_bytes; ++ if (last_block_len != 0) ++ nr_blocks++; ++ else ++ last_block_len = digest_bytes; ++ ++ for (i = 0; i < nr_blocks; ++i) { ++ const uint32_t iv = htobe32 (i); ++ const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len; ++ ++ /* Hash iv + i'th block into temp. */ ++ r = gnutls_hash_init (&hash, hash_alg); ++ if (r != 0) { ++ nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ gnutls_hash (hash, &iv, sizeof iv); ++ gnutls_hash (hash, &block[i*digest_bytes], blen); ++ gnutls_hash_deinit (hash, temp); ++ ++ memcpy (&block[i*digest_bytes], temp, blen); ++ } ++ ++ return 0; ++} ++ ++static int ++afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes, ++ const uint8_t *in, uint8_t *out, size_t outlen) ++{ ++ CLEANUP_FREE uint8_t *block = calloc (1, outlen); ++ size_t i; ++ ++ /* NB: input size is stripes * master_key_len where ++ * master_key_len == outlen ++ */ ++ for (i = 0; i < stripes-1; ++i) { ++ xor (&in[i*outlen], block, block, outlen); ++ if (af_hash (hash_alg, block, outlen) == -1) ++ return -1; ++ } ++ xor (&in[i*outlen], block, out, outlen); ++ return 0; ++} ++ ++/* Length of key material in key slot i (sectors). ++ * ++ * This is basically copied from qemu because the spec description is ++ * unintelligible and apparently doesn't match reality. ++ */ ++static uint64_t ++key_material_length_in_sectors (struct handle *h, size_t i) ++{ ++ uint64_t len, r; ++ ++ len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes; ++ r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE); ++ r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE); ++ return r; ++} ++ ++/* Try the passphrase in key slot i. If this returns true then the ++ * passphrase was able to decrypt the master key, and the master key ++ * has been stored in h->masterkey. ++ */ ++static int ++try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) ++{ ++ struct luks_keyslot *ks = &h->phdr.keyslot[i]; ++ size_t split_key_len; ++ CLEANUP_FREE uint8_t *split_key = NULL; ++ CLEANUP_FREE uint8_t *masterkey = NULL; ++ const gnutls_datum_t key = ++ { (unsigned char *) passphrase, strlen (passphrase) }; ++ const gnutls_datum_t salt = ++ { (unsigned char *) ks->password_salt, LUKS_SALTSIZE }; ++ const gnutls_datum_t msalt = ++ { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE }; ++ gnutls_datum_t mkey; ++ gnutls_cipher_hd_t cipher; ++ int r, err = 0; ++ uint64_t start; ++ uint8_t key_digest[LUKS_DIGESTSIZE]; ++ ++ if (ks->active != LUKS_KEY_ENABLED) ++ return 0; ++ ++ split_key_len = h->phdr.master_key_len * ks->stripes; ++ split_key = malloc (split_key_len); ++ if (split_key == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ masterkey = malloc (h->phdr.master_key_len); ++ if (masterkey == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ ++ /* Hash the passphrase to make a possible masterkey. */ ++ r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, ++ masterkey, h->phdr.master_key_len); ++ if (r != 0) { ++ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ /* Read master key material from plugin. */ ++ start = ks->key_material_offset * LUKS_SECTOR_SIZE; ++ if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) { ++ errno = err; ++ return -1; ++ } ++ ++ /* Decrypt the (still AFsplit) master key material. */ ++ mkey.data = (unsigned char *) masterkey; ++ mkey.size = h->phdr.master_key_len; ++ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ r = do_decrypt (h, cipher, 0, split_key, split_key_len); ++ gnutls_cipher_deinit (cipher); ++ if (r == -1) ++ return -1; ++ ++ /* Decode AFsplit key to a possible masterkey. */ ++ if (afmerge (h->hash_alg, ks->stripes, split_key, ++ masterkey, h->phdr.master_key_len) == -1) ++ return -1; ++ ++ /* Check if the masterkey is correct by comparing hash of the ++ * masterkey with LUKS header. ++ */ ++ r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt, ++ h->phdr.master_key_digest_iterations, ++ key_digest, LUKS_DIGESTSIZE); ++ if (r != 0) { ++ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) { ++ /* The passphrase is correct so save the master key in the handle. */ ++ h->masterkey = malloc (h->phdr.master_key_len); ++ if (h->masterkey == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ memcpy (h->masterkey, masterkey, h->phdr.master_key_len); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++static int ++luks_prepare (nbdkit_next *next, void *handle, int readonly) ++{ ++ static const char expected_magic[] = LUKS_MAGIC; ++ struct handle *h = handle; ++ int64_t size; ++ int err = 0, r; ++ size_t i; ++ struct luks_keyslot *ks; ++ char uuid[41]; ++ ++ /* Check we haven't been called before, this should never happen. */ ++ assert (h->phdr.version == 0); ++ ++ /* Check the struct size matches the documentation. */ ++ assert (sizeof (struct luks_phdr) == 592); ++ ++ /* Check this is a LUKSv1 disk. */ ++ size = next->get_size (next); ++ if (size == -1) ++ return -1; ++ if (size < 16384) { ++ nbdkit_error ("disk is too small to be LUKS-encrypted"); ++ return -1; ++ } ++ ++ /* Read the phdr. */ ++ if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) { ++ errno = err; ++ return -1; ++ } ++ ++ if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { ++ nbdkit_error ("this disk does not contain a LUKS header"); ++ return -1; ++ } ++ h->phdr.version = be16toh (h->phdr.version); ++ if (h->phdr.version != 1) { ++ nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, " ++ "but this filter only supports LUKSv1", ++ h->phdr.version); ++ return -1; ++ } ++ ++ /* Byte-swap the rest of the header. */ ++ h->phdr.payload_offset = be32toh (h->phdr.payload_offset); ++ h->phdr.master_key_len = be32toh (h->phdr.master_key_len); ++ h->phdr.master_key_digest_iterations = ++ be32toh (h->phdr.master_key_digest_iterations); ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ ks = &h->phdr.keyslot[i]; ++ ks->active = be32toh (ks->active); ++ ks->password_iterations = be32toh (ks->password_iterations); ++ ks->key_material_offset = be32toh (ks->key_material_offset); ++ ks->stripes = be32toh (ks->stripes); ++ } ++ ++ /* Sanity check some fields. */ ++ if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) { ++ nbdkit_error ("bad LUKSv1 header: payload offset points beyond " ++ "the end of the disk"); ++ return -1; ++ } ++ ++ /* We derive several allocations from master_key_len so make sure ++ * it's not insane. ++ */ ++ if (h->phdr.master_key_len > 1024) { ++ nbdkit_error ("bad LUKSv1 header: master key is too long"); ++ return -1; ++ } ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ uint64_t start, len; ++ ++ ks = &h->phdr.keyslot[i]; ++ switch (ks->active) { ++ case LUKS_KEY_ENABLED: ++ if (!ks->stripes) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i); ++ return -1; ++ } ++ if (ks->stripes >= 10000) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i); ++ return -1; ++ } ++ start = ks->key_material_offset; ++ len = key_material_length_in_sectors (h, i); ++ if (len > 4096) /* bound it at something reasonable */ { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu key material length " ++ "is too large", i); ++ return -1; ++ } ++ if (start * LUKS_SECTOR_SIZE >= size || ++ (start + len) * LUKS_SECTOR_SIZE >= size) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset " ++ "points beyond the end of the disk", i); ++ return -1; ++ } ++ if (ks->password_iterations > ULONG_MAX) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu " ++ "iterations too large", i); ++ return -1; ++ } ++ /*FALLTHROUGH*/ ++ case LUKS_KEY_DISABLED: ++ break; ++ ++ default: ++ nbdkit_error ("bad LUKSv1 header: key slot %zu has " ++ "an invalid active flag", i); ++ return -1; ++ } ++ } ++ ++ /* Decode the ciphers. */ ++ if (parse_cipher_strings (h) == -1) ++ return -1; ++ ++ /* Dump some information about the header. */ ++ memcpy (uuid, h->phdr.uuid, 40); ++ uuid[40] = 0; ++ nbdkit_debug ("LUKS UUID: %s", uuid); ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ uint64_t start, len; ++ ++ ks = &h->phdr.keyslot[i]; ++ if (ks->active == LUKS_KEY_ENABLED) { ++ start = ks->key_material_offset; ++ len = key_material_length_in_sectors (h, i); ++ nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64 ++ "..%" PRIu64, ++ i, start, start+len-1); ++ } ++ } ++ ++ /* Now try to unlock the master key. */ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ r = try_passphrase_in_keyslot (next, h, i); ++ if (r == -1) ++ return -1; ++ if (r > 0) ++ goto unlocked; ++ } ++ nbdkit_error ("LUKS passphrase is not correct, " ++ "no key slot could be unlocked"); ++ return -1; ++ ++ unlocked: ++ assert (h->masterkey != NULL); ++ nbdkit_debug ("LUKS unlocked block device with passphrase"); ++ ++ return 0; ++} ++ ++static int64_t ++luks_get_size (nbdkit_next *next, void *handle) ++{ ++ struct handle *h = handle; ++ int64_t size; ++ ++ /* Check that prepare has been called already. */ ++ assert (h->phdr.version > 0); ++ ++ size = next->get_size (next); ++ if (size == -1) ++ return -1; ++ ++ if (size < h->phdr.payload_offset * LUKS_SECTOR_SIZE) { ++ nbdkit_error ("disk too small, or contains an incomplete LUKS partition"); ++ return -1; ++ } ++ ++ size -= h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ return size; ++} ++ ++/* Whatever the plugin says, several operations are not supported by ++ * this filter: ++ * ++ * - extents ++ * - trim ++ * - zero ++ */ ++static int ++luks_can_extents (nbdkit_next *next, void *handle) ++{ ++ return 0; ++} ++ ++static int ++luks_can_trim (nbdkit_next *next, void *handle) ++{ ++ return 0; ++} ++ ++static int ++luks_can_zero (nbdkit_next *next, void *handle) ++{ ++ return NBDKIT_ZERO_EMULATE; ++} ++ ++static int ++luks_can_fast_zero (nbdkit_next *next, void *handle) ++{ ++ return 0; ++} ++ ++/* Rely on nbdkit to call .pread to emulate .cache calls. We will ++ * respond by decrypting the block which could be stored by the cache ++ * filter or similar on top. ++ */ ++static int ++luks_can_cache (nbdkit_next *next, void *handle) ++{ ++ return NBDKIT_CACHE_EMULATE; ++} ++ ++/* Advertise minimum/preferred sector-sized blocks, although we can in ++ * fact handle any read or write. ++ */ ++static int ++luks_block_size (nbdkit_next *next, void *handle, ++ uint32_t *minimum, uint32_t *preferred, uint32_t *maximum) ++{ ++ if (next->block_size (next, minimum, preferred, maximum) == -1) ++ return -1; ++ ++ if (*minimum == 0) { /* No constraints set by the plugin. */ ++ *minimum = LUKS_SECTOR_SIZE; ++ *preferred = LUKS_SECTOR_SIZE; ++ *maximum = 0xffffffff; ++ } ++ else { ++ *minimum = MAX (*minimum, LUKS_SECTOR_SIZE); ++ *preferred = MAX (*minimum, MAX (*preferred, LUKS_SECTOR_SIZE)); ++ } ++ return 0; ++} ++ ++/* Decrypt data. */ ++static int ++luks_pread (nbdkit_next *next, void *handle, ++ void *buf, uint32_t count, uint64_t offset, ++ uint32_t flags, int *err) ++{ ++ struct handle *h = handle; ++ const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ CLEANUP_FREE uint8_t *sector = NULL; ++ uint64_t sectnum, sectoffs; ++ const gnutls_datum_t mkey = ++ { (unsigned char *) h->masterkey, h->phdr.master_key_len }; ++ gnutls_cipher_hd_t cipher; ++ int r; ++ ++ if (!h->masterkey) { ++ *err = EIO; ++ return -1; ++ } ++ ++ if (!IS_ALIGNED (count | offset, LUKS_SECTOR_SIZE)) { ++ sector = malloc (LUKS_SECTOR_SIZE); ++ if (sector == NULL) { ++ *err = errno; ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ } ++ ++ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); ++ *err = EIO; ++ return -1; ++ } ++ ++ sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */ ++ sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */ ++ ++ /* Unaligned head */ ++ if (sectoffs) { ++ uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count); ++ ++ assert (sector); ++ if (next->pread (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ if (do_decrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, ++ sector, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ memcpy (buf, §or[sectoffs], n); ++ ++ buf += n; ++ count -= n; ++ offset += n; ++ sectnum++; ++ } ++ ++ /* Aligned body */ ++ while (count >= LUKS_SECTOR_SIZE) { ++ if (next->pread (next, buf, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ if (do_decrypt (h, cipher, offset, buf, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ buf += LUKS_SECTOR_SIZE; ++ count -= LUKS_SECTOR_SIZE; ++ offset += LUKS_SECTOR_SIZE; ++ sectnum++; ++ } ++ ++ /* Unaligned tail */ ++ if (count) { ++ assert (sector); ++ if (next->pread (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ if (do_decrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ memcpy (buf, sector, count); ++ } ++ ++ gnutls_cipher_deinit (cipher); ++ return 0; ++ ++ err: ++ gnutls_cipher_deinit (cipher); ++ return -1; ++} ++ ++/* Lock preventing read-modify-write cycles from overlapping. */ ++static pthread_mutex_t read_modify_write_lock = PTHREAD_MUTEX_INITIALIZER; ++ ++/* Encrypt data. */ ++static int ++luks_pwrite (nbdkit_next *next, void *handle, ++ const void *buf, uint32_t count, uint64_t offset, ++ uint32_t flags, int *err) ++{ ++ struct handle *h = handle; ++ const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ CLEANUP_FREE uint8_t *sector = NULL; ++ uint64_t sectnum, sectoffs; ++ const gnutls_datum_t mkey = ++ { (unsigned char *) h->masterkey, h->phdr.master_key_len }; ++ gnutls_cipher_hd_t cipher; ++ int r; ++ ++ if (!h->masterkey) { ++ *err = EIO; ++ return -1; ++ } ++ ++ sector = malloc (LUKS_SECTOR_SIZE); ++ if (sector == NULL) { ++ *err = errno; ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ ++ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); ++ *err = EIO; ++ return -1; ++ } ++ ++ sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */ ++ sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */ ++ ++ /* Unaligned head */ ++ if (sectoffs) { ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock); ++ ++ uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count); ++ ++ if (next->pread (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ memcpy (§or[sectoffs], buf, n); ++ ++ if (do_encrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, ++ sector, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ buf += n; ++ count -= n; ++ offset += n; ++ sectnum++; ++ } ++ ++ /* Aligned body */ ++ while (count >= LUKS_SECTOR_SIZE) { ++ memcpy (sector, buf, LUKS_SECTOR_SIZE); ++ ++ if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ buf += LUKS_SECTOR_SIZE; ++ count -= LUKS_SECTOR_SIZE; ++ offset += LUKS_SECTOR_SIZE; ++ sectnum++; ++ } ++ ++ /* Unaligned tail */ ++ if (count) { ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock); ++ ++ if (next->pread (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ ++ memcpy (sector, buf, count); ++ ++ if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ goto err; ++ ++ if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, ++ sectnum * LUKS_SECTOR_SIZE + payload_offset, ++ flags, err) == -1) ++ goto err; ++ } ++ ++ gnutls_cipher_deinit (cipher); ++ return 0; ++ ++ err: ++ gnutls_cipher_deinit (cipher); ++ return -1; ++} ++ ++static struct nbdkit_filter filter = { ++ .name = "luks", ++ .longname = "nbdkit luks filter", ++ .unload = luks_unload, ++ .thread_model = luks_thread_model, ++ .config = luks_config, ++ .config_complete = luks_config_complete, ++ .config_help = luks_config_help, ++ .open = luks_open, ++ .close = luks_close, ++ .prepare = luks_prepare, ++ .get_size = luks_get_size, ++ .can_extents = luks_can_extents, ++ .can_trim = luks_can_trim, ++ .can_zero = luks_can_zero, ++ .can_fast_zero = luks_can_fast_zero, ++ .can_cache = luks_can_cache, ++ .block_size = luks_block_size, ++ .pread = luks_pread, ++ .pwrite = luks_pwrite, ++}; ++ ++NBDKIT_REGISTER_FILTER(filter) +diff --git a/filters/luks/nbdkit-luks-filter.pod b/filters/luks/nbdkit-luks-filter.pod +new file mode 100644 +index 00000000..56e51561 +--- /dev/null ++++ b/filters/luks/nbdkit-luks-filter.pod +@@ -0,0 +1,120 @@ ++=head1 NAME ++ ++nbdkit-luks-filter - read and write LUKS-encrypted disks and partitions ++ ++=head1 SYNOPSIS ++ ++ nbdkit file encrypted-disk.img --filter=luks passphrase=+/tmp/secret ++ ++=head1 DESCRIPTION ++ ++C is a filter for L which transparently ++opens a LUKS-encrypted disk image. LUKS ("Linux Unified Key Setup") ++is the Full Disk Encryption (FDE) system commonly used by Linux ++systems. This filter is compatible with LUKSv1 as implemented by the ++Linux kernel (dm_crypt), and by qemu. ++ ++You can place this filter on top of L to ++decrypt a local file: ++ ++ nbdkit file encrypted-disk.img --filter=luks passphrase=+/tmp/secret ++ ++If LUKS is present inside a partition in the disk image then you will ++have to combine this filter with L. The ++order of the filters is important: ++ ++ nbdkit file encrypted-disk.img \ ++ --filter=luks passphrase=+/tmp/secret \ ++ --filter=partition partition=1 ++ ++This filter also works on top of other plugins such as ++L: ++ ++ nbdkit curl https://example.com/encrypted-disk.img \ ++ --filter=luks passphrase=+/tmp/secret ++ ++The web server sees only the encrypted data. Without knowing the ++passphrase, the web server cannot access the decrypted disk. Only ++encrypted data is sent over the HTTP connection. nbdkit itself will ++serve I disk data over the NBD connection (if this is a ++problem see L, or use a Unix domain socket I<-U>). ++ ++The passphrase can be stored in a file (as shown), passed directly on ++the command line (insecure), entered interactively, or passed to ++nbdkit over a file descriptor. ++ ++This filter can read and write LUKSv1. It cannot create disks, change ++passphrases, add keyslots, etc. To do that, you can use ordinary ++Linux tools like L. Note you must force LUKSv1 ++(eg. using cryptsetup I<--type luks1>). L can also ++create compatible disk images: ++ ++ qemu-img create -f luks \ ++ --object secret,data=SECRET,id=sec0 \ ++ -o key-secret=sec0 \ ++ encrypted-disk.img 1G ++ ++=head1 PARAMETERS ++ ++=over 4 ++ ++=item BSECRET ++ ++Use the secret passphrase when decrypting the disk. ++ ++Note that passing this on the command line is not secure on shared ++machines. ++ ++=item B ++ ++Ask for the passphrase (interactively) when nbdkit starts up. ++ ++=item BFILENAME ++ ++Read the passphrase from the named file. This is a secure method to ++supply a passphrase, as long as you set the permissions on the file ++appropriately. ++ ++=item BFD ++ ++Read the passphrase from file descriptor number C, inherited from ++the parent process when nbdkit starts up. This is also a secure ++method to supply a passphrase. ++ ++=back ++ ++=head1 FILES ++ ++=over 4 ++ ++=item F<$filterdir/nbdkit-luks-filter.so> ++ ++The plugin. ++ ++Use C to find the location of C<$filterdir>. ++ ++=back ++ ++=head1 VERSION ++ ++C first appeared in nbdkit 1.32. ++ ++=head1 SEE ALSO ++ ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L. ++ ++=head1 AUTHORS ++ ++Richard W.M. Jones ++ ++=head1 COPYRIGHT ++ ++Copyright (C) 2013-2022 Red Hat Inc. +diff --git a/plugins/file/nbdkit-file-plugin.pod b/plugins/file/nbdkit-file-plugin.pod +index f8f0e198..b95e7349 100644 +--- a/plugins/file/nbdkit-file-plugin.pod ++++ b/plugins/file/nbdkit-file-plugin.pod +@@ -223,6 +223,7 @@ L, + L, + L, + L, ++L, + L. + + =head1 AUTHORS +diff --git a/tests/Makefile.am b/tests/Makefile.am +index b310e8a2..c29453ba 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1596,6 +1596,18 @@ EXTRA_DIST += \ + test-log-script-info.sh \ + $(NULL) + ++# luks filter test. ++if HAVE_GNUTLS ++TESTS += \ ++ test-luks-info.sh \ ++ test-luks-copy.sh \ ++ $(NULL) ++endif ++EXTRA_DIST += \ ++ test-luks-info.sh \ ++ test-luks-copy.sh \ ++ $(NULL) ++ + # multi-conn filter test. + TESTS += \ + test-multi-conn.sh \ +diff --git a/tests/test-luks-copy.sh b/tests/test-luks-copy.sh +new file mode 100755 +index 00000000..99f300d0 +--- /dev/null ++++ b/tests/test-luks-copy.sh +@@ -0,0 +1,125 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++source ./functions.sh ++set -e ++set -x ++ ++requires nbdcopy --version ++requires nbdsh --version ++requires_nbdsh_uri ++requires qemu-img --version ++requires bash -c 'qemu-img --help | grep -- --target-image-opts' ++requires hexdump --version ++requires truncate --version ++requires_filter luks ++ ++encrypt_disk=luks-copy1.img ++plain_disk=luks-copy2.img ++pid=luks-copy.pid ++sock=$(mktemp -u /tmp/nbdkit-test-sock.XXXXXX) ++cleanup_fn rm -f $encrypt_disk $plain_disk $pid $sock ++rm -f $encrypt_disk $plain_disk $pid $sock ++ ++# Create an empty encrypted disk container. ++# ++# NB: This is complicated because qemu doesn't create an all-zeroes ++# plaintext disk for some reason when you use create -f luks. It ++# starts with random plaintext. ++# ++# https://stackoverflow.com/a/44669936 ++qemu-img create -f luks \ ++ --object secret,data=123456,id=sec0 \ ++ -o key-secret=sec0 \ ++ $encrypt_disk 10M ++truncate -s 10M $plain_disk ++qemu-img convert --target-image-opts -n \ ++ --object secret,data=123456,id=sec0 \ ++ $plain_disk \ ++ driver=luks,file.filename=$encrypt_disk,key-secret=sec0 ++rm $plain_disk ++ ++# Start nbdkit on the encrypted disk. ++start_nbdkit -P $pid -U $sock \ ++ file $encrypt_disk --filter=luks passphrase=123456 ++uri="nbd+unix:///?socket=$sock" ++ ++# Copy the whole disk out. It should be empty. ++nbdcopy "$uri" $plain_disk ++ ++if [ "$(hexdump -C $plain_disk)" != '00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++00a00000' ]; then ++ echo "$0: expected plaintext disk to be empty" ++ exit 1 ++fi ++ ++# Use nbdsh to overwrite with some known data and check we can read ++# back what we wrote. ++nbdsh -u "$uri" \ ++ -c 'h.pwrite(b"1"*65536, 0)' \ ++ -c 'h.pwrite(b"2"*65536, 128*1024)' \ ++ -c 'h.pwrite(b"3"*65536, 9*1024*1024)' \ ++ -c 'buf = h.pread(65536, 0)' \ ++ -c 'assert buf == b"1"*65536' \ ++ -c 'buf = h.pread(65536, 65536)' \ ++ -c 'assert buf == bytearray(65536)' \ ++ -c 'buf = h.pread(65536, 128*1024)' \ ++ -c 'assert buf == b"2"*65536' \ ++ -c 'buf = h.pread(65536, 9*1024*1024)' \ ++ -c 'assert buf == b"3"*65536' \ ++ -c 'h.flush()' ++ ++# Use qemu to copy out the whole disk. Note we called flush() above ++# so the disk should be synchronised. ++qemu-img convert --image-opts \ ++ --object secret,data=123456,id=sec0 \ ++ driver=luks,file.filename=$encrypt_disk,key-secret=sec0 \ ++ $plain_disk ++ ++# Check the contents are expected. ++if [ "$(hexdump -C $plain_disk)" != '00000000 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 31 |1111111111111111| ++* ++00010000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++00020000 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 |2222222222222222| ++* ++00030000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++00900000 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333| ++* ++00910000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++* ++00a00000' ]; then ++ echo "$0: unexpected content" ++ exit 1 ++fi +diff --git a/tests/test-luks-info.sh b/tests/test-luks-info.sh +new file mode 100755 +index 00000000..3eff657b +--- /dev/null ++++ b/tests/test-luks-info.sh +@@ -0,0 +1,56 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++source ./functions.sh ++set -e ++set -x ++ ++requires nbdinfo --version ++requires qemu-img --version ++requires_filter luks ++ ++disk=luks-info.img ++info=luks-info.log ++cleanup_fn rm -f $disk $info ++rm -f $disk $info ++ ++qemu-img create -f luks \ ++ --object secret,data=123456,id=sec0 \ ++ -o key-secret=sec0 \ ++ $disk 10M ++ ++nbdkit -U - file $disk --filter=luks passphrase=123456 \ ++ --run 'nbdinfo $uri' > $info ++cat $info ++ ++# Check the size is 10M (so it doesn't include the LUKS header). ++grep "10485760" $info +-- +2.31.1 + diff --git a/0004-cow-Fix-for-qemu-6.1-which-requires-backing-format.patch b/0004-cow-Fix-for-qemu-6.1-which-requires-backing-format.patch new file mode 100644 index 0000000..eb7e88e --- /dev/null +++ b/0004-cow-Fix-for-qemu-6.1-which-requires-backing-format.patch @@ -0,0 +1,59 @@ +From 3c2879a38c299b725091cea45329879e3f46fc99 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 31 Aug 2021 11:23:27 +0100 +Subject: [PATCH] cow: Fix for qemu 6.1 which requires backing format + +The diffing example in the manual created a qcow2 file with a backing +file but did not specify the backing format. However qemu 6.1 now +requires this and fails with: + + qemu-img: cow-diff.qcow2: Backing file specified without backing format + +or: + + qemu-img: Could not change the backing file to 'cow-base.img': backing format must be specified + +Fix the example by adding the -F option to the command line. + +Also there was a test of this rebasing sequence which failed, so this +commit updates the test too. + +(cherry picked from commit 618290ef33ce13b75c1a79fea1f1ffb327b5ba07) +--- + filters/cow/nbdkit-cow-filter.pod | 4 ++-- + tests/test-cow.sh | 4 ++-- + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/filters/cow/nbdkit-cow-filter.pod b/filters/cow/nbdkit-cow-filter.pod +index 4d5ae856..510bdd40 100644 +--- a/filters/cow/nbdkit-cow-filter.pod ++++ b/filters/cow/nbdkit-cow-filter.pod +@@ -101,8 +101,8 @@ At the end, disconnect the client. + Run these C commands to construct a qcow2 file containing + the differences: + +- qemu-img create -f qcow2 -b nbd:localhost diff.qcow2 +- qemu-img rebase -b disk.img diff.qcow2 ++ qemu-img create -F raw -b nbd:localhost -f qcow2 diff.qcow2 ++ qemu-img rebase -F raw -b disk.img -f qcow2 diff.qcow2 + + F now contains the differences between the base + (F) and the changes stored in nbdkit-cow-filter. C +diff --git a/tests/test-cow.sh b/tests/test-cow.sh +index 8772afd7..edc4c223 100755 +--- a/tests/test-cow.sh ++++ b/tests/test-cow.sh +@@ -72,8 +72,8 @@ fi + # If we have qemu-img, try the hairy rebase operation documented + # in the nbdkit-cow-filter manual. + if qemu-img --version >/dev/null 2>&1; then +- qemu-img create -f qcow2 -b nbd:unix:$sock cow-diff.qcow2 +- time qemu-img rebase -b cow-base.img cow-diff.qcow2 ++ qemu-img create -F raw -b nbd:unix:$sock -f qcow2 cow-diff.qcow2 ++ time qemu-img rebase -F raw -b cow-base.img -f qcow2 cow-diff.qcow2 + qemu-img info cow-diff.qcow2 + + # This checks the file we created exists. +-- +2.31.1 + diff --git a/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch b/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch new file mode 100644 index 0000000..2ff4e07 --- /dev/null +++ b/0005-luks-Disable-filter-with-old-GnuTLS-in-Debian-10.patch @@ -0,0 +1,95 @@ +From 66daae1a7daf680e06f884e9af6a14830263c932 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:13:39 +0100 +Subject: [PATCH] luks: Disable filter with old GnuTLS in Debian 10 +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +On Debian 10: + +luks.c: In function ‘parse_cipher_strings’: +luks.c:574:26: error: ‘GNUTLS_CIPHER_AES_128_XTS’ undeclared (first use in this function); did you mean ‘GNUTLS_CIPHER_AES_128_CCM’? + h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS; + ^~~~~~~~~~~~~~~~~~~~~~~~~ + GNUTLS_CIPHER_AES_128_CCM +luks.c:574:26: note: each undeclared identifier is reported only once for each function it appears in +luks.c:577:26: error: ‘GNUTLS_CIPHER_AES_256_XTS’ undeclared (first use in this function); did you mean ‘GNUTLS_CIPHER_AES_256_CCM’? + h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS; + ^~~~~~~~~~~~~~~~~~~~~~~~~ + GNUTLS_CIPHER_AES_256_CCM +luks.c: In function ‘try_passphrase_in_keyslot’: +luks.c:728:7: error: implicit declaration of function ‘gnutls_pbkdf2’; did you mean ‘gnutls_prf’? [-Werror=implicit-function-declaration] + r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, + ^~~~~~~~~~~~~ + gnutls_prf + +Because gnutls_pbkdf2 is missing there's no chance of making this +filter work on this platform so it's best to compile it out. + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit f9f67e483f4aad19ad6101163d32562f13504ca7) +--- + configure.ac | 5 ++++- + filters/luks/Makefile.am | 2 +- + tests/Makefile.am | 2 +- + 3 files changed, 6 insertions(+), 3 deletions(-) + +diff --git a/configure.ac b/configure.ac +index de85b4da..1d209f67 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -636,12 +636,15 @@ AS_IF([test "x$GNUTLS_LIBS" != "x"],[ + gnutls_certificate_set_known_dh_params \ + gnutls_group_get \ + gnutls_group_get_name \ ++ gnutls_pbkdf2 \ + gnutls_session_set_verify_cert \ + gnutls_srp_server_get_username \ + gnutls_transport_is_ktls_enabled \ + ]) + LIBS="$old_LIBS" + ]) ++AM_CONDITIONAL([HAVE_GNUTLS_PBKDF2], ++ [test "x$GNUTLS_LIBS" != "x" && test "x$ac_cv_func_gnutls_pbkdf2" = xyes]) + + AC_ARG_ENABLE([linuxdisk], + [AS_HELP_STRING([--disable-linuxdisk], +@@ -1484,7 +1487,7 @@ echo "Optional filters:" + echo + feature "ext2" test "x$HAVE_EXT2_TRUE" = "x" + feature "gzip" test "x$HAVE_ZLIB_TRUE" = "x" +-feature "LUKS" test "x$HAVE_GNUTLS_TRUE" != "x" ++feature "luks" test "x$HAVE_GNUTLS_PBKDF2_TRUE" = "x" + feature "xz" test "x$HAVE_LIBLZMA_TRUE" = "x" + + echo +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 30089621..622e5c3d 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -33,7 +33,7 @@ include $(top_srcdir)/common-rules.mk + + EXTRA_DIST = nbdkit-luks-filter.pod + +-if HAVE_GNUTLS ++if HAVE_GNUTLS_PBKDF2 + + filter_LTLIBRARIES = nbdkit-luks-filter.la + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index c29453ba..5585b3b7 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1597,7 +1597,7 @@ EXTRA_DIST += \ + $(NULL) + + # luks filter test. +-if HAVE_GNUTLS ++if HAVE_GNUTLS_PBKDF2 + TESTS += \ + test-luks-info.sh \ + test-luks-copy.sh \ +-- +2.31.1 + diff --git a/0006-luks-Various-fixes-for-Clang.patch b/0006-luks-Various-fixes-for-Clang.patch new file mode 100644 index 0000000..c0f7f2f --- /dev/null +++ b/0006-luks-Various-fixes-for-Clang.patch @@ -0,0 +1,71 @@ +From b3c05065801c723966a3e8d93c9b84e808ff38b9 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:30:09 +0100 +Subject: [PATCH] luks: Various fixes for Clang + +With Clang: + +luks.c:728:25: error: implicit conversion from enumeration type 'gnutls_digest_algorithm_t' to different enumeration type 'gnutls_mac_algorithm_t' [-Werror,-Wenum-conversion] + r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, + ~~~~~~~~~~~~~ ~~~^~~~~~~~ +luks.c:764:25: error: implicit conversion from enumeration type 'gnutls_digest_algorithm_t' to different enumeration type 'gnutls_mac_algorithm_t' [-Werror,-Wenum-conversion] + r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt, + ~~~~~~~~~~~~~ ~~~^~~~~~~~ +luks.c:886:35: error: result of comparison of constant 18446744073709551615 with expression of type 'uint32_t' (aka 'unsigned int') is always false [-Werror,-Wtautological-constant-out-of-range-compare] + if (ks->password_iterations > ULONG_MAX) { + ~~~~~~~~~~~~~~~~~~~~~~~ ^ ~~~~~~~~~ + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 87d488ede9101a2effc71cd1851bf4a4caa521d2) +--- + filters/luks/luks.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/filters/luks/luks.c b/filters/luks/luks.c +index 706a9bd2..cc619698 100644 +--- a/filters/luks/luks.c ++++ b/filters/luks/luks.c +@@ -693,6 +693,10 @@ key_material_length_in_sectors (struct handle *h, size_t i) + static int + try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + { ++ /* I believe this is supposed to be safe, looking at the GnuTLS ++ * header file. ++ */ ++ const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; + struct luks_keyslot *ks = &h->phdr.keyslot[i]; + size_t split_key_len; + CLEANUP_FREE uint8_t *split_key = NULL; +@@ -725,7 +729,7 @@ try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + } + + /* Hash the passphrase to make a possible masterkey. */ +- r = gnutls_pbkdf2 (h->hash_alg, &key, &salt, ks->password_iterations, ++ r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations, + masterkey, h->phdr.master_key_len); + if (r != 0) { + nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); +@@ -761,7 +765,7 @@ try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) + /* Check if the masterkey is correct by comparing hash of the + * masterkey with LUKS header. + */ +- r = gnutls_pbkdf2 (h->hash_alg, &mkey, &msalt, ++ r = gnutls_pbkdf2 (mac, &mkey, &msalt, + h->phdr.master_key_digest_iterations, + key_digest, LUKS_DIGESTSIZE); + if (r != 0) { +@@ -883,11 +887,6 @@ luks_prepare (nbdkit_next *next, void *handle, int readonly) + "points beyond the end of the disk", i); + return -1; + } +- if (ks->password_iterations > ULONG_MAX) { +- nbdkit_error ("bad LUKSv1 header: key slot %zu " +- "iterations too large", i); +- return -1; +- } + /*FALLTHROUGH*/ + case LUKS_KEY_DISABLED: + break; +-- +2.31.1 + diff --git a/0007-luks-Link-with-libcompat-on-Windows.patch b/0007-luks-Link-with-libcompat-on-Windows.patch new file mode 100644 index 0000000..f93465b --- /dev/null +++ b/0007-luks-Link-with-libcompat-on-Windows.patch @@ -0,0 +1,43 @@ +From 9416effd73a5cb2e1c929449fca88fd7152aa1be Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 12:38:00 +0100 +Subject: [PATCH] luks: Link with libcompat on Windows + +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-full-rw.o): in function `full_pread': +/builds/nbdkit/nbdkit/common/utils/full-rw.c:53: undefined reference to `pread' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-full-rw.o): in function `full_pwrite': +/builds/nbdkit/nbdkit/common/utils/full-rw.c:76: undefined reference to `pwrite' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: ../../common/utils/.libs/libutils.a(libutils_la-vector.o): in function `generic_vector_reserve_page_aligned': +/builds/nbdkit/nbdkit/common/utils/vector.c:112: undefined reference to `sysconf' +/usr/lib/gcc/x86_64-w64-mingw32/11.2.1/../../../../x86_64-w64-mingw32/bin/ld: /builds/nbdkit/nbdkit/common/utils/vector.c:134: undefined reference to `posix_memalign' +collect2: error: ld returned 1 exit status + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 4a28c4c46aedf270929a62a1c5ecf2c1129cd456) +--- + filters/luks/Makefile.am | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 622e5c3d..2688f696 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -45,6 +45,7 @@ nbdkit_luks_filter_la_SOURCES = \ + nbdkit_luks_filter_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + -I$(top_srcdir)/common/include \ ++ -I$(top_srcdir)/common/replacements \ + -I$(top_srcdir)/common/utils \ + $(NULL) + nbdkit_luks_filter_la_CFLAGS = \ +@@ -53,6 +54,7 @@ nbdkit_luks_filter_la_CFLAGS = \ + $(NULL) + nbdkit_luks_filter_la_LIBADD = \ + $(top_builddir)/common/utils/libutils.la \ ++ $(top_builddir)/common/replacements/libcompat.la \ + $(IMPORT_LIBRARY_ON_WINDOWS) \ + $(GNUTLS_LIBS) \ + $(NULL) +-- +2.31.1 + diff --git a/0008-luks-Refactor-the-filter.patch b/0008-luks-Refactor-the-filter.patch new file mode 100644 index 0000000..feaf986 --- /dev/null +++ b/0008-luks-Refactor-the-filter.patch @@ -0,0 +1,2096 @@ +From e8279107801bb93303b22e1b927929ce18279dc5 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 16:13:13 +0100 +Subject: [PATCH] luks: Refactor the filter + +Move the LUKS-specific code out into a separate file. This is mostly +pure refactoring to tidy things up. + +(cherry picked from commit 2159d85e0bed1943542da58b43c91f2caa096d6c) +--- + filters/luks/Makefile.am | 2 + + filters/luks/luks-encryption.c | 926 +++++++++++++++++++++++++++++++++ + filters/luks/luks-encryption.h | 78 +++ + filters/luks/luks.c | 874 ++----------------------------- + 4 files changed, 1037 insertions(+), 843 deletions(-) + create mode 100644 filters/luks/luks-encryption.c + create mode 100644 filters/luks/luks-encryption.h + +diff --git a/filters/luks/Makefile.am b/filters/luks/Makefile.am +index 2688f696..0894ac8b 100644 +--- a/filters/luks/Makefile.am ++++ b/filters/luks/Makefile.am +@@ -38,6 +38,8 @@ if HAVE_GNUTLS_PBKDF2 + filter_LTLIBRARIES = nbdkit-luks-filter.la + + nbdkit_luks_filter_la_SOURCES = \ ++ luks-encryption.c \ ++ luks-encryption.h \ + luks.c \ + $(top_srcdir)/include/nbdkit-filter.h \ + $(NULL) +diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c +new file mode 100644 +index 00000000..8ee0eb35 +--- /dev/null ++++ b/filters/luks/luks-encryption.c +@@ -0,0 +1,926 @@ ++/* nbdkit ++ * Copyright (C) 2018-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include ++ ++#include "luks-encryption.h" ++ ++#include "byte-swapping.h" ++#include "cleanup.h" ++#include "isaligned.h" ++#include "rounding.h" ++ ++/* LUKSv1 constants. */ ++#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE } ++#define LUKS_MAGIC_LEN 6 ++#define LUKS_DIGESTSIZE 20 ++#define LUKS_SALTSIZE 32 ++#define LUKS_NUMKEYS 8 ++#define LUKS_KEY_DISABLED 0x0000DEAD ++#define LUKS_KEY_ENABLED 0x00AC71F3 ++#define LUKS_STRIPES 4000 ++#define LUKS_ALIGN_KEYSLOTS 4096 ++ ++/* Key slot. */ ++struct luks_keyslot { ++ uint32_t active; /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */ ++ uint32_t password_iterations; ++ char password_salt[LUKS_SALTSIZE]; ++ uint32_t key_material_offset; ++ uint32_t stripes; ++} __attribute__((__packed__)); ++ ++/* LUKS superblock. */ ++struct luks_phdr { ++ char magic[LUKS_MAGIC_LEN]; /* LUKS_MAGIC */ ++ uint16_t version; /* Only 1 is supported. */ ++ char cipher_name[32]; ++ char cipher_mode[32]; ++ char hash_spec[32]; ++ uint32_t payload_offset; ++ uint32_t master_key_len; ++ uint8_t master_key_digest[LUKS_DIGESTSIZE]; ++ uint8_t master_key_salt[LUKS_SALTSIZE]; ++ uint32_t master_key_digest_iterations; ++ uint8_t uuid[40]; ++ ++ struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */ ++} __attribute__((__packed__)); ++ ++/* Block cipher mode of operation. ++ * https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation ++ */ ++enum cipher_mode { ++ CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR, ++}; ++ ++static enum cipher_mode ++lookup_cipher_mode (const char *str) ++{ ++ if (strcmp (str, "ecb") == 0) ++ return CIPHER_MODE_ECB; ++ if (strcmp (str, "cbc") == 0) ++ return CIPHER_MODE_CBC; ++ if (strcmp (str, "xts") == 0) ++ return CIPHER_MODE_XTS; ++ if (strcmp (str, "ctr") == 0) ++ return CIPHER_MODE_CTR; ++ nbdkit_error ("unknown cipher mode: %s " ++ "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str); ++ return -1; ++} ++ ++static const char * ++cipher_mode_to_string (enum cipher_mode v) ++{ ++ switch (v) { ++ case CIPHER_MODE_ECB: return "ecb"; ++ case CIPHER_MODE_CBC: return "cbc"; ++ case CIPHER_MODE_XTS: return "xts"; ++ case CIPHER_MODE_CTR: return "ctr"; ++ default: abort (); ++ } ++} ++ ++/* Methods used by LUKS to generate initial vectors. ++ * ++ * ESSIV is a bit more complicated to implement. It is supported by ++ * qemu but not by us. ++ */ ++enum ivgen { ++ IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */ ++}; ++ ++static enum ivgen ++lookup_ivgen (const char *str) ++{ ++ if (strcmp (str, "plain") == 0) ++ return IVGEN_PLAIN; ++ if (strcmp (str, "plain64") == 0) ++ return IVGEN_PLAIN64; ++/* ++ if (strcmp (str, "essiv") == 0) ++ return IVGEN_ESSIV; ++*/ ++ nbdkit_error ("unknown IV generation algorithm: %s " ++ "(expecting \"plain\", \"plain64\" etc)", str); ++ return -1; ++} ++ ++static const char * ++ivgen_to_string (enum ivgen v) ++{ ++ switch (v) { ++ case IVGEN_PLAIN: return "plain"; ++ case IVGEN_PLAIN64: return "plain64"; ++ /*case IVGEN_ESSIV: return "essiv";*/ ++ default: abort (); ++ } ++} ++ ++static void ++calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector) ++{ ++ size_t prefixlen; ++ uint32_t sector32; ++ ++ switch (v) { ++ case IVGEN_PLAIN: ++ prefixlen = 4; /* 32 bits */ ++ if (prefixlen > ivlen) ++ prefixlen = ivlen; ++ sector32 = (uint32_t) sector; /* truncate to only lower bits */ ++ sector32 = htole32 (sector32); ++ memcpy (iv, §or32, prefixlen); ++ memset (iv + prefixlen, 0, ivlen - prefixlen); ++ break; ++ ++ case IVGEN_PLAIN64: ++ prefixlen = 8; /* 64 bits */ ++ if (prefixlen > ivlen) ++ prefixlen = ivlen; ++ sector = htole64 (sector); ++ memcpy (iv, §or, prefixlen); ++ memset (iv + prefixlen, 0, ivlen - prefixlen); ++ break; ++ ++ /*case IVGEN_ESSIV:*/ ++ default: abort (); ++ } ++} ++ ++/* Cipher algorithm. ++ * ++ * qemu in theory supports many more, but with the GnuTLS backend only ++ * AES is supported. The kernel seems to only support AES for LUKSv1. ++ */ ++enum cipher_alg { ++ CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256, ++}; ++ ++static enum cipher_alg ++lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes) ++{ ++ if (mode == CIPHER_MODE_XTS) ++ key_bytes /= 2; ++ ++ if (strcmp (str, "aes") == 0) { ++ if (key_bytes == 16) ++ return CIPHER_ALG_AES_128; ++ if (key_bytes == 24) ++ return CIPHER_ALG_AES_192; ++ if (key_bytes == 32) ++ return CIPHER_ALG_AES_256; ++ } ++ nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str); ++ return -1; ++} ++ ++static const char * ++cipher_alg_to_string (enum cipher_alg v) ++{ ++ switch (v) { ++ case CIPHER_ALG_AES_128: return "aes-128"; ++ case CIPHER_ALG_AES_192: return "aes-192"; ++ case CIPHER_ALG_AES_256: return "aes-256"; ++ default: abort (); ++ } ++} ++ ++#if 0 ++static int ++cipher_alg_key_bytes (enum cipher_alg v) ++{ ++ switch (v) { ++ case CIPHER_ALG_AES_128: return 16; ++ case CIPHER_ALG_AES_192: return 24; ++ case CIPHER_ALG_AES_256: return 32; ++ default: abort (); ++ } ++} ++#endif ++ ++static int ++cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode) ++{ ++ if (CIPHER_MODE_ECB) ++ return 0; /* Don't need an IV in this mode. */ ++ ++ switch (v) { ++ case CIPHER_ALG_AES_128: ++ case CIPHER_ALG_AES_192: ++ case CIPHER_ALG_AES_256: ++ return 16; ++ default: abort (); ++ } ++} ++ ++/* Hash, eg.MD5, SHA1 etc. ++ * ++ * We reuse the GnuTLS digest algorithm enum here since it supports at ++ * least all the ones that LUKSv1 does. ++ */ ++static gnutls_digest_algorithm_t ++lookup_hash (const char *str) ++{ ++ if (strcmp (str, "md5") == 0) ++ return GNUTLS_DIG_MD5; ++ if (strcmp (str, "sha1") == 0) ++ return GNUTLS_DIG_SHA1; ++ if (strcmp (str, "sha224") == 0) ++ return GNUTLS_DIG_SHA224; ++ if (strcmp (str, "sha256") == 0) ++ return GNUTLS_DIG_SHA256; ++ if (strcmp (str, "sha384") == 0) ++ return GNUTLS_DIG_SHA384; ++ if (strcmp (str, "sha512") == 0) ++ return GNUTLS_DIG_SHA512; ++ if (strcmp (str, "ripemd160") == 0) ++ return GNUTLS_DIG_RMD160; ++ nbdkit_error ("unknown hash algorithm: %s " ++ "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str); ++ return -1; ++} ++ ++static const char * ++hash_to_string (gnutls_digest_algorithm_t v) ++{ ++ switch (v) { ++ case GNUTLS_DIG_UNKNOWN: return "unknown"; ++ case GNUTLS_DIG_MD5: return "md5"; ++ case GNUTLS_DIG_SHA1: return "sha1"; ++ case GNUTLS_DIG_SHA224: return "sha224"; ++ case GNUTLS_DIG_SHA256: return "sha256"; ++ case GNUTLS_DIG_SHA384: return "sha384"; ++ case GNUTLS_DIG_SHA512: return "sha512"; ++ case GNUTLS_DIG_RMD160: return "ripemd160"; ++ default: abort (); ++ } ++} ++ ++#if 0 ++/* See qemu & dm-crypt implementations for an explanation of what's ++ * going on here. ++ */ ++enum cipher_alg ++lookup_essiv_cipher (enum cipher_alg cipher_alg, ++ gnutls_digest_algorithm_t ivgen_hash_alg) ++{ ++ int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg); ++ int key_bytes = cipher_alg_key_bytes (cipher_alg); ++ ++ if (digest_bytes == key_bytes) ++ return cipher_alg; ++ ++ switch (cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ case CIPHER_ALG_AES_192: ++ case CIPHER_ALG_AES_256: ++ if (digest_bytes == 16) return CIPHER_ALG_AES_128; ++ if (digest_bytes == 24) return CIPHER_ALG_AES_192; ++ if (digest_bytes == 32) return CIPHER_ALG_AES_256; ++ nbdkit_error ("no %s cipher available with key size %d", ++ "AES", digest_bytes); ++ return -1; ++ default: ++ nbdkit_error ("ESSIV does not support cipher %s", ++ cipher_alg_to_string (cipher_alg)); ++ return -1; ++ } ++} ++#endif ++ ++/* Per-connection data. */ ++struct luks_data { ++ /* LUKS header, if necessary byte-swapped into host order. */ ++ struct luks_phdr phdr; ++ ++ /* Decoded algorithm etc. */ ++ enum cipher_alg cipher_alg; ++ enum cipher_mode cipher_mode; ++ gnutls_digest_algorithm_t hash_alg; ++ enum ivgen ivgen_alg; ++ gnutls_digest_algorithm_t ivgen_hash_alg; ++ enum cipher_alg ivgen_cipher_alg; ++ ++ /* GnuTLS algorithm. */ ++ gnutls_cipher_algorithm_t gnutls_cipher; ++ ++ /* If we managed to decrypt one of the keyslots using the passphrase ++ * then this contains the master key, otherwise NULL. ++ */ ++ uint8_t *masterkey; ++}; ++ ++/* Parse the header fields containing cipher algorithm, mode, etc. */ ++static int ++parse_cipher_strings (struct luks_data *h) ++{ ++ char cipher_name[33], cipher_mode[33], hash_spec[33]; ++ char *ivgen, *ivhash; ++ ++ /* Copy the header fields locally and ensure they are \0 terminated. */ ++ memcpy (cipher_name, h->phdr.cipher_name, 32); ++ cipher_name[32] = 0; ++ memcpy (cipher_mode, h->phdr.cipher_mode, 32); ++ cipher_mode[32] = 0; ++ memcpy (hash_spec, h->phdr.hash_spec, 32); ++ hash_spec[32] = 0; ++ ++ nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s " ++ "master key: %" PRIu32 " bits", ++ h->phdr.version, cipher_name, cipher_mode, hash_spec, ++ h->phdr.master_key_len * 8); ++ ++ /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]" ++ * QEmu writes: "xts-plain64" ++ */ ++ ivgen = strchr (cipher_mode, '-'); ++ if (!ivgen) { ++ nbdkit_error ("incorrect cipher_mode header, " ++ "expecting mode-ivgenerator but got \"%s\"", cipher_mode); ++ return -1; ++ } ++ *ivgen = '\0'; ++ ivgen++; ++ ++ ivhash = strchr (ivgen, ':'); ++ if (!ivhash) ++ h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN; ++ else { ++ *ivhash = '\0'; ++ ivhash++; ++ ++ h->ivgen_hash_alg = lookup_hash (ivhash); ++ if (h->ivgen_hash_alg == -1) ++ return -1; ++ } ++ ++ h->cipher_mode = lookup_cipher_mode (cipher_mode); ++ if (h->cipher_mode == -1) ++ return -1; ++ ++ h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode, ++ h->phdr.master_key_len); ++ if (h->cipher_alg == -1) ++ return -1; ++ ++ h->hash_alg = lookup_hash (hash_spec); ++ if (h->hash_alg == -1) ++ return -1; ++ ++ h->ivgen_alg = lookup_ivgen (ivgen); ++ if (h->ivgen_alg == -1) ++ return -1; ++ ++#if 0 ++ if (h->ivgen_alg == IVGEN_ESSIV) { ++ if (!ivhash) { ++ nbdkit_error ("incorrect IV generator hash specification"); ++ return -1; ++ } ++ h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg, ++ h->ivgen_hash_alg); ++ if (h->ivgen_cipher_alg == -1) ++ return -1; ++ } ++ else ++#endif ++ h->ivgen_cipher_alg = h->cipher_alg; ++ ++ nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s", ++ cipher_alg_to_string (h->cipher_alg), ++ cipher_mode_to_string (h->cipher_mode), ++ hash_to_string (h->hash_alg), ++ ivgen_to_string (h->ivgen_alg), ++ hash_to_string (h->ivgen_hash_alg), ++ cipher_alg_to_string (h->ivgen_cipher_alg)); ++ ++ /* GnuTLS combines cipher and block mode into a single value. Not ++ * all possible combinations are available in GnuTLS. See: ++ * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html ++ */ ++ h->gnutls_cipher = GNUTLS_CIPHER_NULL; ++ switch (h->cipher_mode) { ++ case CIPHER_MODE_XTS: ++ switch (h->cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS; ++ break; ++ case CIPHER_ALG_AES_256: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS; ++ break; ++ default: break; ++ } ++ break; ++ case CIPHER_MODE_CBC: ++ switch (h->cipher_alg) { ++ case CIPHER_ALG_AES_128: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC; ++ break; ++ case CIPHER_ALG_AES_192: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC; ++ break; ++ case CIPHER_ALG_AES_256: ++ h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC; ++ break; ++ default: break; ++ } ++ default: break; ++ } ++ if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) { ++ nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS", ++ cipher_alg_to_string (h->cipher_alg), ++ cipher_mode_to_string (h->cipher_mode)); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* Anti-Forensic merge operation. */ ++static void ++xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len) ++{ ++ size_t i; ++ ++ for (i = 0; i < len; ++i) ++ out[i] = in1[i] ^ in2[i]; ++} ++ ++static int ++af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len) ++{ ++ size_t digest_bytes = gnutls_hash_get_len (hash_alg); ++ size_t nr_blocks, last_block_len; ++ size_t i; ++ CLEANUP_FREE uint8_t *temp = malloc (digest_bytes); ++ int r; ++ gnutls_hash_hd_t hash; ++ ++ nr_blocks = len / digest_bytes; ++ last_block_len = len % digest_bytes; ++ if (last_block_len != 0) ++ nr_blocks++; ++ else ++ last_block_len = digest_bytes; ++ ++ for (i = 0; i < nr_blocks; ++i) { ++ const uint32_t iv = htobe32 (i); ++ const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len; ++ ++ /* Hash iv + i'th block into temp. */ ++ r = gnutls_hash_init (&hash, hash_alg); ++ if (r != 0) { ++ nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ gnutls_hash (hash, &iv, sizeof iv); ++ gnutls_hash (hash, &block[i*digest_bytes], blen); ++ gnutls_hash_deinit (hash, temp); ++ ++ memcpy (&block[i*digest_bytes], temp, blen); ++ } ++ ++ return 0; ++} ++ ++static int ++afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes, ++ const uint8_t *in, uint8_t *out, size_t outlen) ++{ ++ CLEANUP_FREE uint8_t *block = calloc (1, outlen); ++ size_t i; ++ ++ /* NB: input size is stripes * master_key_len where ++ * master_key_len == outlen ++ */ ++ for (i = 0; i < stripes-1; ++i) { ++ xor (&in[i*outlen], block, block, outlen); ++ if (af_hash (hash_alg, block, outlen) == -1) ++ return -1; ++ } ++ xor (&in[i*outlen], block, out, outlen); ++ return 0; ++} ++ ++/* Length of key material in key slot i (sectors). ++ * ++ * This is basically copied from qemu because the spec description is ++ * unintelligible and apparently doesn't match reality. ++ */ ++static uint64_t ++key_material_length_in_sectors (struct luks_data *h, size_t i) ++{ ++ uint64_t len, r; ++ ++ len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes; ++ r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE); ++ r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE); ++ return r; ++} ++ ++/* Try the passphrase in key slot i. If this returns true then the ++ * passphrase was able to decrypt the master key, and the master key ++ * has been stored in h->masterkey. ++ */ ++static int ++try_passphrase_in_keyslot (nbdkit_next *next, struct luks_data *h, ++ size_t i, const char *passphrase) ++{ ++ /* I believe this is supposed to be safe, looking at the GnuTLS ++ * header file. ++ */ ++ const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; ++ struct luks_keyslot *ks = &h->phdr.keyslot[i]; ++ size_t split_key_len; ++ CLEANUP_FREE uint8_t *split_key = NULL; ++ CLEANUP_FREE uint8_t *masterkey = NULL; ++ const gnutls_datum_t key = ++ { (unsigned char *) passphrase, strlen (passphrase) }; ++ const gnutls_datum_t salt = ++ { (unsigned char *) ks->password_salt, LUKS_SALTSIZE }; ++ const gnutls_datum_t msalt = ++ { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE }; ++ gnutls_datum_t mkey; ++ gnutls_cipher_hd_t cipher; ++ int r, err = 0; ++ uint64_t start; ++ uint8_t key_digest[LUKS_DIGESTSIZE]; ++ ++ if (ks->active != LUKS_KEY_ENABLED) ++ return 0; ++ ++ split_key_len = h->phdr.master_key_len * ks->stripes; ++ split_key = malloc (split_key_len); ++ if (split_key == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ masterkey = malloc (h->phdr.master_key_len); ++ if (masterkey == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ ++ /* Hash the passphrase to make a possible masterkey. */ ++ r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations, ++ masterkey, h->phdr.master_key_len); ++ if (r != 0) { ++ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ /* Read master key material from plugin. */ ++ start = ks->key_material_offset * LUKS_SECTOR_SIZE; ++ if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) { ++ errno = err; ++ return -1; ++ } ++ ++ /* Decrypt the (still AFsplit) master key material. */ ++ mkey.data = (unsigned char *) masterkey; ++ mkey.size = h->phdr.master_key_len; ++ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ r = do_decrypt (h, cipher, 0, split_key, split_key_len / LUKS_SECTOR_SIZE); ++ gnutls_cipher_deinit (cipher); ++ if (r == -1) ++ return -1; ++ ++ /* Decode AFsplit key to a possible masterkey. */ ++ if (afmerge (h->hash_alg, ks->stripes, split_key, ++ masterkey, h->phdr.master_key_len) == -1) ++ return -1; ++ ++ /* Check if the masterkey is correct by comparing hash of the ++ * masterkey with LUKS header. ++ */ ++ r = gnutls_pbkdf2 (mac, &mkey, &msalt, ++ h->phdr.master_key_digest_iterations, ++ key_digest, LUKS_DIGESTSIZE); ++ if (r != 0) { ++ nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) { ++ /* The passphrase is correct so save the master key in the handle. */ ++ h->masterkey = malloc (h->phdr.master_key_len); ++ if (h->masterkey == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } ++ memcpy (h->masterkey, masterkey, h->phdr.master_key_len); ++ return 1; ++ } ++ ++ return 0; ++} ++ ++struct luks_data * ++load_header (nbdkit_next *next, const char *passphrase) ++{ ++ static const char expected_magic[] = LUKS_MAGIC; ++ struct luks_data *h; ++ int64_t size; ++ int err = 0, r; ++ size_t i; ++ struct luks_keyslot *ks; ++ char uuid[41]; ++ ++ h = calloc (1, sizeof *h); ++ if (h == NULL) { ++ nbdkit_error ("calloc: %m"); ++ return NULL; ++ } ++ ++ /* Check the struct size matches the documentation. */ ++ assert (sizeof (struct luks_phdr) == 592); ++ ++ /* Check this is a LUKSv1 disk. */ ++ size = next->get_size (next); ++ if (size == -1) { ++ free (h); ++ return NULL; ++ } ++ if (size < 16384) { ++ nbdkit_error ("disk is too small to be LUKS-encrypted"); ++ free (h); ++ return NULL; ++ } ++ ++ /* Read the phdr. */ ++ if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) { ++ free (h); ++ errno = err; ++ return NULL; ++ } ++ ++ if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { ++ nbdkit_error ("this disk does not contain a LUKS header"); ++ return NULL; ++ } ++ h->phdr.version = be16toh (h->phdr.version); ++ if (h->phdr.version != 1) { ++ nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, " ++ "but this filter only supports LUKSv1", ++ h->phdr.version); ++ free (h); ++ return NULL; ++ } ++ ++ /* Byte-swap the rest of the header. */ ++ h->phdr.payload_offset = be32toh (h->phdr.payload_offset); ++ h->phdr.master_key_len = be32toh (h->phdr.master_key_len); ++ h->phdr.master_key_digest_iterations = ++ be32toh (h->phdr.master_key_digest_iterations); ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ ks = &h->phdr.keyslot[i]; ++ ks->active = be32toh (ks->active); ++ ks->password_iterations = be32toh (ks->password_iterations); ++ ks->key_material_offset = be32toh (ks->key_material_offset); ++ ks->stripes = be32toh (ks->stripes); ++ } ++ ++ /* Sanity check some fields. */ ++ if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) { ++ nbdkit_error ("bad LUKSv1 header: payload offset points beyond " ++ "the end of the disk"); ++ free (h); ++ return NULL; ++ } ++ ++ /* We derive several allocations from master_key_len so make sure ++ * it's not insane. ++ */ ++ if (h->phdr.master_key_len > 1024) { ++ nbdkit_error ("bad LUKSv1 header: master key is too long"); ++ free (h); ++ return NULL; ++ } ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ uint64_t start, len; ++ ++ ks = &h->phdr.keyslot[i]; ++ switch (ks->active) { ++ case LUKS_KEY_ENABLED: ++ if (!ks->stripes) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i); ++ free (h); ++ return NULL; ++ } ++ if (ks->stripes >= 10000) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i); ++ return NULL; ++ } ++ start = ks->key_material_offset; ++ len = key_material_length_in_sectors (h, i); ++ if (len > 4096) /* bound it at something reasonable */ { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu key material length " ++ "is too large", i); ++ free (h); ++ return NULL; ++ } ++ if (start * LUKS_SECTOR_SIZE >= size || ++ (start + len) * LUKS_SECTOR_SIZE >= size) { ++ nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset " ++ "points beyond the end of the disk", i); ++ free (h); ++ return NULL; ++ } ++ /*FALLTHROUGH*/ ++ case LUKS_KEY_DISABLED: ++ break; ++ ++ default: ++ nbdkit_error ("bad LUKSv1 header: key slot %zu has " ++ "an invalid active flag", i); ++ return NULL; ++ } ++ } ++ ++ /* Decode the ciphers. */ ++ if (parse_cipher_strings (h) == -1) { ++ free (h); ++ return NULL; ++ } ++ ++ /* Dump some information about the header. */ ++ memcpy (uuid, h->phdr.uuid, 40); ++ uuid[40] = 0; ++ nbdkit_debug ("LUKS UUID: %s", uuid); ++ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ uint64_t start, len; ++ ++ ks = &h->phdr.keyslot[i]; ++ if (ks->active == LUKS_KEY_ENABLED) { ++ start = ks->key_material_offset; ++ len = key_material_length_in_sectors (h, i); ++ nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64 ++ "..%" PRIu64, ++ i, start, start+len-1); ++ } ++ } ++ ++ /* Now try to unlock the master key. */ ++ for (i = 0; i < LUKS_NUMKEYS; ++i) { ++ r = try_passphrase_in_keyslot (next, h, i, passphrase); ++ if (r == -1) { ++ free (h); ++ return NULL; ++ } ++ if (r > 0) ++ goto unlocked; ++ } ++ nbdkit_error ("LUKS passphrase is not correct, " ++ "no key slot could be unlocked"); ++ free (h); ++ return NULL; ++ ++ unlocked: ++ assert (h->masterkey != NULL); ++ nbdkit_debug ("LUKS unlocked block device with passphrase"); ++ ++ return h; ++} ++ ++/* Free fields in the handle that might have been set by load_header. */ ++void ++free_luks_data (struct luks_data *h) ++{ ++ if (h->masterkey) { ++ memset (h->masterkey, 0, h->phdr.master_key_len); ++ free (h->masterkey); ++ } ++ free (h); ++} ++ ++uint64_t ++get_payload_offset (struct luks_data *h) ++{ ++ return h->phdr.payload_offset; ++} ++ ++gnutls_cipher_hd_t ++create_cipher (struct luks_data *h) ++{ ++ gnutls_datum_t mkey; ++ gnutls_cipher_hd_t cipher; ++ int r; ++ ++ assert (h->masterkey != NULL); ++ ++ mkey.data = (unsigned char *) h->masterkey; ++ mkey.size = h->phdr.master_key_len; ++ r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); ++ return NULL; ++ } ++ return cipher; ++} ++ ++/* Perform decryption of a block of data in memory. */ ++int ++do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ while (nr_sectors) { ++ calculate_iv (h->ivgen_alg, iv, ivlen, sector); ++ gnutls_cipher_set_iv (cipher, iv, ivlen); ++ r = gnutls_cipher_decrypt2 (cipher, ++ buf, LUKS_SECTOR_SIZE, /* ciphertext */ ++ buf, LUKS_SECTOR_SIZE /* plaintext */); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ buf += LUKS_SECTOR_SIZE; ++ nr_sectors--; ++ sector++; ++ } ++ ++ return 0; ++} ++ ++/* Perform encryption of a block of data in memory. */ ++int ++do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors) ++{ ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv = malloc (ivlen); ++ int r; ++ ++ while (nr_sectors) { ++ calculate_iv (h->ivgen_alg, iv, ivlen, sector); ++ gnutls_cipher_set_iv (cipher, iv, ivlen); ++ r = gnutls_cipher_encrypt2 (cipher, ++ buf, LUKS_SECTOR_SIZE, /* plaintext */ ++ buf, LUKS_SECTOR_SIZE /* ciphertext */); ++ if (r != 0) { ++ nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); ++ return -1; ++ } ++ ++ buf += LUKS_SECTOR_SIZE; ++ nr_sectors--; ++ sector++; ++ } ++ ++ return 0; ++} +diff --git a/filters/luks/luks-encryption.h b/filters/luks/luks-encryption.h +new file mode 100644 +index 00000000..3f8b9c9e +--- /dev/null ++++ b/filters/luks/luks-encryption.h +@@ -0,0 +1,78 @@ ++/* nbdkit ++ * Copyright (C) 2013-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++/* This header file defines the file format used by LUKSv1. See also: ++ * https://gitlab.com/cryptsetup/cryptsetup/-/wikis/LUKS-standard/on-disk-format.pdf ++ * Note we do not yet support LUKSv2. ++ */ ++ ++#ifndef NBDKIT_LUKS_ENCRYPTION_H ++#define NBDKIT_LUKS_ENCRYPTION_H ++ ++#include ++#include ++ ++#define LUKS_SECTOR_SIZE 512 ++ ++/* Per-connection data. */ ++struct luks_data; ++ ++/* Load the LUKS header, parse the algorithms, unlock the masterkey ++ * using the passphrase, initialize all the fields in the handle. ++ * ++ * This function may call next->pread (many times). ++ */ ++extern struct luks_data *load_header (nbdkit_next *next, ++ const char *passphrase); ++ ++/* Free the handle and all fields inside it. */ ++extern void free_luks_data (struct luks_data *h); ++ ++/* Get the offset where the encrypted data starts (in sectors). */ ++extern uint64_t get_payload_offset (struct luks_data *h); ++ ++/* Create an GnuTLS cipher, initialized with the master key. Must be ++ * freed by the caller using gnutls_cipher_deinit. ++ */ ++extern gnutls_cipher_hd_t create_cipher (struct luks_data *h); ++ ++/* Perform decryption/encryption of a block of memory in-place. ++ * ++ * 'sector' is the sector number on disk, used to calculate IVs. (The ++ * keyslots also use these functions, but sector must be 0). ++ */ ++extern int do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors); ++extern int do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, ++ uint64_t sector, uint8_t *buf, size_t nr_sectors); ++ ++#endif /* NBDKIT_LUKS_ENCRYPTION_H */ +diff --git a/filters/luks/luks.c b/filters/luks/luks.c +index cc619698..8ad3f4ec 100644 +--- a/filters/luks/luks.c ++++ b/filters/luks/luks.c +@@ -45,49 +45,11 @@ + + #include + +-#include "byte-swapping.h" ++#include "luks-encryption.h" ++ + #include "cleanup.h" + #include "isaligned.h" + #include "minmax.h" +-#include "rounding.h" +- +-/* LUKSv1 constants. */ +-#define LUKS_MAGIC { 'L', 'U', 'K', 'S', 0xBA, 0xBE } +-#define LUKS_MAGIC_LEN 6 +-#define LUKS_DIGESTSIZE 20 +-#define LUKS_SALTSIZE 32 +-#define LUKS_NUMKEYS 8 +-#define LUKS_KEY_DISABLED 0x0000DEAD +-#define LUKS_KEY_ENABLED 0x00AC71F3 +-#define LUKS_STRIPES 4000 +-#define LUKS_ALIGN_KEYSLOTS 4096 +-#define LUKS_SECTOR_SIZE 512 +- +-/* Key slot. */ +-struct luks_keyslot { +- uint32_t active; /* LUKS_KEY_DISABLED|LUKS_KEY_ENABLED */ +- uint32_t password_iterations; +- char password_salt[LUKS_SALTSIZE]; +- uint32_t key_material_offset; +- uint32_t stripes; +-} __attribute__((__packed__)); +- +-/* LUKS superblock. */ +-struct luks_phdr { +- char magic[LUKS_MAGIC_LEN]; /* LUKS_MAGIC */ +- uint16_t version; /* Only 1 is supported. */ +- char cipher_name[32]; +- char cipher_mode[32]; +- char hash_spec[32]; +- uint32_t payload_offset; +- uint32_t master_key_len; +- uint8_t master_key_digest[LUKS_DIGESTSIZE]; +- uint8_t master_key_salt[LUKS_SALTSIZE]; +- uint32_t master_key_digest_iterations; +- uint8_t uuid[40]; +- +- struct luks_keyslot keyslot[LUKS_NUMKEYS]; /* Key slots. */ +-} __attribute__((__packed__)); + + static char *passphrase = NULL; + +@@ -135,251 +97,9 @@ luks_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata) + #define luks_config_help \ + "passphrase= Secret passphrase." + +-enum cipher_mode { +- CIPHER_MODE_ECB, CIPHER_MODE_CBC, CIPHER_MODE_XTS, CIPHER_MODE_CTR, +-}; +- +-static enum cipher_mode +-lookup_cipher_mode (const char *str) +-{ +- if (strcmp (str, "ecb") == 0) +- return CIPHER_MODE_ECB; +- if (strcmp (str, "cbc") == 0) +- return CIPHER_MODE_CBC; +- if (strcmp (str, "xts") == 0) +- return CIPHER_MODE_XTS; +- if (strcmp (str, "ctr") == 0) +- return CIPHER_MODE_CTR; +- nbdkit_error ("unknown cipher mode: %s " +- "(expecting \"ecb\", \"cbc\", \"xts\" or \"ctr\")", str); +- return -1; +-} +- +-static const char * +-cipher_mode_to_string (enum cipher_mode v) +-{ +- switch (v) { +- case CIPHER_MODE_ECB: return "ecb"; +- case CIPHER_MODE_CBC: return "cbc"; +- case CIPHER_MODE_XTS: return "xts"; +- case CIPHER_MODE_CTR: return "ctr"; +- default: abort (); +- } +-} +- +-enum ivgen { +- IVGEN_PLAIN, IVGEN_PLAIN64, /* IVGEN_ESSIV, */ +-}; +- +-static enum ivgen +-lookup_ivgen (const char *str) +-{ +- if (strcmp (str, "plain") == 0) +- return IVGEN_PLAIN; +- if (strcmp (str, "plain64") == 0) +- return IVGEN_PLAIN64; +-/* +- if (strcmp (str, "essiv") == 0) +- return IVGEN_ESSIV; +-*/ +- nbdkit_error ("unknown IV generation algorithm: %s " +- "(expecting \"plain\", \"plain64\" etc)", str); +- return -1; +-} +- +-static const char * +-ivgen_to_string (enum ivgen v) +-{ +- switch (v) { +- case IVGEN_PLAIN: return "plain"; +- case IVGEN_PLAIN64: return "plain64"; +- /*case IVGEN_ESSIV: return "essiv";*/ +- default: abort (); +- } +-} +- +-static void +-calculate_iv (enum ivgen v, uint8_t *iv, size_t ivlen, uint64_t sector) +-{ +- size_t prefixlen; +- uint32_t sector32; +- +- switch (v) { +- case IVGEN_PLAIN: +- prefixlen = 4; /* 32 bits */ +- if (prefixlen > ivlen) +- prefixlen = ivlen; +- sector32 = (uint32_t) sector; /* truncate to only lower bits */ +- sector32 = htole32 (sector32); +- memcpy (iv, §or32, prefixlen); +- memset (iv + prefixlen, 0, ivlen - prefixlen); +- break; +- +- case IVGEN_PLAIN64: +- prefixlen = 8; /* 64 bits */ +- if (prefixlen > ivlen) +- prefixlen = ivlen; +- sector = htole64 (sector); +- memcpy (iv, §or, prefixlen); +- memset (iv + prefixlen, 0, ivlen - prefixlen); +- break; +- +- /*case IVGEN_ESSIV:*/ +- default: abort (); +- } +-} +- +-enum cipher_alg { +- CIPHER_ALG_AES_128, CIPHER_ALG_AES_192, CIPHER_ALG_AES_256, +-}; +- +-static enum cipher_alg +-lookup_cipher_alg (const char *str, enum cipher_mode mode, int key_bytes) +-{ +- if (mode == CIPHER_MODE_XTS) +- key_bytes /= 2; +- +- if (strcmp (str, "aes") == 0) { +- if (key_bytes == 16) +- return CIPHER_ALG_AES_128; +- if (key_bytes == 24) +- return CIPHER_ALG_AES_192; +- if (key_bytes == 32) +- return CIPHER_ALG_AES_256; +- } +- nbdkit_error ("unknown cipher algorithm: %s (expecting \"aes\", etc)", str); +- return -1; +-} +- +-static const char * +-cipher_alg_to_string (enum cipher_alg v) +-{ +- switch (v) { +- case CIPHER_ALG_AES_128: return "aes-128"; +- case CIPHER_ALG_AES_192: return "aes-192"; +- case CIPHER_ALG_AES_256: return "aes-256"; +- default: abort (); +- } +-} +- +-#if 0 +-static int +-cipher_alg_key_bytes (enum cipher_alg v) +-{ +- switch (v) { +- case CIPHER_ALG_AES_128: return 16; +- case CIPHER_ALG_AES_192: return 24; +- case CIPHER_ALG_AES_256: return 32; +- default: abort (); +- } +-} +-#endif +- +-static int +-cipher_alg_iv_len (enum cipher_alg v, enum cipher_mode mode) +-{ +- if (CIPHER_MODE_ECB) +- return 0; /* Don't need an IV in this mode. */ +- +- switch (v) { +- case CIPHER_ALG_AES_128: +- case CIPHER_ALG_AES_192: +- case CIPHER_ALG_AES_256: +- return 16; +- default: abort (); +- } +-} +- +-static gnutls_digest_algorithm_t +-lookup_hash (const char *str) +-{ +- if (strcmp (str, "md5") == 0) +- return GNUTLS_DIG_MD5; +- if (strcmp (str, "sha1") == 0) +- return GNUTLS_DIG_SHA1; +- if (strcmp (str, "sha224") == 0) +- return GNUTLS_DIG_SHA224; +- if (strcmp (str, "sha256") == 0) +- return GNUTLS_DIG_SHA256; +- if (strcmp (str, "sha384") == 0) +- return GNUTLS_DIG_SHA384; +- if (strcmp (str, "sha512") == 0) +- return GNUTLS_DIG_SHA512; +- if (strcmp (str, "ripemd160") == 0) +- return GNUTLS_DIG_RMD160; +- nbdkit_error ("unknown hash algorithm: %s " +- "(expecting \"md5\", \"sha1\", \"sha224\", etc)", str); +- return -1; +-} +- +-static const char * +-hash_to_string (gnutls_digest_algorithm_t v) +-{ +- switch (v) { +- case GNUTLS_DIG_UNKNOWN: return "unknown"; +- case GNUTLS_DIG_MD5: return "md5"; +- case GNUTLS_DIG_SHA1: return "sha1"; +- case GNUTLS_DIG_SHA224: return "sha224"; +- case GNUTLS_DIG_SHA256: return "sha256"; +- case GNUTLS_DIG_SHA384: return "sha384"; +- case GNUTLS_DIG_SHA512: return "sha512"; +- case GNUTLS_DIG_RMD160: return "ripemd160"; +- default: abort (); +- } +-} +- +-#if 0 +-/* See qemu & dm-crypt implementations for an explanation of what's +- * going on here. +- */ +-static enum cipher_alg +-lookup_essiv_cipher (enum cipher_alg cipher_alg, +- gnutls_digest_algorithm_t ivgen_hash_alg) +-{ +- int digest_bytes = gnutls_hash_get_len (ivgen_hash_alg); +- int key_bytes = cipher_alg_key_bytes (cipher_alg); +- +- if (digest_bytes == key_bytes) +- return cipher_alg; +- +- switch (cipher_alg) { +- case CIPHER_ALG_AES_128: +- case CIPHER_ALG_AES_192: +- case CIPHER_ALG_AES_256: +- if (digest_bytes == 16) return CIPHER_ALG_AES_128; +- if (digest_bytes == 24) return CIPHER_ALG_AES_192; +- if (digest_bytes == 32) return CIPHER_ALG_AES_256; +- nbdkit_error ("no %s cipher available with key size %d", +- "AES", digest_bytes); +- return -1; +- default: +- nbdkit_error ("ESSIV does not support cipher %s", +- cipher_alg_to_string (cipher_alg)); +- return -1; +- } +-} +-#endif +- + /* Per-connection handle. */ + struct handle { +- /* LUKS header, if necessary byte-swapped into host order. */ +- struct luks_phdr phdr; +- +- /* Decoded algorithm etc. */ +- enum cipher_alg cipher_alg; +- enum cipher_mode cipher_mode; +- gnutls_digest_algorithm_t hash_alg; +- enum ivgen ivgen_alg; +- gnutls_digest_algorithm_t ivgen_hash_alg; +- enum cipher_alg ivgen_cipher_alg; +- +- /* GnuTLS algorithm. */ +- gnutls_cipher_algorithm_t gnutls_cipher; +- +- /* If we managed to decrypt one of the keyslots using the passphrase +- * then this contains the master key, otherwise NULL. +- */ +- uint8_t *masterkey; ++ struct luks_data *h; + }; + + static void * +@@ -405,536 +125,21 @@ luks_close (void *handle) + { + struct handle *h = handle; + +- if (h->masterkey) { +- memset (h->masterkey, 0, h->phdr.master_key_len); +- free (h->masterkey); +- } ++ free_luks_data (h->h); + free (h); + } + +-/* Perform decryption of a block of data in memory. */ +-static int +-do_decrypt (struct handle *h, gnutls_cipher_hd_t cipher, +- uint64_t offset, uint8_t *buf, size_t len) +-{ +- const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); +- uint64_t sector = offset / LUKS_SECTOR_SIZE; +- CLEANUP_FREE uint8_t *iv = malloc (ivlen); +- int r; +- +- assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE)); +- assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE)); +- +- while (len) { +- calculate_iv (h->ivgen_alg, iv, ivlen, sector); +- gnutls_cipher_set_iv (cipher, iv, ivlen); +- r = gnutls_cipher_decrypt2 (cipher, +- buf, LUKS_SECTOR_SIZE, /* ciphertext */ +- buf, LUKS_SECTOR_SIZE /* plaintext */); +- if (r != 0) { +- nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); +- return -1; +- } +- +- buf += LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; +- len -= LUKS_SECTOR_SIZE; +- sector++; +- } +- +- return 0; +-} +- +-/* Perform encryption of a block of data in memory. */ +-static int +-do_encrypt (struct handle *h, gnutls_cipher_hd_t cipher, +- uint64_t offset, uint8_t *buf, size_t len) +-{ +- const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); +- uint64_t sector = offset / LUKS_SECTOR_SIZE; +- CLEANUP_FREE uint8_t *iv = malloc (ivlen); +- int r; +- +- assert (IS_ALIGNED (offset, LUKS_SECTOR_SIZE)); +- assert (IS_ALIGNED (len, LUKS_SECTOR_SIZE)); +- +- while (len) { +- calculate_iv (h->ivgen_alg, iv, ivlen, sector); +- gnutls_cipher_set_iv (cipher, iv, ivlen); +- r = gnutls_cipher_encrypt2 (cipher, +- buf, LUKS_SECTOR_SIZE, /* plaintext */ +- buf, LUKS_SECTOR_SIZE /* ciphertext */); +- if (r != 0) { +- nbdkit_error ("gnutls_cipher_decrypt2: %s", gnutls_strerror (r)); +- return -1; +- } +- +- buf += LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; +- len -= LUKS_SECTOR_SIZE; +- sector++; +- } +- +- return 0; +-} +- +-/* Parse the header fields containing cipher algorithm, mode, etc. */ +-static int +-parse_cipher_strings (struct handle *h) +-{ +- char cipher_name[33], cipher_mode[33], hash_spec[33]; +- char *ivgen, *ivhash; +- +- /* Copy the header fields locally and ensure they are \0 terminated. */ +- memcpy (cipher_name, h->phdr.cipher_name, 32); +- cipher_name[32] = 0; +- memcpy (cipher_mode, h->phdr.cipher_mode, 32); +- cipher_mode[32] = 0; +- memcpy (hash_spec, h->phdr.hash_spec, 32); +- hash_spec[32] = 0; +- +- nbdkit_debug ("LUKS v%" PRIu16 " cipher: %s mode: %s hash: %s " +- "master key: %" PRIu32 " bits", +- h->phdr.version, cipher_name, cipher_mode, hash_spec, +- h->phdr.master_key_len * 8); +- +- /* The cipher_mode header has the form: "ciphermode-ivgen[:ivhash]" +- * QEmu writes: "xts-plain64" +- */ +- ivgen = strchr (cipher_mode, '-'); +- if (!ivgen) { +- nbdkit_error ("incorrect cipher_mode header, " +- "expecting mode-ivgenerator but got \"%s\"", cipher_mode); +- return -1; +- } +- *ivgen = '\0'; +- ivgen++; +- +- ivhash = strchr (ivgen, ':'); +- if (!ivhash) +- h->ivgen_hash_alg = GNUTLS_DIG_UNKNOWN; +- else { +- *ivhash = '\0'; +- ivhash++; +- +- h->ivgen_hash_alg = lookup_hash (ivhash); +- if (h->ivgen_hash_alg == -1) +- return -1; +- } +- +- h->cipher_mode = lookup_cipher_mode (cipher_mode); +- if (h->cipher_mode == -1) +- return -1; +- +- h->cipher_alg = lookup_cipher_alg (cipher_name, h->cipher_mode, +- h->phdr.master_key_len); +- if (h->cipher_alg == -1) +- return -1; +- +- h->hash_alg = lookup_hash (hash_spec); +- if (h->hash_alg == -1) +- return -1; +- +- h->ivgen_alg = lookup_ivgen (ivgen); +- if (h->ivgen_alg == -1) +- return -1; +- +-#if 0 +- if (h->ivgen_alg == IVGEN_ESSIV) { +- if (!ivhash) { +- nbdkit_error ("incorrect IV generator hash specification"); +- return -1; +- } +- h->ivgen_cipher_alg = lookup_essiv_cipher (h->cipher_alg, +- h->ivgen_hash_alg); +- if (h->ivgen_cipher_alg == -1) +- return -1; +- } +- else +-#endif +- h->ivgen_cipher_alg = h->cipher_alg; +- +- nbdkit_debug ("LUKS parsed ciphers: %s %s %s %s %s %s", +- cipher_alg_to_string (h->cipher_alg), +- cipher_mode_to_string (h->cipher_mode), +- hash_to_string (h->hash_alg), +- ivgen_to_string (h->ivgen_alg), +- hash_to_string (h->ivgen_hash_alg), +- cipher_alg_to_string (h->ivgen_cipher_alg)); +- +- /* GnuTLS combines cipher and block mode into a single value. Not +- * all possible combinations are available in GnuTLS. See: +- * https://www.gnutls.org/manual/html_node/Supported-ciphersuites.html +- */ +- h->gnutls_cipher = GNUTLS_CIPHER_NULL; +- switch (h->cipher_mode) { +- case CIPHER_MODE_XTS: +- switch (h->cipher_alg) { +- case CIPHER_ALG_AES_128: +- h->gnutls_cipher = GNUTLS_CIPHER_AES_128_XTS; +- break; +- case CIPHER_ALG_AES_256: +- h->gnutls_cipher = GNUTLS_CIPHER_AES_256_XTS; +- break; +- default: break; +- } +- break; +- case CIPHER_MODE_CBC: +- switch (h->cipher_alg) { +- case CIPHER_ALG_AES_128: +- h->gnutls_cipher = GNUTLS_CIPHER_AES_128_CBC; +- break; +- case CIPHER_ALG_AES_192: +- h->gnutls_cipher = GNUTLS_CIPHER_AES_192_CBC; +- break; +- case CIPHER_ALG_AES_256: +- h->gnutls_cipher = GNUTLS_CIPHER_AES_256_CBC; +- break; +- default: break; +- } +- default: break; +- } +- if (h->gnutls_cipher == GNUTLS_CIPHER_NULL) { +- nbdkit_error ("cipher algorithm %s in mode %s is not supported by GnuTLS", +- cipher_alg_to_string (h->cipher_alg), +- cipher_mode_to_string (h->cipher_mode)); +- return -1; +- } +- +- return 0; +-} +- +-/* Anti-Forensic merge operation. */ +-static void +-xor (const uint8_t *in1, const uint8_t *in2, uint8_t *out, size_t len) +-{ +- size_t i; +- +- for (i = 0; i < len; ++i) +- out[i] = in1[i] ^ in2[i]; +-} +- +-static int +-af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len) +-{ +- size_t digest_bytes = gnutls_hash_get_len (hash_alg); +- size_t nr_blocks, last_block_len; +- size_t i; +- CLEANUP_FREE uint8_t *temp = malloc (digest_bytes); +- int r; +- gnutls_hash_hd_t hash; +- +- nr_blocks = len / digest_bytes; +- last_block_len = len % digest_bytes; +- if (last_block_len != 0) +- nr_blocks++; +- else +- last_block_len = digest_bytes; +- +- for (i = 0; i < nr_blocks; ++i) { +- const uint32_t iv = htobe32 (i); +- const size_t blen = i < nr_blocks - 1 ? digest_bytes : last_block_len; +- +- /* Hash iv + i'th block into temp. */ +- r = gnutls_hash_init (&hash, hash_alg); +- if (r != 0) { +- nbdkit_error ("gnutls_hash_init: %s", gnutls_strerror (r)); +- return -1; +- } +- gnutls_hash (hash, &iv, sizeof iv); +- gnutls_hash (hash, &block[i*digest_bytes], blen); +- gnutls_hash_deinit (hash, temp); +- +- memcpy (&block[i*digest_bytes], temp, blen); +- } +- +- return 0; +-} +- +-static int +-afmerge (gnutls_digest_algorithm_t hash_alg, uint32_t stripes, +- const uint8_t *in, uint8_t *out, size_t outlen) +-{ +- CLEANUP_FREE uint8_t *block = calloc (1, outlen); +- size_t i; +- +- /* NB: input size is stripes * master_key_len where +- * master_key_len == outlen +- */ +- for (i = 0; i < stripes-1; ++i) { +- xor (&in[i*outlen], block, block, outlen); +- if (af_hash (hash_alg, block, outlen) == -1) +- return -1; +- } +- xor (&in[i*outlen], block, out, outlen); +- return 0; +-} +- +-/* Length of key material in key slot i (sectors). +- * +- * This is basically copied from qemu because the spec description is +- * unintelligible and apparently doesn't match reality. +- */ +-static uint64_t +-key_material_length_in_sectors (struct handle *h, size_t i) +-{ +- uint64_t len, r; +- +- len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes; +- r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE); +- r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE); +- return r; +-} +- +-/* Try the passphrase in key slot i. If this returns true then the +- * passphrase was able to decrypt the master key, and the master key +- * has been stored in h->masterkey. +- */ +-static int +-try_passphrase_in_keyslot (nbdkit_next *next, struct handle *h, size_t i) +-{ +- /* I believe this is supposed to be safe, looking at the GnuTLS +- * header file. +- */ +- const gnutls_mac_algorithm_t mac = (gnutls_mac_algorithm_t) h->hash_alg; +- struct luks_keyslot *ks = &h->phdr.keyslot[i]; +- size_t split_key_len; +- CLEANUP_FREE uint8_t *split_key = NULL; +- CLEANUP_FREE uint8_t *masterkey = NULL; +- const gnutls_datum_t key = +- { (unsigned char *) passphrase, strlen (passphrase) }; +- const gnutls_datum_t salt = +- { (unsigned char *) ks->password_salt, LUKS_SALTSIZE }; +- const gnutls_datum_t msalt = +- { (unsigned char *) h->phdr.master_key_salt, LUKS_SALTSIZE }; +- gnutls_datum_t mkey; +- gnutls_cipher_hd_t cipher; +- int r, err = 0; +- uint64_t start; +- uint8_t key_digest[LUKS_DIGESTSIZE]; +- +- if (ks->active != LUKS_KEY_ENABLED) +- return 0; +- +- split_key_len = h->phdr.master_key_len * ks->stripes; +- split_key = malloc (split_key_len); +- if (split_key == NULL) { +- nbdkit_error ("malloc: %m"); +- return -1; +- } +- masterkey = malloc (h->phdr.master_key_len); +- if (masterkey == NULL) { +- nbdkit_error ("malloc: %m"); +- return -1; +- } +- +- /* Hash the passphrase to make a possible masterkey. */ +- r = gnutls_pbkdf2 (mac, &key, &salt, ks->password_iterations, +- masterkey, h->phdr.master_key_len); +- if (r != 0) { +- nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); +- return -1; +- } +- +- /* Read master key material from plugin. */ +- start = ks->key_material_offset * LUKS_SECTOR_SIZE; +- if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) { +- errno = err; +- return -1; +- } +- +- /* Decrypt the (still AFsplit) master key material. */ +- mkey.data = (unsigned char *) masterkey; +- mkey.size = h->phdr.master_key_len; +- r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); +- if (r != 0) { +- nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); +- return -1; +- } +- +- r = do_decrypt (h, cipher, 0, split_key, split_key_len); +- gnutls_cipher_deinit (cipher); +- if (r == -1) +- return -1; +- +- /* Decode AFsplit key to a possible masterkey. */ +- if (afmerge (h->hash_alg, ks->stripes, split_key, +- masterkey, h->phdr.master_key_len) == -1) +- return -1; +- +- /* Check if the masterkey is correct by comparing hash of the +- * masterkey with LUKS header. +- */ +- r = gnutls_pbkdf2 (mac, &mkey, &msalt, +- h->phdr.master_key_digest_iterations, +- key_digest, LUKS_DIGESTSIZE); +- if (r != 0) { +- nbdkit_error ("gnutls_pbkdf2: %s", gnutls_strerror (r)); +- return -1; +- } +- +- if (memcmp (key_digest, h->phdr.master_key_digest, LUKS_DIGESTSIZE) == 0) { +- /* The passphrase is correct so save the master key in the handle. */ +- h->masterkey = malloc (h->phdr.master_key_len); +- if (h->masterkey == NULL) { +- nbdkit_error ("malloc: %m"); +- return -1; +- } +- memcpy (h->masterkey, masterkey, h->phdr.master_key_len); +- return 1; +- } +- +- return 0; +-} +- + static int + luks_prepare (nbdkit_next *next, void *handle, int readonly) + { +- static const char expected_magic[] = LUKS_MAGIC; + struct handle *h = handle; +- int64_t size; +- int err = 0, r; +- size_t i; +- struct luks_keyslot *ks; +- char uuid[41]; + + /* Check we haven't been called before, this should never happen. */ +- assert (h->phdr.version == 0); ++ assert (h->h == NULL); + +- /* Check the struct size matches the documentation. */ +- assert (sizeof (struct luks_phdr) == 592); +- +- /* Check this is a LUKSv1 disk. */ +- size = next->get_size (next); +- if (size == -1) +- return -1; +- if (size < 16384) { +- nbdkit_error ("disk is too small to be LUKS-encrypted"); +- return -1; +- } +- +- /* Read the phdr. */ +- if (next->pread (next, &h->phdr, sizeof h->phdr, 0, 0, &err) == -1) { +- errno = err; +- return -1; +- } +- +- if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { +- nbdkit_error ("this disk does not contain a LUKS header"); ++ h->h = load_header (next, passphrase); ++ if (h->h == NULL) + return -1; +- } +- h->phdr.version = be16toh (h->phdr.version); +- if (h->phdr.version != 1) { +- nbdkit_error ("this disk contains a LUKS version %" PRIu16 " header, " +- "but this filter only supports LUKSv1", +- h->phdr.version); +- return -1; +- } +- +- /* Byte-swap the rest of the header. */ +- h->phdr.payload_offset = be32toh (h->phdr.payload_offset); +- h->phdr.master_key_len = be32toh (h->phdr.master_key_len); +- h->phdr.master_key_digest_iterations = +- be32toh (h->phdr.master_key_digest_iterations); +- +- for (i = 0; i < LUKS_NUMKEYS; ++i) { +- ks = &h->phdr.keyslot[i]; +- ks->active = be32toh (ks->active); +- ks->password_iterations = be32toh (ks->password_iterations); +- ks->key_material_offset = be32toh (ks->key_material_offset); +- ks->stripes = be32toh (ks->stripes); +- } +- +- /* Sanity check some fields. */ +- if (h->phdr.payload_offset >= size / LUKS_SECTOR_SIZE) { +- nbdkit_error ("bad LUKSv1 header: payload offset points beyond " +- "the end of the disk"); +- return -1; +- } +- +- /* We derive several allocations from master_key_len so make sure +- * it's not insane. +- */ +- if (h->phdr.master_key_len > 1024) { +- nbdkit_error ("bad LUKSv1 header: master key is too long"); +- return -1; +- } +- +- for (i = 0; i < LUKS_NUMKEYS; ++i) { +- uint64_t start, len; +- +- ks = &h->phdr.keyslot[i]; +- switch (ks->active) { +- case LUKS_KEY_ENABLED: +- if (!ks->stripes) { +- nbdkit_error ("bad LUKSv1 header: key slot %zu is corrupted", i); +- return -1; +- } +- if (ks->stripes >= 10000) { +- nbdkit_error ("bad LUKSv1 header: key slot %zu stripes too large", i); +- return -1; +- } +- start = ks->key_material_offset; +- len = key_material_length_in_sectors (h, i); +- if (len > 4096) /* bound it at something reasonable */ { +- nbdkit_error ("bad LUKSv1 header: key slot %zu key material length " +- "is too large", i); +- return -1; +- } +- if (start * LUKS_SECTOR_SIZE >= size || +- (start + len) * LUKS_SECTOR_SIZE >= size) { +- nbdkit_error ("bad LUKSv1 header: key slot %zu key material offset " +- "points beyond the end of the disk", i); +- return -1; +- } +- /*FALLTHROUGH*/ +- case LUKS_KEY_DISABLED: +- break; +- +- default: +- nbdkit_error ("bad LUKSv1 header: key slot %zu has " +- "an invalid active flag", i); +- return -1; +- } +- } +- +- /* Decode the ciphers. */ +- if (parse_cipher_strings (h) == -1) +- return -1; +- +- /* Dump some information about the header. */ +- memcpy (uuid, h->phdr.uuid, 40); +- uuid[40] = 0; +- nbdkit_debug ("LUKS UUID: %s", uuid); +- +- for (i = 0; i < LUKS_NUMKEYS; ++i) { +- uint64_t start, len; +- +- ks = &h->phdr.keyslot[i]; +- if (ks->active == LUKS_KEY_ENABLED) { +- start = ks->key_material_offset; +- len = key_material_length_in_sectors (h, i); +- nbdkit_debug ("LUKS key slot %zu: key material in sectors %" PRIu64 +- "..%" PRIu64, +- i, start, start+len-1); +- } +- } +- +- /* Now try to unlock the master key. */ +- for (i = 0; i < LUKS_NUMKEYS; ++i) { +- r = try_passphrase_in_keyslot (next, h, i); +- if (r == -1) +- return -1; +- if (r > 0) +- goto unlocked; +- } +- nbdkit_error ("LUKS passphrase is not correct, " +- "no key slot could be unlocked"); +- return -1; +- +- unlocked: +- assert (h->masterkey != NULL); +- nbdkit_debug ("LUKS unlocked block device with passphrase"); + + return 0; + } +@@ -946,19 +151,20 @@ luks_get_size (nbdkit_next *next, void *handle) + int64_t size; + + /* Check that prepare has been called already. */ +- assert (h->phdr.version > 0); ++ assert (h->h != NULL); ++ ++ const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE; + + size = next->get_size (next); + if (size == -1) + return -1; + +- if (size < h->phdr.payload_offset * LUKS_SECTOR_SIZE) { ++ if (size < payload_offset) { + nbdkit_error ("disk too small, or contains an incomplete LUKS partition"); + return -1; + } + +- size -= h->phdr.payload_offset * LUKS_SECTOR_SIZE; +- return size; ++ return size - payload_offset; + } + + /* Whatever the plugin says, several operations are not supported by +@@ -1031,15 +237,12 @@ luks_pread (nbdkit_next *next, void *handle, + uint32_t flags, int *err) + { + struct handle *h = handle; +- const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE; + CLEANUP_FREE uint8_t *sector = NULL; + uint64_t sectnum, sectoffs; +- const gnutls_datum_t mkey = +- { (unsigned char *) h->masterkey, h->phdr.master_key_len }; + gnutls_cipher_hd_t cipher; +- int r; + +- if (!h->masterkey) { ++ if (!h->h) { + *err = EIO; + return -1; + } +@@ -1053,16 +256,13 @@ luks_pread (nbdkit_next *next, void *handle, + } + } + +- r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); +- if (r != 0) { +- nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); +- *err = EIO; +- return -1; +- } +- + sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */ + sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */ + ++ cipher = create_cipher (h->h); ++ if (!cipher) ++ return -1; ++ + /* Unaligned head */ + if (sectoffs) { + uint64_t n = MIN (LUKS_SECTOR_SIZE - sectoffs, count); +@@ -1073,15 +273,13 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, +- sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + memcpy (buf, §or[sectoffs], n); + + buf += n; + count -= n; +- offset += n; + sectnum++; + } + +@@ -1092,12 +290,11 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset, buf, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, buf, 1) == -1) + goto err; + + buf += LUKS_SECTOR_SIZE; + count -= LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; + sectnum++; + } + +@@ -1109,7 +306,7 @@ luks_pread (nbdkit_next *next, void *handle, + flags, err) == -1) + goto err; + +- if (do_decrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_decrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + memcpy (buf, sector, count); +@@ -1120,7 +317,7 @@ luks_pread (nbdkit_next *next, void *handle, + + err: + gnutls_cipher_deinit (cipher); +- return -1; ++ goto err; + } + + /* Lock preventing read-modify-write cycles from overlapping. */ +@@ -1133,15 +330,12 @@ luks_pwrite (nbdkit_next *next, void *handle, + uint32_t flags, int *err) + { + struct handle *h = handle; +- const uint64_t payload_offset = h->phdr.payload_offset * LUKS_SECTOR_SIZE; ++ const uint64_t payload_offset = get_payload_offset (h->h) * LUKS_SECTOR_SIZE; + CLEANUP_FREE uint8_t *sector = NULL; + uint64_t sectnum, sectoffs; +- const gnutls_datum_t mkey = +- { (unsigned char *) h->masterkey, h->phdr.master_key_len }; + gnutls_cipher_hd_t cipher; +- int r; + +- if (!h->masterkey) { ++ if (!h->h) { + *err = EIO; + return -1; + } +@@ -1153,16 +347,13 @@ luks_pwrite (nbdkit_next *next, void *handle, + return -1; + } + +- r = gnutls_cipher_init (&cipher, h->gnutls_cipher, &mkey, NULL); +- if (r != 0) { +- nbdkit_error ("gnutls_cipher_init: %s", gnutls_strerror (r)); +- *err = EIO; +- return -1; +- } +- + sectnum = offset / LUKS_SECTOR_SIZE; /* sector number */ + sectoffs = offset % LUKS_SECTOR_SIZE; /* offset within the sector */ + ++ cipher = create_cipher (h->h); ++ if (!cipher) ++ return -1; ++ + /* Unaligned head */ + if (sectoffs) { + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&read_modify_write_lock); +@@ -1176,8 +367,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + + memcpy (§or[sectoffs], buf, n); + +- if (do_encrypt (h, cipher, offset & ~LUKS_SECTOR_SIZE, +- sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +@@ -1187,7 +377,6 @@ luks_pwrite (nbdkit_next *next, void *handle, + + buf += n; + count -= n; +- offset += n; + sectnum++; + } + +@@ -1195,7 +384,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + while (count >= LUKS_SECTOR_SIZE) { + memcpy (sector, buf, LUKS_SECTOR_SIZE); + +- if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +@@ -1205,7 +394,6 @@ luks_pwrite (nbdkit_next *next, void *handle, + + buf += LUKS_SECTOR_SIZE; + count -= LUKS_SECTOR_SIZE; +- offset += LUKS_SECTOR_SIZE; + sectnum++; + } + +@@ -1220,7 +408,7 @@ luks_pwrite (nbdkit_next *next, void *handle, + + memcpy (sector, buf, count); + +- if (do_encrypt (h, cipher, offset, sector, LUKS_SECTOR_SIZE) == -1) ++ if (do_encrypt (h->h, cipher, sectnum, sector, 1) == -1) + goto err; + + if (next->pwrite (next, sector, LUKS_SECTOR_SIZE, +-- +2.31.1 + diff --git a/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch b/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch new file mode 100644 index 0000000..34419d8 --- /dev/null +++ b/0009-tests-luks-Reduce-time-taken-to-run-these-tests.patch @@ -0,0 +1,101 @@ +From 387bd4c6fee8ab339fd04e0b841b0c67e6020c8a Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sun, 8 May 2022 18:05:45 +0100 +Subject: [PATCH] tests: luks: Reduce time taken to run these tests + +Under valgrind they ran very slowly. Turns out valgrinding over +GnuTLS hashing code is not pretty. About half the time seems to be +taken opening the keyslot, and the rest copying the data. + +This change reduces the time (under valgrind) from 15 minutes 45 seconds +to about 6 mins 30 seconds. + +(cherry picked from commit 7320ae5dba476171a024ca44b889b3474302dc40) +--- + tests/test-luks-copy.sh | 18 +++++++++--------- + tests/test-luks-info.sh | 6 +++--- + 2 files changed, 12 insertions(+), 12 deletions(-) + +diff --git a/tests/test-luks-copy.sh b/tests/test-luks-copy.sh +index 99f300d0..01801811 100755 +--- a/tests/test-luks-copy.sh ++++ b/tests/test-luks-copy.sh +@@ -60,8 +60,8 @@ rm -f $encrypt_disk $plain_disk $pid $sock + qemu-img create -f luks \ + --object secret,data=123456,id=sec0 \ + -o key-secret=sec0 \ +- $encrypt_disk 10M +-truncate -s 10M $plain_disk ++ $encrypt_disk 1M ++truncate -s 1M $plain_disk + qemu-img convert --target-image-opts -n \ + --object secret,data=123456,id=sec0 \ + $plain_disk \ +@@ -74,11 +74,11 @@ start_nbdkit -P $pid -U $sock \ + uri="nbd+unix:///?socket=$sock" + + # Copy the whole disk out. It should be empty. +-nbdcopy "$uri" $plain_disk ++nbdcopy -C 1 "$uri" $plain_disk + + if [ "$(hexdump -C $plain_disk)" != '00000000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-00a00000' ]; then ++00100000' ]; then + echo "$0: expected plaintext disk to be empty" + exit 1 + fi +@@ -88,14 +88,14 @@ fi + nbdsh -u "$uri" \ + -c 'h.pwrite(b"1"*65536, 0)' \ + -c 'h.pwrite(b"2"*65536, 128*1024)' \ +- -c 'h.pwrite(b"3"*65536, 9*1024*1024)' \ ++ -c 'h.pwrite(b"3"*65536, 900*1024)' \ + -c 'buf = h.pread(65536, 0)' \ + -c 'assert buf == b"1"*65536' \ + -c 'buf = h.pread(65536, 65536)' \ + -c 'assert buf == bytearray(65536)' \ + -c 'buf = h.pread(65536, 128*1024)' \ + -c 'assert buf == b"2"*65536' \ +- -c 'buf = h.pread(65536, 9*1024*1024)' \ ++ -c 'buf = h.pread(65536, 900*1024)' \ + -c 'assert buf == b"3"*65536' \ + -c 'h.flush()' + +@@ -115,11 +115,11 @@ if [ "$(hexdump -C $plain_disk)" != '00000000 31 31 31 31 31 31 31 31 31 31 31 + * + 00030000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-00900000 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333| ++000e1000 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 33 |3333333333333333| + * +-00910000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| ++000f1000 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................| + * +-00a00000' ]; then ++00100000' ]; then + echo "$0: unexpected content" + exit 1 + fi +diff --git a/tests/test-luks-info.sh b/tests/test-luks-info.sh +index 3eff657b..ef141ecd 100755 +--- a/tests/test-luks-info.sh ++++ b/tests/test-luks-info.sh +@@ -46,11 +46,11 @@ rm -f $disk $info + qemu-img create -f luks \ + --object secret,data=123456,id=sec0 \ + -o key-secret=sec0 \ +- $disk 10M ++ $disk 1M + + nbdkit -U - file $disk --filter=luks passphrase=123456 \ + --run 'nbdinfo $uri' > $info + cat $info + +-# Check the size is 10M (so it doesn't include the LUKS header). +-grep "10485760" $info ++# Check the size is 1M (so it doesn't include the LUKS header). ++grep "1048576" $info +-- +2.31.1 + diff --git a/0010-Add-nbdkit.parse_size-Python-function.patch b/0010-Add-nbdkit.parse_size-Python-function.patch new file mode 100644 index 0000000..5cb340b --- /dev/null +++ b/0010-Add-nbdkit.parse_size-Python-function.patch @@ -0,0 +1,112 @@ +From 52ee1dab95436128b44c37cc495022ff90108b2e Mon Sep 17 00:00:00 2001 +From: Nikolaus Rath +Date: Mon, 9 May 2022 10:04:30 +0100 +Subject: [PATCH] Add nbdkit.parse_size() Python function. + +This enables Python plugins to parse sizes the same way as C plugins. + +I'm not sure about the best way to test this - input is appreciated. + +I'm not too happy with the way this code is tested. It workes, but putting the tests into +test-python-plugin.py feels misplaced: this file is intended to support the unit tests in +test_python.py, not run its own unit tests. + +(cherry picked from commit 1b7d72542be68e254c1ef86ecb1a82b05c78ff63) +--- + plugins/python/modfunctions.c | 21 +++++++++++++++++++++ + plugins/python/nbdkit-python-plugin.pod | 5 +++++ + tests/test-python-plugin.py | 19 +++++++++++++++++++ + 3 files changed, 45 insertions(+) + +diff --git a/plugins/python/modfunctions.c b/plugins/python/modfunctions.c +index fffbaab2..46b0c904 100644 +--- a/plugins/python/modfunctions.c ++++ b/plugins/python/modfunctions.c +@@ -93,11 +93,32 @@ do_shutdown (PyObject *self, PyObject *args) + Py_RETURN_NONE; + } + ++/* nbdkit.parse_size */ ++static PyObject * ++parse_size (PyObject *self, PyObject *args) ++{ ++ const char *s; ++ if (!PyArg_ParseTuple (args, "s", &s)) { ++ PyErr_SetString (PyExc_TypeError, "Expected string, got something else"); ++ return NULL; ++ } ++ ++ int64_t size = nbdkit_parse_size(s); ++ if (size == -1) { ++ PyErr_SetString (PyExc_ValueError, "Unable to parse string as size"); ++ return NULL; ++ } ++ ++ return PyLong_FromSize_t((size_t)size); ++} ++ + static PyMethodDef NbdkitMethods[] = { + { "debug", debug, METH_VARARGS, + "Print a debug message" }, + { "export_name", export_name, METH_VARARGS, + "Return the optional export name negotiated with the client" }, ++ { "parse_size", parse_size, METH_VARARGS, ++ "Parse human-readable size strings into bytes" }, + { "set_error", set_error, METH_VARARGS, + "Store an errno value prior to throwing an exception" }, + { "shutdown", do_shutdown, METH_VARARGS, +diff --git a/plugins/python/nbdkit-python-plugin.pod b/plugins/python/nbdkit-python-plugin.pod +index 051b0237..ccc9406f 100644 +--- a/plugins/python/nbdkit-python-plugin.pod ++++ b/plugins/python/nbdkit-python-plugin.pod +@@ -131,6 +131,11 @@ Record C as the reason you are about to throw an exception. C + should correspond to usual errno values, where it may help to + C. + ++=head3 C ++ ++Parse a string (such as "100M") into a size in bytes. Wraps the ++C C function. ++ + =head3 C + + Request asynchronous server shutdown. +diff --git a/tests/test-python-plugin.py b/tests/test-python-plugin.py +index 0b34d532..d4f379fc 100644 +--- a/tests/test-python-plugin.py ++++ b/tests/test-python-plugin.py +@@ -34,12 +34,31 @@ + import nbdkit + import pickle + import base64 ++import unittest + + API_VERSION = 2 + + cfg = {} + + ++# Not nice, but there doesn't seem to be a better way of putting this ++class TestAPI(unittest.TestCase): ++ ++ def test_parse_size(self): ++ self.assertEqual(nbdkit.parse_size('511'), 511) ++ self.assertEqual(nbdkit.parse_size('7k'), 7*1024) ++ self.assertEqual(nbdkit.parse_size('17M'), 17*1024*1024) ++ ++ with self.assertRaises(TypeError): ++ nbdkit.parse_size(17) ++ ++ with self.assertRaises(ValueError): ++ nbdkit.parse_size('foo') ++ ++ ++TestAPI().test_parse_size() ++ ++ + def config(k, v): + global cfg + if k == "cfg": +-- +2.31.1 + diff --git a/0011-cache-Fix-cross-reference-nbdkit-readahead-filter.patch b/0011-cache-Fix-cross-reference-nbdkit-readahead-filter.patch new file mode 100644 index 0000000..ed4a4e3 --- /dev/null +++ b/0011-cache-Fix-cross-reference-nbdkit-readahead-filter.patch @@ -0,0 +1,34 @@ +From 644e0ed6333cf5fe2c1e39da157e8f1ce97267b9 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 13:47:19 +0100 +Subject: [PATCH] cache: Fix cross-reference nbdkit-readahead-filter + +After the readahead filter was reimplemented so that it only issues +cache requests, the two filters should be used together, not as +alternatives. Update the documentation of the cache filter to make +this clear. + +Fixes: commit 2ff548d66ad3eae87868402ec5b3319edd12090f +(cherry picked from commit 894771f39a8fd2632caad00e497146d69cac4bac) +--- + filters/cache/nbdkit-cache-filter.pod | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod +index d85fef09..f4234e1a 100644 +--- a/filters/cache/nbdkit-cache-filter.pod ++++ b/filters/cache/nbdkit-cache-filter.pod +@@ -28,8 +28,8 @@ loss, as the name suggests). + + This filter only caches image contents. To cache image metadata, use + L between this filter and the plugin. +-To accelerate sequential reads, use L +-instead. ++To accelerate sequential reads, use L on ++top of this filter. + + =head1 PARAMETERS + +-- +2.31.1 + diff --git a/0012-curl-Don-t-document-curl-plugin-readahead-filter.patch b/0012-curl-Don-t-document-curl-plugin-readahead-filter.patch new file mode 100644 index 0000000..4c8288e --- /dev/null +++ b/0012-curl-Don-t-document-curl-plugin-readahead-filter.patch @@ -0,0 +1,48 @@ +From 4a7e5169935c8850fddcea8da79639ded907c549 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 14:00:16 +0100 +Subject: [PATCH] curl: Don't document curl plugin + readahead filter + +nbdkit readahead filter does not support plugins which do not use the +parallel thread model. + +Fixes: commit 2ff548d66ad3eae87868402ec5b3319edd12090f +(cherry picked from commit 92fbb76d11b9f17c527debd803aa2505f3642783) +--- + docs/nbdkit-captive.pod | 7 ------- + plugins/curl/nbdkit-curl-plugin.pod | 1 - + 2 files changed, 8 deletions(-) + +diff --git a/docs/nbdkit-captive.pod b/docs/nbdkit-captive.pod +index eafe36d8..d41a824d 100644 +--- a/docs/nbdkit-captive.pod ++++ b/docs/nbdkit-captive.pod +@@ -110,13 +110,6 @@ an embedded disk image. To copy it out: + + nbdkit -U - example1 --run 'qemu-img convert $nbd disk.img' + +-If plugin requests have a high overhead (for example making HTTP +-requests to a remote server), adding L may +-help performance: +- +- nbdkit -U - --filter=readahead curl https://example.com/disk.img \ +- --run 'qemu-img convert $nbd disk.img' +- + If the source suffers from temporary network failures + L or L may + help. +diff --git a/plugins/curl/nbdkit-curl-plugin.pod b/plugins/curl/nbdkit-curl-plugin.pod +index 54fce66c..fc422ca2 100644 +--- a/plugins/curl/nbdkit-curl-plugin.pod ++++ b/plugins/curl/nbdkit-curl-plugin.pod +@@ -509,7 +509,6 @@ L, + L, + L, + L, +-L, + L, + L, + L, +-- +2.31.1 + diff --git a/0013-New-filter-scan.patch b/0013-New-filter-scan.patch new file mode 100644 index 0000000..527a597 --- /dev/null +++ b/0013-New-filter-scan.patch @@ -0,0 +1,1021 @@ +From 8bfe6512d07caf778fd001425435b048c45513eb Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 13:46:56 +0100 +Subject: [PATCH] New filter: scan + +This filter will simply scan across the disk issuing a series of cache +requests to the underlying plugin. It is similar in scope and usage +to the new nbdkit-readahead-filter. + +(cherry picked from commit 65c20a09ceacb4431986a2982f2c2e746df63fcb) +--- + TODO | 8 - + configure.ac | 2 + + filters/cache/nbdkit-cache-filter.pod | 4 +- + .../nbdkit-cacheextents-filter.pod | 1 + + filters/readahead/nbdkit-readahead-filter.pod | 5 + + filters/scan/Makefile.am | 72 +++++ + filters/scan/bgthread.c | 131 ++++++++ + filters/scan/nbdkit-scan-filter.pod | 159 ++++++++++ + filters/scan/scan.c | 280 ++++++++++++++++++ + filters/scan/scan.h | 64 ++++ + plugins/ssh/nbdkit-ssh-plugin.pod | 1 + + plugins/torrent/nbdkit-torrent-plugin.pod | 1 + + plugins/vddk/nbdkit-vddk-plugin.pod | 1 + + tests/Makefile.am | 10 + + tests/test-scan-copy.sh | 42 +++ + tests/test-scan-info.sh | 46 +++ + 16 files changed, 817 insertions(+), 10 deletions(-) + create mode 100644 filters/scan/Makefile.am + create mode 100644 filters/scan/bgthread.c + create mode 100644 filters/scan/nbdkit-scan-filter.pod + create mode 100644 filters/scan/scan.c + create mode 100644 filters/scan/scan.h + create mode 100755 tests/test-scan-copy.sh + create mode 100755 tests/test-scan-info.sh + +diff --git a/TODO b/TODO +index 0f5dc41d..8600d9e4 100644 +--- a/TODO ++++ b/TODO +@@ -182,14 +182,6 @@ Python: + Suggestions for filters + ----------------------- + +-* Add scan filter. This would be placed on top of cache filters and +- would scan (read) the whole disk in the background, ensuring it is +- copied into the cache. Useful if you have a slow plugin, limited +- size device, and lots of local disk space, especially if you know +- that the NBD clients will eventually read all of the device. RWMJ +- wrote an implementation of this but it doesn't work well without a +- background thread. +- + * Add shared filter. Take advantage of filter context APIs to open a + single context into the backend shared among multiple client + connections. This may even allow a filter to offer a more parallel +diff --git a/configure.ac b/configure.ac +index 1d209f67..466dbd9b 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -142,6 +142,7 @@ filters="\ + readahead \ + retry \ + retry-request \ ++ scan \ + stats \ + swab \ + tar \ +@@ -1403,6 +1404,7 @@ AC_CONFIG_FILES([Makefile + filters/readahead/Makefile + filters/retry/Makefile + filters/retry-request/Makefile ++ filters/scan/Makefile + filters/stats/Makefile + filters/swab/Makefile + filters/tar/Makefile +diff --git a/filters/cache/nbdkit-cache-filter.pod b/filters/cache/nbdkit-cache-filter.pod +index f4234e1a..935804b5 100644 +--- a/filters/cache/nbdkit-cache-filter.pod ++++ b/filters/cache/nbdkit-cache-filter.pod +@@ -28,8 +28,8 @@ loss, as the name suggests). + + This filter only caches image contents. To cache image metadata, use + L between this filter and the plugin. +-To accelerate sequential reads, use L on +-top of this filter. ++To accelerate sequential reads, use L or ++L on top of this filter. + + =head1 PARAMETERS + +diff --git a/filters/cacheextents/nbdkit-cacheextents-filter.pod b/filters/cacheextents/nbdkit-cacheextents-filter.pod +index bb2514a4..6464eac2 100644 +--- a/filters/cacheextents/nbdkit-cacheextents-filter.pod ++++ b/filters/cacheextents/nbdkit-cacheextents-filter.pod +@@ -54,6 +54,7 @@ L, + L, + L, + L, ++L, + L, + L, + L. +diff --git a/filters/readahead/nbdkit-readahead-filter.pod b/filters/readahead/nbdkit-readahead-filter.pod +index 630e5924..99d64dfb 100644 +--- a/filters/readahead/nbdkit-readahead-filter.pod ++++ b/filters/readahead/nbdkit-readahead-filter.pod +@@ -27,6 +27,10 @@ option. + The filter uses a simple adaptive algorithm which accelerates + sequential reads and requires no further configuration. + ++A similar filter is L which reads ahead over ++the whole disk, useful if you know that the client will be reading ++sequentially across most or all of the disk. ++ + =head2 Limitations + + In a number of significant cases this filter will do nothing. The +@@ -91,6 +95,7 @@ L, + L, + L, + L, ++L, + L, + L, + L, +diff --git a/filters/scan/Makefile.am b/filters/scan/Makefile.am +new file mode 100644 +index 00000000..d4aabfc6 +--- /dev/null ++++ b/filters/scan/Makefile.am +@@ -0,0 +1,72 @@ ++# nbdkit ++# Copyright (C) 2019-2021 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++include $(top_srcdir)/common-rules.mk ++ ++EXTRA_DIST = nbdkit-scan-filter.pod ++ ++filter_LTLIBRARIES = nbdkit-scan-filter.la ++ ++nbdkit_scan_filter_la_SOURCES = \ ++ scan.c \ ++ scan.h \ ++ bgthread.c \ ++ $(top_srcdir)/include/nbdkit-filter.h \ ++ $(NULL) ++ ++nbdkit_scan_filter_la_CPPFLAGS = \ ++ -I$(top_srcdir)/include \ ++ -I$(top_srcdir)/common/include \ ++ -I$(top_srcdir)/common/utils \ ++ $(NULL) ++nbdkit_scan_filter_la_CFLAGS = $(WARNINGS_CFLAGS) ++nbdkit_scan_filter_la_LDFLAGS = \ ++ -module -avoid-version -shared $(NO_UNDEFINED_ON_WINDOWS) \ ++ -Wl,--version-script=$(top_srcdir)/filters/filters.syms \ ++ $(NULL) ++nbdkit_scan_filter_la_LIBADD = \ ++ $(top_builddir)/common/utils/libutils.la \ ++ $(top_builddir)/common/replacements/libcompat.la \ ++ $(IMPORT_LIBRARY_ON_WINDOWS) \ ++ $(NULL) ++ ++if HAVE_POD ++ ++man_MANS = nbdkit-scan-filter.1 ++CLEANFILES += $(man_MANS) ++ ++nbdkit-scan-filter.1: nbdkit-scan-filter.pod \ ++ $(top_builddir)/podwrapper.pl ++ $(PODWRAPPER) --section=1 --man $@ \ ++ --html $(top_builddir)/html/$@.html \ ++ $< ++ ++endif HAVE_POD +diff --git a/filters/scan/bgthread.c b/filters/scan/bgthread.c +new file mode 100644 +index 00000000..384e79b6 +--- /dev/null ++++ b/filters/scan/bgthread.c +@@ -0,0 +1,131 @@ ++/* nbdkit ++ * Copyright (C) 2019-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "scan.h" ++ ++#include "cleanup.h" ++#include "minmax.h" ++ ++static pthread_mutex_t clock_lock; ++static uint64_t clock_ = 0; ++ ++static void ++adjust_clock (uint64_t offset) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&clock_lock); ++ if (clock_ < offset) ++ clock_ = offset; ++} ++ ++static void ++reset_clock (uint64_t offset) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&clock_lock); ++ clock_ = 0; ++} ++ ++static uint64_t ++get_starting_offset (void) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&clock_lock); ++ return scan_clock ? clock_ : 0; ++} ++ ++void * ++scan_thread (void *vp) ++{ ++ struct bgthread_ctrl *ctrl = vp; ++ uint64_t offset, size; ++ int64_t r; ++ ++ assert (ctrl->next != NULL); ++ ++ /* Get the size of the underlying plugin. Exit the thread on error ++ * because there's not much we can do without knowing the size. ++ */ ++ r = ctrl->next->get_size (ctrl->next); ++ if (r == -1) ++ return NULL; ++ size = r; ++ ++ /* Start scanning. */ ++ start: ++ for (offset = get_starting_offset (); offset < size; offset += scan_size) { ++ uint64_t n; ++ ++ /* Execute any commands in the queue. */ ++ { ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&ctrl->lock); ++ struct command cmd; ++ ++ while (ctrl->cmds.len) { ++ cmd = ctrl->cmds.ptr[0]; ++ command_queue_remove (&ctrl->cmds, 0); ++ ++ switch (cmd.type) { ++ case CMD_QUIT: ++ nbdkit_debug ("scan: exiting background thread on connection close"); ++ return NULL; ++ ++ case CMD_NOTIFY_PREAD: ++ if (offset < cmd.offset) ++ offset = cmd.offset; ++ } ++ } ++ } ++ ++ adjust_clock (offset); ++ if (offset > size) ++ continue; ++ ++ /* Issue the next prefetch. */ ++ n = MIN (scan_size, size - offset); ++ ctrl->next->cache (ctrl->next, n, offset, 0, NULL); ++ } ++ ++ if (scan_forever) { ++ reset_clock (offset); ++ goto start; ++ } ++ ++ nbdkit_debug ("scan: finished scanning the plugin"); ++ return NULL; ++} +diff --git a/filters/scan/nbdkit-scan-filter.pod b/filters/scan/nbdkit-scan-filter.pod +new file mode 100644 +index 00000000..4a8d0ef9 +--- /dev/null ++++ b/filters/scan/nbdkit-scan-filter.pod +@@ -0,0 +1,159 @@ ++=head1 NAME ++ ++nbdkit-scan-filter - scan disk prefetching data ahead of sequential reads ++ ++=head1 SYNOPSIS ++ ++ nbdkit --filter=scan PLUGIN [scan-ahead=false] [scan-clock=false] ++ [scan-forever=true] [scan-size=]NN ++ ++ nbdkit --filter=scan --filter=cache PLUGIN ++ ++ nbdkit --filter=scan --filter=cow PLUGIN cow-on-cache=true ++ ++=head1 DESCRIPTION ++ ++C is a filter that scans the disk prefetching ++data. It is sometimes useful if you expect that the client will read ++the disk sequentially. ++ ++The basic operation of the filter is that when a client connects, the ++filter will start issuing C<.cache> (prefetch) requests to the plugin ++across the whole disk. Plugins which support this command will ++prefetch the data, making subsequent reads faster. For plugins which ++do not support this command, you can inject L ++below (after) this filter, giving approximately the same effect. ++L can be used instead of nbdkit-cache-filter, if ++you add the C option. ++ ++Various C parameters can be used to tune scanning, although ++the defaults should be suitable in most cases. ++ ++A similar filter is L. ++ ++=head2 Limitations ++ ++In a number of significant cases this filter will do nothing. The ++filter will print a warning message if this happens. ++ ++=over 4 ++ ++=item Thread model must be parallel * ++ ++For example L only supports ++C, and so this filter cannot perform prefetches in ++parallel with the read requests. ++ ++=item Only scans while clients are connected * ++ ++The current filter only scans while there is at least one client ++connected. ++ ++=item Only scans the default export * ++ ++The current filter only scans the default export and ignores all ++clients connecting to the non-default export name. ++ ++* We may be able to lift these restrictions in future. ++ ++=item Underlying filters or plugin must support C<.cache> (prefetch) ++ ++Very many plugins do not have the concept of prefetching and/or ++do not implement the C<.cache> callback, and so there is no ++way for this filter to issue prefetches. ++ ++You can usually get around this by adding I<--filter=cache> after this ++filter as explained above. ++ ++=item Prefetching the whole disk may load it all into cache ++ ++In particular if you use this filter together with ++L or L, they will cache ++the whole content of the plugin into a temporary file. This may be ++many gigabytes of data, consuming all space in F. Of course ++this is the whole point of using this filter, but you should be aware ++of it. ++ ++If using the cache filter, the total size of the cache can be limited ++(see L). ++ ++=back ++ ++=head1 PARAMETERS ++ ++=over 4 ++ ++=item B ++ ++By default the filter tries to stay ahead of incoming read requests. ++That is to say, it starts prefetching at the beginning of the disk and ++continues incrementally, but if the client issues a read beyond the ++current prefetch point then the filter skips forward and begins ++prefetching after the read. ++ ++However if you set this parameter to false, then this behaviour is ++disabled. The filter simply prefetches sequentially regardless of ++client requests. ++ ++=item B ++ ++By default, if all clients disconnect and then another client ++connects, prefetching resumes at the same place in the disk. (Like ++stopping and starting a clock.) ++ ++If you set this parameter to false, then the filter starts prefetching ++from the beginning of the disk again. ++ ++=item B ++ ++By default the filter scans over the disk once and then stops. ++ ++If you set this parameter to true, then after the disk has been ++prefetched completely, the filter goes back to the beginning and ++starts over, repeating this for as long as nbdkit is running and there ++are clients connected. ++ ++=item BNN ++ ++This parameter controls the prefetch block size. The default is ++C<2M>. This must be a power of 2 and most plugins will have their own ++limits on the amount of data they can prefetch in a single request. ++ ++=back ++ ++=head1 FILES ++ ++=over 4 ++ ++=item F<$filterdir/nbdkit-scan-filter.so> ++ ++The filter. ++ ++Use C to find the location of C<$filterdir>. ++ ++=back ++ ++=head1 VERSION ++ ++C first appeared in nbdkit 1.32. ++ ++=head1 SEE ALSO ++ ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L, ++L. ++ ++=head1 AUTHORS ++ ++Richard W.M. Jones ++ ++=head1 COPYRIGHT ++ ++Copyright (C) 2019-2022 Red Hat Inc. +diff --git a/filters/scan/scan.c b/filters/scan/scan.c +new file mode 100644 +index 00000000..ac5b18d2 +--- /dev/null ++++ b/filters/scan/scan.c +@@ -0,0 +1,280 @@ ++/* nbdkit ++ * Copyright (C) 2019-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++ ++#include "scan.h" ++ ++#include "cleanup.h" ++#include "ispowerof2.h" ++#include "vector.h" ++ ++static bool scan_ahead = true; ++bool scan_clock = true; ++bool scan_forever = false; ++unsigned scan_size = 2*1024*1024; ++ ++static int thread_model = -1; /* Thread model of the underlying plugin. */ ++ ++/* Per-connection data. */ ++struct scan_handle { ++ bool is_default_export; /* If exportname == "". */ ++ bool running; /* True if background thread is running. */ ++ pthread_t thread; /* The background thread, one per connection. */ ++ struct bgthread_ctrl ctrl; ++}; ++ ++static int ++scan_config (nbdkit_next_config *next, nbdkit_backend *nxdata, ++ const char *key, const char *value) ++{ ++ int r; ++ ++ if (strcmp (key, "scan-ahead") == 0) { ++ r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ scan_ahead = r; ++ return 0; ++ } ++ else if (strcmp (key, "scan-clock") == 0) { ++ r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ scan_clock = r; ++ return 0; ++ } ++ else if (strcmp (key, "scan-forever") == 0) { ++ r = nbdkit_parse_bool (value); ++ if (r == -1) ++ return -1; ++ scan_forever = r; ++ return 0; ++ } ++ else if (strcmp (key, "scan-size") == 0) { ++ scan_size = nbdkit_parse_size (value); ++ if (scan_size == -1) ++ return -1; ++ return 0; ++ } ++ ++ return next (nxdata, key, value); ++} ++ ++static int ++scan_config_complete (nbdkit_next_config_complete *next, nbdkit_backend *nxdata) ++{ ++ if (scan_size < 512 || scan_size > 32*1024*1024 || ++ !is_power_of_2 (scan_size)) { ++ nbdkit_error ("scan-size parameter should be [512..32M] " ++ "and a power of two"); ++ return -1; ++ } ++ ++ return next (nxdata); ++} ++ ++#define scan_config_help \ ++ "scan-ahead=false Skip ahead when client reads faster.\n" \ ++ "scan-clock=false Always start prefetching from beginning.\n" \ ++ "scan-forever=true Scan in a loop while clients connected.\n" \ ++ "scan-size=NN Set scan block size." ++ ++/* We need to hook into .get_ready() so we can read the final thread ++ * model (of the whole server). ++ */ ++static int ++scan_get_ready (int final_thread_model) ++{ ++ thread_model = final_thread_model; ++ return 0; ++} ++ ++static int ++send_command_to_background_thread (struct bgthread_ctrl *ctrl, ++ const struct command cmd) ++{ ++ ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&ctrl->lock); ++ if (command_queue_append (&ctrl->cmds, cmd) == -1) ++ return -1; ++ /* Signal the thread if it could be sleeping on an empty queue. */ ++ if (ctrl->cmds.len == 1) ++ pthread_cond_signal (&ctrl->cond); ++ return 0; ++} ++ ++static void * ++scan_open (nbdkit_next_open *next, nbdkit_context *nxdata, ++ int readonly, const char *exportname, int is_tls) ++{ ++ struct scan_handle *h; ++ ++ if (next (nxdata, readonly, exportname) == -1) ++ return NULL; ++ ++ h = calloc (1, sizeof *h); ++ if (h == NULL) { ++ nbdkit_error ("malloc: %m"); ++ return NULL; ++ } ++ ++ h->is_default_export = strcmp (exportname, "") == 0; ++ return h; ++} ++ ++/* In prepare we check if it's possible to support the scan filter on ++ * this connection (or print a warning), and start the background ++ * thread. ++ */ ++static int ++scan_prepare (nbdkit_next *next, void *handle, int readonly) ++{ ++ struct scan_handle *h = handle; ++ int r, err; ++ ++ if (!h->is_default_export) { ++ nbdkit_error ("scan: warning: not the default export, not scanning"); ++ return 0; ++ } ++ ++ if (thread_model != NBDKIT_THREAD_MODEL_PARALLEL) { ++ nbdkit_error ("scan: warning: underlying plugin does not support " ++ "the PARALLEL thread model, not scanning"); ++ return 0; ++ } ++ ++ /* Call next->can_cache to read the underlying 'can_cache'. */ ++ r = next->can_cache (next); ++ if (r == -1) ++ return -1; ++ if (r != NBDKIT_CACHE_NATIVE) { ++ nbdkit_error ("scan: warning: underlying plugin does not support " ++ "NBD_CMD_CACHE, not scanning; try adding --filter=cache " ++ "after this filter"); ++ return 0; ++ } ++ ++ /* Save the connection in the handle, for the background thread to use. */ ++ h->ctrl.next = next; ++ ++ /* Create the background thread. */ ++ h->ctrl.cmds = (command_queue) empty_vector; ++ pthread_mutex_init (&h->ctrl.lock, NULL); ++ pthread_cond_init (&h->ctrl.cond, NULL); ++ ++ err = pthread_create (&h->thread, NULL, scan_thread, &h->ctrl); ++ if (err != 0) { ++ errno = err; ++ nbdkit_error ("pthread_create: %m"); ++ pthread_cond_destroy (&h->ctrl.cond); ++ pthread_mutex_destroy (&h->ctrl.lock); ++ return -1; ++ } ++ ++ h->running = true; ++ ++ return 0; ++} ++ ++/* Finalize cleans up the thread if it is running. */ ++static int ++scan_finalize (nbdkit_next *next, void *handle) ++{ ++ struct scan_handle *h = handle; ++ const struct command quit_cmd = { .type = CMD_QUIT }; ++ ++ if (!h->running) ++ return 0; ++ ++ send_command_to_background_thread (&h->ctrl, quit_cmd); ++ pthread_join (h->thread, NULL); ++ pthread_cond_destroy (&h->ctrl.cond); ++ pthread_mutex_destroy (&h->ctrl.lock); ++ command_queue_reset (&h->ctrl.cmds); ++ h->running = false; ++ ++ return 0; ++} ++ ++static void ++scan_close (void *handle) ++{ ++ struct scan_handle *h = handle; ++ ++ free (h); ++} ++ ++/* Read data. */ ++static int ++scan_pread (nbdkit_next *next, ++ void *handle, void *buf, uint32_t count, uint64_t offset, ++ uint32_t flags, int *err) ++{ ++ struct scan_handle *h = handle; ++ ++ if (scan_ahead && h->running) { ++ const struct command cmd = ++ { .type = CMD_NOTIFY_PREAD, .offset = offset + count }; ++ ++ if (send_command_to_background_thread (&h->ctrl, cmd) == -1) ++ return -1; ++ } ++ ++ /* Issue the normal read. */ ++ return next->pread (next, buf, count, offset, flags, err); ++} ++ ++static struct nbdkit_filter filter = { ++ .name = "scan", ++ .longname = "nbdkit scan filter", ++ .get_ready = scan_get_ready, ++ .config = scan_config, ++ .config_complete = scan_config_complete, ++ .config_help = scan_config_help, ++ .open = scan_open, ++ .prepare = scan_prepare, ++ .finalize = scan_finalize, ++ .close = scan_close, ++ .pread = scan_pread, ++}; ++ ++NBDKIT_REGISTER_FILTER(filter) +diff --git a/filters/scan/scan.h b/filters/scan/scan.h +new file mode 100644 +index 00000000..7ff39310 +--- /dev/null ++++ b/filters/scan/scan.h +@@ -0,0 +1,64 @@ ++/* nbdkit ++ * Copyright (C) 2019-2022 Red Hat Inc. ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions are ++ * met: ++ * ++ * * Redistributions of source code must retain the above copyright ++ * notice, this list of conditions and the following disclaimer. ++ * ++ * * Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * ++ * * Neither the name of Red Hat nor the names of its contributors may be ++ * used to endorse or promote products derived from this software without ++ * specific prior written permission. ++ * ++ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++ * SUCH DAMAGE. ++ */ ++ ++#ifndef NBDKIT_SCAN_H ++#define NBDKIT_SCAN_H ++ ++#include ++#include ++ ++#include ++ ++#include "vector.h" ++ ++extern bool scan_clock; ++extern bool scan_forever; ++extern unsigned scan_size; ++ ++/* List of commands issued to the background thread. */ ++struct command { ++ enum { CMD_QUIT, CMD_NOTIFY_PREAD } type; ++ uint64_t offset; ++}; ++DEFINE_VECTOR_TYPE(command_queue, struct command); ++ ++struct bgthread_ctrl { ++ command_queue cmds; /* Command queue. */ ++ pthread_mutex_t lock; /* Lock for queue. */ ++ pthread_cond_t cond; /* Condition queue size 0 -> 1. */ ++ nbdkit_next *next; /* For sending cache operations. */ ++}; ++ ++/* Start background thread (one per connection). */ ++extern void *scan_thread (void *vp); ++ ++#endif /* NBDKIT_SCAN_H */ +diff --git a/plugins/ssh/nbdkit-ssh-plugin.pod b/plugins/ssh/nbdkit-ssh-plugin.pod +index 2bc2c4a7..214957d6 100644 +--- a/plugins/ssh/nbdkit-ssh-plugin.pod ++++ b/plugins/ssh/nbdkit-ssh-plugin.pod +@@ -349,6 +349,7 @@ L, + L, + L, + L, ++L, + L, + L, + L, +diff --git a/plugins/torrent/nbdkit-torrent-plugin.pod b/plugins/torrent/nbdkit-torrent-plugin.pod +index 196ce4e9..f09ac3d2 100644 +--- a/plugins/torrent/nbdkit-torrent-plugin.pod ++++ b/plugins/torrent/nbdkit-torrent-plugin.pod +@@ -175,6 +175,7 @@ L, + L, + L, + L, ++L, + L, + L, + L. +diff --git a/plugins/vddk/nbdkit-vddk-plugin.pod b/plugins/vddk/nbdkit-vddk-plugin.pod +index ea5899dc..3991e86b 100644 +--- a/plugins/vddk/nbdkit-vddk-plugin.pod ++++ b/plugins/vddk/nbdkit-vddk-plugin.pod +@@ -733,6 +733,7 @@ L, + L, + L, + L, ++L, + L, + L, + L, +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 5585b3b7..799aa6c2 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1754,6 +1754,16 @@ test_retry_request_mirror_LDADD = \ + $(LIBNBD_LIBS) \ + $(NULL) + ++# scan filter test. ++TESTS += \ ++ test-scan-copy.sh \ ++ test-scan-info.sh \ ++ $(NULL) ++EXTRA_DIST += \ ++ test-scan-copy.sh \ ++ test-scan-info.sh \ ++ $(NULL) ++ + # swab filter test. + TESTS += \ + test-swab-8.sh \ +diff --git a/tests/test-scan-copy.sh b/tests/test-scan-copy.sh +new file mode 100755 +index 00000000..227ad7b2 +--- /dev/null ++++ b/tests/test-scan-copy.sh +@@ -0,0 +1,42 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++source ./functions.sh ++set -e ++set -x ++ ++requires nbdcopy --version ++requires_plugin sparse-random ++requires_filter scan ++ ++nbdkit -fv -U - sparse-random 1M --filter=scan --run 'nbdcopy "$uri" "$uri"' ++nbdkit -fv -U - sparse-random 1G --filter=scan --run 'nbdcopy "$uri" "$uri"' +diff --git a/tests/test-scan-info.sh b/tests/test-scan-info.sh +new file mode 100755 +index 00000000..6b109ca8 +--- /dev/null ++++ b/tests/test-scan-info.sh +@@ -0,0 +1,46 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++source ./functions.sh ++set -e ++set -x ++ ++requires nbdinfo --version ++requires_filter scan ++ ++# We're just testing that there are no problematic races with the ++# background thread. ++ ++nbdkit -fv -U - memory 1 --filter=scan --run 'nbdinfo $uri' ++nbdkit -fv -U - memory 1M --filter=scan --run 'nbdinfo $uri' ++nbdkit -fv -U - memory 1G --filter=scan --run 'nbdinfo $uri' ++nbdkit -fv -U - memory 1G --filter=scan -e test --run 'nbdinfo $uri' +-- +2.31.1 + diff --git a/0014-scan-Remove-condition-variable.patch b/0014-scan-Remove-condition-variable.patch new file mode 100644 index 0000000..7500254 --- /dev/null +++ b/0014-scan-Remove-condition-variable.patch @@ -0,0 +1,67 @@ +From 91677241184ab1aa77adadd612fa069d084863ec Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 18:54:32 +0100 +Subject: [PATCH] scan: Remove condition variable + +This was copied in from the readahead filter code, but is not actually +needed in this filter because it never has to sleep waiting for a +command. + +Fixes: commit 65c20a09ceacb4431986a2982f2c2e746df63fcb +(cherry picked from commit 43ad586698347997cdfa1bd56bfed0292f89f134) +--- + filters/scan/scan.c | 6 ------ + filters/scan/scan.h | 1 - + 2 files changed, 7 deletions(-) + +diff --git a/filters/scan/scan.c b/filters/scan/scan.c +index ac5b18d2..8a966577 100644 +--- a/filters/scan/scan.c ++++ b/filters/scan/scan.c +@@ -136,9 +136,6 @@ send_command_to_background_thread (struct bgthread_ctrl *ctrl, + ACQUIRE_LOCK_FOR_CURRENT_SCOPE (&ctrl->lock); + if (command_queue_append (&ctrl->cmds, cmd) == -1) + return -1; +- /* Signal the thread if it could be sleeping on an empty queue. */ +- if (ctrl->cmds.len == 1) +- pthread_cond_signal (&ctrl->cond); + return 0; + } + +@@ -199,13 +196,11 @@ scan_prepare (nbdkit_next *next, void *handle, int readonly) + /* Create the background thread. */ + h->ctrl.cmds = (command_queue) empty_vector; + pthread_mutex_init (&h->ctrl.lock, NULL); +- pthread_cond_init (&h->ctrl.cond, NULL); + + err = pthread_create (&h->thread, NULL, scan_thread, &h->ctrl); + if (err != 0) { + errno = err; + nbdkit_error ("pthread_create: %m"); +- pthread_cond_destroy (&h->ctrl.cond); + pthread_mutex_destroy (&h->ctrl.lock); + return -1; + } +@@ -227,7 +222,6 @@ scan_finalize (nbdkit_next *next, void *handle) + + send_command_to_background_thread (&h->ctrl, quit_cmd); + pthread_join (h->thread, NULL); +- pthread_cond_destroy (&h->ctrl.cond); + pthread_mutex_destroy (&h->ctrl.lock); + command_queue_reset (&h->ctrl.cmds); + h->running = false; +diff --git a/filters/scan/scan.h b/filters/scan/scan.h +index 7ff39310..98c0228b 100644 +--- a/filters/scan/scan.h ++++ b/filters/scan/scan.h +@@ -54,7 +54,6 @@ DEFINE_VECTOR_TYPE(command_queue, struct command); + struct bgthread_ctrl { + command_queue cmds; /* Command queue. */ + pthread_mutex_t lock; /* Lock for queue. */ +- pthread_cond_t cond; /* Condition queue size 0 -> 1. */ + nbdkit_next *next; /* For sending cache operations. */ + }; + +-- +2.31.1 + diff --git a/0015-scan-Small-typographical-fix-in-manual.patch b/0015-scan-Small-typographical-fix-in-manual.patch new file mode 100644 index 0000000..c2c6ae0 --- /dev/null +++ b/0015-scan-Small-typographical-fix-in-manual.patch @@ -0,0 +1,57 @@ +From c191f45530d4dd7f978803c0bfa402ca0fc950df Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 19:02:48 +0100 +Subject: [PATCH] scan: Small typographical fix in manual + +Fixes: commit 65c20a09ceacb4431986a2982f2c2e746df63fcb +(cherry picked from commit 67d4e3437d2e28fa3ce1c4b3818d2b1e7939c5ec) +--- + filters/scan/nbdkit-scan-filter.pod | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/filters/scan/nbdkit-scan-filter.pod b/filters/scan/nbdkit-scan-filter.pod +index 4a8d0ef9..2fe9bb80 100644 +--- a/filters/scan/nbdkit-scan-filter.pod ++++ b/filters/scan/nbdkit-scan-filter.pod +@@ -26,8 +26,8 @@ below (after) this filter, giving approximately the same effect. + L can be used instead of nbdkit-cache-filter, if + you add the C option. + +-Various C parameters can be used to tune scanning, although +-the defaults should be suitable in most cases. ++Various parameters can be used to tune scanning, although the defaults ++should be suitable in most cases. + + A similar filter is L. + +@@ -38,23 +38,23 @@ filter will print a warning message if this happens. + + =over 4 + +-=item Thread model must be parallel * ++=item Thread model must be parallel* + + For example L only supports + C, and so this filter cannot perform prefetches in + parallel with the read requests. + +-=item Only scans while clients are connected * ++=item Only scans while clients are connected* + + The current filter only scans while there is at least one client + connected. + +-=item Only scans the default export * ++=item Only scans the default export* + + The current filter only scans the default export and ignores all + clients connecting to the non-default export name. + +-* We may be able to lift these restrictions in future. ++*We may be able to lift these restrictions in future. + + =item Underlying filters or plugin must support C<.cache> (prefetch) + +-- +2.31.1 + diff --git a/0016-ssh-Don-t-reference-readahead-or-scan-filters-from-t.patch b/0016-ssh-Don-t-reference-readahead-or-scan-filters-from-t.patch new file mode 100644 index 0000000..d6e387b --- /dev/null +++ b/0016-ssh-Don-t-reference-readahead-or-scan-filters-from-t.patch @@ -0,0 +1,34 @@ +From 651045d703804d7dafab04a0387ca92573f52467 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 14 May 2022 20:57:38 +0100 +Subject: [PATCH] ssh: Don't reference readahead or scan filters from this + plugin + +These filters do not support plugins which do not use the parallel +thread model. + +Fixes: commit 2ff548d66ad3eae87868402ec5b3319edd12090f +Fixes: commit 65c20a09ceacb4431986a2982f2c2e746df63fcb +See-also: commit 92fbb76d11b9f17c527debd803aa2505f3642783 +(cherry picked from commit 7eb356719376c4d0b2379cea5d39c81602d2d304) +--- + plugins/ssh/nbdkit-ssh-plugin.pod | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/plugins/ssh/nbdkit-ssh-plugin.pod b/plugins/ssh/nbdkit-ssh-plugin.pod +index 214957d6..bb922d37 100644 +--- a/plugins/ssh/nbdkit-ssh-plugin.pod ++++ b/plugins/ssh/nbdkit-ssh-plugin.pod +@@ -347,9 +347,7 @@ C first appeared in nbdkit 1.12. + L, + L, + L, +-L, + L, +-L, + L, + L, + L, +-- +2.31.1 + diff --git a/0017-scan-Fix-bound-so-we-don-t-try-to-prefetch-beyond-en.patch b/0017-scan-Fix-bound-so-we-don-t-try-to-prefetch-beyond-en.patch new file mode 100644 index 0000000..f78049c --- /dev/null +++ b/0017-scan-Fix-bound-so-we-don-t-try-to-prefetch-beyond-en.patch @@ -0,0 +1,56 @@ +From f58d2a04338edc647e2334ff58b49508424e3f3b Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 17 May 2022 13:20:17 +0100 +Subject: [PATCH] scan: Fix bound so we don't try to prefetch beyond end of + disk + +An off-by-one error in the bound could cause the filter to try to +prefetch beyond the end of the underlying plugin. This would cause +nbdkit to crash with this assertion failure: + +nbdkit: backend.c:782: backend_cache: Assertion `backend_valid_range (c, offset, count)' failed. + +The sequence of events was: + + - scan filter background thread started + + - client reads to the end of the disk + + - background thread skips ahead to end of disk (offset == size) + + - background thread tries to prefetch from this point + +In the final step the calculations caused to the background thread to +prefetch a scan-size block beyond the end of the plugin. + +Fixes: commit 65c20a09ceacb4431986a2982f2c2e746df63fcb +(cherry picked from commit 953643429b8c57b4dd20a6c0e5b83704ae9a0e88) +--- + filters/scan/bgthread.c | 10 +++++----- + 1 file changed, 5 insertions(+), 5 deletions(-) + +diff --git a/filters/scan/bgthread.c b/filters/scan/bgthread.c +index 384e79b6..5fa5f27f 100644 +--- a/filters/scan/bgthread.c ++++ b/filters/scan/bgthread.c +@@ -113,12 +113,12 @@ scan_thread (void *vp) + } + + adjust_clock (offset); +- if (offset > size) +- continue; + +- /* Issue the next prefetch. */ +- n = MIN (scan_size, size - offset); +- ctrl->next->cache (ctrl->next, n, offset, 0, NULL); ++ if (offset < size) { ++ /* Issue the next prefetch. */ ++ n = MIN (scan_size, size - offset); ++ ctrl->next->cache (ctrl->next, n, offset, 0, NULL); ++ } + } + + if (scan_forever) { +-- +2.31.1 + diff --git a/0018-tests-Add-a-regression-test-for-LUKS-zeroing-crash.patch b/0018-tests-Add-a-regression-test-for-LUKS-zeroing-crash.patch new file mode 100644 index 0000000..123c091 --- /dev/null +++ b/0018-tests-Add-a-regression-test-for-LUKS-zeroing-crash.patch @@ -0,0 +1,110 @@ +From d1d2f43223bcda062d10c8e68776590956892f71 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Fri, 10 Jun 2022 22:11:44 +0100 +Subject: [PATCH] tests: Add a regression test for LUKS zeroing crash + +https://listman.redhat.com/archives/libguestfs/2022-June/029188.html +(cherry picked from commit 7ab2ef96803bfc385f786be82ebfdd4cc977d504) +--- + tests/Makefile.am | 2 ++ + tests/test-luks-copy-zero.sh | 70 ++++++++++++++++++++++++++++++++++++ + 2 files changed, 72 insertions(+) + create mode 100755 tests/test-luks-copy-zero.sh + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 799aa6c2..0f4b0746 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1601,11 +1601,13 @@ if HAVE_GNUTLS_PBKDF2 + TESTS += \ + test-luks-info.sh \ + test-luks-copy.sh \ ++ test-luks-copy-zero.sh \ + $(NULL) + endif + EXTRA_DIST += \ + test-luks-info.sh \ + test-luks-copy.sh \ ++ test-luks-copy-zero.sh \ + $(NULL) + + # multi-conn filter test. +diff --git a/tests/test-luks-copy-zero.sh b/tests/test-luks-copy-zero.sh +new file mode 100755 +index 00000000..6ff560e3 +--- /dev/null ++++ b/tests/test-luks-copy-zero.sh +@@ -0,0 +1,70 @@ ++#!/usr/bin/env bash ++# nbdkit ++# Copyright (C) 2018-2022 Red Hat Inc. ++# ++# Redistribution and use in source and binary forms, with or without ++# modification, are permitted provided that the following conditions are ++# met: ++# ++# * Redistributions of source code must retain the above copyright ++# notice, this list of conditions and the following disclaimer. ++# ++# * Redistributions in binary form must reproduce the above copyright ++# notice, this list of conditions and the following disclaimer in the ++# documentation and/or other materials provided with the distribution. ++# ++# * Neither the name of Red Hat nor the names of its contributors may be ++# used to endorse or promote products derived from this software without ++# specific prior written permission. ++# ++# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND ++# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, ++# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A ++# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR ++# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ++# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ++# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF ++# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ++# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, ++# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT ++# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++# SUCH DAMAGE. ++ ++# Regression test for: ++# https://listman.redhat.com/archives/libguestfs/2022-June/029188.html ++ ++source ./functions.sh ++set -e ++set -x ++ ++requires qemu-img --version ++requires nbdcopy --version ++requires truncate --version ++requires file --version ++requires_filter luks ++ ++encrypt_disk=luks-copy-zero1.img ++zero_disk=luks-copy-zero2.img ++cleanup_fn rm -f $encrypt_disk $zero_disk ++rm -f $encrypt_disk $zero_disk ++ ++# Create an empty encrypted disk container. ++qemu-img create -f luks \ ++ --object secret,data=123456,id=sec0 \ ++ -o key-secret=sec0 \ ++ $encrypt_disk 100M ++ ++# Create an all zeroes disk of the same size. ++truncate -s 100M $zero_disk ++ ++# Using nbdkit-luks-filter, write the zero disk into the encrypted ++# disk. nbdcopy will do this using NBD_CMD_ZERO operations. ++nbdkit -U - -fv \ ++ file $encrypt_disk --filter=luks passphrase=123456 \ ++ --run "nbdcopy -C 1 $zero_disk \$nbd" ++ ++# Check that the encrypted disk is still a LUKS disk. If zeroing is ++# wrong in the filter it's possible that it writes through to the ++# underlying disk, erasing the container. ++file $encrypt_disk ++file $encrypt_disk | grep "LUKS encrypted file" +-- +2.31.1 + diff --git a/0019-rate-Allow-burstiness-to-be-controlled.patch b/0019-rate-Allow-burstiness-to-be-controlled.patch new file mode 100644 index 0000000..3453169 --- /dev/null +++ b/0019-rate-Allow-burstiness-to-be-controlled.patch @@ -0,0 +1,121 @@ +From c1a7c87fb9710fb29d699d1f39d0da19caf98da0 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Sat, 11 Jun 2022 12:34:02 +0100 +Subject: [PATCH] rate: Allow burstiness to be controlled + +Previously it was fixed at 2.0 seconds. Allowing it to be adjusted +upwards could help with large, lumpy requests. + +(cherry picked from commit f79e951c20510381d5cd83c203c670874a4978f4) +--- + filters/rate/nbdkit-rate-filter.pod | 12 ++++++++++-- + filters/rate/rate.c | 20 +++++++++++++------- + tests/test-rate.sh | 2 +- + 3 files changed, 24 insertions(+), 10 deletions(-) + +diff --git a/filters/rate/nbdkit-rate-filter.pod b/filters/rate/nbdkit-rate-filter.pod +index 8956e641..09ce7dbc 100644 +--- a/filters/rate/nbdkit-rate-filter.pod ++++ b/filters/rate/nbdkit-rate-filter.pod +@@ -9,6 +9,7 @@ nbdkit-rate-filter - limit bandwidth by connection or server + [connection-rate=BITSPERSEC] + [rate-file=FILENAME] + [connection-rate-file=FILENAME] ++ [burstiness=SECS] + + =head1 DESCRIPTION + +@@ -63,6 +64,13 @@ Limit total bandwidth across all connections to C. + Adjust the per-connection or total bandwidth dynamically by writing + C into C. See L below. + ++=item BSECS ++ ++Control the bucket capacity, expressed as a length of time in ++"rate-equivalent seconds" that the client is allowed to burst for ++after a period of inactivity. The default is 2.0 seconds. It's not ++recommended to set this smaller than the default. ++ + =back + + C can be specified as a simple number, or you can use a +@@ -105,8 +113,8 @@ If the size of requests made by your client is much larger than the + rate limit then you can see long, lumpy sleeps in this filter. In the + future we may modify the filter to break up large requests + automatically in order to limit the length of sleeps. Placing the +-L in front of this filter may help in the +-meantime. ++L in front of this filter, or adjusting ++C upwards may help. + + =head1 FILES + +diff --git a/filters/rate/rate.c b/filters/rate/rate.c +index 1a70d212..26082f8c 100644 +--- a/filters/rate/rate.c ++++ b/filters/rate/rate.c +@@ -68,10 +68,9 @@ static char *rate_file = NULL; + + /* Bucket capacity controls the burst rate. It is expressed as the + * length of time in "rate-equivalent seconds" that the client can +- * burst for after a period of inactivity. This could be adjustable +- * in future. ++ * burst for after a period of inactivity. + */ +-#define BUCKET_CAPACITY 2.0 ++static double bucket_capacity = 2.0 /* seconds */; + + /* Global read and write buckets. */ + static struct bucket read_bucket; +@@ -142,6 +141,13 @@ rate_config (nbdkit_next_config *next, nbdkit_backend *nxdata, + return -1; + return 0; + } ++ else if (strcmp (key, "burstiness") == 0) { ++ if (sscanf (value, "%lg", &bucket_capacity) != 1) { ++ nbdkit_error ("burstiness must be a floating point number (seconds)"); ++ return -1; ++ } ++ return 0; ++ } + else + return next (nxdata, key, value); + } +@@ -150,8 +156,8 @@ static int + rate_get_ready (int thread_model) + { + /* Initialize the global buckets. */ +- bucket_init (&read_bucket, rate, BUCKET_CAPACITY); +- bucket_init (&write_bucket, rate, BUCKET_CAPACITY); ++ bucket_init (&read_bucket, rate, bucket_capacity); ++ bucket_init (&write_bucket, rate, bucket_capacity); + + return 0; + } +@@ -178,8 +184,8 @@ rate_open (nbdkit_next_open *next, nbdkit_context *nxdata, + return NULL; + } + +- bucket_init (&h->read_bucket, connection_rate, BUCKET_CAPACITY); +- bucket_init (&h->write_bucket, connection_rate, BUCKET_CAPACITY); ++ bucket_init (&h->read_bucket, connection_rate, bucket_capacity); ++ bucket_init (&h->write_bucket, connection_rate, bucket_capacity); + pthread_mutex_init (&h->read_bucket_lock, NULL); + pthread_mutex_init (&h->write_bucket_lock, NULL); + +diff --git a/tests/test-rate.sh b/tests/test-rate.sh +index 7305c928..ff781c21 100755 +--- a/tests/test-rate.sh ++++ b/tests/test-rate.sh +@@ -56,7 +56,7 @@ nbdkit -U - \ + --filter=blocksize --filter=rate \ + pattern 25M \ + maxdata=65536 \ +- rate=10M \ ++ rate=10M burstiness=2.0 \ + --run 'nbdcopy "$uri" rate.img' + end_t=$SECONDS + +-- +2.31.1 + diff --git a/0020-luks-Check-return-values-from-malloc-more-carefully.patch b/0020-luks-Check-return-values-from-malloc-more-carefully.patch new file mode 100644 index 0000000..f4a8d86 --- /dev/null +++ b/0020-luks-Check-return-values-from-malloc-more-carefully.patch @@ -0,0 +1,104 @@ +From 4e8599886ba4802fef1683811a725e7c4bc4fe72 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 12 Jul 2022 18:00:38 +0100 +Subject: [PATCH] luks: Check return values from malloc more carefully + +Found by Coverity: + + Error: GCC_ANALYZER_WARNING (CWE-688): [#def53] + nbdkit-1.30.7/filters/luks/luks-encryption.c: scope_hint: In function 'calculate_iv' + nbdkit-1.30.7/filters/luks/luks-encryption.c:175:5: warning[-Wanalyzer-possible-null-argument]: use of possibly-NULL 'iv' where non-null expected + nbdkit-1.30.7/filters/luks/luks-encryption.c:39: included_from: Included from here. + /usr/include/string.h:43:14: note: argument 1 of 'memcpy' must be non-null + # 173| sector32 = (uint32_t) sector; /* truncate to only lower bits */ + # 174| sector32 = htole32 (sector32); + # 175|-> memcpy (iv, §or32, prefixlen); + # 176| memset (iv + prefixlen, 0, ivlen - prefixlen); + # 177| break; + + Error: GCC_ANALYZER_WARNING (CWE-688): [#def54] + nbdkit-1.30.7/filters/luks/luks-encryption.c:184:5: warning[-Wanalyzer-possible-null-argument]: use of possibly-NULL 'iv' where non-null expected + nbdkit-1.30.7/filters/luks/luks-encryption.c:39: included_from: Included from here. + /usr/include/string.h:43:14: note: argument 1 of 'memcpy' must be non-null + # 182| prefixlen = ivlen; + # 183| sector = htole64 (sector); + # 184|-> memcpy (iv, §or, prefixlen); + # 185| memset (iv + prefixlen, 0, ivlen - prefixlen); + # 186| break; + + Error: NULL_RETURNS (CWE-476): [#def55] + nbdkit-1.30.7/filters/luks/luks-encryption.c:498: returned_null: "malloc" returns "NULL" (checked 86 out of 94 times). + nbdkit-1.30.7/filters/luks/luks-encryption.c:498: var_assigned: Assigning: "temp" = "NULL" return value from "malloc". + nbdkit-1.30.7/filters/luks/luks-encryption.c:523: dereference: Dereferencing a pointer that might be "NULL" "temp" when calling "memcpy". [Note: The source code implementation of the function has been overridden by a builtin model.] + # 521| gnutls_hash_deinit (hash, temp); + # 522| + # 523|-> memcpy (&block[i*digest_bytes], temp, blen); + # 524| } + # 525| + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 00c8bbd9e321681843140f697985505de7177f34) +--- + filters/luks/luks-encryption.c | 28 +++++++++++++++++++++++----- + 1 file changed, 23 insertions(+), 5 deletions(-) + +diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c +index 8ee0eb35..19aaf06a 100644 +--- a/filters/luks/luks-encryption.c ++++ b/filters/luks/luks-encryption.c +@@ -495,9 +495,15 @@ af_hash (gnutls_digest_algorithm_t hash_alg, uint8_t *block, size_t len) + size_t digest_bytes = gnutls_hash_get_len (hash_alg); + size_t nr_blocks, last_block_len; + size_t i; +- CLEANUP_FREE uint8_t *temp = malloc (digest_bytes); + int r; + gnutls_hash_hd_t hash; ++ CLEANUP_FREE uint8_t *temp; ++ ++ temp = malloc (digest_bytes); ++ if (!temp) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } + + nr_blocks = len / digest_bytes; + last_block_len = len % digest_bytes; +@@ -874,9 +880,15 @@ int + do_decrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, + uint64_t sector, uint8_t *buf, size_t nr_sectors) + { +- const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); +- CLEANUP_FREE uint8_t *iv = malloc (ivlen); + int r; ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv; ++ ++ iv = malloc (ivlen); ++ if (!iv) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } + + while (nr_sectors) { + calculate_iv (h->ivgen_alg, iv, ivlen, sector); +@@ -902,9 +914,15 @@ int + do_encrypt (struct luks_data *h, gnutls_cipher_hd_t cipher, + uint64_t sector, uint8_t *buf, size_t nr_sectors) + { +- const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); +- CLEANUP_FREE uint8_t *iv = malloc (ivlen); + int r; ++ const size_t ivlen = cipher_alg_iv_len (h->cipher_alg, h->cipher_mode); ++ CLEANUP_FREE uint8_t *iv; ++ ++ iv = malloc (ivlen); ++ if (!iv) { ++ nbdkit_error ("malloc: %m"); ++ return -1; ++ } + + while (nr_sectors) { + calculate_iv (h->ivgen_alg, iv, ivlen, sector); +-- +2.31.1 + diff --git a/0021-luks-Avoid-potential-overflow-when-computing-key-mat.patch b/0021-luks-Avoid-potential-overflow-when-computing-key-mat.patch new file mode 100644 index 0000000..9c95dce --- /dev/null +++ b/0021-luks-Avoid-potential-overflow-when-computing-key-mat.patch @@ -0,0 +1,57 @@ +From 1d593a76796574845d7e32aaadd9f7d1ed4e7987 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 12 Jul 2022 18:07:25 +0100 +Subject: [PATCH] luks: Avoid potential overflow when computing key material + offset and length + +Found by Coverity: + + Error: OVERFLOW_BEFORE_WIDEN (CWE-190): [#def58] + nbdkit-1.30.7/filters/luks/luks-encryption.c:558: overflow_before_widen: Potentially overflowing expression "h->phdr.master_key_len * h->phdr.keyslot[i].stripes" with type "unsigned int" (32 bits, unsigned) is evaluated using 32-bit arithmetic, and then used in a context that expects an expression of type "uint64_t" (64 bits, unsigned). + nbdkit-1.30.7/filters/luks/luks-encryption.c:558: remediation: To avoid overflow, cast either "h->phdr.master_key_len" or "h->phdr.keyslot[i].stripes" to type "uint64_t". + # 556| uint64_t len, r; + # 557| + # 558|-> len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes; + # 559| r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE); + # 560| r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE); + + Error: OVERFLOW_BEFORE_WIDEN (CWE-190): [#def62] + nbdkit-1.30.7/filters/luks/luks-encryption.c:616: overflow_before_widen: Potentially overflowing expression "ks->key_material_offset * 512U" with type "unsigned int" (32 bits, unsigned) is evaluated using 32-bit arithmetic, and then used in a context that expects an expression of type "uint64_t" (64 bits, unsigned). + nbdkit-1.30.7/filters/luks/luks-encryption.c:616: remediation: To avoid overflow, cast either "ks->key_material_offset" or "512U" to type "uint64_t". + # 614| + # 615| /* Read master key material from plugin. */ + # 616|-> start = ks->key_material_offset * LUKS_SECTOR_SIZE; + # 617| if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) { + # 618| errno = err; + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit 808d88fbc7b58b7c95e05f41fec729cba92ef518) +--- + filters/luks/luks-encryption.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c +index 19aaf06a..06435b27 100644 +--- a/filters/luks/luks-encryption.c ++++ b/filters/luks/luks-encryption.c +@@ -561,7 +561,7 @@ key_material_length_in_sectors (struct luks_data *h, size_t i) + { + uint64_t len, r; + +- len = h->phdr.master_key_len * h->phdr.keyslot[i].stripes; ++ len = (uint64_t) h->phdr.master_key_len * h->phdr.keyslot[i].stripes; + r = DIV_ROUND_UP (len, LUKS_SECTOR_SIZE); + r = ROUND_UP (r, LUKS_ALIGN_KEYSLOTS / LUKS_SECTOR_SIZE); + return r; +@@ -619,7 +619,7 @@ try_passphrase_in_keyslot (nbdkit_next *next, struct luks_data *h, + } + + /* Read master key material from plugin. */ +- start = ks->key_material_offset * LUKS_SECTOR_SIZE; ++ start = (uint64_t) ks->key_material_offset * LUKS_SECTOR_SIZE; + if (next->pread (next, split_key, split_key_len, start, 0, &err) == -1) { + errno = err; + return -1; +-- +2.31.1 + diff --git a/0022-luks-Avoid-memory-leak-on-error-path.patch b/0022-luks-Avoid-memory-leak-on-error-path.patch new file mode 100644 index 0000000..22a2708 --- /dev/null +++ b/0022-luks-Avoid-memory-leak-on-error-path.patch @@ -0,0 +1,36 @@ +From ee25c1be953bf385caf23f96384a9834c1f1c250 Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 12 Jul 2022 18:10:30 +0100 +Subject: [PATCH] luks: Avoid memory leak on error path + +Found by Coverity: + + Error: CPPCHECK_WARNING (CWE-401): [#def65] [important] + nbdkit-1.30.7/filters/luks/luks-encryption.c:707: error[memleak]: Memory leak: h + # 705| if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { + # 706| nbdkit_error ("this disk does not contain a LUKS header"); + # 707|-> return NULL; + # 708| } + # 709| h->phdr.version = be16toh (h->phdr.version); + +Fixes: commit 468919dce6c5eb57503eacac0f67e5dd87c58e6c +(cherry picked from commit a345cff137763f105f07bb8942c1bbefd0959cff) +--- + filters/luks/luks-encryption.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/filters/luks/luks-encryption.c b/filters/luks/luks-encryption.c +index 06435b27..207a4e46 100644 +--- a/filters/luks/luks-encryption.c ++++ b/filters/luks/luks-encryption.c +@@ -710,6 +710,7 @@ load_header (nbdkit_next *next, const char *passphrase) + + if (memcmp (h->phdr.magic, expected_magic, LUKS_MAGIC_LEN) != 0) { + nbdkit_error ("this disk does not contain a LUKS header"); ++ free (h); + return NULL; + } + h->phdr.version = be16toh (h->phdr.version); +-- +2.31.1 + diff --git a/0023-tests-Hoist-some-EXTRA_DIST-out-of-automake-conditio.patch b/0023-tests-Hoist-some-EXTRA_DIST-out-of-automake-conditio.patch new file mode 100644 index 0000000..0bfe9b4 --- /dev/null +++ b/0023-tests-Hoist-some-EXTRA_DIST-out-of-automake-conditio.patch @@ -0,0 +1,48 @@ +From 5ccf1068703d300c8b5579b3a6ef0e409b5a713e Mon Sep 17 00:00:00 2001 +From: "Richard W.M. Jones" +Date: Tue, 19 Jul 2022 11:56:47 +0100 +Subject: [PATCH] tests: Hoist some EXTRA_DIST out of automake conditionals + +We can fail to add some test files (test.tcl, test.lua) to the tarball +if compiling with those languages disabled, which would cause knock-on +failures when the tarball was used with the languages enabled. We +already fixed this for Ruby etc, this commit fixes it for Tcl and Lua. + +(cherry picked from commit 3b6763c82909c95431ff57c2fe9be1b98316b057) +--- + tests/Makefile.am | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/tests/Makefile.am b/tests/Makefile.am +index 0f4b0746..2667be32 100644 +--- a/tests/Makefile.am ++++ b/tests/Makefile.am +@@ -1203,10 +1203,11 @@ EXTRA_DIST += \ + $(NULL) + + # Tcl plugin test. ++EXTRA_DIST += test.tcl ++ + if HAVE_TCL + + LIBGUESTFS_TESTS += test-tcl +-EXTRA_DIST += test.tcl + + test_tcl_SOURCES = test-lang-plugins.c test.h + test_tcl_CFLAGS = \ +@@ -1219,10 +1220,11 @@ test_tcl_LDADD = libtest.la $(LIBGUESTFS_LIBS) + endif HAVE_TCL + + # Lua plugin test. ++EXTRA_DIST += test.lua ++ + if HAVE_LUA + + LIBGUESTFS_TESTS += test-lua +-EXTRA_DIST += test.lua + + test_lua_SOURCES = test-lang-plugins.c test.h + test_lua_CFLAGS = \ +-- +2.31.1 + diff --git a/copy-patches.sh b/copy-patches.sh new file mode 100755 index 0000000..299454a --- /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 nbdkit.spec ]; then + echo "$0: run this from the directory containing 'nbdkit.spec'" + exit 1 +fi + +git_checkout=$HOME/d/nbdkit-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 "nbdkit release on RHEL." + exit 1 +fi + +# Get the base version of nbdkit. +version=`grep '^Version:' nbdkit.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 nbdkit.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/nbdkit-1.24.0.tar.gz.sig b/nbdkit-1.24.0.tar.gz.sig new file mode 100644 index 0000000..14ff4e8 --- /dev/null +++ b/nbdkit-1.24.0.tar.gz.sig @@ -0,0 +1,17 @@ +-----BEGIN PGP SIGNATURE----- + +iQJFBAABCAAvFiEE93dPsa0HSn6Mh2fqkXOPc+G3aKAFAl/3RBgRHHJpY2hAYW5u +ZXhpYS5vcmcACgkQkXOPc+G3aKBIIRAAmgoGrmJ8aYO7z+kKgNFjd/p0QxRTZhS/ +ol59ojG6jIzN2x/C2PFbRmPB6HJTEg4anrDX04WrP6R+lID1RrH9pTFQabv0YDQC +z49oeXAqINYHvAqgFUJCwlymd7BHEYUudLlK3yu7gQKxMM+J/2v0glpxrtLM7KlD +vvSZkVfbvHlCWIbMWLWIaRHeoWZIXNOjsAp3uEWN2YgikDoxbXVKoh07JoQx5tJ5 +2U+a/zo4BQuRspjnhmWc252ZF/8d954/L8J+2mKvbRRf2iAmsqPgS+MNi7WKWO4K +w7/urKn0osuOaArs5xYHJnApmJ9U88CzZpoHQkYhcGgnDOipW9ByJRzT41vVQPW5 +IluQODpZUuawWtRIwV/Eoi+LaV2gINAL48Afr02UFYj4gmYQ5TeayLP7NKRQO0VL +jwL4Z3a0cDyUX4i1OArn2ll8THfiog38HfLb70AG1l3P1BVoVVBYWCYbs4xgC9IK +LWkjPKuGXvkGVfZi0nCGdPTOoB1CqCXUvKHXm52FCHg12uJMrBQEivodBoCTbtl0 +fSjULQcfrovUEb4d/rDAX7EgJbFS+1jDnodaFHsmNToo3CqfkMBdhLkxG3XExwjy +OOR34wZssjTLsLlWH/RPucWD25RDy1vdPBska9QvvO7W0p+aOtFbnttkTh5cqs45 +rHg/sDEiaLA= +=OrsS +-----END PGP SIGNATURE----- diff --git a/nbdkit-find-provides b/nbdkit-find-provides new file mode 100755 index 0000000..7013ccd --- /dev/null +++ b/nbdkit-find-provides @@ -0,0 +1,23 @@ +#!/bin/bash - + +# Generate RPM provides automatically for nbdkit packages and filters. +# Copyright (C) 2009-2022 Red Hat Inc. + +# To test: +# find /usr/lib64/nbdkit/plugins | ./nbdkit-find-provides VER REL +# find /usr/lib64/nbdkit/filters | ./nbdkit-find-provides VER REL + +ver="$1" +rel="$2" + +function process_file +{ + if [[ $1 =~ /plugins/nbdkit-.*-plugin ]] || + [[ $1 =~ /filters/nbdkit-.*-filter ]]; then + echo "Provides:" "$(basename $1 .so)" "=" "$ver-$rel" + fi +} + +while read line; do + process_file "$line" +done diff --git a/nbdkit.attr b/nbdkit.attr new file mode 100644 index 0000000..2757679 --- /dev/null +++ b/nbdkit.attr @@ -0,0 +1,3 @@ +%__nbdkit_provides %{_rpmconfigdir}/nbdkit-find-provides %{version} %{release} +%__nbdkit_path %{_libdir}/nbdkit/(plugins|filters)/nbdkit-.*-(plugin|filter)(\.so)?$ +%__nbdkit_flags exeonly diff --git a/nbdkit.spec b/nbdkit.spec new file mode 100644 index 0000000..ec31876 --- /dev/null +++ b/nbdkit.spec @@ -0,0 +1,1569 @@ +%global _hardened_build 1 + +%ifarch aarch64 %{arm} x86_64 ppc %{power64} +%global have_libguestfs 1 +%endif + +# We can only compile the OCaml plugin on platforms which have native +# OCaml support (not bytecode). +%ifarch %{ocaml_native_compiler} +%global have_ocaml 1 +%endif + +# Architectures where the complete test suite must pass. +# +# On all other architectures, a simpler test suite must pass. This +# omits any tests that run full qemu, since running qemu under TCG is +# often broken on non-x86_64 arches. +%global complete_test_arches x86_64 + +# If the test suite is broken on a particular architecture, document +# it as a bug and add it to this list. +%global broken_test_arches aarch64 + +%if 0%{?rhel} == 7 +# On RHEL 7, nothing in the virt stack is shipped on aarch64 and +# libguestfs was not shipped on POWER (fixed in 7.5). We could in +# theory make all of this work by having lots more conditionals, but +# for now limit this package to x86_64 on RHEL. +ExclusiveArch: x86_64 +%endif + +# 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 %{nil} + +# The source directory. +%global source_directory 1.24-stable + +Name: nbdkit +Version: 1.24.0 +Release: 4%{?dist} +Summary: NBD server + +License: BSD +URL: https://github.com/libguestfs/nbdkit + +%if 0%{?rhel} >= 8 +# On RHEL 8+, we cannot build the package on i686 (no virt stack). +ExcludeArch: i686 +%endif + +Source0: http://libguestfs.org/download/nbdkit/%{source_directory}/%{name}-%{version}.tar.gz +%if 0%{verify_tarball_signature} +Source1: http://libguestfs.org/download/nbdkit/%{source_directory}/%{name}-%{version}.tar.gz.sig +# Keyring used to verify tarball signature. +Source2: libguestfs.keyring +%endif + +# Maintainer script which helps with handling patches. +Source3: copy-patches.sh + +# Patches come from this upstream branch: +# https://github.com/libguestfs/nbdkit/tree/rhel-8.6 + +# Patches. +Patch0001: 0001-cache-cow-Fix-data-corruption-in-zero-and-trim-on-un.patch +Patch0002: 0002-server-CVE-2021-3716-reset-structured-replies-on-sta.patch +Patch0003: 0003-server-reset-meta-context-replies-on-starttls.patch +Patch0004: 0004-cow-Fix-for-qemu-6.1-which-requires-backing-format.patch + +%if 0%{patches_touch_autotools} +BuildRequires: autoconf, automake, libtool +%endif + +%ifnarch %{complete_test_arches} +BuildRequires: autoconf, automake, libtool +%endif +BuildRequires: gcc, gcc-c++ +BuildRequires: /usr/bin/pod2man +BuildRequires: gnutls-devel +BuildRequires: libselinux-devel +%if !0%{?rhel} && 0%{?have_libguestfs} +BuildRequires: libguestfs-devel +%endif +BuildRequires: libvirt-devel +BuildRequires: xz-devel +BuildRequires: zlib-devel +BuildRequires: libzstd-devel +BuildRequires: libcurl-devel +BuildRequires: libnbd-devel >= 1.3.11 +BuildRequires: libssh-devel +BuildRequires: e2fsprogs, e2fsprogs-devel +%if !0%{?rhel} +BuildRequires: genisoimage +BuildRequires: rb_libtorrent-devel +%endif +BuildRequires: bash-completion +BuildRequires: perl-devel +BuildRequires: perl(ExtUtils::Embed) +%if !0%{?rhel} +BuildRequires: python3-devel +%else +BuildRequires: platform-python-devel +%endif +%if !0%{?rhel} +%if 0%{?have_ocaml} +# Requires OCaml 4.02.2 which contains fix for +# http://caml.inria.fr/mantis/view.php?id=6693 +BuildRequires: ocaml >= 4.02.2 +BuildRequires: ocaml-ocamldoc +%endif +BuildRequires: ruby-devel +BuildRequires: tcl-devel +BuildRequires: lua-devel +%endif +%if 0%{verify_tarball_signature} +BuildRequires: gnupg2 +%endif + +# Only for running the test suite: +BuildRequires: %{_bindir}/bc +BuildRequires: %{_bindir}/certtool +BuildRequires: %{_bindir}/cut +BuildRequires: expect +BuildRequires: %{_bindir}/hexdump +BuildRequires: %{_sbindir}/ip +BuildRequires: jq +BuildRequires: %{_bindir}/nbdcopy +BuildRequires: %{_bindir}/nbdinfo +BuildRequires: %{_bindir}/nbdsh +BuildRequires: %{_bindir}/qemu-img +BuildRequires: %{_bindir}/qemu-io +BuildRequires: %{_bindir}/qemu-nbd +BuildRequires: %{_sbindir}/sfdisk +BuildRequires: %{_bindir}/socat +BuildRequires: %{_sbindir}/ss +BuildRequires: %{_bindir}/stat +BuildRequires: %{_bindir}/ssh-keygen + +# nbdkit is a metapackage pulling the server and a useful subset +# of the plugins and filters. +Requires: nbdkit-server%{?_isa} = %{version}-%{release} +Requires: nbdkit-basic-plugins%{?_isa} = %{version}-%{release} +Requires: nbdkit-basic-filters%{?_isa} = %{version}-%{release} + + +%description +NBD is a protocol for accessing block devices (hard disks and +disk-like things) over the network. + +nbdkit is a toolkit for creating NBD servers. + +The key features are: + +* Multithreaded NBD server written in C with good performance. + +* Minimal dependencies for the basic server. + +* Liberal license (BSD) allows nbdkit to be linked to proprietary + libraries or included in proprietary code. + +* Well-documented, simple plugin API with a stable ABI guarantee. + Lets you to export "unconventional" block devices easily. + +* You can write plugins in C or many other languages. + +* Filters can be stacked in front of plugins to transform the output. + +'%{name}' is a meta-package which pulls in the core server and a +useful subset of plugins and filters with minimal dependencies. + +If you want just the server, install '%{name}-server'. + +To develop plugins, install the '%{name}-devel' package and start by +reading the nbdkit(1) and nbdkit-plugin(3) manual pages. + + +%package server +Summary: The %{name} server +License: BSD + + +%description server +This package contains the %{name} server with no plugins or filters. + + +%package basic-plugins +Summary: Basic plugins for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Provides: %{name}-data-plugin = %{version}-%{release} +Provides: %{name}-eval-plugin = %{version}-%{release} +Provides: %{name}-file-plugin = %{version}-%{release} +Provides: %{name}-floppy-plugin = %{version}-%{release} +Provides: %{name}-full-plugin = %{version}-%{release} +Provides: %{name}-info-plugin = %{version}-%{release} +Provides: %{name}-memory-plugin = %{version}-%{release} +Provides: %{name}-null-plugin = %{version}-%{release} +Provides: %{name}-ondemand-plugin = %{version}-%{release} +Provides: %{name}-pattern-plugin = %{version}-%{release} +Provides: %{name}-partitioning-plugin = %{version}-%{release} +Provides: %{name}-random-plugin = %{version}-%{release} +Provides: %{name}-sh-plugin = %{version}-%{release} +Provides: %{name}-sparse-random-plugin = %{version}-%{release} +Provides: %{name}-split-plugin = %{version}-%{release} +Provides: %{name}-streaming-plugin = %{version}-%{release} +Provides: %{name}-zero-plugin = %{version}-%{release} + + +%description basic-plugins +This package contains plugins for %{name} which only depend on simple +C libraries: glibc, gnutls, libzstd. Other plugins for nbdkit with +more complex dependencies are packaged separately. + +nbdkit-data-plugin Serve small amounts of data from the command line. + +nbdkit-eval-plugin Write a shell script plugin on the command line. + +nbdkit-file-plugin The normal file plugin for serving files. + +nbdkit-floppy-plugin Create a virtual floppy disk from a directory. + +nbdkit-full-plugin A virtual disk that returns ENOSPC errors. + +nbdkit-info-plugin Serve client and server information. + +nbdkit-memory-plugin A virtual memory plugin. + +nbdkit-null-plugin A null (bitbucket) plugin. + +nbdkit-ondemand-plugin Create filesystems on demand. + +nbdkit-pattern-plugin Fixed test pattern. + +nbdkit-partitioning-plugin Create virtual disks from partitions. + +nbdkit-random-plugin Random content plugin for testing. + +nbdkit-sh-plugin Write plugins as shell scripts or executables. + +nbdkit-sparse-random-plugin Make sparse random disks. + +nbdkit-split-plugin Concatenate one or more files. + +nbdkit-streaming-plugin A streaming file serving plugin. + +nbdkit-zero-plugin Zero-length plugin for testing. + + +%package example-plugins +Summary: Example plugins for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +%if !0%{?rhel} +# example4 is written in Perl. +Requires: %{name}-perl-plugin +%endif + + +%description example-plugins +This package contains example plugins for %{name}. + + +# The plugins below have non-trivial dependencies are so are +# packaged separately. + +%if !0%{?rhel} +%package cc-plugin +Summary: Write small inline C plugins and scripts for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: gcc +Requires: %{_bindir}/cat + + +%description cc-plugin +This package contains support for writing inline C plugins and scripts +for %{name}. NOTE this is NOT the right package for writing plugins +in C, install %{name}-devel for that. +%endif + + +%if !0%{?rhel} +%package cdi-plugin +Summary: Containerized Data Import plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: jq +Requires: podman + + +%description cdi-plugin +This package contains Containerized Data Import support for %{name}. +%endif + + +%package curl-plugin +Summary: HTTP/FTP (cURL) plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description curl-plugin +This package contains cURL (HTTP/FTP) support for %{name}. + + +%if !0%{?rhel} && 0%{?have_libguestfs} +%package guestfs-plugin +Summary: libguestfs plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description guestfs-plugin +This package is a libguestfs plugin for %{name}. +%endif + + +%if 0%{?rhel} == 8 +%package gzip-plugin +Summary: GZip plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description gzip-plugin +This package is a gzip plugin for %{name}. +%endif + + +%if !0%{?rhel} +%package iso-plugin +Summary: Virtual ISO 9660 plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: genisoimage + + +%description iso-plugin +This package is a virtual ISO 9660 (CD-ROM) plugin for %{name}. +%endif + + +%if !0%{?rhel} +%package libvirt-plugin +Summary: Libvirt plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description libvirt-plugin +This package is a libvirt plugin for %{name}. It lets you access +libvirt guest disks readonly. It is implemented using the libvirt +virDomainBlockPeek API. +%endif + + +%package linuxdisk-plugin +Summary: Virtual Linux disk plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +# for mke2fs +Requires: e2fsprogs + + +%description linuxdisk-plugin +This package is a virtual Linux disk plugin for %{name}. + + +%if !0%{?rhel} +%package lua-plugin +Summary: Lua plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description lua-plugin +This package lets you write Lua plugins for %{name}. +%endif + + +%package nbd-plugin +Summary: NBD proxy / forward plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description nbd-plugin +This package lets you forward NBD connections from %{name} +to another NBD server. + + +%if !0%{?rhel} && 0%{?have_ocaml} +%package ocaml-plugin +Summary: OCaml plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description ocaml-plugin +This package lets you run OCaml plugins for %{name}. + +To compile OCaml plugins you will also need to install +%{name}-ocaml-plugin-devel. + + +%package ocaml-plugin-devel +Summary: OCaml development environment for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: %{name}-ocaml-plugin%{?_isa} = %{version}-%{release} + + +%description ocaml-plugin-devel +This package lets you write OCaml plugins for %{name}. +%endif + + +%if !0%{?rhel} +%package perl-plugin +Summary: Perl plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description perl-plugin +This package lets you write Perl plugins for %{name}. +%endif + + +%package python-plugin +Summary: Python 3 plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description python-plugin +This package lets you write Python 3 plugins for %{name}. + + +%if !0%{?rhel} +%package ruby-plugin +Summary: Ruby plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description ruby-plugin +This package lets you write Ruby plugins for %{name}. +%endif + + +%package ssh-plugin +Summary: SSH plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description ssh-plugin +This package contains SSH support for %{name}. + + +%package tar-plugin +Summary: Tar archive plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: tar + + +%description tar-plugin +This package is a tar archive plugin for %{name}. + + +%if !0%{?rhel} +%package tcl-plugin +Summary: Tcl plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description tcl-plugin +This package lets you write Tcl plugins for %{name}. +%endif + + +%package tmpdisk-plugin +Summary: Remote temporary filesystem disk plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +# For mkfs and mke2fs (defaults). +Requires: util-linux, e2fsprogs +# For other filesystems. +Suggests: xfsprogs +%if !0%{?rhel} +Suggests: ntfsprogs, dosfstools +%endif + + +%description tmpdisk-plugin +This package is a remote temporary filesystem disk plugin for %{name}. + + +%if !0%{?rhel} +%package torrent-plugin +Summary: BitTorrent plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description torrent-plugin +This package is a BitTorrent plugin for %{name}. +%endif + + +%ifarch x86_64 +%package vddk-plugin +Summary: VMware VDDK plugin for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description vddk-plugin +This package is a plugin for %{name} which connects to +VMware VDDK for accessing VMware disks and servers. +%endif + + +%package basic-filters +Summary: Basic filters for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Provides: %{name}-blocksize-filter = %{version}-%{release} +Provides: %{name}-cache-filter = %{version}-%{release} +Provides: %{name}-cacheextents-filter = %{version}-%{release} +Provides: %{name}-checkwrite-filter = %{version}-%{release} +Provides: %{name}-cow-filter = %{version}-%{release} +Provides: %{name}-ddrescue-filter = %{version}-%{release} +Provides: %{name}-delay-filter = %{version}-%{release} +Provides: %{name}-error-filter = %{version}-%{release} +Provides: %{name}-exitlast-filter = %{version}-%{release} +Provides: %{name}-exitwhen-filter = %{version}-%{release} +Provides: %{name}-exportname-filter = %{version}-%{release} +Provides: %{name}-extentlist-filter = %{version}-%{release} +Provides: %{name}-fua-filter = %{version}-%{release} +Provides: %{name}-ip-filter = %{version}-%{release} +Provides: %{name}-limit-filter = %{version}-%{release} +Provides: %{name}-log-filter = %{version}-%{release} +Provides: %{name}-nocache-filter = %{version}-%{release} +Provides: %{name}-noextents-filter = %{version}-%{release} +Provides: %{name}-nofilter-filter = %{version}-%{release} +Provides: %{name}-noparallel-filter = %{version}-%{release} +Provides: %{name}-nozero-filter = %{version}-%{release} +Provides: %{name}-offset-filter = %{version}-%{release} +Provides: %{name}-partition-filter = %{version}-%{release} +Provides: %{name}-pause-filter = %{version}-%{release} +Provides: %{name}-rate-filter = %{version}-%{release} +Provides: %{name}-readahead-filter = %{version}-%{release} +Provides: %{name}-retry-filter = %{version}-%{release} +Provides: %{name}-stats-filter = %{version}-%{release} +Provides: %{name}-swab-filter = %{version}-%{release} +Provides: %{name}-tls-fallback-filter = %{version}-%{release} +Provides: %{name}-truncate-filter = %{version}-%{release} + + +%description basic-filters +This package contains filters for %{name} which only depend on simple +C libraries: glibc, gnutls. Other filters for nbdkit with more +complex dependencies are packaged separately. + +nbdkit-blocksize-filter Adjust block size of requests sent to plugins. + +nbdkit-cache-filter Server-side cache. + +nbdkit-cacheextents-filter Cache extents. + +nbdkit-checkwrite-filter Check writes match contents of plugin. + +nbdkit-cow-filter Copy-on-write overlay for read-only plugins. + +nbdkit-ddrescue-filter Filter for serving from ddrescue dump. + +nbdkit-delay-filter Inject read and write delays. + +nbdkit-error-filter Inject errors. + +nbdkit-exitlast-filter Exit on last client connection. + +nbdkit-exitwhen-filter Exit gracefully when an event occurs. + +nbdkit-exportname-filter Adjust export names between client and plugin. + +nbdkit-extentlist-filter Place extent list over a plugin. + +nbdkit-fua-filter Modify flush behaviour in plugins. + +nbdkit-ip-filter Filter clients by IP address. + +nbdkit-limit-filter Limit nr clients that can connect concurrently. + +nbdkit-log-filter Log all transactions to a file. + +nbdkit-nocache-filter Disable cache requests in the underlying plugin. + +nbdkit-noextents-filter Disable extents in the underlying plugin. + +nbdkit-nofilter-filter Passthrough filter. + +nbdkit-noparallel-filter Serialize requests to the underlying plugin. + +nbdkit-nozero-filter Adjust handling of zero requests by plugins. + +nbdkit-offset-filter Serve an offset and range. + +nbdkit-partition-filter Serve a single partition. + +nbdkit-pause-filter Pause NBD requests. + +nbdkit-rate-filter Limit bandwidth by connection or server. + +nbdkit-readahead-filter Prefetch data when reading sequentially. + +nbdkit-retry-filter Reopen connection on error. + +nbdkit-stats-filter Display statistics about operations. + +nbdkit-swab-filter Filter for swapping byte order. + +nbdkit-tls-fallback-filter TLS protection filter. + +nbdkit-truncate-filter Truncate, expand, round up or round down size. + + +%if !0%{?rhel} +%package ext2-filter +Summary: ext2, ext3 and ext4 filesystem support for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + +# Remove in Fedora 34: +Provides: %{name}-ext2-plugin = %{version}-%{release} +Obsoletes: %{name}-ext2-plugin <= %{version}-%{release} + + +%description ext2-filter +This package contains ext2, ext3 and ext4 filesystem support for +%{name}. +%endif + + +%package gzip-filter +Summary: GZip filter for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description gzip-filter +This package is a gzip filter for %{name}. + + +%package tar-filter +Summary: Tar archive filter for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: tar + + +%description tar-filter +This package is a tar archive filter for %{name}. + + +%package xz-filter +Summary: XZ filter for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} + + +%description xz-filter +This package is the xz filter for %{name}. + + +%package devel +Summary: Development files and documentation for %{name} +License: BSD + +Requires: %{name}-server%{?_isa} = %{version}-%{release} +Requires: pkgconfig + + +%description devel +This package contains development files and documentation +for %{name}. Install this package if you want to develop +plugins for %{name}. + + +%package bash-completion +Summary: Bash tab-completion for %{name} +BuildArch: noarch +Requires: bash-completion >= 2.0 +Requires: %{name}-server = %{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 + +%ifnarch %{complete_test_arches} +# Simplify the test suite so it doesn't require qemu. +sed -i -e 's/^LIBGUESTFS_TESTS/xLIBGUESTFS_TESTS/' tests/Makefile.am +sed -i -e '/^if HAVE_GUESTFISH/,/^endif HAVE_GUESTFISH/d' tests/Makefile.am +autoreconf -i +%endif + + +%build +# Golang bindings are not enabled in the build since they don't +# need to be. Most people would use them by copying the upstream +# package into their vendor/ directory. +# +# %%{__python3} expands to platform-python, so we don't depend on +# the python module (see RHBZ#1659159, RHBZ#1867964). +export PYTHON=%{__python3} +%configure \ + --with-extra='%{name}-%{version}-%{release}' \ + --disable-static \ + --disable-golang \ + --disable-rust \ +%if !0%{?rhel} && 0%{?have_ocaml} + --enable-ocaml \ +%else + --disable-ocaml \ +%endif +%if 0%{?rhel} + --disable-lua \ + --disable-perl \ + --disable-ruby \ + --disable-tcl \ + --without-ext2 \ + --without-iso \ + --without-libvirt \ +%endif +%if !0%{?rhel} && 0%{?have_libguestfs} + --with-libguestfs \ +%else + --without-libguestfs \ +%endif + --with-tls-priority=@NBDKIT,SYSTEM + +# Verify that it picked the correct version of Python +# to avoid RHBZ#1404631 happening again silently. +grep '^PYTHON_VERSION = 3' Makefile + +%make_build + + +%install +%make_install + +# Delete libtool crap. +find $RPM_BUILD_ROOT -name '*.la' -delete + +# If cargo happens to be installed on the machine then the +# rust plugin is built. Delete it if this happens. +rm -f $RPM_BUILD_ROOT%{_mandir}/man3/nbdkit-rust-plugin.3* + +%if 0%{?rhel} != 8 +# Remove the deprecated gzip plugin (use gzip filter instead). +rm $RPM_BUILD_ROOT%{_libdir}/%{name}/plugins/nbdkit-gzip-plugin.so +rm $RPM_BUILD_ROOT%{_mandir}/man1/nbdkit-gzip-plugin.1* +%endif + +%if 0%{?rhel} +# In RHEL, remove some plugins we cannot --disable. +for f in cc cdi torrent; do + rm -f $RPM_BUILD_ROOT%{_libdir}/%{name}/plugins/nbdkit-$f-plugin.so + rm -f $RPM_BUILD_ROOT%{_mandir}/man?/nbdkit-$f-plugin.* +done +rm -f $RPM_BUILD_ROOT%{_libdir}/%{name}/plugins/nbdkit-S3-plugin +rm -f $RPM_BUILD_ROOT%{_mandir}/man1/nbdkit-S3-plugin.1* +%endif + + +%check +%ifnarch %{broken_test_arches} +# Workaround for broken libvirt (RHBZ#1138604). +mkdir -p $HOME/.cache/libvirt + +# tests/test-captive.sh is racy especially on s390x. We need to +# rethink this test upstream. +truncate -s 0 tests/test-captive.sh + +%ifarch s390x +# Temporarily kill tests/test-cache-max-size.sh since it fails +# sometimes on s390x for unclear reasons. +truncate -s 0 tests/test-cache-max-size.sh +%endif + +# Temporarily kill test-nbd-tls.sh and test-nbd-tls-psk.sh +# https://www.redhat.com/archives/libguestfs/2020-March/msg00191.html +truncate -s 0 tests/test-nbd-tls.sh tests/test-nbd-tls-psk.sh + +# Kill tests/test-cc-ocaml.sh. Requires upstream fix (commit bce54e7df25). +truncate -s 0 tests/test-cc-ocaml.sh + +# Make sure we can see the debug messages (RHBZ#1230160). +export LIBGUESTFS_DEBUG=1 +export LIBGUESTFS_TRACE=1 + +%make_build check || { + cat tests/test-suite.log + exit 1 + } +%endif + + +%if 0%{?have_ocaml} +%ldconfig_scriptlets plugin-ocaml +%endif + + +%files +# metapackage so empty + + +%files server +%doc README +%license LICENSE +%{_sbindir}/nbdkit +%dir %{_libdir}/%{name} +%dir %{_libdir}/%{name}/plugins +%dir %{_libdir}/%{name}/filters +%{_mandir}/man1/nbdkit.1* +%{_mandir}/man1/nbdkit-captive.1* +%{_mandir}/man1/nbdkit-client.1* +%{_mandir}/man1/nbdkit-loop.1* +%{_mandir}/man1/nbdkit-probing.1* +%{_mandir}/man1/nbdkit-protocol.1* +%{_mandir}/man1/nbdkit-service.1* +%{_mandir}/man1/nbdkit-security.1* +%{_mandir}/man1/nbdkit-tls.1* + + +%files basic-plugins +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-data-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-eval-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-file-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-floppy-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-full-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-info-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-memory-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-null-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-ondemand-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-partitioning-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-pattern-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-random-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-sh-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-sparse-random-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-split-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-streaming-plugin.so +%{_libdir}/%{name}/plugins/nbdkit-zero-plugin.so +%{_mandir}/man1/nbdkit-data-plugin.1* +%{_mandir}/man1/nbdkit-eval-plugin.1* +%{_mandir}/man1/nbdkit-file-plugin.1* +%{_mandir}/man1/nbdkit-floppy-plugin.1* +%{_mandir}/man1/nbdkit-full-plugin.1* +%{_mandir}/man1/nbdkit-info-plugin.1* +%{_mandir}/man1/nbdkit-memory-plugin.1* +%{_mandir}/man1/nbdkit-null-plugin.1* +%{_mandir}/man1/nbdkit-ondemand-plugin.1* +%{_mandir}/man1/nbdkit-partitioning-plugin.1* +%{_mandir}/man1/nbdkit-pattern-plugin.1* +%{_mandir}/man1/nbdkit-random-plugin.1* +%{_mandir}/man3/nbdkit-sh-plugin.3* +%{_mandir}/man1/nbdkit-sparse-random-plugin.1* +%{_mandir}/man1/nbdkit-split-plugin.1* +%{_mandir}/man1/nbdkit-streaming-plugin.1* +%{_mandir}/man1/nbdkit-zero-plugin.1* + + +%files example-plugins +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-example*-plugin.so +%if !0%{?rhel} +%{_libdir}/%{name}/plugins/nbdkit-example4-plugin +%endif +%{_mandir}/man1/nbdkit-example*-plugin.1* + + +%if !0%{?rhel} +%files cc-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-cc-plugin.so +%{_mandir}/man3/nbdkit-cc-plugin.3* +%endif + + +%if !0%{?rhel} +%files cdi-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-cdi-plugin.so +%{_mandir}/man1/nbdkit-cdi-plugin.1* +%endif + + +%files curl-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-curl-plugin.so +%{_mandir}/man1/nbdkit-curl-plugin.1* + + +%if !0%{?rhel} && 0%{?have_libguestfs} +%files guestfs-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-guestfs-plugin.so +%{_mandir}/man1/nbdkit-guestfs-plugin.1* +%endif + + +%if 0%{?rhel} == 8 +%files gzip-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-gzip-plugin.so +%{_mandir}/man1/nbdkit-gzip-plugin.1* +%endif + + +%if !0%{?rhel} +%files iso-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-iso-plugin.so +%{_mandir}/man1/nbdkit-iso-plugin.1* +%endif + + +%if !0%{?rhel} +%files libvirt-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-libvirt-plugin.so +%{_mandir}/man1/nbdkit-libvirt-plugin.1* +%endif + + +%files linuxdisk-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-linuxdisk-plugin.so +%{_mandir}/man1/nbdkit-linuxdisk-plugin.1* + + +%if !0%{?rhel} +%files lua-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-lua-plugin.so +%{_mandir}/man3/nbdkit-lua-plugin.3* +%endif + + +%files nbd-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-nbd-plugin.so +%{_mandir}/man1/nbdkit-nbd-plugin.1* + + +%if !0%{?rhel} && 0%{?have_ocaml} +%files ocaml-plugin +%doc README +%license LICENSE +%{_libdir}/libnbdkitocaml.so.* + +%files ocaml-plugin-devel +%{_libdir}/libnbdkitocaml.so +%{_libdir}/ocaml/NBDKit.* +%{_mandir}/man3/nbdkit-ocaml-plugin.3* +%{_mandir}/man3/NBDKit.3* +%endif + + +%if !0%{?rhel} +%files perl-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-perl-plugin.so +%{_mandir}/man3/nbdkit-perl-plugin.3* +%endif + + +%files python-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-python-plugin.so +%{_mandir}/man3/nbdkit-python-plugin.3* + + +%if !0%{?rhel} +%files ruby-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-ruby-plugin.so +%{_mandir}/man3/nbdkit-ruby-plugin.3* +%endif + + +%files ssh-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-ssh-plugin.so +%{_mandir}/man1/nbdkit-ssh-plugin.1* + + +%files tar-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-tar-plugin.so +%{_mandir}/man1/nbdkit-tar-plugin.1* + + +%if !0%{?rhel} +%files tcl-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-tcl-plugin.so +%{_mandir}/man3/nbdkit-tcl-plugin.3* +%endif + + +%files tmpdisk-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-tmpdisk-plugin.so +%{_mandir}/man1/nbdkit-tmpdisk-plugin.1* + + +%if !0%{?rhel} +%files torrent-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-torrent-plugin.so +%{_mandir}/man1/nbdkit-torrent-plugin.1* +%endif + + +%ifarch x86_64 +%files vddk-plugin +%doc README +%license LICENSE +%{_libdir}/%{name}/plugins/nbdkit-vddk-plugin.so +%{_mandir}/man1/nbdkit-vddk-plugin.1* +%endif + + +%files basic-filters +%doc README +%license LICENSE +%{_libdir}/%{name}/filters/nbdkit-blocksize-filter.so +%{_libdir}/%{name}/filters/nbdkit-cache-filter.so +%{_libdir}/%{name}/filters/nbdkit-cacheextents-filter.so +%{_libdir}/%{name}/filters/nbdkit-checkwrite-filter.so +%{_libdir}/%{name}/filters/nbdkit-cow-filter.so +%{_libdir}/%{name}/filters/nbdkit-ddrescue-filter.so +%{_libdir}/%{name}/filters/nbdkit-delay-filter.so +%{_libdir}/%{name}/filters/nbdkit-error-filter.so +%{_libdir}/%{name}/filters/nbdkit-exitlast-filter.so +%{_libdir}/%{name}/filters/nbdkit-exitwhen-filter.so +%{_libdir}/%{name}/filters/nbdkit-exportname-filter.so +%{_libdir}/%{name}/filters/nbdkit-extentlist-filter.so +%{_libdir}/%{name}/filters/nbdkit-fua-filter.so +%{_libdir}/%{name}/filters/nbdkit-ip-filter.so +%{_libdir}/%{name}/filters/nbdkit-limit-filter.so +%{_libdir}/%{name}/filters/nbdkit-log-filter.so +%{_libdir}/%{name}/filters/nbdkit-nocache-filter.so +%{_libdir}/%{name}/filters/nbdkit-noextents-filter.so +%{_libdir}/%{name}/filters/nbdkit-nofilter-filter.so +%{_libdir}/%{name}/filters/nbdkit-noparallel-filter.so +%{_libdir}/%{name}/filters/nbdkit-nozero-filter.so +%{_libdir}/%{name}/filters/nbdkit-offset-filter.so +%{_libdir}/%{name}/filters/nbdkit-partition-filter.so +%{_libdir}/%{name}/filters/nbdkit-pause-filter.so +%{_libdir}/%{name}/filters/nbdkit-rate-filter.so +%{_libdir}/%{name}/filters/nbdkit-readahead-filter.so +%{_libdir}/%{name}/filters/nbdkit-retry-filter.so +%{_libdir}/%{name}/filters/nbdkit-stats-filter.so +%{_libdir}/%{name}/filters/nbdkit-swab-filter.so +%{_libdir}/%{name}/filters/nbdkit-tls-fallback-filter.so +%{_libdir}/%{name}/filters/nbdkit-truncate-filter.so +%{_mandir}/man1/nbdkit-blocksize-filter.1* +%{_mandir}/man1/nbdkit-cache-filter.1* +%{_mandir}/man1/nbdkit-cacheextents-filter.1* +%{_mandir}/man1/nbdkit-checkwrite-filter.1* +%{_mandir}/man1/nbdkit-cow-filter.1* +%{_mandir}/man1/nbdkit-ddrescue-filter.1* +%{_mandir}/man1/nbdkit-delay-filter.1* +%{_mandir}/man1/nbdkit-error-filter.1* +%{_mandir}/man1/nbdkit-exitlast-filter.1* +%{_mandir}/man1/nbdkit-exitwhen-filter.1* +%{_mandir}/man1/nbdkit-exportname-filter.1* +%{_mandir}/man1/nbdkit-extentlist-filter.1* +%{_mandir}/man1/nbdkit-fua-filter.1* +%{_mandir}/man1/nbdkit-ip-filter.1* +%{_mandir}/man1/nbdkit-limit-filter.1* +%{_mandir}/man1/nbdkit-log-filter.1* +%{_mandir}/man1/nbdkit-nocache-filter.1* +%{_mandir}/man1/nbdkit-noextents-filter.1* +%{_mandir}/man1/nbdkit-nofilter-filter.1* +%{_mandir}/man1/nbdkit-noparallel-filter.1* +%{_mandir}/man1/nbdkit-nozero-filter.1* +%{_mandir}/man1/nbdkit-offset-filter.1* +%{_mandir}/man1/nbdkit-partition-filter.1* +%{_mandir}/man1/nbdkit-pause-filter.1* +%{_mandir}/man1/nbdkit-rate-filter.1* +%{_mandir}/man1/nbdkit-readahead-filter.1* +%{_mandir}/man1/nbdkit-retry-filter.1* +%{_mandir}/man1/nbdkit-stats-filter.1* +%{_mandir}/man1/nbdkit-swab-filter.1* +%{_mandir}/man1/nbdkit-tls-fallback-filter.1* +%{_mandir}/man1/nbdkit-truncate-filter.1* + + +%if !0%{?rhel} +%files ext2-filter +%doc README +%license LICENSE +%{_libdir}/%{name}/filters/nbdkit-ext2-filter.so +%{_mandir}/man1/nbdkit-ext2-filter.1* +%endif + + +%files gzip-filter +%doc README +%license LICENSE +%{_libdir}/%{name}/filters/nbdkit-gzip-filter.so +%{_mandir}/man1/nbdkit-gzip-filter.1* + + +%files tar-filter +%doc README +%license LICENSE +%{_libdir}/%{name}/filters/nbdkit-tar-filter.so +%{_mandir}/man1/nbdkit-tar-filter.1* + + +%files xz-filter +%doc README +%license LICENSE +%{_libdir}/%{name}/filters/nbdkit-xz-filter.so +%{_mandir}/man1/nbdkit-xz-filter.1* + + +%files devel +%doc BENCHMARKING OTHER_PLUGINS README SECURITY TODO +%license LICENSE +# Include the source of the example plugins in the documentation. +%doc plugins/example*/*.c +%if !0%{?rhel} +%doc plugins/example4/nbdkit-example4-plugin +%doc plugins/lua/example.lua +%endif +%if !0%{?rhel} && 0%{?have_ocaml} +%doc plugins/ocaml/example.ml +%endif +%if !0%{?rhel} +%doc plugins/perl/example.pl +%endif +%doc plugins/python/examples/*.py +%if !0%{?rhel} +%doc plugins/ruby/example.rb +%endif +%doc plugins/sh/example.sh +%if !0%{?rhel} +%doc plugins/tcl/example.tcl +%endif +%{_includedir}/nbdkit-common.h +%{_includedir}/nbdkit-filter.h +%{_includedir}/nbdkit-plugin.h +%{_includedir}/nbdkit-version.h +%{_includedir}/nbd-protocol.h +%{_mandir}/man3/nbdkit-filter.3* +%{_mandir}/man3/nbdkit-plugin.3* +%{_mandir}/man1/nbdkit-release-notes-1.*.1* +%{_libdir}/pkgconfig/nbdkit.pc + + +%files bash-completion +%license LICENSE +%dir %{_datadir}/bash-completion/completions +%{_datadir}/bash-completion/completions/nbdkit + + +%changelog +* Wed Jan 26 2022 Richard W.M. Jones - 1.24.0-4 +- Fix build on RHEL 8.6 with qemu >= 6.1 + resolves: rhbz#2045945 + +* Mon Sep 06 2021 Richard W.M. Jones - 1.24.0-3 +- Fix CVE-2021-3716 NBD_OPT_STRUCTURED_REPLY injection on STARTTLS + resolves: rhbz#1994915 + +* Mon Sep 06 2021 Richard W.M. Jones - 1.24.0-2 +- Fix data corruption in zero and trim on unaligned tail + resolves: rhbz#1990135 + +* Thu Sep 2 2021 Danilo C. L. de Paula - 1.24.0-1.el8 +- Resolves: bz#2000225 + (Rebase virt:rhel module:stream based on AV-8.6) + +* Thu Jun 04 2020 Danilo C. L. de Paula - 1.16.2 +- Resolves: bz#1810193 + (Upgrade components in virt:rhel module:stream for RHEL-8.3 release) + +* Mon Apr 27 2020 Danilo C. L. de Paula - 1.16.2 +- Resolves: bz#1810193 + (Upgrade components in virt:rhel module:stream for RHEL-8.3 release) + +* Fri Jun 28 2019 Danilo de Paula - 1.4.2-5 +- Rebuild all virt packages to fix RHEL's upgrade path +- Resolves: rhbz#1695587 + (Ensure modular RPM upgrade path) + +* Mon Dec 17 2018 Richard W.M. Jones - 1.4.2-4 +- Remove misguided LDFLAGS hack which removed server hardening. + https://bugzilla.redhat.com/show_bug.cgi?id=1624149#c6 + resolves: rhbz#1624149 + +* Fri Dec 14 2018 Richard W.M. Jones - 1.4.2-3 +- Use platform-python + resolves: rhbz#1659159 + +* Fri Aug 10 2018 Richard W.M. Jones - 1.4.2-2 +- Add Enhanced Python error reporting + resolves: rhbz#1614750. +- Use copy-patches.sh script. + +* Wed Aug 1 2018 Richard W.M. Jones - 1.4.2-1 +- New stable version 1.4.2. + +* Wed Jul 25 2018 Richard W.M. Jones - 1.4.1-3 +- Enable VDDK plugin on x86-64 only. + +* Fri Jul 20 2018 Richard W.M. Jones - 1.4.1-1 +- New upstream version 1.4.1. +- Small refactorings in the spec file. + +* Fri Jul 6 2018 Richard W.M. Jones - 1.4.0-1 +- New upstream version 1.4.0. +- New plugins: random, zero. +- New bash tab completion subpackage. +- Remove unused build dependencies. + +* Sun Jul 1 2018 Richard W.M. Jones - 1.2.4-3 +- Add all upstream patches since 1.2.4 was released. + +* Tue Jun 12 2018 Richard W.M. Jones - 1.2.4-2 +- Add all upstream patches since 1.2.4 was released. + +* Mon Jun 11 2018 Richard W.M. Jones - 1.2.4-2 +- Disable plugins and filters that we do not want to ship in RHEL 8. + +* Sat Jun 9 2018 Richard W.M. Jones - 1.2.4-1 +- New stable version 1.2.4. +- Remove upstream patches. +- Enable tarball signatures. +- Add upstream patch to fix tests when guestfish not available. + +* Wed Jun 6 2018 Richard W.M. Jones - 1.2.3-1 +- New stable version 1.2.3. +- Add patch to work around libvirt problem with relative socket paths. +- Add patch to fix the xz plugin test with recent guestfish. + +* Sat Apr 21 2018 Richard W.M. Jones - 1.2.2-1 +- New stable version 1.2.2. + +* Mon Apr 9 2018 Richard W.M. Jones - 1.2.1-1 +- New stable version 1.2.1. + +* Fri Apr 6 2018 Richard W.M. Jones - 1.2.0-1 +- Move to stable branch version 1.2.0. + +* Fri Feb 09 2018 Igor Gnatenko - 1.1.28-5 +- Escape macros in %%changelog + +* Thu Feb 08 2018 Fedora Release Engineering - 1.1.28-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_28_Mass_Rebuild + +* Wed Jan 31 2018 Igor Gnatenko - 1.1.28-3 +- Switch to %%ldconfig_scriptlets + +* Fri Jan 26 2018 Richard W.M. Jones - 1.1.28-2 +- Run a simplified test suite on all arches. + +* Mon Jan 22 2018 Richard W.M. Jones - 1.1.28-1 +- New upstream version 1.1.28. +- Add two new filters to nbdkit-basic-filters. + +* Sat Jan 20 2018 Björn Esser - 1.1.27-2 +- Rebuilt for switch to libxcrypt + +* Sat Jan 20 2018 Richard W.M. Jones - 1.1.27-1 +- New upstream version 1.1.27. +- Add new subpackage nbdkit-basic-filters containing new filters. + +* Thu Jan 11 2018 Richard W.M. Jones - 1.1.26-2 +- Rebuild against updated Ruby. + +* Sat Dec 23 2017 Richard W.M. Jones - 1.1.26-1 +- New upstream version 1.1.26. +- Add new pkg-config file and dependency. + +* Wed Dec 06 2017 Richard W.M. Jones - 1.1.25-1 +- New upstream version 1.1.25. + +* Tue Dec 05 2017 Richard W.M. Jones - 1.1.24-1 +- New upstream version 1.1.24. +- Add tar plugin (new subpackage nbdkit-plugin-tar). + +* Tue Dec 05 2017 Richard W.M. Jones - 1.1.23-1 +- New upstream version 1.1.23. +- Add example4 plugin. +- Python3 tests require libguestfs so disable on s390x. + +* Sun Dec 03 2017 Richard W.M. Jones - 1.1.22-1 +- New upstream version 1.1.22. +- Enable tests on Fedora. + +* Sat Dec 02 2017 Richard W.M. Jones - 1.1.20-1 +- New upstream version 1.1.20. +- Add nbdkit-split-plugin to basic plugins. + +* Sat Dec 02 2017 Richard W.M. Jones - 1.1.19-2 +- OCaml 4.06.0 rebuild. + +* Thu Nov 30 2017 Richard W.M. Jones - 1.1.19-1 +- New upstream version 1.1.19. +- Combine all the simple plugins in %%{name}-basic-plugins. +- Add memory and null plugins. +- Rename the example plugins subpackage. +- Use %%license instead of %%doc for license file. +- Remove patches now upstream. + +* Wed Nov 29 2017 Richard W.M. Jones - 1.1.18-4 +- Fix Python 3 builds / RHEL macros (RHBZ#1404631). + +* Tue Nov 21 2017 Richard W.M. Jones - 1.1.18-3 +- New upstream version 1.1.18. +- Add NBD forwarding plugin. +- Add libselinux-devel so that SELinux support is enabled in the daemon. +- Apply all patches from upstream since 1.1.18. + +* Fri Oct 20 2017 Richard W.M. Jones - 1.1.16-2 +- New upstream version 1.1.16. +- Disable python3 plugin on RHEL/EPEL <= 7. +- Only ship on x86_64 in RHEL/EPEL <= 7. + +* Wed Sep 27 2017 Richard W.M. Jones - 1.1.15-1 +- New upstream version 1.1.15. +- Enable TLS support. + +* Fri Sep 01 2017 Richard W.M. Jones - 1.1.14-1 +- New upstream version 1.1.14. + +* Fri Aug 25 2017 Richard W.M. Jones - 1.1.13-1 +- New upstream version 1.1.13. +- Remove patches which are all upstream. +- Remove grubby hack, should not be needed with modern supermin. + +* Sat Aug 19 2017 Richard W.M. Jones - 1.1.12-13 +- Rebuild for OCaml 4.05.0. + +* Thu Aug 03 2017 Fedora Release Engineering - 1.1.12-12 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Binutils_Mass_Rebuild + +* Wed Jul 26 2017 Fedora Release Engineering - 1.1.12-11 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_27_Mass_Rebuild + +* Tue Jun 27 2017 Richard W.M. Jones - 1.1.12-10 +- Rebuild for OCaml 4.04.2. + +* Sun Jun 04 2017 Jitka Plesnikova - 1.1.12-9 +- Perl 5.26 rebuild + +* Mon May 15 2017 Richard W.M. Jones - 1.1.12-8 +- Rebuild for OCaml 4.04.1. + +* Fri Feb 10 2017 Fedora Release Engineering - 1.1.12-7 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_26_Mass_Rebuild + +* Thu Jan 12 2017 Vít Ondruch - 1.1.12-6 +- Rebuilt for https://fedoraproject.org/wiki/Changes/Ruby_2.4 + +* Fri Dec 23 2016 Richard W.M. Jones - 1.1.12-5 +- Rebuild for Python 3.6 update. + +* Wed Dec 14 2016 Richard W.M. Jones - 1.1.12-4 +- Fix python3 subpackage so it really uses python3 (RHBZ#1404631). + +* Sat Nov 05 2016 Richard W.M. Jones - 1.1.12-3 +- Rebuild for OCaml 4.04.0. + +* Mon Oct 03 2016 Richard W.M. Jones - 1.1.12-2 +- Compile Python 2 and Python 3 versions of the plugin. + +* Wed Jun 08 2016 Richard W.M. Jones - 1.1.12-1 +- New upstream version 1.1.12 +- Enable Ruby plugin. +- Disable tests on Rawhide because libvirt is broken again (RHBZ#1344016). + +* Wed May 25 2016 Richard W.M. Jones - 1.1.11-10 +- Add another upstream patch since 1.1.11. + +* Mon May 23 2016 Richard W.M. Jones - 1.1.11-9 +- Add all patches upstream since 1.1.11 (fixes RHBZ#1336758). + +* Tue May 17 2016 Jitka Plesnikova - 1.1.11-7 +- Perl 5.24 rebuild + +* Wed Mar 09 2016 Richard W.M. Jones - 1.1.11-6 +- When tests fail, dump out test-suite.log so we can debug it. + +* Fri Feb 05 2016 Richard W.M. Jones - 1.1.11-5 +- Don't run tests on x86, because kernel is broken there + (https://bugzilla.redhat.com/show_bug.cgi?id=1302071) + +* Thu Feb 04 2016 Fedora Release Engineering - 1.1.11-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_24_Mass_Rebuild + +* Mon Jan 11 2016 Richard W.M. Jones - 1.1.11-3 +- Add support for newstyle NBD protocol (RHBZ#1297100). + +* Sat Oct 31 2015 Richard W.M. Jones - 1.1.11-1 +- New upstream version 1.1.11. + +* Thu Jul 30 2015 Richard W.M. Jones - 1.1.10-3 +- OCaml 4.02.3 rebuild. + +* Sat Jun 20 2015 Richard W.M. Jones - 1.1.10-2 +- Enable libguestfs plugin on aarch64. + +* Fri Jun 19 2015 Richard W.M. Jones - 1.1.10-1 +- New upstream version. +- Enable now working OCaml plugin (requires OCaml >= 4.02.2). + +* Wed Jun 17 2015 Fedora Release Engineering - 1.1.9-6 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_23_Mass_Rebuild + +* Thu Jun 11 2015 Jitka Plesnikova - 1.1.9-5 +- Perl 5.22 rebuild + +* Wed Jun 10 2015 Richard W.M. Jones - 1.1.9-4 +- Enable debugging messages when running make check. + +* Sat Jun 06 2015 Jitka Plesnikova - 1.1.9-3 +- Perl 5.22 rebuild + +* Tue Oct 14 2014 Richard W.M. Jones - 1.1.9-2 +- New upstream version 1.1.9. +- Add the streaming plugin. +- Include fix for streaming plugin in 1.1.9. + +* Wed Sep 10 2014 Richard W.M. Jones - 1.1.8-4 +- Rebuild for updated Perl in Rawhide. +- Workaround for broken libvirt (RHBZ#1138604). + +* Sun Aug 17 2014 Fedora Release Engineering - 1.1.8-2 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_22_Mass_Rebuild + +* Sat Jun 21 2014 Richard W.M. Jones - 1.1.8-1 +- New upstream version 1.1.8. +- Add support for cURL, and new nbdkit-plugin-curl package. + +* Fri Jun 20 2014 Richard W.M. Jones - 1.1.7-1 +- New upstream version 1.1.7. +- Remove patches which are now all upstream. + +* Sat Jun 07 2014 Fedora Release Engineering - 1.1.6-5 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_21_Mass_Rebuild + +* Thu Mar 06 2014 Dan Horák - 1.1.6-4 +- libguestfs is available only on selected arches + +* Fri Feb 21 2014 Richard W.M. Jones - 1.1.6-3 +- Backport some upstream patches, fixing a minor bug and adding more tests. +- Enable the tests since kernel bug is fixed. + +* Sun Feb 16 2014 Richard W.M. Jones - 1.1.6-1 +- New upstream version 1.1.6. + +* Sat Feb 15 2014 Richard W.M. Jones - 1.1.5-2 +- New upstream version 1.1.5. +- Enable the new Python plugin. +- Perl plugin man page moved to section 3. +- Perl now requires ExtUtils::Embed. + +* Mon Feb 10 2014 Richard W.M. Jones - 1.1.4-1 +- New upstream version 1.1.4. +- Enable the new Perl plugin. + +* Sun Aug 4 2013 Richard W.M. Jones - 1.1.3-1 +- New upstream version 1.1.3 which fixes some test problems. +- Disable tests because Rawhide kernel is broken (RHBZ#991808). +- Remove a single quote from description which confused emacs. +- Remove patch, now upstream. + +* Sat Aug 03 2013 Fedora Release Engineering - 1.1.2-4 +- Rebuilt for https://fedoraproject.org/wiki/Fedora_20_Mass_Rebuild + +* Sun Jul 21 2013 Richard W.M. Jones - 1.1.2-3 +- Fix segfault when IPv6 client is used (RHBZ#986601). + +* Tue Jul 16 2013 Richard W.M. Jones - 1.1.2-2 +- New development version 1.1.2. +- Disable the tests on Fedora <= 18. + +* Tue Jun 25 2013 Richard W.M. Jones - 1.1.1-1 +- New development version 1.1.1. +- Add libguestfs plugin. +- Run the test suite. + +* Mon Jun 24 2013 Richard W.M. Jones - 1.0.0-4 +- Initial release. diff --git a/sources b/sources new file mode 100644 index 0000000..b855885 --- /dev/null +++ b/sources @@ -0,0 +1,2 @@ +SHA1 (libguestfs.keyring) = 1bbc40f501a7fef9eef2a39b701a71aee2fea7c4 +SHA1 (nbdkit-1.24.0.tar.gz) = 069720cc0d1502b007652101d293a57d7b4d7c41 diff --git a/tests/basic-test.sh b/tests/basic-test.sh new file mode 100755 index 0000000..07eb459 --- /dev/null +++ b/tests/basic-test.sh @@ -0,0 +1,6 @@ +#!/bin/bash - +set -e +set -x + +# Run nbdkit and check that nbdinfo can connect back to it. +nbdkit -U - memory 1G --run 'nbdinfo "$uri"' diff --git a/tests/tests.yml b/tests/tests.yml new file mode 100755 index 0000000..28c6a43 --- /dev/null +++ b/tests/tests.yml @@ -0,0 +1,12 @@ +- hosts: localhost + roles: + - role: standard-test-basic + tags: + - classic + required_packages: + - libnbd + - nbdkit + tests: + - simple: + dir: . + run: ./basic-test.sh