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);
     }
 }
 
