Introduce qemu_security_policy_taint() which allows unsafe (read "not very maintained") code to 'taint' QEMU security policy.
The "security policy" is the @SecurityPolicy QAPI enum, composed of: - "none" (no policy, current behavior) - "warn" (display a warning when the policy is tainted, keep going) - "strict" (once tainted, exit QEMU before starting the VM) The qemu_security_policy_is_strict() helper is also provided, which will be proved useful once a VM is started (example we do not want to kill a running VM if an unsafe device is hot-added). Signed-off-by: Philippe Mathieu-Daudé <phi...@redhat.com> --- qapi/run-state.json | 16 +++++++++++ include/qemu-common.h | 19 ++++++++++++ softmmu/vl.c | 67 +++++++++++++++++++++++++++++++++++++++++++ qemu-options.hx | 17 +++++++++++ 4 files changed, 119 insertions(+) diff --git a/qapi/run-state.json b/qapi/run-state.json index 43d66d700fc..b15a107fa01 100644 --- a/qapi/run-state.json +++ b/qapi/run-state.json @@ -638,3 +638,19 @@ { 'struct': 'MemoryFailureFlags', 'data': { 'action-required': 'bool', 'recursive': 'bool'} } + +## +# @SecurityPolicy: +# +# An enumeration of the actions taken when the security policy is tainted. +# +# @none: do nothing. +# +# @warn: display a warning. +# +# @strict: prohibit QEMU to start a VM. +# +# Since: 6.2 +## +{ 'enum': 'SecurityPolicy', + 'data': [ 'none', 'warn', 'strict' ] } diff --git a/include/qemu-common.h b/include/qemu-common.h index 73bcf763ed8..bf0b054bb66 100644 --- a/include/qemu-common.h +++ b/include/qemu-common.h @@ -139,4 +139,23 @@ void page_size_init(void); * returned. */ bool dump_in_progress(void); +/** + * qemu_security_policy_taint: + * @tainting whether any security policy is tainted (compromised). + * @fmt: taint reason format string + * ...: list of arguments to interpolate into @fmt, like printf(). + * + * Allow unsafe code path to taint the global security policy. + * See #SecurityPolicy. + */ +void qemu_security_policy_taint(bool tainting, const char *fmt, ...) + GCC_FMT_ATTR(2, 3); + +/** + * qemu_security_policy_is_strict: + * + * Return %true if the global security policy is 'strict', %false otherwise. + */ +bool qemu_security_policy_is_strict(void); + #endif diff --git a/softmmu/vl.c b/softmmu/vl.c index 55ab70eb97f..92c05ac97ee 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -489,6 +489,20 @@ static QemuOptsList qemu_action_opts = { }, }; +static QemuOptsList qemu_security_policy_opts = { + .name = "security-policy", + .implied_opt_name = "policy", + .merge_lists = true, + .head = QTAILQ_HEAD_INITIALIZER(qemu_security_policy_opts.head), + .desc = { + { + .name = "policy", + .type = QEMU_OPT_STRING, + }, + { /* end of list */ } + }, +}; + const char *qemu_get_vm_name(void) { return qemu_name; @@ -600,6 +614,52 @@ static int cleanup_add_fd(void *opaque, QemuOpts *opts, Error **errp) } #endif +static SecurityPolicy security_policy = SECURITY_POLICY_NONE; + +bool qemu_security_policy_is_strict(void) +{ + return security_policy == SECURITY_POLICY_STRICT; +} + +static int select_security_policy(const char *p) +{ + int policy; + char *qapi_value; + + qapi_value = g_ascii_strdown(p, -1); + policy = qapi_enum_parse(&SecurityPolicy_lookup, qapi_value, -1, NULL); + g_free(qapi_value); + if (policy < 0) { + return -1; + } + security_policy = policy; + + return 0; +} + +void qemu_security_policy_taint(bool tainting, const char *fmt, ...) +{ + va_list ap; + g_autofree char *efmt = NULL; + + if (security_policy == SECURITY_POLICY_NONE || !tainting) { + return; + } + + va_start(ap, fmt); + if (security_policy == SECURITY_POLICY_STRICT) { + efmt = g_strdup_printf("%s taints QEMU security policy, exiting.", fmt); + error_vreport(efmt, ap); + exit(EXIT_FAILURE); + } else if (security_policy == SECURITY_POLICY_WARN) { + efmt = g_strdup_printf("%s taints QEMU security policy.", fmt); + warn_vreport(efmt, ap); + } else { + g_assert_not_reached(); + } + va_end(ap); +} + /***********************************************************/ /* QEMU Block devices */ @@ -2764,6 +2824,7 @@ void qemu_init(int argc, char **argv, char **envp) qemu_add_opts(&qemu_semihosting_config_opts); qemu_add_opts(&qemu_fw_cfg_opts); qemu_add_opts(&qemu_action_opts); + qemu_add_opts(&qemu_security_policy_opts); module_call_init(MODULE_INIT_OPTS); error_init(argv[0]); @@ -3230,6 +3291,12 @@ void qemu_init(int argc, char **argv, char **envp) exit(1); } break; + case QEMU_OPTION_security_policy: + if (select_security_policy(optarg) == -1) { + error_report("unknown -security-policy parameter"); + exit(1); + } + break; case QEMU_OPTION_parallel: add_device_config(DEV_PARALLEL, optarg); default_parallel = 0; diff --git a/qemu-options.hx b/qemu-options.hx index 8f603cc7e65..d9939f7ae1d 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4298,6 +4298,23 @@ SRST ERST +DEF("security-policy", HAS_ARG, QEMU_OPTION_security_policy, \ + "-security-policy none|warn|strict\n" \ + " action when security policy is tainted [default=none]\n", + QEMU_ARCH_ALL) +SRST +``-security-policy policy`` + The policy controls what QEMU will do when an unsecure feature is + used, tainting the process security. The default is ``none`` (do + nothing). Other possible actions are: ``warn`` (display a warning + and keep going) or ``strict`` (exits QEMU before launching a VM). + + Examples: + + ``-security-policy warn``; \ ``-security-policy strict`` + +ERST + DEF("echr", HAS_ARG, QEMU_OPTION_echr, \ "-echr chr set terminal escape character instead of ctrl-a\n", QEMU_ARCH_ALL) -- 2.31.1