From: Marian Rotariu <marian.c.rota...@gmail.com> Because the introspection tool can run on another VM, suspending either of these two VMs requires signaling the introspection tool to remove any changes made to the introspected VM. This is done through the KVM_INTROSPECTION_PREUNHOOK ioctl. KVM will send an event through the introspection socket, if active. QEMU will wait for the introspection tool to let the VM run without being introspected and close the socket.
While the guest is suspended, the socket reconnection is disabled. CC: Markus Armbruster <arm...@redhat.com> Signed-off-by: Marian Rotariu <marian.c.rota...@gmail.com> Signed-off-by: Adalbert Lazăr <ala...@bitdefender.com> --- accel/kvm/vmi.c | 147 +++++++++++++++++++++++++++++++++ accel/stubs/Makefile.objs | 1 + accel/stubs/vmi-stubs.c | 7 ++ include/sysemu/vmi-intercept.h | 21 +++++ monitor/qmp-cmds.c | 10 +++ 5 files changed, 186 insertions(+) create mode 100644 accel/stubs/vmi-stubs.c create mode 100644 include/sysemu/vmi-intercept.h diff --git a/accel/kvm/vmi.c b/accel/kvm/vmi.c index 5beec2b091..151e27265a 100644 --- a/accel/kvm/vmi.c +++ b/accel/kvm/vmi.c @@ -14,12 +14,14 @@ #include "qom/object_interfaces.h" #include "sysemu/sysemu.h" #include "sysemu/reset.h" +#include "sysemu/runstate.h" #include "sysemu/kvm.h" #include "crypto/secret.h" #include "crypto/hash.h" #include "chardev/char.h" #include "chardev/char-fe.h" +#include "sysemu/vmi-intercept.h" #include "sysemu/vmi-handshake.h" #define HANDSHAKE_TIMEOUT_SEC 10 @@ -45,6 +47,10 @@ typedef struct VMIntrospection { GSource *hsk_timer; uint32_t handshake_timeout; + int intercepted_action; + + int reconnect_time; + int64_t vm_start_time; Notifier machine_ready; @@ -59,6 +65,14 @@ typedef struct VMIntrospectionClass { VMIntrospection *uniq; } VMIntrospectionClass; +static const char *action_string[] = { + "none", + "suspend", + "resume", +}; + +static bool suspend_pending; + #define TYPE_VM_INTROSPECTION "introspection" #define VM_INTROSPECTION(obj) \ @@ -412,6 +426,39 @@ static bool connect_kernel(VMIntrospection *i, Error **errp) return true; } +static void enable_socket_reconnect(VMIntrospection *i) +{ + if (i->sock_fd == -1 && i->reconnect_time) { + qemu_chr_fe_reconnect_time(&i->sock, i->reconnect_time); + qemu_chr_fe_disconnect(&i->sock); + i->reconnect_time = 0; + } +} + +static void maybe_disable_socket_reconnect(VMIntrospection *i) +{ + if (i->reconnect_time == 0) { + info_report("VMI: disable socket reconnect"); + i->reconnect_time = qemu_chr_fe_reconnect_time(&i->sock, 0); + } +} + +static void continue_with_the_intercepted_action(VMIntrospection *i) +{ + switch (i->intercepted_action) { + case VMI_INTERCEPT_SUSPEND: + vm_stop(RUN_STATE_PAUSED); + break; + default: + error_report("VMI: %s: unexpected action %d", + __func__, i->intercepted_action); + break; + } + + info_report("VMI: continue with '%s'", + action_string[i->intercepted_action]); +} + /* * We should read only the handshake structure, * which might have a different size than what we expect. @@ -495,6 +542,14 @@ static void chr_event_open(VMIntrospection *i) { Error *local_err = NULL; + if (suspend_pending) { + info_report("VMI: %s: too soon (suspend=%d)", + __func__, suspend_pending); + maybe_disable_socket_reconnect(i); + qemu_chr_fe_disconnect(&i->sock); + return; + } + if (!send_handshake_info(i, &local_err)) { error_append_hint(&local_err, "reconnecting\n"); warn_report_err(local_err); @@ -522,6 +577,15 @@ static void chr_event_close(VMIntrospection *i) } cancel_handshake_timer(i); + + if (suspend_pending) { + maybe_disable_socket_reconnect(i); + + if (i->intercepted_action != VMI_INTERCEPT_NONE) { + continue_with_the_intercepted_action(i); + i->intercepted_action = VMI_INTERCEPT_NONE; + } + } } static void chr_event(void *opaque, QEMUChrEvent event) @@ -540,6 +604,89 @@ static void chr_event(void *opaque, QEMUChrEvent event) } } +static VMIntrospection *vm_introspection_object(void) +{ + VMIntrospectionClass *ic; + + ic = VM_INTROSPECTION_CLASS(object_class_by_name(TYPE_VM_INTROSPECTION)); + + return ic ? ic->uniq : NULL; +} + +/* + * This ioctl succeeds only when KVM signals the introspection tool. + * (the socket is connected and the event was sent without error). + */ +static bool signal_introspection_tool_to_unhook(VMIntrospection *i) +{ + int err; + + err = kvm_vm_ioctl(kvm_state, KVM_INTROSPECTION_PREUNHOOK, NULL); + + return !err; +} + +static bool record_intercept_action(VMI_intercept_command action) +{ + switch (action) { + case VMI_INTERCEPT_SUSPEND: + suspend_pending = true; + break; + case VMI_INTERCEPT_RESUME: + suspend_pending = false; + break; + default: + return false; + } + + return true; +} + +static bool intercept_action(VMIntrospection *i, + VMI_intercept_command action, Error **errp) +{ + if (i->intercepted_action != VMI_INTERCEPT_NONE) { + error_report("VMI: unhook in progress"); + return false; + } + + switch (action) { + case VMI_INTERCEPT_RESUME: + enable_socket_reconnect(i); + return false; + default: + break; + } + + if (!signal_introspection_tool_to_unhook(i)) { + disconnect_and_unhook_kvmi(i); + return false; + } + + i->intercepted_action = action; + return true; +} + +bool vm_introspection_intercept(VMI_intercept_command action, Error **errp) +{ + VMIntrospection *i = vm_introspection_object(); + bool intercepted = false; + + info_report("VMI: intercept command: %s", + action < ARRAY_SIZE(action_string) + ? action_string[action] + : "unknown"); + + if (record_intercept_action(action) && i) { + intercepted = intercept_action(i, action, errp); + } + + info_report("VMI: intercept action: %s", + intercepted ? "delayed" : "continue"); + + return intercepted; +} + static void vm_introspection_reset(void *opaque) { VMIntrospection *i = opaque; diff --git a/accel/stubs/Makefile.objs b/accel/stubs/Makefile.objs index 3894caf95d..fcec6edf0f 100644 --- a/accel/stubs/Makefile.objs +++ b/accel/stubs/Makefile.objs @@ -2,4 +2,5 @@ obj-$(call lnot,$(CONFIG_HAX)) += hax-stub.o obj-$(call lnot,$(CONFIG_HVF)) += hvf-stub.o obj-$(call lnot,$(CONFIG_WHPX)) += whpx-stub.o obj-$(call lnot,$(CONFIG_KVM)) += kvm-stub.o +obj-$(call lnot,$(CONFIG_KVM)) += vmi-stubs.o obj-$(call lnot,$(CONFIG_TCG)) += tcg-stub.o diff --git a/accel/stubs/vmi-stubs.c b/accel/stubs/vmi-stubs.c new file mode 100644 index 0000000000..1bd93b2ca5 --- /dev/null +++ b/accel/stubs/vmi-stubs.c @@ -0,0 +1,7 @@ +#include "qemu/osdep.h" +#include "sysemu/vmi-intercept.h" + +bool vm_introspection_intercept(VMI_intercept_command ic, Error **errp) +{ + return false; +} diff --git a/include/sysemu/vmi-intercept.h b/include/sysemu/vmi-intercept.h new file mode 100644 index 0000000000..06998ff18a --- /dev/null +++ b/include/sysemu/vmi-intercept.h @@ -0,0 +1,21 @@ +/* + * QEMU VM Introspection + * + * Copyright (C) 2018-2020 Bitdefender S.R.L. + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef QEMU_VMI_INTERCEPT_H +#define QEMU_VMI_INTERCEPT_H + +typedef enum { + VMI_INTERCEPT_NONE = 0, + VMI_INTERCEPT_SUSPEND, + VMI_INTERCEPT_RESUME, +} VMI_intercept_command; + +bool vm_introspection_intercept(VMI_intercept_command ic, Error **errp); + +#endif /* QEMU_VMI_INTERCEPT_H */ diff --git a/monitor/qmp-cmds.c b/monitor/qmp-cmds.c index 864cbfa32e..eabd20fca3 100644 --- a/monitor/qmp-cmds.c +++ b/monitor/qmp-cmds.c @@ -39,6 +39,8 @@ #include "hw/mem/memory-device.h" #include "hw/acpi/acpi_dev_interface.h" +#include "sysemu/vmi-intercept.h" + NameInfo *qmp_query_name(Error **errp) { NameInfo *info = g_malloc0(sizeof(*info)); @@ -87,6 +89,9 @@ void qmp_stop(Error **errp) if (runstate_check(RUN_STATE_INMIGRATE)) { autostart = 0; } else { + if (vm_introspection_intercept(VMI_INTERCEPT_SUSPEND, errp)) { + return; + } vm_stop(RUN_STATE_PAUSED); } } @@ -158,6 +163,11 @@ void qmp_cont(Error **errp) autostart = 1; } else { vm_start(); + /* + * this interception is post-event as we might need the vm to run before + * doing the interception, therefore we do not need the return value. + */ + vm_introspection_intercept(VMI_INTERCEPT_RESUME, errp); } }