From ab458750d06bc5dec241722df8b2d5cd2456f66d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 13 Sep 2021 17:55:58 +0100 Subject: [PATCH 01/10] block: better document SSH host key fingerprint checking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The docs still illustrate host key fingerprint checking using the old md5 hashes which are considered insecure and obsolete. Change it to illustrate using a sha256 hash. Also show how to extract the hash value from the known_hosts file. Reviewed-by: Hanna Reitz Signed-off-by: Daniel P. Berrangé --- docs/system/qemu-block-drivers.rst.inc | 30 ++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/docs/system/qemu-block-drivers.rst.inc b/docs/system/qemu-block-drivers.rst.inc index e313784426..dfe5d2293d 100644 --- a/docs/system/qemu-block-drivers.rst.inc +++ b/docs/system/qemu-block-drivers.rst.inc @@ -778,10 +778,32 @@ The optional *HOST_KEY_CHECK* parameter controls how the remote host's key is checked. The default is ``yes`` which means to use the local ``.ssh/known_hosts`` file. Setting this to ``no`` turns off known-hosts checking. Or you can check that the host key -matches a specific fingerprint: -``host_key_check=md5:78:45:8e:14:57:4f:d5:45:83:0a:0e:f3:49:82:c9:c8`` -(``sha1:`` can also be used as a prefix, but note that OpenSSH -tools only use MD5 to print fingerprints). +matches a specific fingerprint. The fingerprint can be provided in +``md5``, ``sha1``, or ``sha256`` format, however, it is strongly +recommended to only use ``sha256``, since the other options are +considered insecure by modern standards. The fingerprint value +must be given as a hex encoded string:: + + host_key_check=sha256:04ce2ae89ff4295a6b9c4111640bdcb3297858ee55cb434d9dd88796e93aa795 + +The key string may optionally contain ":" separators between +each pair of hex digits. + +The ``$HOME/.ssh/known_hosts`` file contains the base64 encoded +host keys. These can be converted into the format needed for +QEMU using a command such as:: + + $ for key in `grep 10.33.8.112 known_hosts | awk '{print $3}'` + do + echo $key | base64 -d | sha256sum + done + 6c3aa525beda9dc83eadfbd7e5ba7d976ecb59575d1633c87cd06ed2ed6e366f - + 12214fd9ea5b408086f98ecccd9958609bd9ac7c0ea316734006bc7818b45dc8 - + d36420137bcbd101209ef70c3b15dc07362fbe0fa53c5b135eba6e6afa82f0ce - + +Note that there can be multiple keys present per host, each with +different key ciphers. Care is needed to pick the key fingerprint +that matches the cipher QEMU will negotiate with the remote server. Currently authentication must be done using ssh-agent. Other authentication methods may be supported in future. From ea0f60e6f274ca8ecb45296272b2561e088b6fc8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 13 Sep 2021 17:57:34 +0100 Subject: [PATCH 02/10] block: support sha256 fingerprint with pre-blockdev options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When support for sha256 fingerprint checking was aded in commit bf783261f0aee6e81af3916bff7606d71ccdc153 Author: Daniel P. Berrangé Date: Tue Jun 22 12:51:56 2021 +0100 block/ssh: add support for sha256 host key fingerprints it was only made to work with -blockdev. Getting it working with -drive requires some extra custom parsing. Reviewed-by: Hanna Reitz Signed-off-by: Daniel P. Berrangé --- block/ssh.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/block/ssh.c b/block/ssh.c index 3b5bf34031..ac01301409 100644 --- a/block/ssh.c +++ b/block/ssh.c @@ -556,6 +556,11 @@ static bool ssh_process_legacy_options(QDict *output_opts, qdict_put_str(output_opts, "host-key-check.type", "sha1"); qdict_put_str(output_opts, "host-key-check.hash", &host_key_check[5]); + } else if (strncmp(host_key_check, "sha256:", 7) == 0) { + qdict_put_str(output_opts, "host-key-check.mode", "hash"); + qdict_put_str(output_opts, "host-key-check.type", "sha256"); + qdict_put_str(output_opts, "host-key-check.hash", + &host_key_check[7]); } else if (strcmp(host_key_check, "yes") == 0) { qdict_put_str(output_opts, "host-key-check.mode", "known_hosts"); } else { From e3296cc796aeaf319f3ed4e064ec309baf5e4da4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Mon, 13 Sep 2021 17:59:00 +0100 Subject: [PATCH 03/10] block: print the server key type and fingerprint on failure MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When validating the server key fingerprint fails, it is difficult for the user to know what they got wrong. The fingerprint accepted by QEMU is received in a different format than OpenSSH displays. There can also be keys for multiple different ciphers in known_hosts. It may not be obvious which cipher QEMU will use and whether it will be the same as OpenSSH. Address this by printing the server key type and its corresponding fingerprint in the format QEMU accepts. Reviewed-by: Hanna Reitz Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- block/ssh.c | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/block/ssh.c b/block/ssh.c index ac01301409..a64db146db 100644 --- a/block/ssh.c +++ b/block/ssh.c @@ -386,14 +386,28 @@ static int compare_fingerprint(const unsigned char *fingerprint, size_t len, return *host_key_check - '\0'; } +static char *format_fingerprint(const unsigned char *fingerprint, size_t len) +{ + static const char *hex = "0123456789abcdef"; + char *ret = g_new0(char, (len * 2) + 1); + for (size_t i = 0; i < len; i++) { + ret[i * 2] = hex[((fingerprint[i] >> 4) & 0xf)]; + ret[(i * 2) + 1] = hex[(fingerprint[i] & 0xf)]; + } + ret[len * 2] = '\0'; + return ret; +} + static int check_host_key_hash(BDRVSSHState *s, const char *hash, - enum ssh_publickey_hash_type type, Error **errp) + enum ssh_publickey_hash_type type, const char *typestr, + Error **errp) { int r; ssh_key pubkey; unsigned char *server_hash; size_t server_hash_len; + const char *keytype; r = ssh_get_server_publickey(s->session, &pubkey); if (r != SSH_OK) { @@ -401,6 +415,8 @@ check_host_key_hash(BDRVSSHState *s, const char *hash, return -EINVAL; } + keytype = ssh_key_type_to_char(ssh_key_type(pubkey)); + r = ssh_get_publickey_hash(pubkey, type, &server_hash, &server_hash_len); ssh_key_free(pubkey); if (r != 0) { @@ -410,12 +426,16 @@ check_host_key_hash(BDRVSSHState *s, const char *hash, } r = compare_fingerprint(server_hash, server_hash_len, hash); - ssh_clean_pubkey_hash(&server_hash); if (r != 0) { - error_setg(errp, "remote host key does not match host_key_check '%s'", - hash); + g_autofree char *server_fp = format_fingerprint(server_hash, + server_hash_len); + error_setg(errp, "remote host %s key fingerprint '%s:%s' " + "does not match host_key_check '%s:%s'", + keytype, typestr, server_fp, typestr, hash); + ssh_clean_pubkey_hash(&server_hash); return -EPERM; } + ssh_clean_pubkey_hash(&server_hash); return 0; } @@ -436,13 +456,16 @@ static int check_host_key(BDRVSSHState *s, SshHostKeyCheck *hkc, Error **errp) case SSH_HOST_KEY_CHECK_MODE_HASH: if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_MD5) { return check_host_key_hash(s, hkc->u.hash.hash, - SSH_PUBLICKEY_HASH_MD5, errp); + SSH_PUBLICKEY_HASH_MD5, "md5", + errp); } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA1) { return check_host_key_hash(s, hkc->u.hash.hash, - SSH_PUBLICKEY_HASH_SHA1, errp); + SSH_PUBLICKEY_HASH_SHA1, "sha1", + errp); } else if (hkc->u.hash.type == SSH_HOST_KEY_CHECK_HASH_TYPE_SHA256) { return check_host_key_hash(s, hkc->u.hash.hash, - SSH_PUBLICKEY_HASH_SHA256, errp); + SSH_PUBLICKEY_HASH_SHA256, "sha256", + errp); } g_assert_not_reached(); break; From 8f46f56260cb7eaf1bcc77366c8ce69af9bd5bec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 29 Jul 2021 12:15:57 +0100 Subject: [PATCH 04/10] seccomp: allow action to be customized per syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We're currently tailoring whether to use kill process or return EPERM based on the syscall set. This is not flexible enough for future requirements where we also need to be able to return a variety of actions on a per-syscall granularity. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- softmmu/qemu-seccomp.c | 172 +++++++++++++++++++++++++---------------- 1 file changed, 106 insertions(+), 66 deletions(-) diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c index f50026778c..9f6df8d033 100644 --- a/softmmu/qemu-seccomp.c +++ b/softmmu/qemu-seccomp.c @@ -38,6 +38,7 @@ struct QemuSeccompSyscall { uint8_t set; uint8_t narg; const struct scmp_arg_cmp *arg_cmp; + uint32_t action; }; const struct scmp_arg_cmp sched_setscheduler_arg[] = { @@ -47,61 +48,111 @@ const struct scmp_arg_cmp sched_setscheduler_arg[] = { static const struct QemuSeccompSyscall denylist[] = { /* default set of syscalls that should get blocked */ - { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT }, - { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT }, + { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(swapon), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(swapoff), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(syslog), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(mount), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(umount), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(kexec_load), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(afs_syscall), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(break), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(ftime), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(getpmsg), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(gtty), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(lock), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(mpx), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(prof), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(profil), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(putpmsg), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(security), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(stty), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(tuxcall), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(ulimit), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(vserver), QEMU_SECCOMP_SET_DEFAULT, + 0, NULL, SCMP_ACT_TRAP }, /* obsolete */ - { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE }, - { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE }, + { SCMP_SYS(readdir), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(_sysctl), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(bdflush), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(create_module), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(get_kernel_syms), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(query_module), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(sgetmask), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(ssetmask), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(sysfs), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(uselib), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(ustat), QEMU_SECCOMP_SET_OBSOLETE, + 0, NULL, SCMP_ACT_TRAP }, /* privileged */ - { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED }, - { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED }, + { SCMP_SYS(setuid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setgid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setpgid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setsid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setreuid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setregid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setresuid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setresgid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setfsuid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(setfsgid), QEMU_SECCOMP_SET_PRIVILEGED, + 0, NULL, SCMP_ACT_TRAP }, /* spawn */ - { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN }, - { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN }, - { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN }, + { SCMP_SYS(fork), QEMU_SECCOMP_SET_SPAWN, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(vfork), QEMU_SECCOMP_SET_SPAWN, + 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN, + 0, NULL, SCMP_ACT_TRAP }, /* resource control */ - { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL }, - { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL }, + { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL, + 0, NULL, SCMP_ACT_ERRNO(EPERM) }, + { SCMP_SYS(sched_setparam), QEMU_SECCOMP_SET_RESOURCECTL, + 0, NULL, SCMP_ACT_ERRNO(EPERM) }, { SCMP_SYS(sched_setscheduler), QEMU_SECCOMP_SET_RESOURCECTL, - ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg }, - { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL }, + ARRAY_SIZE(sched_setscheduler_arg), sched_setscheduler_arg, + SCMP_ACT_ERRNO(EPERM) }, + { SCMP_SYS(sched_setaffinity), QEMU_SECCOMP_SET_RESOURCECTL, + 0, NULL, SCMP_ACT_ERRNO(EPERM) }, }; static inline __attribute__((unused)) int @@ -115,15 +166,11 @@ qemu_seccomp(unsigned int operation, unsigned int flags, void *args) #endif } -static uint32_t qemu_seccomp_get_action(int set) +static uint32_t qemu_seccomp_update_action(uint32_t action) { - switch (set) { - case QEMU_SECCOMP_SET_DEFAULT: - case QEMU_SECCOMP_SET_OBSOLETE: - case QEMU_SECCOMP_SET_PRIVILEGED: - case QEMU_SECCOMP_SET_SPAWN: { #if defined(SECCOMP_GET_ACTION_AVAIL) && defined(SCMP_ACT_KILL_PROCESS) && \ defined(SECCOMP_RET_KILL_PROCESS) + if (action == SCMP_ACT_TRAP) { static int kill_process = -1; if (kill_process == -1) { uint32_t action = SECCOMP_RET_KILL_PROCESS; @@ -137,16 +184,9 @@ static uint32_t qemu_seccomp_get_action(int set) if (kill_process == 1) { return SCMP_ACT_KILL_PROCESS; } + } #endif - return SCMP_ACT_TRAP; - } - - case QEMU_SECCOMP_SET_RESOURCECTL: - return SCMP_ACT_ERRNO(EPERM); - - default: - g_assert_not_reached(); - } + return action; } @@ -175,7 +215,7 @@ static int seccomp_start(uint32_t seccomp_opts, Error **errp) continue; } - action = qemu_seccomp_get_action(denylist[i].set); + action = qemu_seccomp_update_action(denylist[i].action); rc = seccomp_rule_add_array(ctx, action, denylist[i].num, denylist[i].narg, denylist[i].arg_cmp); if (rc < 0) { From d2ea8dac705fe9bca9b6b26a7aeb063d1a406f7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 29 Jul 2021 13:15:43 +0100 Subject: [PATCH 05/10] seccomp: add unit test for seccomp filtering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The handling of some syscalls / libc function is quite subtle. For example, 'fork' at a libc level doesn't always correspond to 'fork' at a syscall level, since the 'clone' syscall is preferred usually. The unit test will help to detect these kind of problems. A point of difficulty in writing a test though is that the QEMU build process may already be confined by seccomp. For example, if running inside a container. Since we can't predict what filtering might have been applied already, we are quite conservative and skip all tests if we see any kind of seccomp filter active. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- MAINTAINERS | 1 + tests/unit/meson.build | 4 + tests/unit/test-seccomp.c | 270 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 275 insertions(+) create mode 100644 tests/unit/test-seccomp.c diff --git a/MAINTAINERS b/MAINTAINERS index 4b3ae2ab08..1fe647eb08 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2982,6 +2982,7 @@ M: Eduardo Otubo S: Supported F: softmmu/qemu-seccomp.c F: include/sysemu/seccomp.h +F: tests/unit/test-seccomp.c Cryptography M: Daniel P. Berrange diff --git a/tests/unit/meson.build b/tests/unit/meson.build index 64a5e7bfde..cd06f0eaf5 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -53,6 +53,10 @@ if have_system or have_tools tests += { 'test-qmp-event': [testqapi], } + + if seccomp.found() + tests += {'test-seccomp': ['../../softmmu/qemu-seccomp.c', seccomp]} + endif endif if have_block diff --git a/tests/unit/test-seccomp.c b/tests/unit/test-seccomp.c new file mode 100644 index 0000000000..10ab3e8fe5 --- /dev/null +++ b/tests/unit/test-seccomp.c @@ -0,0 +1,270 @@ +/* + * QEMU seccomp test suite + * + * Copyright (c) 2021 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see . + * + */ + +#include "qemu/osdep.h" +#include "qemu/config-file.h" +#include "qemu/option.h" +#include "sysemu/seccomp.h" +#include "qapi/error.h" +#include "qemu/module.h" + +#include +#include + +static void test_seccomp_helper(const char *args, bool killed, + int errnum, int (*doit)(void)) +{ + if (g_test_subprocess()) { + QemuOptsList *olist; + QemuOpts *opts; + int ret; + + module_call_init(MODULE_INIT_OPTS); + olist = qemu_find_opts("sandbox"); + g_assert(olist != NULL); + + opts = qemu_opts_parse_noisily(olist, args, true); + g_assert(opts != NULL); + + parse_sandbox(NULL, opts, &error_abort); + + /* Running in a child process */ + ret = doit(); + + if (errnum != 0) { + g_assert(ret != 0); + g_assert(errno == errnum); + } else { + g_assert(ret == 0); + } + + _exit(0); + } else { + /* Running in main test process, spawning the child */ + g_test_trap_subprocess(NULL, 0, 0); + if (killed) { + g_test_trap_assert_failed(); + } else { + g_test_trap_assert_passed(); + } + } +} + + +static void test_seccomp_killed(const char *args, int (*doit)(void)) +{ + test_seccomp_helper(args, true, 0, doit); +} + +static void test_seccomp_errno(const char *args, int errnum, int (*doit)(void)) +{ + test_seccomp_helper(args, false, errnum, doit); +} + +static void test_seccomp_passed(const char *args, int (*doit)(void)) +{ + test_seccomp_helper(args, false, 0, doit); +} + +#ifdef SYS_fork +static int doit_sys_fork(void) +{ + int ret = syscall(SYS_fork); + if (ret < 0) { + return ret; + } + if (ret == 0) { + _exit(0); + } + return 0; +} + +static void test_seccomp_sys_fork_on_nospawn(void) +{ + test_seccomp_killed("on,spawn=deny", doit_sys_fork); +} + +static void test_seccomp_sys_fork_on(void) +{ + test_seccomp_passed("on", doit_sys_fork); +} + +static void test_seccomp_sys_fork_off(void) +{ + test_seccomp_passed("off", doit_sys_fork); +} +#endif + +static int doit_fork(void) +{ + int ret = fork(); + if (ret < 0) { + return ret; + } + if (ret == 0) { + _exit(0); + } + return 0; +} + +static void test_seccomp_fork_on_nospawn(void) +{ + /* XXX fixme - should be killed */ + test_seccomp_passed("on,spawn=deny", doit_fork); +} + +static void test_seccomp_fork_on(void) +{ + test_seccomp_passed("on", doit_fork); +} + +static void test_seccomp_fork_off(void) +{ + test_seccomp_passed("off", doit_fork); +} + +static void *noop(void *arg) +{ + return arg; +} + +static int doit_thread(void) +{ + pthread_t th; + int ret = pthread_create(&th, NULL, noop, NULL); + if (ret != 0) { + errno = ret; + return -1; + } else { + pthread_join(th, NULL); + return 0; + } +} + +static void test_seccomp_thread_on(void) +{ + test_seccomp_passed("on", doit_thread); +} + +static void test_seccomp_thread_on_nospawn(void) +{ + test_seccomp_passed("on,spawn=deny", doit_thread); +} + +static void test_seccomp_thread_off(void) +{ + test_seccomp_passed("off", doit_thread); +} + +static int doit_sched(void) +{ + struct sched_param param = { .sched_priority = 0 }; + return sched_setscheduler(getpid(), SCHED_OTHER, ¶m); +} + +static void test_seccomp_sched_on_nores(void) +{ + test_seccomp_errno("on,resourcecontrol=deny", EPERM, doit_sched); +} + +static void test_seccomp_sched_on(void) +{ + test_seccomp_passed("on", doit_sched); +} + +static void test_seccomp_sched_off(void) +{ + test_seccomp_passed("off", doit_sched); +} + +static bool can_play_with_seccomp(void) +{ + g_autofree char *status = NULL; + g_auto(GStrv) lines = NULL; + size_t i; + + if (!g_file_get_contents("/proc/self/status", &status, NULL, NULL)) { + return false; + } + + lines = g_strsplit(status, "\n", 0); + + for (i = 0; lines[i] != NULL; i++) { + if (g_str_has_prefix(lines[i], "Seccomp:")) { + /* + * "Seccomp: 1" or "Seccomp: 2" indicate we're already + * confined, probably as we're inside a container. In + * this case our tests might get unexpected results, + * so we can't run reliably + */ + if (!strchr(lines[i], '0')) { + return false; + } + + return true; + } + } + + /* Doesn't look like seccomp is enabled in the kernel */ + return false; +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + if (can_play_with_seccomp()) { +#ifdef SYS_fork + g_test_add_func("/softmmu/seccomp/sys-fork/on", + test_seccomp_sys_fork_on); + g_test_add_func("/softmmu/seccomp/sys-fork/on-nospawn", + test_seccomp_sys_fork_on_nospawn); + g_test_add_func("/softmmu/seccomp/sys-fork/off", + test_seccomp_sys_fork_off); +#endif + + g_test_add_func("/softmmu/seccomp/fork/on", + test_seccomp_fork_on); + g_test_add_func("/softmmu/seccomp/fork/on-nospawn", + test_seccomp_fork_on_nospawn); + g_test_add_func("/softmmu/seccomp/fork/off", + test_seccomp_fork_off); + + g_test_add_func("/softmmu/seccomp/thread/on", + test_seccomp_thread_on); + g_test_add_func("/softmmu/seccomp/thread/on-nospawn", + test_seccomp_thread_on_nospawn); + g_test_add_func("/softmmu/seccomp/thread/off", + test_seccomp_thread_off); + + if (doit_sched() == 0) { + /* + * musl doesn't impl sched_setscheduler, hence + * we check above if it works first + */ + g_test_add_func("/softmmu/seccomp/sched/on", + test_seccomp_sched_on); + g_test_add_func("/softmmu/seccomp/sched/on-nores", + test_seccomp_sched_on_nores); + g_test_add_func("/softmmu/seccomp/sched/off", + test_seccomp_sched_off); + } + } + return g_test_run(); +} From 5a2f693f07a1e93ada5277b2fb1530b2698be0fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 29 Jul 2021 13:32:37 +0100 Subject: [PATCH 06/10] seccomp: fix blocking of process spawning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When '-sandbox on,spawn=deny' is given, we are supposed to block the ability to spawn processes. We naively blocked the 'fork' syscall, forgetting that any modern libc will use the 'clone' syscall instead. We can't simply block the 'clone' syscall though, as that will break thread creation. We thus list the set of flags used to create threads and block anything that doesn't match this exactly. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- softmmu/qemu-seccomp.c | 101 ++++++++++++++++++++++++++++++++++++++ tests/unit/test-seccomp.c | 3 +- 2 files changed, 102 insertions(+), 2 deletions(-) diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c index 9f6df8d033..57139cc9ce 100644 --- a/softmmu/qemu-seccomp.c +++ b/softmmu/qemu-seccomp.c @@ -46,6 +46,82 @@ const struct scmp_arg_cmp sched_setscheduler_arg[] = { { .arg = 1, .op = SCMP_CMP_NE, .datum_a = SCHED_IDLE } }; +/* + * See 'NOTES' in 'man 2 clone' - s390 & cross have 'flags' in + * different position to other architectures + */ +#if defined(HOST_S390X) || defined(HOST_S390) || defined(HOST_CRIS) +#define CLONE_FLAGS_ARG 1 +#else +#define CLONE_FLAGS_ARG 0 +#endif + +#ifndef CLONE_PIDFD +# define CLONE_PIDFD 0x00001000 +#endif + +#define REQUIRE_CLONE_FLAG(flag) \ + const struct scmp_arg_cmp clone_arg ## flag[] = { \ + { .arg = CLONE_FLAGS_ARG, \ + .op = SCMP_CMP_MASKED_EQ, \ + .datum_a = flag, .datum_b = 0 } } + +#define FORBID_CLONE_FLAG(flag) \ + const struct scmp_arg_cmp clone_arg ## flag[] = { \ + { .arg = CLONE_FLAGS_ARG, \ + .op = SCMP_CMP_MASKED_EQ, \ + .datum_a = flag, .datum_b = flag } } + +#define RULE_CLONE_FLAG(flag) \ + { SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN, \ + ARRAY_SIZE(clone_arg ## flag), clone_arg ## flag, SCMP_ACT_TRAP } + +/* If no CLONE_* flags are set, except CSIGNAL, deny */ +const struct scmp_arg_cmp clone_arg_none[] = { + { .arg = CLONE_FLAGS_ARG, + .op = SCMP_CMP_MASKED_EQ, + .datum_a = ~(CSIGNAL), .datum_b = 0 } +}; + +/* + * pthread_create should always set all of these. + */ +REQUIRE_CLONE_FLAG(CLONE_VM); +REQUIRE_CLONE_FLAG(CLONE_FS); +REQUIRE_CLONE_FLAG(CLONE_FILES); +REQUIRE_CLONE_FLAG(CLONE_SIGHAND); +REQUIRE_CLONE_FLAG(CLONE_THREAD); +REQUIRE_CLONE_FLAG(CLONE_SYSVSEM); +REQUIRE_CLONE_FLAG(CLONE_SETTLS); +REQUIRE_CLONE_FLAG(CLONE_PARENT_SETTID); +REQUIRE_CLONE_FLAG(CLONE_CHILD_CLEARTID); +/* + * Musl sets this in pthread_create too, but it is + * obsolete and harmless since its behaviour is + * subsumed under CLONE_THREAD + */ +/*REQUIRE_CLONE_FLAG(CLONE_DETACHED);*/ + + +/* + * These all indicate an attempt to spawn a process + * instead of a thread, or other undesirable scenarios + */ +FORBID_CLONE_FLAG(CLONE_PIDFD); +FORBID_CLONE_FLAG(CLONE_PTRACE); +FORBID_CLONE_FLAG(CLONE_VFORK); +FORBID_CLONE_FLAG(CLONE_PARENT); +FORBID_CLONE_FLAG(CLONE_NEWNS); +FORBID_CLONE_FLAG(CLONE_UNTRACED); +FORBID_CLONE_FLAG(CLONE_NEWCGROUP); +FORBID_CLONE_FLAG(CLONE_NEWUTS); +FORBID_CLONE_FLAG(CLONE_NEWIPC); +FORBID_CLONE_FLAG(CLONE_NEWUSER); +FORBID_CLONE_FLAG(CLONE_NEWPID); +FORBID_CLONE_FLAG(CLONE_NEWNET); +FORBID_CLONE_FLAG(CLONE_IO); + + static const struct QemuSeccompSyscall denylist[] = { /* default set of syscalls that should get blocked */ { SCMP_SYS(reboot), QEMU_SECCOMP_SET_DEFAULT, @@ -143,6 +219,31 @@ static const struct QemuSeccompSyscall denylist[] = { 0, NULL, SCMP_ACT_TRAP }, { SCMP_SYS(execve), QEMU_SECCOMP_SET_SPAWN, 0, NULL, SCMP_ACT_TRAP }, + { SCMP_SYS(clone), QEMU_SECCOMP_SET_SPAWN, + ARRAY_SIZE(clone_arg_none), clone_arg_none, SCMP_ACT_TRAP }, + RULE_CLONE_FLAG(CLONE_VM), + RULE_CLONE_FLAG(CLONE_FS), + RULE_CLONE_FLAG(CLONE_FILES), + RULE_CLONE_FLAG(CLONE_SIGHAND), + RULE_CLONE_FLAG(CLONE_THREAD), + RULE_CLONE_FLAG(CLONE_SYSVSEM), + RULE_CLONE_FLAG(CLONE_SETTLS), + RULE_CLONE_FLAG(CLONE_PARENT_SETTID), + RULE_CLONE_FLAG(CLONE_CHILD_CLEARTID), + /*RULE_CLONE_FLAG(CLONE_DETACHED),*/ + RULE_CLONE_FLAG(CLONE_PIDFD), + RULE_CLONE_FLAG(CLONE_PTRACE), + RULE_CLONE_FLAG(CLONE_VFORK), + RULE_CLONE_FLAG(CLONE_PARENT), + RULE_CLONE_FLAG(CLONE_NEWNS), + RULE_CLONE_FLAG(CLONE_UNTRACED), + RULE_CLONE_FLAG(CLONE_NEWCGROUP), + RULE_CLONE_FLAG(CLONE_NEWUTS), + RULE_CLONE_FLAG(CLONE_NEWIPC), + RULE_CLONE_FLAG(CLONE_NEWUSER), + RULE_CLONE_FLAG(CLONE_NEWPID), + RULE_CLONE_FLAG(CLONE_NEWNET), + RULE_CLONE_FLAG(CLONE_IO), /* resource control */ { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL, 0, NULL, SCMP_ACT_ERRNO(EPERM) }, diff --git a/tests/unit/test-seccomp.c b/tests/unit/test-seccomp.c index 10ab3e8fe5..3d7771e46c 100644 --- a/tests/unit/test-seccomp.c +++ b/tests/unit/test-seccomp.c @@ -126,8 +126,7 @@ static int doit_fork(void) static void test_seccomp_fork_on_nospawn(void) { - /* XXX fixme - should be killed */ - test_seccomp_passed("on,spawn=deny", doit_fork); + test_seccomp_killed("on,spawn=deny", doit_fork); } static void test_seccomp_fork_on(void) From c542b302707628609f2a33bcbd3e442282806b8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 29 Jul 2021 16:43:01 +0100 Subject: [PATCH 07/10] seccomp: block use of clone3 syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Modern glibc will use clone3 instead of clone, when it detects that it is available. We need to compare flags in order to decide whether to allow clone (thread create vs process fork), but in clone3 the flags are hidden inside a struct. Seccomp can't currently match on data inside a struct, so our only option is to block clone3 entirely. If we use ENOSYS to block it, then glibc transparently falls back to clone. This may need to be revisited if Linux adds a new architecture in future and only provides clone3, without clone. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- softmmu/qemu-seccomp.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c index 57139cc9ce..a7bb5c350f 100644 --- a/softmmu/qemu-seccomp.c +++ b/softmmu/qemu-seccomp.c @@ -244,6 +244,10 @@ static const struct QemuSeccompSyscall denylist[] = { RULE_CLONE_FLAG(CLONE_NEWPID), RULE_CLONE_FLAG(CLONE_NEWNET), RULE_CLONE_FLAG(CLONE_IO), +#ifdef __SNR_clone3 + { SCMP_SYS(clone3), QEMU_SECCOMP_SET_SPAWN, + 0, NULL, SCMP_ACT_ERRNO(ENOSYS) }, +#endif /* resource control */ { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL, 0, NULL, SCMP_ACT_ERRNO(EPERM) }, From 463805711056b431a74917a75cda75ec67fec3d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 29 Apr 2021 11:25:13 +0100 Subject: [PATCH 08/10] seccomp: block setns, unshare and execveat syscalls MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setns/unshare are used to change namespaces which is not something QEMU needs to be able todo. execveat is a new variant of execve so should be blocked just like execve already is. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- softmmu/qemu-seccomp.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/softmmu/qemu-seccomp.c b/softmmu/qemu-seccomp.c index a7bb5c350f..deaf8a4ef5 100644 --- a/softmmu/qemu-seccomp.c +++ b/softmmu/qemu-seccomp.c @@ -248,6 +248,11 @@ static const struct QemuSeccompSyscall denylist[] = { { SCMP_SYS(clone3), QEMU_SECCOMP_SET_SPAWN, 0, NULL, SCMP_ACT_ERRNO(ENOSYS) }, #endif +#ifdef __SNR_execveat + { SCMP_SYS(execveat), QEMU_SECCOMP_SET_SPAWN }, +#endif + { SCMP_SYS(setns), QEMU_SECCOMP_SET_SPAWN }, + { SCMP_SYS(unshare), QEMU_SECCOMP_SET_SPAWN }, /* resource control */ { SCMP_SYS(setpriority), QEMU_SECCOMP_SET_RESOURCECTL, 0, NULL, SCMP_ACT_ERRNO(EPERM) }, From e3160dea5dccfca7b5dd19ebb7bbd069c58c0ea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Thu, 3 Feb 2022 10:49:28 +0000 Subject: [PATCH 09/10] MAINTAINERS: take over seccomp from Eduardo Otubo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eduardo has indicated that he no longer has time to be involved in a QEMU maintainership role. As one of the more frequent contributors of patches and design ideas to seccomp, I'll take over in an "Odd Fixes" role. Acked-by: Eduardo Otubo Signed-off-by: Daniel P. Berrangé --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/MAINTAINERS b/MAINTAINERS index 1fe647eb08..7f345e30eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2978,8 +2978,8 @@ F: docs/sphinx/fakedbusdoc.py F: tests/qtest/dbus* Seccomp -M: Eduardo Otubo -S: Supported +M: Daniel P. Berrange +S: Odd Fixes F: softmmu/qemu-seccomp.c F: include/sysemu/seccomp.h F: tests/unit/test-seccomp.c From 2720ceda0521bc43139cfdf45e3e470559e11ce3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20P=2E=20Berrang=C3=A9?= Date: Fri, 21 Jan 2022 14:46:37 +0000 Subject: [PATCH 10/10] docs: expand firmware descriptor to allow flash without NVRAM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current firmware descriptor schema for flash requires that both the executable to NVRAM template paths be provided. This is fine for the most common usage of EDK2 builds in virtualization where the separate _CODE and _VARS files are provided. With confidential computing technology like AMD SEV, persistent storage of variables may be completely disabled because the firmware requires a known clean state on every cold boot. There is no way to express this in the firmware descriptor today. Even with regular EDK2 builds it is possible to create a firmware that has both executable code and variable persistence in a single file. This hasn't been commonly used, since it would mean every guest bootup would need to clone the full firmware file, leading to redundant duplicate storage of the code portion. In some scenarios this may not matter and might even be beneficial. For example if a public cloud allows users to bring their own firmware, such that the user can pre-enroll their own secure boot keys, you're going to have this copied on disk for each tenant already. At this point the it can be simpler to just deal with a single file rather than split builds. The firmware descriptor ought to be able to express this combined firmware model too. This all points towards expanding the schema for flash with a 'mode' concept: - "split" - the current implicit behaviour with separate files for code and variables. - "combined" - the alternate behaviour where a single file contains both code and variables. - "stateless" - the confidential computing use case where storage of variables is completely disable, leaving only the code. Reviewed-by: Kashyap Chamarthy Reviewed-by: Philippe Mathieu-Daudé Signed-off-by: Daniel P. Berrangé --- docs/interop/firmware.json | 54 ++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 8 deletions(-) diff --git a/docs/interop/firmware.json b/docs/interop/firmware.json index 8d8b0be030..4e049b1c7c 100644 --- a/docs/interop/firmware.json +++ b/docs/interop/firmware.json @@ -210,24 +210,61 @@ 'data' : { 'filename' : 'str', 'format' : 'BlockdevDriver' } } + +## +# @FirmwareFlashType: +# +# Describes how the firmware build handles code versus variable +# persistence. +# +# @split: the executable file contains code while the NVRAM +# template provides variable storage. The executable +# must be configured read-only and can be shared between +# multiple guests. The NVRAM template must be cloned +# for each new guest and configured read-write. +# +# @combined: the executable file contains both code and +# variable storage. The executable must be cloned +# for each new guest and configured read-write. +# No NVRAM template will be specified. +# +# @stateless: the executable file contains code and variable +# storage is not persisted. The executable must +# be configured read-only and can be shared +# between multiple guests. No NVRAM template +# will be specified. +# +# Since: 7.0.0 +## +{ 'enum': 'FirmwareFlashMode', + 'data': [ 'split', 'combined', 'stateless' ] } + ## # @FirmwareMappingFlash: # # Describes loading and mapping properties for the firmware executable # and its accompanying NVRAM file, when @FirmwareDevice is @flash. # -# @executable: Identifies the firmware executable. The firmware -# executable may be shared by multiple virtual machine -# definitions. The preferred corresponding QEMU command -# line options are +# @mode: Describes how the firmware build handles code versus variable +# storage. If not present, it must be treated as if it was +# configured with value ``split``. Since: 7.0.0 +# +# @executable: Identifies the firmware executable. The @mode +# indicates whether there will be an associated +# NVRAM template present. The preferred +# corresponding QEMU command line options are # -drive if=none,id=pflash0,readonly=on,file=@executable.@filename,format=@executable.@format # -machine pflash0=pflash0 -# or equivalent -blockdev instead of -drive. +# or equivalent -blockdev instead of -drive. When +# @mode is ``combined`` the executable must be +# cloned before use and configured with readonly=off. # With QEMU versions older than 4.0, you have to use # -drive if=pflash,unit=0,readonly=on,file=@executable.@filename,format=@executable.@format # # @nvram-template: Identifies the NVRAM template compatible with -# @executable. Management software instantiates an +# @executable, when @mode is set to ``split``, +# otherwise it should not be present. +# Management software instantiates an # individual copy -- a specific NVRAM file -- from # @nvram-template.@filename for each new virtual # machine definition created. @nvram-template.@filename @@ -246,8 +283,9 @@ # Since: 3.0 ## { 'struct' : 'FirmwareMappingFlash', - 'data' : { 'executable' : 'FirmwareFlashFile', - 'nvram-template' : 'FirmwareFlashFile' } } + 'data' : { '*mode': 'FirmwareFlashMode', + 'executable' : 'FirmwareFlashFile', + '*nvram-template' : 'FirmwareFlashFile' } } ## # @FirmwareMappingKernel: