From fdd51403a35a484f29c405c3d5cb0058f80bd3ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 21 Sep 2023 10:12:51 +0100 Subject: [PATCH 01/17] meson: sort C warning flags alphabetically MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When scanning the list of warning flags to see if one is present, it is helpful if they are in alphabetical order. It is further helpful to separate out the 'no-' prefixed warnings. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- meson.build | 40 +++++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/meson.build b/meson.build index e5d6f2d057..74d3aa0b12 100644 --- a/meson.build +++ b/meson.build @@ -571,36 +571,38 @@ qemu_common_flags += cc.get_supported_arguments(hardening_flags) add_global_arguments(qemu_common_flags, native: false, language: all_languages) add_global_link_arguments(qemu_ldflags, native: false, language: all_languages) -# Collect warnings that we want to enable - +# Collect warning flags we want to set, sorted alphabetically warn_flags = [ - '-Wundef', - '-Wwrite-strings', - '-Wmissing-prototypes', - '-Wstrict-prototypes', - '-Wredundant-decls', - '-Wold-style-declaration', - '-Wold-style-definition', - '-Wtype-limits', - '-Wformat-security', - '-Wformat-y2k', - '-Winit-self', - '-Wignored-qualifiers', + # First enable interesting warnings '-Wempty-body', - '-Wnested-externs', '-Wendif-labels', '-Wexpansion-to-defined', + '-Wformat-security', + '-Wformat-y2k', + '-Wignored-qualifiers', '-Wimplicit-fallthrough=2', + '-Winit-self', '-Wmissing-format-attribute', + '-Wmissing-prototypes', + '-Wnested-externs', + '-Wold-style-declaration', + '-Wold-style-definition', + '-Wredundant-decls', + '-Wshadow=local', + '-Wstrict-prototypes', + '-Wtype-limits', + '-Wundef', + '-Wwrite-strings', + + # Then disable some undesirable warnings + '-Wno-gnu-variable-sized-type-not-at-end', '-Wno-initializer-overrides', '-Wno-missing-include-dirs', + '-Wno-psabi', '-Wno-shift-negative-value', '-Wno-string-plus-int', - '-Wno-typedef-redefinition', '-Wno-tautological-type-limit-compare', - '-Wno-psabi', - '-Wno-gnu-variable-sized-type-not-at-end', - '-Wshadow=local', + '-Wno-typedef-redefinition', ] if host_os != 'darwin' From 52ed9f455ee0063958a1b18e54cab9a427fc422f Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Thu, 7 Dec 2023 23:47:35 +0800 Subject: [PATCH 02/17] crypto: Introduce SM4 symmetric cipher algorithm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce the SM4 cipher algorithms (OSCCA GB/T 32907-2016). SM4 (GBT.32907-2016) is a cryptographic standard issued by the Organization of State Commercial Administration of China (OSCCA) as an authorized cryptographic algorithms for the use within China. Detect the SM4 cipher algorithms and enable the feature silently if it is available. Signed-off-by: Hyman Huang Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- crypto/block-luks.c | 11 ++++++++ crypto/cipher-gcrypt.c.inc | 8 ++++++ crypto/cipher-nettle.c.inc | 49 +++++++++++++++++++++++++++++++++ crypto/cipher.c | 6 ++++ meson.build | 26 +++++++++++++++++ qapi/crypto.json | 5 +++- tests/unit/test-crypto-cipher.c | 13 +++++++++ 7 files changed, 117 insertions(+), 1 deletion(-) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index fb01ec38bb..f0813d69b4 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -95,12 +95,23 @@ qcrypto_block_luks_cipher_size_map_twofish[] = { { 0, 0 }, }; +#ifdef CONFIG_CRYPTO_SM4 +static const QCryptoBlockLUKSCipherSizeMap +qcrypto_block_luks_cipher_size_map_sm4[] = { + { 16, QCRYPTO_CIPHER_ALG_SM4}, + { 0, 0 }, +}; +#endif + static const QCryptoBlockLUKSCipherNameMap qcrypto_block_luks_cipher_name_map[] = { { "aes", qcrypto_block_luks_cipher_size_map_aes }, { "cast5", qcrypto_block_luks_cipher_size_map_cast5 }, { "serpent", qcrypto_block_luks_cipher_size_map_serpent }, { "twofish", qcrypto_block_luks_cipher_size_map_twofish }, +#ifdef CONFIG_CRYPTO_SM4 + { "sm4", qcrypto_block_luks_cipher_size_map_sm4}, +#endif }; QEMU_BUILD_BUG_ON(sizeof(struct QCryptoBlockLUKSKeySlot) != 48); diff --git a/crypto/cipher-gcrypt.c.inc b/crypto/cipher-gcrypt.c.inc index a6a0117717..1377cbaf14 100644 --- a/crypto/cipher-gcrypt.c.inc +++ b/crypto/cipher-gcrypt.c.inc @@ -35,6 +35,9 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg, case QCRYPTO_CIPHER_ALG_SERPENT_256: case QCRYPTO_CIPHER_ALG_TWOFISH_128: case QCRYPTO_CIPHER_ALG_TWOFISH_256: +#ifdef CONFIG_CRYPTO_SM4 + case QCRYPTO_CIPHER_ALG_SM4: +#endif break; default: return false; @@ -219,6 +222,11 @@ static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg, case QCRYPTO_CIPHER_ALG_TWOFISH_256: gcryalg = GCRY_CIPHER_TWOFISH; break; +#ifdef CONFIG_CRYPTO_SM4 + case QCRYPTO_CIPHER_ALG_SM4: + gcryalg = GCRY_CIPHER_SM4; + break; +#endif default: error_setg(errp, "Unsupported cipher algorithm %s", QCryptoCipherAlgorithm_str(alg)); diff --git a/crypto/cipher-nettle.c.inc b/crypto/cipher-nettle.c.inc index 24cc61f87b..42b39e18a2 100644 --- a/crypto/cipher-nettle.c.inc +++ b/crypto/cipher-nettle.c.inc @@ -33,6 +33,9 @@ #ifndef CONFIG_QEMU_PRIVATE_XTS #include #endif +#ifdef CONFIG_CRYPTO_SM4 +#include +#endif static inline bool qcrypto_length_check(size_t len, size_t blocksize, Error **errp) @@ -426,6 +429,30 @@ DEFINE_ECB_CBC_CTR_XTS(qcrypto_nettle_twofish, QCryptoNettleTwofish, TWOFISH_BLOCK_SIZE, twofish_encrypt_native, twofish_decrypt_native) +#ifdef CONFIG_CRYPTO_SM4 +typedef struct QCryptoNettleSm4 { + QCryptoCipher base; + struct sm4_ctx key[2]; +} QCryptoNettleSm4; + +static void sm4_encrypt_native(void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + struct sm4_ctx *keys = ctx; + sm4_crypt(&keys[0], length, dst, src); +} + +static void sm4_decrypt_native(void *ctx, size_t length, + uint8_t *dst, const uint8_t *src) +{ + struct sm4_ctx *keys = ctx; + sm4_crypt(&keys[1], length, dst, src); +} + +DEFINE_ECB(qcrypto_nettle_sm4, + QCryptoNettleSm4, SM4_BLOCK_SIZE, + sm4_encrypt_native, sm4_decrypt_native) +#endif bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg, QCryptoCipherMode mode) @@ -443,6 +470,9 @@ bool qcrypto_cipher_supports(QCryptoCipherAlgorithm alg, case QCRYPTO_CIPHER_ALG_TWOFISH_128: case QCRYPTO_CIPHER_ALG_TWOFISH_192: case QCRYPTO_CIPHER_ALG_TWOFISH_256: +#ifdef CONFIG_CRYPTO_SM4 + case QCRYPTO_CIPHER_ALG_SM4: +#endif break; default: return false; @@ -701,6 +731,25 @@ static QCryptoCipher *qcrypto_cipher_ctx_new(QCryptoCipherAlgorithm alg, return &ctx->base; } +#ifdef CONFIG_CRYPTO_SM4 + case QCRYPTO_CIPHER_ALG_SM4: + { + QCryptoNettleSm4 *ctx = g_new0(QCryptoNettleSm4, 1); + + switch (mode) { + case QCRYPTO_CIPHER_MODE_ECB: + ctx->base.driver = &qcrypto_nettle_sm4_driver_ecb; + break; + default: + goto bad_cipher_mode; + } + + sm4_set_encrypt_key(&ctx->key[0], key); + sm4_set_decrypt_key(&ctx->key[1], key); + + return &ctx->base; + } +#endif default: error_setg(errp, "Unsupported cipher algorithm %s", diff --git a/crypto/cipher.c b/crypto/cipher.c index 74b09a5b26..5f512768ea 100644 --- a/crypto/cipher.c +++ b/crypto/cipher.c @@ -38,6 +38,9 @@ static const size_t alg_key_len[QCRYPTO_CIPHER_ALG__MAX] = { [QCRYPTO_CIPHER_ALG_TWOFISH_128] = 16, [QCRYPTO_CIPHER_ALG_TWOFISH_192] = 24, [QCRYPTO_CIPHER_ALG_TWOFISH_256] = 32, +#ifdef CONFIG_CRYPTO_SM4 + [QCRYPTO_CIPHER_ALG_SM4] = 16, +#endif }; static const size_t alg_block_len[QCRYPTO_CIPHER_ALG__MAX] = { @@ -53,6 +56,9 @@ static const size_t alg_block_len[QCRYPTO_CIPHER_ALG__MAX] = { [QCRYPTO_CIPHER_ALG_TWOFISH_128] = 16, [QCRYPTO_CIPHER_ALG_TWOFISH_192] = 16, [QCRYPTO_CIPHER_ALG_TWOFISH_256] = 16, +#ifdef CONFIG_CRYPTO_SM4 + [QCRYPTO_CIPHER_ALG_SM4] = 16, +#endif }; static const bool mode_need_iv[QCRYPTO_CIPHER_MODE__MAX] = { diff --git a/meson.build b/meson.build index 74d3aa0b12..c1dc83e4c0 100644 --- a/meson.build +++ b/meson.build @@ -1633,6 +1633,7 @@ endif gcrypt = not_found nettle = not_found hogweed = not_found +crypto_sm4 = not_found xts = 'none' if get_option('nettle').enabled() and get_option('gcrypt').enabled() @@ -1658,6 +1659,17 @@ if not gnutls_crypto.found() cc.find_library('gpg-error', required: true)], version: gcrypt.version()) endif + crypto_sm4 = gcrypt + # SM4 ALG is available in libgcrypt >= 1.9 + if gcrypt.found() and not cc.links(''' + #include + int main(void) { + gcry_cipher_hd_t handler; + gcry_cipher_open(&handler, GCRY_CIPHER_SM4, GCRY_CIPHER_MODE_ECB, 0); + return 0; + }''', dependencies: gcrypt) + crypto_sm4 = not_found + endif endif if (not get_option('nettle').auto() or have_system) and not gcrypt.found() nettle = dependency('nettle', version: '>=3.4', @@ -1666,6 +1678,18 @@ if not gnutls_crypto.found() if nettle.found() and not cc.has_header('nettle/xts.h', dependencies: nettle) xts = 'private' endif + crypto_sm4 = nettle + # SM4 ALG is available in nettle >= 3.9 + if nettle.found() and not cc.links(''' + #include + int main(void) { + struct sm4_ctx ctx; + unsigned char key[16] = {0}; + sm4_set_encrypt_key(&ctx, key); + return 0; + }''', dependencies: nettle) + crypto_sm4 = not_found + endif endif endif @@ -2267,6 +2291,7 @@ config_host_data.set('CONFIG_GNUTLS_CRYPTO', gnutls_crypto.found()) config_host_data.set('CONFIG_TASN1', tasn1.found()) config_host_data.set('CONFIG_GCRYPT', gcrypt.found()) config_host_data.set('CONFIG_NETTLE', nettle.found()) +config_host_data.set('CONFIG_CRYPTO_SM4', crypto_sm4.found()) config_host_data.set('CONFIG_HOGWEED', hogweed.found()) config_host_data.set('CONFIG_QEMU_PRIVATE_XTS', xts == 'private') config_host_data.set('CONFIG_MALLOC_TRIM', has_malloc_trim) @@ -4306,6 +4331,7 @@ summary_info += {'nettle': nettle} if nettle.found() summary_info += {' XTS': xts != 'private'} endif +summary_info += {'SM4 ALG support': crypto_sm4} summary_info += {'AF_ALG support': have_afalg} summary_info += {'rng-none': get_option('rng_none')} summary_info += {'Linux keyring': have_keyring} diff --git a/qapi/crypto.json b/qapi/crypto.json index fd3d46ebd1..2f2aeff5fd 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -94,6 +94,8 @@ # # @twofish-256: Twofish with 256 bit / 32 byte keys # +# @sm4: SM4 with 128 bit / 16 byte keys (since 9.0) +# # Since: 2.6 ## { 'enum': 'QCryptoCipherAlgorithm', @@ -102,7 +104,8 @@ 'des', '3des', 'cast5-128', 'serpent-128', 'serpent-192', 'serpent-256', - 'twofish-128', 'twofish-192', 'twofish-256']} + 'twofish-128', 'twofish-192', 'twofish-256', + 'sm4']} ## # @QCryptoCipherMode: diff --git a/tests/unit/test-crypto-cipher.c b/tests/unit/test-crypto-cipher.c index d9d9d078ff..11ab1a54fc 100644 --- a/tests/unit/test-crypto-cipher.c +++ b/tests/unit/test-crypto-cipher.c @@ -382,6 +382,19 @@ static QCryptoCipherTestData test_data[] = { .plaintext = "90afe91bb288544f2c32dc239b2635e6", .ciphertext = "6cb4561c40bf0a9705931cb6d408e7fa", }, +#ifdef CONFIG_CRYPTO_SM4 + { + /* SM4, GB/T 32907-2016, Appendix A.1 */ + .path = "/crypto/cipher/sm4", + .alg = QCRYPTO_CIPHER_ALG_SM4, + .mode = QCRYPTO_CIPHER_MODE_ECB, + .key = "0123456789abcdeffedcba9876543210", + .plaintext = + "0123456789abcdeffedcba9876543210", + .ciphertext = + "681edf34d206965e86b3e94f536e4246", + }, +#endif { /* #1 32 byte key, 32 byte PTX */ .path = "/crypto/cipher/aes-xts-128-1", From 03e471c41d8b1b6eb16c9714f387449f52fe5c1d Mon Sep 17 00:00:00 2001 From: Fiona Ebner Date: Mon, 18 Dec 2023 11:13:40 +0100 Subject: [PATCH 03/17] qemu_init: increase NOFILE soft limit on POSIX MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In many configurations, e.g. multiple vNICs with multiple queues or with many Ceph OSDs, the default soft limit of 1024 is not enough. QEMU is supposed to work fine with file descriptors >= 1024 and does not use select() on POSIX. Bump the soft limit to the allowed hard limit to avoid issues with the aforementioned configurations. Of course the limit could be raised from the outside, but the man page of systemd.exec states about 'LimitNOFILE=': > Don't use. > [...] > Typically applications should increase their soft limit to the hard > limit on their own, if they are OK with working with file > descriptors above 1023, If the soft limit is already the same as the hard limit, avoid the superfluous setrlimit call. This can avoid a warning with a strict seccomp filter blocking setrlimit if NOFILE was already raised before executing QEMU. Buglink: https://bugzilla.proxmox.com/show_bug.cgi?id=4507 Reviewed-by: Daniel P. Berrangé Signed-off-by: Fiona Ebner Signed-off-by: Daniel P. Berrangé --- include/sysemu/os-posix.h | 1 + include/sysemu/os-win32.h | 5 +++++ os-posix.c | 22 ++++++++++++++++++++++ system/vl.c | 2 ++ 4 files changed, 30 insertions(+) diff --git a/include/sysemu/os-posix.h b/include/sysemu/os-posix.h index dff32ae185..b881ac6c6f 100644 --- a/include/sysemu/os-posix.h +++ b/include/sysemu/os-posix.h @@ -51,6 +51,7 @@ bool is_daemonized(void); void os_daemonize(void); bool os_set_runas(const char *user_id); void os_set_chroot(const char *path); +void os_setup_limits(void); void os_setup_post(void); int os_mlock(void); diff --git a/include/sysemu/os-win32.h b/include/sysemu/os-win32.h index 1047d260cb..b82a5d3ad9 100644 --- a/include/sysemu/os-win32.h +++ b/include/sysemu/os-win32.h @@ -128,6 +128,11 @@ static inline int os_mlock(void) return -ENOSYS; } +static inline void os_setup_limits(void) +{ + return; +} + #define fsync _commit #if !defined(lseek) diff --git a/os-posix.c b/os-posix.c index 52ef6990ff..a4284e2c07 100644 --- a/os-posix.c +++ b/os-posix.c @@ -24,6 +24,7 @@ */ #include "qemu/osdep.h" +#include #include #include #include @@ -256,6 +257,27 @@ void os_daemonize(void) } } +void os_setup_limits(void) +{ + struct rlimit nofile; + + if (getrlimit(RLIMIT_NOFILE, &nofile) < 0) { + warn_report("unable to query NOFILE limit: %s", strerror(errno)); + return; + } + + if (nofile.rlim_cur == nofile.rlim_max) { + return; + } + + nofile.rlim_cur = nofile.rlim_max; + + if (setrlimit(RLIMIT_NOFILE, &nofile) < 0) { + warn_report("unable to set NOFILE limit: %s", strerror(errno)); + return; + } +} + void os_setup_post(void) { int fd = 0; diff --git a/system/vl.c b/system/vl.c index 2a0bd08ff1..95cd2d91c4 100644 --- a/system/vl.c +++ b/system/vl.c @@ -2778,6 +2778,8 @@ void qemu_init(int argc, char **argv) error_init(argv[0]); qemu_init_exec_dir(argv[0]); + os_setup_limits(); + qemu_init_arch_modules(); qemu_init_subsystems(); From 0e74eb86eaf3ef57e9f9741cb668784c96e654ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 7 Dec 2023 13:45:01 +0000 Subject: [PATCH 04/17] ui: drop VNC feature _MASK constants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each VNC feature enum entry has a corresponding _MASK constant which is the bit-shifted value. It is very easy for contributors to accidentally use the _MASK constant, instead of the non-_MASK constant, or the reverse. No compiler warning is possible and it'll just silently do the wrong thing at runtime. By introducing the vnc_set_feature helper method, we can drop all the _MASK constants and thus prevent any future accidents. Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- ui/vnc.c | 34 +++++++++++++++++----------------- ui/vnc.h | 22 +++++----------------- 2 files changed, 22 insertions(+), 34 deletions(-) diff --git a/ui/vnc.c b/ui/vnc.c index 4f23a0fa79..3db87fd89c 100644 --- a/ui/vnc.c +++ b/ui/vnc.c @@ -2144,16 +2144,16 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) vs->vnc_encoding = enc; break; case VNC_ENCODING_HEXTILE: - vs->features |= VNC_FEATURE_HEXTILE_MASK; + vnc_set_feature(vs, VNC_FEATURE_HEXTILE); vs->vnc_encoding = enc; break; case VNC_ENCODING_TIGHT: - vs->features |= VNC_FEATURE_TIGHT_MASK; + vnc_set_feature(vs, VNC_FEATURE_TIGHT); vs->vnc_encoding = enc; break; #ifdef CONFIG_PNG case VNC_ENCODING_TIGHT_PNG: - vs->features |= VNC_FEATURE_TIGHT_PNG_MASK; + vnc_set_feature(vs, VNC_FEATURE_TIGHT_PNG); vs->vnc_encoding = enc; break; #endif @@ -2163,57 +2163,57 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) * So prioritize ZRLE, even if the client hints that it prefers * ZLIB. */ - if ((vs->features & VNC_FEATURE_ZRLE_MASK) == 0) { - vs->features |= VNC_FEATURE_ZLIB_MASK; + if (!vnc_has_feature(vs, VNC_FEATURE_ZRLE)) { + vnc_set_feature(vs, VNC_FEATURE_ZLIB); vs->vnc_encoding = enc; } break; case VNC_ENCODING_ZRLE: - vs->features |= VNC_FEATURE_ZRLE_MASK; + vnc_set_feature(vs, VNC_FEATURE_ZRLE); vs->vnc_encoding = enc; break; case VNC_ENCODING_ZYWRLE: - vs->features |= VNC_FEATURE_ZYWRLE_MASK; + vnc_set_feature(vs, VNC_FEATURE_ZYWRLE); vs->vnc_encoding = enc; break; case VNC_ENCODING_DESKTOPRESIZE: - vs->features |= VNC_FEATURE_RESIZE_MASK; + vnc_set_feature(vs, VNC_FEATURE_RESIZE); break; case VNC_ENCODING_DESKTOP_RESIZE_EXT: - vs->features |= VNC_FEATURE_RESIZE_EXT_MASK; + vnc_set_feature(vs, VNC_FEATURE_RESIZE_EXT); break; case VNC_ENCODING_POINTER_TYPE_CHANGE: - vs->features |= VNC_FEATURE_POINTER_TYPE_CHANGE_MASK; + vnc_set_feature(vs, VNC_FEATURE_POINTER_TYPE_CHANGE); break; case VNC_ENCODING_RICH_CURSOR: - vs->features |= VNC_FEATURE_RICH_CURSOR_MASK; + vnc_set_feature(vs, VNC_FEATURE_RICH_CURSOR); break; case VNC_ENCODING_ALPHA_CURSOR: - vs->features |= VNC_FEATURE_ALPHA_CURSOR_MASK; + vnc_set_feature(vs, VNC_FEATURE_ALPHA_CURSOR); break; case VNC_ENCODING_EXT_KEY_EVENT: send_ext_key_event_ack(vs); break; case VNC_ENCODING_AUDIO: if (vs->vd->audio_state) { - vs->features |= VNC_FEATURE_AUDIO_MASK; + vnc_set_feature(vs, VNC_FEATURE_AUDIO); send_ext_audio_ack(vs); } break; case VNC_ENCODING_WMVi: - vs->features |= VNC_FEATURE_WMVI_MASK; + vnc_set_feature(vs, VNC_FEATURE_WMVI); break; case VNC_ENCODING_LED_STATE: - vs->features |= VNC_FEATURE_LED_STATE_MASK; + vnc_set_feature(vs, VNC_FEATURE_LED_STATE); break; case VNC_ENCODING_XVP: if (vs->vd->power_control) { - vs->features |= VNC_FEATURE_XVP_MASK; + vnc_set_feature(vs, VNC_FEATURE_XVP); send_xvp_message(vs, VNC_XVP_CODE_INIT); } break; case VNC_ENCODING_CLIPBOARD_EXT: - vs->features |= VNC_FEATURE_CLIPBOARD_EXT_MASK; + vnc_set_feature(vs, VNC_FEATURE_CLIPBOARD_EXT); vnc_server_cut_text_caps(vs); break; case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9: diff --git a/ui/vnc.h b/ui/vnc.h index 96d19dce19..4521dc88f7 100644 --- a/ui/vnc.h +++ b/ui/vnc.h @@ -467,23 +467,6 @@ enum VncFeatures { VNC_FEATURE_AUDIO, }; -#define VNC_FEATURE_RESIZE_MASK (1 << VNC_FEATURE_RESIZE) -#define VNC_FEATURE_RESIZE_EXT_MASK (1 << VNC_FEATURE_RESIZE_EXT) -#define VNC_FEATURE_HEXTILE_MASK (1 << VNC_FEATURE_HEXTILE) -#define VNC_FEATURE_POINTER_TYPE_CHANGE_MASK (1 << VNC_FEATURE_POINTER_TYPE_CHANGE) -#define VNC_FEATURE_WMVI_MASK (1 << VNC_FEATURE_WMVI) -#define VNC_FEATURE_TIGHT_MASK (1 << VNC_FEATURE_TIGHT) -#define VNC_FEATURE_ZLIB_MASK (1 << VNC_FEATURE_ZLIB) -#define VNC_FEATURE_RICH_CURSOR_MASK (1 << VNC_FEATURE_RICH_CURSOR) -#define VNC_FEATURE_ALPHA_CURSOR_MASK (1 << VNC_FEATURE_ALPHA_CURSOR) -#define VNC_FEATURE_TIGHT_PNG_MASK (1 << VNC_FEATURE_TIGHT_PNG) -#define VNC_FEATURE_ZRLE_MASK (1 << VNC_FEATURE_ZRLE) -#define VNC_FEATURE_ZYWRLE_MASK (1 << VNC_FEATURE_ZYWRLE) -#define VNC_FEATURE_LED_STATE_MASK (1 << VNC_FEATURE_LED_STATE) -#define VNC_FEATURE_XVP_MASK (1 << VNC_FEATURE_XVP) -#define VNC_FEATURE_CLIPBOARD_EXT_MASK (1 << VNC_FEATURE_CLIPBOARD_EXT) -#define VNC_FEATURE_AUDIO_MASK (1 << VNC_FEATURE_AUDIO) - /* Client -> Server message IDs */ #define VNC_MSG_CLIENT_SET_PIXEL_FORMAT 0 @@ -599,6 +582,11 @@ static inline uint32_t vnc_has_feature(VncState *vs, int feature) { return (vs->features & (1 << feature)); } +static inline void vnc_set_feature(VncState *vs, enum VncFeatures feature) +{ + vs->features |= (1 << feature); +} + /* Framebuffer */ void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, int32_t encoding); From 3b219d99af282b21b3c87ad948dbb21082e8fa1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 8 Sep 2023 18:55:36 +0100 Subject: [PATCH 05/17] softmmu: remove obsolete comment about libvirt timeouts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For a long time now, libvirt has pre-created the monitor connection socket and passed the pre-opened FD into QEMU during startup. Thus libvirt does not have any timeouts waiting for the monitor socket to appear, it is immediately connected. Reviewed-by: Zhao Liu Signed-off-by: Daniel P. Berrangé --- system/vl.c | 1 - 1 file changed, 1 deletion(-) diff --git a/system/vl.c b/system/vl.c index 95cd2d91c4..a82555ae15 100644 --- a/system/vl.c +++ b/system/vl.c @@ -1914,7 +1914,6 @@ static bool object_create_early(const char *type) * Allocation of large amounts of memory may delay * chardev initialization for too long, and trigger timeouts * on software that waits for a monitor socket to be created - * (e.g. libvirt). */ if (g_str_has_prefix(type, "memory-backend-")) { return false; From f424bc3312cbc1ff2748c263883e4480fa1586bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Wed, 10 May 2023 12:52:42 +0100 Subject: [PATCH 06/17] scripts: drop comment about autogenerated CPU API file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The RST doc include can't be made to skip the comment indicating the CPU CSV file is auto-generated when importing it. This comment line was previously manually removed from the generated output that was committed. Signed-off-by: Daniel P. Berrangé --- scripts/cpu-x86-uarch-abi.py | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/cpu-x86-uarch-abi.py b/scripts/cpu-x86-uarch-abi.py index 052ddd7514..7360e55c6e 100644 --- a/scripts/cpu-x86-uarch-abi.py +++ b/scripts/cpu-x86-uarch-abi.py @@ -179,7 +179,6 @@ for level in range(len(abi_models)): models[name]["delta"][level] = delta def print_uarch_abi_csv(): - print("# Automatically generated from '%s'" % __file__) print("Model,baseline,v2,v3,v4") for name in models.keys(): print(name, end="") From 86cf437d7adb6d79953db9496abef5e332385b37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Wed, 10 May 2023 12:53:57 +0100 Subject: [PATCH 07/17] docs: fix highlighting of CPU ABI header rows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 'header-rows' directive indicates how many rows in the generated table are to be highlighted as headers. We only have one such row in the CSV file included. This removes the accident bold highlighting of the 'i486' CPU model. Signed-off-by: Daniel P. Berrangé --- docs/system/cpu-models-x86.rst.inc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/system/cpu-models-x86.rst.inc b/docs/system/cpu-models-x86.rst.inc index 7f6368f999..ba27b5683f 100644 --- a/docs/system/cpu-models-x86.rst.inc +++ b/docs/system/cpu-models-x86.rst.inc @@ -58,7 +58,7 @@ depending on the machine type is in use. .. csv-table:: x86-64 ABI compatibility levels :file: cpu-models-x86-abi.csv :widths: 40,15,15,15,15 - :header-rows: 2 + :header-rows: 1 Preferred CPU models for Intel x86 hosts From 30c917b0d82b1e793d0004ef14792369b7cd80df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Wed, 10 May 2023 12:51:01 +0100 Subject: [PATCH 08/17] docs: re-generate x86_64 ABI compatibility CSV MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This picks up the new EPYC-Genoa, SapphireRapids & GraniteRapids CPUs, removes the now deleted Icelake-Client CPU, and adds the newer versions of many existing CPUs. Signed-off-by: Daniel P. Berrangé --- docs/system/cpu-models-x86-abi.csv | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/system/cpu-models-x86-abi.csv b/docs/system/cpu-models-x86-abi.csv index f3f3b60be1..38b9bae310 100644 --- a/docs/system/cpu-models-x86-abi.csv +++ b/docs/system/cpu-models-x86-abi.csv @@ -8,27 +8,37 @@ Cascadelake-Server-v1,✅,✅,✅,✅ Cascadelake-Server-v2,✅,✅,✅,✅ Cascadelake-Server-v3,✅,✅,✅,✅ Cascadelake-Server-v4,✅,✅,✅,✅ +Cascadelake-Server-v5,✅,✅,✅,✅ Conroe-v1,✅,,, Cooperlake-v1,✅,✅,✅,✅ +Cooperlake-v2,✅,✅,✅,✅ Denverton-v1,✅,✅,, Denverton-v2,✅,✅,, +Denverton-v3,✅,✅,, Dhyana-v1,✅,✅,✅, +Dhyana-v2,✅,✅,✅, +EPYC-Genoa-v1,✅,✅,✅,✅ EPYC-Milan-v1,✅,✅,✅, +EPYC-Milan-v2,✅,✅,✅, EPYC-Rome-v1,✅,✅,✅, EPYC-Rome-v2,✅,✅,✅, +EPYC-Rome-v3,✅,✅,✅, +EPYC-Rome-v4,✅,✅,✅, EPYC-v1,✅,✅,✅, EPYC-v2,✅,✅,✅, EPYC-v3,✅,✅,✅, +EPYC-v4,✅,✅,✅, +GraniteRapids-v1,✅,✅,✅,✅ Haswell-v1,✅,✅,✅, Haswell-v2,✅,✅,✅, Haswell-v3,✅,✅,✅, Haswell-v4,✅,✅,✅, -Icelake-Client-v1,✅,✅,✅, -Icelake-Client-v2,✅,✅,✅, Icelake-Server-v1,✅,✅,✅,✅ Icelake-Server-v2,✅,✅,✅,✅ Icelake-Server-v3,✅,✅,✅,✅ Icelake-Server-v4,✅,✅,✅,✅ +Icelake-Server-v5,✅,✅,✅,✅ +Icelake-Server-v6,✅,✅,✅,✅ IvyBridge-v1,✅,✅,, IvyBridge-v2,✅,✅,, KnightsMill-v1,✅,✅,✅, @@ -42,15 +52,21 @@ Opteron_G5-v1,✅,✅,, Penryn-v1,✅,,, SandyBridge-v1,✅,✅,, SandyBridge-v2,✅,✅,, +SapphireRapids-v1,✅,✅,✅,✅ +SapphireRapids-v2,✅,✅,✅,✅ Skylake-Client-v1,✅,✅,✅, Skylake-Client-v2,✅,✅,✅, Skylake-Client-v3,✅,✅,✅, +Skylake-Client-v4,✅,✅,✅, Skylake-Server-v1,✅,✅,✅,✅ Skylake-Server-v2,✅,✅,✅,✅ Skylake-Server-v3,✅,✅,✅,✅ Skylake-Server-v4,✅,✅,✅,✅ +Skylake-Server-v5,✅,✅,✅,✅ Snowridge-v1,✅,✅,, Snowridge-v2,✅,✅,, +Snowridge-v3,✅,✅,, +Snowridge-v4,✅,✅,, Westmere-v1,✅,✅,, Westmere-v2,✅,✅,, athlon-v1,,,, From cb8ded0f6d919a61ef890be14b28e765fba81f66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 5 Jan 2024 16:09:52 +0000 Subject: [PATCH 09/17] chardev: close QIOChannel before unref'ing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chardev socket backend will unref the QIOChannel object while it is still potentially open. When using TLS there could be a pending TLS handshake taking place. If the channel is left open then when the TLS handshake callback runs, it can end up accessing free'd memory in the tcp_chr_tls_handshake method. Closing the QIOChannel will unregister any pending handshake source. Reported-by: jiangyegen Reviewed-by: Marc-André Lureau Signed-off-by: Daniel P. Berrangé --- chardev/char-socket.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/chardev/char-socket.c b/chardev/char-socket.c index 73947da188..7105753815 100644 --- a/chardev/char-socket.c +++ b/chardev/char-socket.c @@ -378,6 +378,10 @@ static void tcp_chr_free_connection(Chardev *chr) char_socket_yank_iochannel, QIO_CHANNEL(s->sioc)); } + + if (s->ioc) { + qio_channel_close(s->ioc, NULL); + } object_unref(OBJECT(s->sioc)); s->sioc = NULL; object_unref(OBJECT(s->ioc)); From 003f15369de4e290a4d2e58292d96c5a506e4ee6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 5 Jan 2024 15:48:07 +0000 Subject: [PATCH 10/17] io: add trace event when cancelling TLS handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- io/channel-tls.c | 1 + io/trace-events | 1 + 2 files changed, 2 insertions(+) diff --git a/io/channel-tls.c b/io/channel-tls.c index 58fe1aceee..1d9c9c72bf 100644 --- a/io/channel-tls.c +++ b/io/channel-tls.c @@ -381,6 +381,7 @@ static int qio_channel_tls_close(QIOChannel *ioc, QIOChannelTLS *tioc = QIO_CHANNEL_TLS(ioc); if (tioc->hs_ioc_tag) { + trace_qio_channel_tls_handshake_cancel(ioc); g_clear_handle_id(&tioc->hs_ioc_tag, g_source_remove); } diff --git a/io/trace-events b/io/trace-events index 3cc5cf1efd..d4c0f84a9a 100644 --- a/io/trace-events +++ b/io/trace-events @@ -43,6 +43,7 @@ qio_channel_tls_handshake_start(void *ioc) "TLS handshake start ioc=%p" qio_channel_tls_handshake_pending(void *ioc, int status) "TLS handshake pending ioc=%p status=%d" qio_channel_tls_handshake_fail(void *ioc) "TLS handshake fail ioc=%p" qio_channel_tls_handshake_complete(void *ioc) "TLS handshake complete ioc=%p" +qio_channel_tls_handshake_cancel(void *ioc) "TLS handshake cancel ioc=%p" qio_channel_tls_credentials_allow(void *ioc) "TLS credentials allow ioc=%p" qio_channel_tls_credentials_deny(void *ioc) "TLS credentials deny ioc=%p" From 9ad5c4e7ee513019e75ea8ece0d1da6ecce540e6 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:19 +0800 Subject: [PATCH 11/17] crypto: Support LUKS volume with detached header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By enhancing the LUKS driver, it is possible to implement the LUKS volume with a detached header. Normally a LUKS volume has a layout: disk: | header | key material | disk payload data | With a detached LUKS header, you need 2 disks so getting: disk1: | header | key material | disk2: | disk payload data | There are a variety of benefits to doing this: * Secrecy - the disk2 cannot be identified as containing LUKS volume since there's no header * Control - if access to the disk1 is restricted, then even if someone has access to disk2 they can't unlock it. Might be useful if you have disks on NFS but want to restrict which host can launch a VM instance from it, by dynamically providing access to the header to a designated host * Flexibility - your application data volume may be a given size and it is inconvenient to resize it to add encryption.You can store the LUKS header separately and use the existing storage volume for payload * Recovery - corruption of a bit in the header may make the entire payload inaccessible. It might be convenient to take backups of the header. If your primary disk header becomes corrupt, you can unlock the data still by pointing to the backup detached header Take the raw-format image as an example to introduce the usage of the LUKS volume with a detached header: 1. prepare detached LUKS header images $ dd if=/dev/zero of=test-header.img bs=1M count=32 $ dd if=/dev/zero of=test-payload.img bs=1M count=1000 $ cryptsetup luksFormat --header test-header.img test-payload.img > --force-password --type luks1 2. block-add a protocol blockdev node of payload image $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments":{"node-name":"libvirt-1-storage", "driver":"file", > "filename":"test-payload.img"}}' 3. block-add a protocol blockdev node of LUKS header as above. $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments":{"node-name":"libvirt-2-storage", "driver":"file", > "filename": "test-header.img" }}' 4. object-add the secret for decrypting the cipher stored in LUKS header above $ virsh qemu-monitor-command vm '{"execute":"object-add", > "arguments":{"qom-type":"secret", "id": > "libvirt-2-storage-secret0", "data":"abc123"}}' 5. block-add the raw-drived blockdev format node $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments":{"node-name":"libvirt-1-format", "driver":"raw", > "file":"libvirt-1-storage"}}' 6. block-add the luks-drived blockdev to link the raw disk with the LUKS header by specifying the field "header" $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments":{"node-name":"libvirt-2-format", "driver":"luks", > "file":"libvirt-1-format", "header":"libvirt-2-storage", > "key-secret":"libvirt-2-format-secret0"}}' 7. hot-plug the virtio-blk device finally $ virsh qemu-monitor-command vm '{"execute":"device_add", > "arguments": {"num-queues":"1", "driver":"virtio-blk-pci", > "drive": "libvirt-2-format", "id":"virtio-disk2"}}' Starting a VM with a LUKS volume with detached header is somewhat similar to hot-plug in that both maintaining the same json command while the starting VM changes the "blockdev-add/device_add" parameters to "blockdev/device". Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- block/crypto.c | 21 +++++++++++++++++++-- crypto/block-luks.c | 11 +++++++---- include/crypto/block.h | 5 +++++ qapi/block-core.json | 5 ++++- 4 files changed, 35 insertions(+), 7 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 921933a5e5..68656158e9 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -39,6 +39,7 @@ typedef struct BlockCrypto BlockCrypto; struct BlockCrypto { QCryptoBlock *block; bool updating_keys; + BdrvChild *header; /* Reference to the detached LUKS header */ }; @@ -63,12 +64,14 @@ static int block_crypto_read_func(QCryptoBlock *block, Error **errp) { BlockDriverState *bs = opaque; + BlockCrypto *crypto = bs->opaque; ssize_t ret; GLOBAL_STATE_CODE(); GRAPH_RDLOCK_GUARD_MAINLOOP(); - ret = bdrv_pread(bs->file, offset, buflen, buf, 0); + ret = bdrv_pread(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); if (ret < 0) { error_setg_errno(errp, -ret, "Could not read encryption header"); return ret; @@ -84,12 +87,14 @@ static int block_crypto_write_func(QCryptoBlock *block, Error **errp) { BlockDriverState *bs = opaque; + BlockCrypto *crypto = bs->opaque; ssize_t ret; GLOBAL_STATE_CODE(); GRAPH_RDLOCK_GUARD_MAINLOOP(); - ret = bdrv_pwrite(bs->file, offset, buflen, buf, 0); + ret = bdrv_pwrite(crypto->header ? crypto->header : bs->file, + offset, buflen, buf, 0); if (ret < 0) { error_setg_errno(errp, -ret, "Could not write encryption header"); return ret; @@ -262,6 +267,8 @@ static int block_crypto_open_generic(QCryptoBlockFormat format, int flags, Error **errp) { + ERRP_GUARD(); + BlockCrypto *crypto = bs->opaque; QemuOpts *opts = NULL; int ret; @@ -276,6 +283,13 @@ static int block_crypto_open_generic(QCryptoBlockFormat format, return ret; } + crypto->header = bdrv_open_child(NULL, options, "header", bs, + &child_of_bds, BDRV_CHILD_METADATA, + true, errp); + if (*errp != NULL) { + return -EINVAL; + } + GRAPH_RDLOCK_GUARD_MAINLOOP(); bs->supported_write_flags = BDRV_REQ_FUA & @@ -299,6 +313,9 @@ static int block_crypto_open_generic(QCryptoBlockFormat format, if (flags & BDRV_O_NO_IO) { cflags |= QCRYPTO_BLOCK_OPEN_NO_IO; } + if (crypto->header != NULL) { + cflags |= QCRYPTO_BLOCK_OPEN_DETACHED; + } crypto->block = qcrypto_block_open(open_opts, NULL, block_crypto_read_func, bs, diff --git a/crypto/block-luks.c b/crypto/block-luks.c index f0813d69b4..7e1235c213 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -468,12 +468,15 @@ qcrypto_block_luks_load_header(QCryptoBlock *block, * Does basic sanity checks on the LUKS header */ static int -qcrypto_block_luks_check_header(const QCryptoBlockLUKS *luks, Error **errp) +qcrypto_block_luks_check_header(const QCryptoBlockLUKS *luks, + unsigned int flags, + Error **errp) { size_t i, j; unsigned int header_sectors = QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET / QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; + bool detached = flags & QCRYPTO_BLOCK_OPEN_DETACHED; if (memcmp(luks->header.magic, qcrypto_block_luks_magic, QCRYPTO_BLOCK_LUKS_MAGIC_LEN) != 0) { @@ -505,7 +508,7 @@ qcrypto_block_luks_check_header(const QCryptoBlockLUKS *luks, Error **errp) return -1; } - if (luks->header.payload_offset_sector < + if (!detached && luks->header.payload_offset_sector < DIV_ROUND_UP(QCRYPTO_BLOCK_LUKS_KEY_SLOT_OFFSET, QCRYPTO_BLOCK_LUKS_SECTOR_SIZE)) { error_setg(errp, "LUKS payload is overlapping with the header"); @@ -554,7 +557,7 @@ qcrypto_block_luks_check_header(const QCryptoBlockLUKS *luks, Error **errp) return -1; } - if (start1 + len1 > luks->header.payload_offset_sector) { + if (!detached && start1 + len1 > luks->header.payload_offset_sector) { error_setg(errp, "Keyslot %zu is overlapping with the encrypted payload", i); @@ -1214,7 +1217,7 @@ qcrypto_block_luks_open(QCryptoBlock *block, goto fail; } - if (qcrypto_block_luks_check_header(luks, errp) < 0) { + if (qcrypto_block_luks_check_header(luks, flags, errp) < 0) { goto fail; } diff --git a/include/crypto/block.h b/include/crypto/block.h index 4f63a37872..d0d97f5d12 100644 --- a/include/crypto/block.h +++ b/include/crypto/block.h @@ -66,6 +66,7 @@ bool qcrypto_block_has_format(QCryptoBlockFormat format, typedef enum { QCRYPTO_BLOCK_OPEN_NO_IO = (1 << 0), + QCRYPTO_BLOCK_OPEN_DETACHED = (1 << 1), } QCryptoBlockOpenFlags; /** @@ -95,6 +96,10 @@ typedef enum { * metadata such as the payload offset. There will be * no cipher or ivgen objects available. * + * If @flags contains QCRYPTO_BLOCK_OPEN_DETACHED then + * the open process will be optimized to skip the LUKS + * payload overlap check. + * * If any part of initializing the encryption context * fails an error will be returned. This could be due * to the volume being in the wrong format, a cipher diff --git a/qapi/block-core.json b/qapi/block-core.json index 781c9bd03e..e7d4fb539b 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -3365,11 +3365,14 @@ # decryption key (since 2.6). Mandatory except when doing a # metadata-only probe of the image. # +# @header: block device holding a detached LUKS header. (since 9.0) +# # Since: 2.9 ## { 'struct': 'BlockdevOptionsLUKS', 'base': 'BlockdevOptionsGenericFormat', - 'data': { '*key-secret': 'str' } } + 'data': { '*key-secret': 'str', + '*header': 'BlockdevRef'} } ## # @BlockdevOptionsGenericCOWFormat: From 433957bb7f99d7577601949c92f2fffa9457cd7b Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:20 +0800 Subject: [PATCH 12/17] qapi: Make parameter 'file' optional for BlockdevCreateOptionsLUKS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To support detached LUKS header creation, make the existing 'file' field in BlockdevCreateOptionsLUKS optional. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- block/crypto.c | 21 ++++++++++++++------- qapi/block-core.json | 5 +++-- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 68656158e9..e87dc84111 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -663,9 +663,9 @@ block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) assert(create_options->driver == BLOCKDEV_DRIVER_LUKS); luks_opts = &create_options->u.luks; - bs = bdrv_co_open_blockdev_ref(luks_opts->file, errp); - if (bs == NULL) { - return -EIO; + if (luks_opts->file == NULL) { + error_setg(errp, "Formatting LUKS disk requires parameter 'file'"); + return -EINVAL; } create_opts = (QCryptoBlockCreateOptions) { @@ -677,10 +677,17 @@ block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) preallocation = luks_opts->preallocation; } - ret = block_crypto_co_create_generic(bs, luks_opts->size, &create_opts, - preallocation, errp); - if (ret < 0) { - goto fail; + if (luks_opts->file) { + bs = bdrv_co_open_blockdev_ref(luks_opts->file, errp); + if (bs == NULL) { + return -EIO; + } + + ret = block_crypto_co_create_generic(bs, luks_opts->size, &create_opts, + preallocation, errp); + if (ret < 0) { + goto fail; + } } ret = 0; diff --git a/qapi/block-core.json b/qapi/block-core.json index e7d4fb539b..77d1f50d55 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4955,7 +4955,8 @@ # # Driver specific image creation options for LUKS. # -# @file: Node to create the image format on +# @file: Node to create the image format on, mandatory except when +# 'preallocation' is not requested # # @size: Size of the virtual disk in bytes # @@ -4966,7 +4967,7 @@ ## { 'struct': 'BlockdevCreateOptionsLUKS', 'base': 'QCryptoBlockCreateOptionsLUKS', - 'data': { 'file': 'BlockdevRef', + 'data': { '*file': 'BlockdevRef', 'size': 'size', '*preallocation': 'PreallocMode' } } From d74523a3b395dc8470177dcdfa13644b98494334 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:21 +0800 Subject: [PATCH 13/17] crypto: Modify the qcrypto_block_create to support creation flags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Expand the signature of qcrypto_block_create to enable the formation of LUKS volumes with detachable headers. To accomplish that, introduce QCryptoBlockCreateFlags to instruct the creation process to set the payload_offset_sector to 0. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- block/crypto.c | 1 + block/qcow.c | 2 +- block/qcow2.c | 2 +- crypto/block-luks.c | 28 +++++++++++++++++++++------- crypto/block.c | 4 +++- crypto/blockpriv.h | 2 ++ include/crypto/block.h | 11 +++++++++++ tests/unit/test-crypto-block.c | 2 ++ 8 files changed, 42 insertions(+), 10 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index e87dc84111..1b3f87922a 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -369,6 +369,7 @@ block_crypto_co_create_generic(BlockDriverState *bs, int64_t size, block_crypto_create_init_func, block_crypto_create_write_func, &data, + 0, errp); if (!crypto) { diff --git a/block/qcow.c b/block/qcow.c index c6d0e15f1e..ca8e1d5ec8 100644 --- a/block/qcow.c +++ b/block/qcow.c @@ -885,7 +885,7 @@ qcow_co_create(BlockdevCreateOptions *opts, Error **errp) header.crypt_method = cpu_to_be32(QCOW_CRYPT_AES); crypto = qcrypto_block_create(qcow_opts->encrypt, "encrypt.", - NULL, NULL, NULL, errp); + NULL, NULL, NULL, 0, errp); if (!crypto) { ret = -EINVAL; goto exit; diff --git a/block/qcow2.c b/block/qcow2.c index 9bee66fff5..204f5854cf 100644 --- a/block/qcow2.c +++ b/block/qcow2.c @@ -3216,7 +3216,7 @@ qcow2_set_up_encryption(BlockDriverState *bs, crypto = qcrypto_block_create(cryptoopts, "encrypt.", qcow2_crypto_hdr_init_func, qcow2_crypto_hdr_write_func, - bs, errp); + bs, 0, errp); if (!crypto) { return -EINVAL; } diff --git a/crypto/block-luks.c b/crypto/block-luks.c index 7e1235c213..ab52c9dce1 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -1315,6 +1315,7 @@ qcrypto_block_luks_create(QCryptoBlock *block, const char *hash_alg; g_autofree char *cipher_mode_spec = NULL; uint64_t iters; + uint64_t detached_header_size; memcpy(&luks_opts, &options->u.luks, sizeof(luks_opts)); if (!luks_opts.has_iter_time) { @@ -1543,19 +1544,32 @@ qcrypto_block_luks_create(QCryptoBlock *block, slot->stripes = QCRYPTO_BLOCK_LUKS_STRIPES; } - /* The total size of the LUKS headers is the partition header + key - * slot headers, rounded up to the nearest sector, combined with - * the size of each master key material region, also rounded up - * to the nearest sector */ - luks->header.payload_offset_sector = header_sectors + - QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS * split_key_sectors; + if (block->detached_header) { + /* + * For a detached LUKS header image, set the payload_offset_sector + * to 0 to specify the starting point for read/write + */ + luks->header.payload_offset_sector = 0; + } else { + /* + * The total size of the LUKS headers is the partition header + key + * slot headers, rounded up to the nearest sector, combined with + * the size of each master key material region, also rounded up + * to the nearest sector + */ + luks->header.payload_offset_sector = header_sectors + + QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS * split_key_sectors; + } block->sector_size = QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; block->payload_offset = luks->header.payload_offset_sector * block->sector_size; + detached_header_size = + (header_sectors + QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS * + split_key_sectors) * block->sector_size; /* Reserve header space to match payload offset */ - initfunc(block, block->payload_offset, opaque, &local_err); + initfunc(block, detached_header_size, opaque, &local_err); if (local_err) { error_propagate(errp, local_err); goto error; diff --git a/crypto/block.c b/crypto/block.c index 7bb4b74a37..506ea1d1a3 100644 --- a/crypto/block.c +++ b/crypto/block.c @@ -87,6 +87,7 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, QCryptoBlockInitFunc initfunc, QCryptoBlockWriteFunc writefunc, void *opaque, + unsigned int flags, Error **errp) { QCryptoBlock *block = g_new0(QCryptoBlock, 1); @@ -102,6 +103,7 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, } block->driver = qcrypto_block_drivers[options->format]; + block->detached_header = flags & QCRYPTO_BLOCK_CREATE_DETACHED; if (block->driver->create(block, options, optprefix, initfunc, writefunc, opaque, errp) < 0) { @@ -146,7 +148,7 @@ qcrypto_block_calculate_payload_offset(QCryptoBlockCreateOptions *create_opts, qcrypto_block_create(create_opts, optprefix, qcrypto_block_headerlen_hdr_init_func, qcrypto_block_headerlen_hdr_write_func, - len, errp); + len, 0, errp); return crypto != NULL; } diff --git a/crypto/blockpriv.h b/crypto/blockpriv.h index 3c7ccea504..836f3b4726 100644 --- a/crypto/blockpriv.h +++ b/crypto/blockpriv.h @@ -42,6 +42,8 @@ struct QCryptoBlock { size_t niv; uint64_t payload_offset; /* In bytes */ uint64_t sector_size; /* In bytes */ + + bool detached_header; /* True if disk has a detached LUKS header */ }; struct QCryptoBlockDriver { diff --git a/include/crypto/block.h b/include/crypto/block.h index d0d97f5d12..92e823c9f2 100644 --- a/include/crypto/block.h +++ b/include/crypto/block.h @@ -116,6 +116,10 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options, size_t n_threads, Error **errp); +typedef enum { + QCRYPTO_BLOCK_CREATE_DETACHED = (1 << 0), +} QCryptoBlockCreateFlags; + /** * qcrypto_block_create: * @options: the encryption options @@ -123,6 +127,7 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options, * @initfunc: callback for initializing volume header * @writefunc: callback for writing data to the volume header * @opaque: data to pass to @initfunc and @writefunc + * @flags: bitmask of QCryptoBlockCreateFlags values * @errp: pointer to a NULL-initialized error object * * Create a new block encryption object for initializing @@ -134,6 +139,11 @@ QCryptoBlock *qcrypto_block_open(QCryptoBlockOpenOptions *options, * generating new master keys, etc as required. Any existing * data present on the volume will be irrevocably destroyed. * + * If @flags contains QCRYPTO_BLOCK_CREATE_DETACHED then + * the open process will set the payload_offset_sector to 0 + * to specify the starting point for the read/write of a + * detached LUKS header image. + * * If any part of initializing the encryption context * fails an error will be returned. This could be due * to the volume being in the wrong format, a cipher @@ -147,6 +157,7 @@ QCryptoBlock *qcrypto_block_create(QCryptoBlockCreateOptions *options, QCryptoBlockInitFunc initfunc, QCryptoBlockWriteFunc writefunc, void *opaque, + unsigned int flags, Error **errp); /** diff --git a/tests/unit/test-crypto-block.c b/tests/unit/test-crypto-block.c index 347cd5f3d7..6cfc817a92 100644 --- a/tests/unit/test-crypto-block.c +++ b/tests/unit/test-crypto-block.c @@ -283,6 +283,7 @@ static void test_block(gconstpointer opaque) test_block_init_func, test_block_write_func, &header, + 0, &error_abort); g_assert(blk); @@ -362,6 +363,7 @@ test_luks_bad_header(gconstpointer data) test_block_init_func, test_block_write_func, &buf, + 0, &error_abort); g_assert(blk); From d0112eb415bd1bfae3b61818d9ded936f05f1afb Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:22 +0800 Subject: [PATCH 14/17] block: Support detached LUKS header creation using blockdev-create MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Firstly, enable the ability to choose the block device containing a detachable LUKS header by adding the 'header' parameter to BlockdevCreateOptionsLUKS. Secondly, when formatting the LUKS volume with a detachable header, truncate the payload volume to length without a header size. Using the qmp blockdev command, create the LUKS volume with a detachable header as follows: 1. add the secret to lock/unlock the cipher stored in the detached LUKS header $ virsh qemu-monitor-command vm '{"execute":"object-add", > "arguments":{"qom-type": "secret", "id": "sec0", "data": "foo"}}' 2. create a header img with 0 size $ virsh qemu-monitor-command vm '{"execute":"blockdev-create", > "arguments":{"job-id":"job0", "options":{"driver":"file", > "filename":"/path/to/detached_luks_header.img", "size":0 }}}' 3. add protocol blockdev node for header $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments": {"driver":"file", "filename": > "/path/to/detached_luks_header.img", "node-name": > "detached-luks-header-storage"}}' 4. create a payload img with 0 size $ virsh qemu-monitor-command vm '{"execute":"blockdev-create", > "arguments":{"job-id":"job1", "options":{"driver":"file", > "filename":"/path/to/detached_luks_payload_raw.img", "size":0}}}' 5. add protocol blockdev node for payload $ virsh qemu-monitor-command vm '{"execute":"blockdev-add", > "arguments": {"driver":"file", "filename": > "/path/to/detached_luks_payload_raw.img", "node-name": > "luks-payload-raw-storage"}}' 6. do the formatting with 128M size $ virsh qemu-monitor-command c81_node1 '{"execute":"blockdev-create", > "arguments":{"job-id":"job2", "options":{"driver":"luks", "header": > "detached-luks-header-storage", "file":"luks-payload-raw-storage", > "size":134217728, "preallocation":"full", "key-secret":"sec0" }}}' Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- block/crypto.c | 101 +++++++++++++++++++++++++++++++++++++++---- qapi/block-core.json | 3 ++ 2 files changed, 96 insertions(+), 8 deletions(-) diff --git a/block/crypto.c b/block/crypto.c index 1b3f87922a..8e7ee5e9ac 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -162,6 +162,48 @@ error: return ret; } +static int coroutine_fn GRAPH_UNLOCKED +block_crypto_co_format_luks_payload(BlockdevCreateOptionsLUKS *luks_opts, + Error **errp) +{ + BlockDriverState *bs = NULL; + BlockBackend *blk = NULL; + Error *local_error = NULL; + int ret; + + if (luks_opts->size > INT64_MAX) { + return -EFBIG; + } + + bs = bdrv_co_open_blockdev_ref(luks_opts->file, errp); + if (bs == NULL) { + return -EIO; + } + + blk = blk_co_new_with_bs(bs, BLK_PERM_WRITE | BLK_PERM_RESIZE, + BLK_PERM_ALL, errp); + if (!blk) { + ret = -EPERM; + goto fail; + } + + ret = blk_truncate(blk, luks_opts->size, true, + luks_opts->preallocation, 0, &local_error); + if (ret < 0) { + if (ret == -EFBIG) { + /* Replace the error message with a better one */ + error_free(local_error); + error_setg(errp, "The requested file size is too large"); + } + goto fail; + } + + ret = 0; + +fail: + bdrv_co_unref(bs); + return ret; +} static QemuOptsList block_crypto_runtime_opts_luks = { .name = "crypto", @@ -341,7 +383,9 @@ static int block_crypto_open_generic(QCryptoBlockFormat format, static int coroutine_fn GRAPH_UNLOCKED block_crypto_co_create_generic(BlockDriverState *bs, int64_t size, QCryptoBlockCreateOptions *opts, - PreallocMode prealloc, Error **errp) + PreallocMode prealloc, + unsigned int flags, + Error **errp) { int ret; BlockBackend *blk; @@ -369,7 +413,7 @@ block_crypto_co_create_generic(BlockDriverState *bs, int64_t size, block_crypto_create_init_func, block_crypto_create_write_func, &data, - 0, + flags, errp); if (!crypto) { @@ -656,16 +700,26 @@ static int coroutine_fn GRAPH_UNLOCKED block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) { BlockdevCreateOptionsLUKS *luks_opts; + BlockDriverState *hdr_bs = NULL; BlockDriverState *bs = NULL; QCryptoBlockCreateOptions create_opts; PreallocMode preallocation = PREALLOC_MODE_OFF; + unsigned int cflags = 0; int ret; assert(create_options->driver == BLOCKDEV_DRIVER_LUKS); luks_opts = &create_options->u.luks; - if (luks_opts->file == NULL) { - error_setg(errp, "Formatting LUKS disk requires parameter 'file'"); + if (luks_opts->header == NULL && luks_opts->file == NULL) { + error_setg(errp, "Either the parameter 'header' or 'file' must " + "be specified"); + return -EINVAL; + } + + if ((luks_opts->preallocation != PREALLOC_MODE_OFF) && + (luks_opts->file == NULL)) { + error_setg(errp, "Parameter 'preallocation' requires 'file' to be " + "specified for formatting LUKS disk"); return -EINVAL; } @@ -678,14 +732,38 @@ block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) preallocation = luks_opts->preallocation; } - if (luks_opts->file) { + if (luks_opts->header) { + /* LUKS volume with detached header */ + hdr_bs = bdrv_co_open_blockdev_ref(luks_opts->header, errp); + if (hdr_bs == NULL) { + return -EIO; + } + + cflags |= QCRYPTO_BLOCK_CREATE_DETACHED; + + /* Format the LUKS header node */ + ret = block_crypto_co_create_generic(hdr_bs, 0, &create_opts, + PREALLOC_MODE_OFF, cflags, errp); + if (ret < 0) { + goto fail; + } + + /* Format the LUKS payload node */ + if (luks_opts->file) { + ret = block_crypto_co_format_luks_payload(luks_opts, errp); + if (ret < 0) { + goto fail; + } + } + } else if (luks_opts->file) { + /* LUKS volume with none-detached header */ bs = bdrv_co_open_blockdev_ref(luks_opts->file, errp); if (bs == NULL) { return -EIO; } ret = block_crypto_co_create_generic(bs, luks_opts->size, &create_opts, - preallocation, errp); + preallocation, cflags, errp); if (ret < 0) { goto fail; } @@ -693,7 +771,13 @@ block_crypto_co_create_luks(BlockdevCreateOptions *create_options, Error **errp) ret = 0; fail: - bdrv_co_unref(bs); + if (hdr_bs != NULL) { + bdrv_co_unref(hdr_bs); + } + + if (bs != NULL) { + bdrv_co_unref(bs); + } return ret; } @@ -747,7 +831,8 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename, } /* Create format layer */ - ret = block_crypto_co_create_generic(bs, size, create_opts, prealloc, errp); + ret = block_crypto_co_create_generic(bs, size, create_opts, + prealloc, 0, errp); if (ret < 0) { goto fail; } diff --git a/qapi/block-core.json b/qapi/block-core.json index 77d1f50d55..e4ef36d2aa 100644 --- a/qapi/block-core.json +++ b/qapi/block-core.json @@ -4958,6 +4958,8 @@ # @file: Node to create the image format on, mandatory except when # 'preallocation' is not requested # +# @header: Block device holding a detached LUKS header. (since 9.0) +# # @size: Size of the virtual disk in bytes # # @preallocation: Preallocation mode for the new image (since: 4.2) @@ -4968,6 +4970,7 @@ { 'struct': 'BlockdevCreateOptionsLUKS', 'base': 'QCryptoBlockCreateOptionsLUKS', 'data': { '*file': 'BlockdevRef', + '*header': 'BlockdevRef', 'size': 'size', '*preallocation': 'PreallocMode' } } From 35286daeca43444aa1a9c7a36ffc499877eb6668 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:23 +0800 Subject: [PATCH 15/17] block: Support detached LUKS header creation using qemu-img MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Even though a LUKS header might be created with cryptsetup, qemu-img should be enhanced to accommodate it as well. Add the 'detached-header' option to specify the creation of a detached LUKS header. This is how it is used: $ qemu-img create --object secret,id=sec0,data=abc123 -f luks > -o cipher-alg=aes-256,cipher-mode=xts -o key-secret=sec0 > -o detached-header=true header.luks Using qemu-img or cryptsetup tools to query information of an LUKS header image as follows: Assume a detached LUKS header image has been created by: $ dd if=/dev/zero of=test-header.img bs=1M count=32 $ dd if=/dev/zero of=test-payload.img bs=1M count=1000 $ cryptsetup luksFormat --header test-header.img test-payload.img > --force-password --type luks1 Header image information could be queried using cryptsetup: $ cryptsetup luksDump test-header.img or qemu-img: $ qemu-img info 'json:{"driver":"luks","file":{"filename": > "test-payload.img"},"header":{"filename":"test-header.img"}}' When using qemu-img, keep in mind that the entire disk information specified by the JSON-format string above must be supplied on the commandline; if not, an overlay check will reveal a problem with the LUKS volume check logic. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé [changed to pass 'cflags' to block_crypto_co_create_generic] Signed-off-by: Daniel P. Berrangé --- block.c | 5 ++++- block/crypto.c | 12 ++++++++++-- block/crypto.h | 8 ++++++++ qapi/crypto.json | 5 ++++- 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/block.c b/block.c index 30afdcbba6..1ed9214f66 100644 --- a/block.c +++ b/block.c @@ -7357,7 +7357,10 @@ void bdrv_img_create(const char *filename, const char *fmt, goto out; } - if (size == -1) { + /* Parameter 'size' is not needed for detached LUKS header */ + if (size == -1 && + !(!strcmp(fmt, "luks") && + qemu_opt_get_bool(opts, "detached-header", false))) { error_setg(errp, "Image creation needs a size parameter"); goto out; } diff --git a/block/crypto.c b/block/crypto.c index 8e7ee5e9ac..21eed909c1 100644 --- a/block/crypto.c +++ b/block/crypto.c @@ -231,6 +231,7 @@ static QemuOptsList block_crypto_create_opts_luks = { BLOCK_CRYPTO_OPT_DEF_LUKS_IVGEN_HASH_ALG(""), BLOCK_CRYPTO_OPT_DEF_LUKS_HASH_ALG(""), BLOCK_CRYPTO_OPT_DEF_LUKS_ITER_TIME(""), + BLOCK_CRYPTO_OPT_DEF_LUKS_DETACHED_HEADER(""), { /* end of list */ } }, }; @@ -405,7 +406,7 @@ block_crypto_co_create_generic(BlockDriverState *bs, int64_t size, data = (struct BlockCryptoCreateData) { .blk = blk, - .size = size, + .size = flags & QCRYPTO_BLOCK_CREATE_DETACHED ? 0 : size, .prealloc = prealloc, }; @@ -791,6 +792,9 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename, PreallocMode prealloc; char *buf = NULL; int64_t size; + bool detached_hdr = + qemu_opt_get_bool(opts, "detached-header", false); + unsigned int cflags = 0; int ret; Error *local_err = NULL; @@ -830,9 +834,13 @@ block_crypto_co_create_opts_luks(BlockDriver *drv, const char *filename, goto fail; } + if (detached_hdr) { + cflags |= QCRYPTO_BLOCK_CREATE_DETACHED; + } + /* Create format layer */ ret = block_crypto_co_create_generic(bs, size, create_opts, - prealloc, 0, errp); + prealloc, cflags, errp); if (ret < 0) { goto fail; } diff --git a/block/crypto.h b/block/crypto.h index 72e792c9af..dc3d2d5ed9 100644 --- a/block/crypto.h +++ b/block/crypto.h @@ -41,6 +41,7 @@ #define BLOCK_CRYPTO_OPT_LUKS_IVGEN_HASH_ALG "ivgen-hash-alg" #define BLOCK_CRYPTO_OPT_LUKS_HASH_ALG "hash-alg" #define BLOCK_CRYPTO_OPT_LUKS_ITER_TIME "iter-time" +#define BLOCK_CRYPTO_OPT_LUKS_DETACHED_HEADER "detached-header" #define BLOCK_CRYPTO_OPT_LUKS_KEYSLOT "keyslot" #define BLOCK_CRYPTO_OPT_LUKS_STATE "state" #define BLOCK_CRYPTO_OPT_LUKS_OLD_SECRET "old-secret" @@ -100,6 +101,13 @@ .help = "Select new state of affected keyslots (active/inactive)",\ } +#define BLOCK_CRYPTO_OPT_DEF_LUKS_DETACHED_HEADER(prefix) \ + { \ + .name = prefix BLOCK_CRYPTO_OPT_LUKS_DETACHED_HEADER, \ + .type = QEMU_OPT_BOOL, \ + .help = "Create a detached LUKS header", \ + } + #define BLOCK_CRYPTO_OPT_DEF_LUKS_KEYSLOT(prefix) \ { \ .name = prefix BLOCK_CRYPTO_OPT_LUKS_KEYSLOT, \ diff --git a/qapi/crypto.json b/qapi/crypto.json index 2f2aeff5fd..22c6cce3ae 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -226,6 +226,8 @@ # @iter-time: number of milliseconds to spend in PBKDF passphrase # processing. Currently defaults to 2000. (since 2.8) # +# @detached-header: create a detached LUKS header. (since 9.0) +# # Since: 2.6 ## { 'struct': 'QCryptoBlockCreateOptionsLUKS', @@ -235,7 +237,8 @@ '*ivgen-alg': 'QCryptoIVGenAlgorithm', '*ivgen-hash-alg': 'QCryptoHashAlgorithm', '*hash-alg': 'QCryptoHashAlgorithm', - '*iter-time': 'int'}} + '*iter-time': 'int', + '*detached-header': 'bool'}} ## # @QCryptoBlockOpenOptions: From 0bd779e27ee7c737fdaee2e52e6d1c5951375177 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:24 +0800 Subject: [PATCH 16/17] crypto: Introduce 'detached-header' field in QCryptoBlockInfoLUKS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When querying the LUKS disk with the qemu-img tool or other APIs, add information about whether the LUKS header is detached. Additionally, update the test case with the appropriate modification. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- crypto/block-luks.c | 2 ++ qapi/crypto.json | 3 +++ tests/qemu-iotests/210.out | 4 ++++ 3 files changed, 9 insertions(+) diff --git a/crypto/block-luks.c b/crypto/block-luks.c index ab52c9dce1..3ee928fb5a 100644 --- a/crypto/block-luks.c +++ b/crypto/block-luks.c @@ -1271,6 +1271,7 @@ qcrypto_block_luks_open(QCryptoBlock *block, block->sector_size = QCRYPTO_BLOCK_LUKS_SECTOR_SIZE; block->payload_offset = luks->header.payload_offset_sector * block->sector_size; + block->detached_header = (block->payload_offset == 0) ? true : false; return 0; @@ -1895,6 +1896,7 @@ static int qcrypto_block_luks_get_info(QCryptoBlock *block, info->u.luks.master_key_iters = luks->header.master_key_iterations; info->u.luks.uuid = g_strndup((const char *)luks->header.uuid, sizeof(luks->header.uuid)); + info->u.luks.detached_header = block->detached_header; for (i = 0; i < QCRYPTO_BLOCK_LUKS_NUM_KEY_SLOTS; i++) { slot = g_new0(QCryptoBlockInfoLUKSSlot, 1); diff --git a/qapi/crypto.json b/qapi/crypto.json index 22c6cce3ae..ad8dd37175 100644 --- a/qapi/crypto.json +++ b/qapi/crypto.json @@ -317,6 +317,8 @@ # # @hash-alg: the master key hash algorithm # +# @detached-header: whether the LUKS header is detached (Since 9.0) +# # @payload-offset: offset to the payload data in bytes # # @master-key-iters: number of PBKDF2 iterations for key material @@ -333,6 +335,7 @@ 'ivgen-alg': 'QCryptoIVGenAlgorithm', '*ivgen-hash-alg': 'QCryptoHashAlgorithm', 'hash-alg': 'QCryptoHashAlgorithm', + 'detached-header': 'bool', 'payload-offset': 'int', 'master-key-iters': 'int', 'uuid': 'str', diff --git a/tests/qemu-iotests/210.out b/tests/qemu-iotests/210.out index 96d9f749dd..94b29b2120 100644 --- a/tests/qemu-iotests/210.out +++ b/tests/qemu-iotests/210.out @@ -18,6 +18,7 @@ virtual size: 128 MiB (134217728 bytes) encrypted: yes Format specific information: ivgen alg: plain64 + detached header: false hash alg: sha256 cipher alg: aes-256 uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX @@ -70,6 +71,7 @@ virtual size: 64 MiB (67108864 bytes) encrypted: yes Format specific information: ivgen alg: plain64 + detached header: false hash alg: sha1 cipher alg: aes-128 uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX @@ -125,6 +127,7 @@ virtual size: 0 B (0 bytes) encrypted: yes Format specific information: ivgen alg: plain64 + detached header: false hash alg: sha256 cipher alg: aes-256 uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX @@ -195,6 +198,7 @@ virtual size: 0 B (0 bytes) encrypted: yes Format specific information: ivgen alg: plain64 + detached header: false hash alg: sha256 cipher alg: aes-256 uuid: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX From d87b258b75498d3e8563ec8ebaaf67efc27be945 Mon Sep 17 00:00:00 2001 From: Hyman Huang Date: Tue, 30 Jan 2024 13:37:25 +0800 Subject: [PATCH 17/17] tests: Add case for LUKS volume with detached header MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also, add a section to the MAINTAINERS file for detached LUKS header, it only has a test case in it currently. Signed-off-by: Hyman Huang Reviewed-by: Daniel P. Berrangé Signed-off-by: Daniel P. Berrangé --- MAINTAINERS | 5 + tests/qemu-iotests/tests/luks-detached-header | 316 ++++++++++++++++++ .../tests/luks-detached-header.out | 5 + 3 files changed, 326 insertions(+) create mode 100755 tests/qemu-iotests/tests/luks-detached-header create mode 100644 tests/qemu-iotests/tests/luks-detached-header.out diff --git a/MAINTAINERS b/MAINTAINERS index 2f9741b898..f80db6a96a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3402,6 +3402,11 @@ F: migration/dirtyrate.c F: migration/dirtyrate.h F: include/sysemu/dirtyrate.h +Detached LUKS header +M: Hyman Huang +S: Maintained +F: tests/qemu-iotests/tests/luks-detached-header + D-Bus M: Marc-André Lureau S: Maintained diff --git a/tests/qemu-iotests/tests/luks-detached-header b/tests/qemu-iotests/tests/luks-detached-header new file mode 100755 index 0000000000..3455fd8de1 --- /dev/null +++ b/tests/qemu-iotests/tests/luks-detached-header @@ -0,0 +1,316 @@ +#!/usr/bin/env python3 +# group: rw auto +# +# Test LUKS volume with detached header +# +# Copyright (C) 2024 SmartX Inc. +# +# Authors: +# Hyman Huang +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# + +import os +import json +import iotests +from iotests import ( + imgfmt, + qemu_img_create, + qemu_img_info, + QMPTestCase, +) + + +image_size = 128 * 1024 * 1024 + +luks_img = os.path.join(iotests.test_dir, "luks.img") +detached_header_img1 = os.path.join(iotests.test_dir, "detached_header.img1") +detached_header_img2 = os.path.join(iotests.test_dir, "detached_header.img2") +detached_payload_raw_img = os.path.join( + iotests.test_dir, "detached_payload_raw.img" +) +detached_payload_qcow2_img = os.path.join( + iotests.test_dir, "detached_payload_qcow2.img" +) +detached_header_raw_img = "json:" + json.dumps( + { + "driver": "luks", + "file": {"filename": detached_payload_raw_img}, + "header": { + "filename": detached_header_img1, + }, + } +) +detached_header_qcow2_img = "json:" + json.dumps( + { + "driver": "luks", + "file": {"filename": detached_payload_qcow2_img}, + "header": {"filename": detached_header_img2}, + } +) + +secret_obj = "secret,id=sec0,data=foo" +luks_opts = "key-secret=sec0" + + +class TestDetachedLUKSHeader(QMPTestCase): + def setUp(self) -> None: + self.vm = iotests.VM() + self.vm.add_object(secret_obj) + self.vm.launch() + + # 1. Create the normal LUKS disk with 128M size + self.vm.blockdev_create( + {"driver": "file", "filename": luks_img, "size": 0} + ) + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=luks_img, + node_name="luks-1-storage", + ) + result = self.vm.blockdev_create( + { + "driver": imgfmt, + "file": "luks-1-storage", + "key-secret": "sec0", + "size": image_size, + "iter-time": 10, + } + ) + # None is expected + self.assertEqual(result, None) + + # 2. Create the LUKS disk with detached header (raw) + + # Create detached LUKS header + self.vm.blockdev_create( + {"driver": "file", "filename": detached_header_img1, "size": 0} + ) + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_header_img1, + node_name="luks-2-header-storage", + ) + + # Create detached LUKS raw payload + self.vm.blockdev_create( + {"driver": "file", "filename": detached_payload_raw_img, "size": 0} + ) + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_payload_raw_img, + node_name="luks-2-payload-storage", + ) + + # Format LUKS disk with detached header + result = self.vm.blockdev_create( + { + "driver": imgfmt, + "header": "luks-2-header-storage", + "file": "luks-2-payload-storage", + "key-secret": "sec0", + "preallocation": "full", + "size": image_size, + "iter-time": 10, + } + ) + self.assertEqual(result, None) + + self.vm.shutdown() + + # 3. Create the LUKS disk with detached header (qcow2) + + # Create detached LUKS header using qemu-img + res = qemu_img_create( + "-f", + "luks", + "--object", + secret_obj, + "-o", + luks_opts, + "-o", + "detached-header=true", + detached_header_img2, + ) + assert res.returncode == 0 + + # Create detached LUKS qcow2 payload + res = qemu_img_create( + "-f", "qcow2", detached_payload_qcow2_img, str(image_size) + ) + assert res.returncode == 0 + + def tearDown(self) -> None: + os.remove(luks_img) + os.remove(detached_header_img1) + os.remove(detached_header_img2) + os.remove(detached_payload_raw_img) + os.remove(detached_payload_qcow2_img) + + # Check if there was any qemu-io run that failed + if "Pattern verification failed" in self.vm.get_log(): + print("ERROR: Pattern verification failed:") + print(self.vm.get_log()) + self.fail("qemu-io pattern verification failed") + + def test_img_creation(self) -> None: + # Check if the images created above are expected + + data = qemu_img_info(luks_img)["format-specific"] + self.assertEqual(data["type"], imgfmt) + self.assertEqual(data["data"]["detached-header"], False) + + data = qemu_img_info(detached_header_raw_img)["format-specific"] + self.assertEqual(data["type"], imgfmt) + self.assertEqual(data["data"]["detached-header"], True) + + data = qemu_img_info(detached_header_qcow2_img)["format-specific"] + self.assertEqual(data["type"], imgfmt) + self.assertEqual(data["data"]["detached-header"], True) + + # Check if preallocation works + size = qemu_img_info(detached_payload_raw_img)["actual-size"] + self.assertGreaterEqual(size, image_size) + + def test_detached_luks_header(self) -> None: + self.vm.launch() + + # 1. Add the disk created above + + # Add normal LUKS disk + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=luks_img, + node_name="luks-1-storage", + ) + result = self.vm.qmp_log( + "blockdev-add", + driver="luks", + file="luks-1-storage", + key_secret="sec0", + node_name="luks-1-format", + ) + + # Expected result{ "return": {} } + self.assert_qmp(result, "return", {}) + + # Add detached LUKS header with raw payload + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_header_img1, + node_name="luks-header1-storage", + ) + + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_payload_raw_img, + node_name="luks-2-payload-raw-storage", + ) + + result = self.vm.qmp_log( + "blockdev-add", + driver=imgfmt, + header="luks-header1-storage", + file="luks-2-payload-raw-storage", + key_secret="sec0", + node_name="luks-2-payload-raw-format", + ) + self.assert_qmp(result, "return", {}) + + # Add detached LUKS header with qcow2 payload + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_header_img2, + node_name="luks-header2-storage", + ) + + self.vm.qmp_log( + "blockdev-add", + driver="file", + filename=detached_payload_qcow2_img, + node_name="luks-3-payload-qcow2-storage", + ) + + result = self.vm.qmp_log( + "blockdev-add", + driver=imgfmt, + header="luks-header2-storage", + file="luks-3-payload-qcow2-storage", + key_secret="sec0", + node_name="luks-3-payload-qcow2-format", + ) + self.assert_qmp(result, "return", {}) + + # 2. Do I/O test + + # Do some I/O to the image to see whether it still works + # (Pattern verification will be checked by tearDown()) + + # Normal LUKS disk + result = self.vm.qmp_log( + "human-monitor-command", + command_line='qemu-io luks-1-format "write -P 40 0 64k"', + ) + self.assert_qmp(result, "return", "") + + result = self.vm.qmp_log( + "human-monitor-command", + command_line='qemu-io luks-1-format "read -P 40 0 64k"', + ) + self.assert_qmp(result, "return", "") + + # Detached LUKS header with raw payload + cmd = 'qemu-io luks-2-payload-raw-format "write -P 41 0 64k"' + result = self.vm.qmp( + "human-monitor-command", + command_line=cmd + ) + self.assert_qmp(result, "return", "") + + cmd = 'qemu-io luks-2-payload-raw-format "read -P 41 0 64k"' + result = self.vm.qmp( + "human-monitor-command", + command_line=cmd + ) + self.assert_qmp(result, "return", "") + + # Detached LUKS header with qcow2 payload + cmd = 'qemu-io luks-3-payload-qcow2-format "write -P 42 0 64k"' + result = self.vm.qmp( + "human-monitor-command", + command_line=cmd + ) + self.assert_qmp(result, "return", "") + + cmd = 'qemu-io luks-3-payload-qcow2-format "read -P 42 0 64k"' + result = self.vm.qmp( + "human-monitor-command", + command_line=cmd + ) + self.assert_qmp(result, "return", "") + + self.vm.shutdown() + + +if __name__ == "__main__": + # Test image creation and I/O + iotests.main(supported_fmts=["luks"], supported_protocols=["file"]) diff --git a/tests/qemu-iotests/tests/luks-detached-header.out b/tests/qemu-iotests/tests/luks-detached-header.out new file mode 100644 index 0000000000..fbc63e62f8 --- /dev/null +++ b/tests/qemu-iotests/tests/luks-detached-header.out @@ -0,0 +1,5 @@ +.. +---------------------------------------------------------------------- +Ran 2 tests + +OK