From: Forrest Yuan Yu <yuanyu@google.com>
To: kvm@vger.kernel.org
Cc: Forrest Yuan Yu <yuanyu@google.com>
Subject: [PATCH RFC 1/1] KVM: x86: add KVM_HC_UCALL hypercall
Date: Fri, 1 May 2020 11:51:47 -0700 [thread overview]
Message-ID: <20200501185147.208192-2-yuanyu@google.com> (raw)
In-Reply-To: <20200501185147.208192-1-yuanyu@google.com>
The purpose of this new hypercall is to exchange message between
guest and hypervisor. For example, a guest may want to ask hypervisor
to harden security by setting restricted access permission on guest
SLAT entry. In this case, the guest can use this hypercall to send
a message to the hypervisor which will do its job and send back
anything it wants the guest to know.
Signed-off-by: Forrest Yuan Yu <yuanyu@google.com>
---
Documentation/virt/kvm/api.rst | 15 +-
Documentation/virt/kvm/cpuid.rst | 3 +
Documentation/virt/kvm/hypercalls.rst | 14 ++
arch/x86/include/asm/kvm_host.h | 1 +
arch/x86/include/uapi/asm/kvm_para.h | 1 +
arch/x86/kvm/x86.c | 39 +++-
include/uapi/linux/kvm.h | 1 +
include/uapi/linux/kvm_para.h | 1 +
tools/testing/selftests/kvm/.gitignore | 1 +
tools/testing/selftests/kvm/Makefile | 1 +
.../selftests/kvm/x86_64/hypercall_ucall.c | 195 ++++++++++++++++++
11 files changed, 264 insertions(+), 8 deletions(-)
create mode 100644 tools/testing/selftests/kvm/x86_64/hypercall_ucall.c
diff --git a/Documentation/virt/kvm/api.rst b/Documentation/virt/kvm/api.rst
index efbbe570aa9b..ae8958a7ad15 100644
--- a/Documentation/virt/kvm/api.rst
+++ b/Documentation/virt/kvm/api.rst
@@ -4854,8 +4854,8 @@ to the byte array.
.. note::
- For KVM_EXIT_IO, KVM_EXIT_MMIO, KVM_EXIT_OSI, KVM_EXIT_PAPR and
- KVM_EXIT_EPR the corresponding
+ For KVM_EXIT_IO, KVM_EXIT_MMIO, KVM_EXIT_HYPERCALL, KVM_EXIT_OSI,
+ KVM_EXIT_PAPR and KVM_EXIT_EPR the corresponding
operations are complete (and guest state is consistent) only after userspace
has re-entered the kernel with KVM_RUN. The kernel side will first finish
@@ -5802,6 +5802,17 @@ If present, this capability can be enabled for a VM, meaning that KVM
will allow the transition to secure guest mode. Otherwise KVM will
veto the transition.
+7.20 KVM_CAP_UCALL
+------------------------------
+
+:Architectures: x86
+
+This capability indicates that KVM supports hypercall ucall. It being enabled
+means userspace is ready to receive a message sent by a guest using hypercall
+ucall. When it is not enabled, a hypercall ucall made by a guest will not cause
+control to be handed to userspace. Instead, kvm will return -KVM_ENOSYS without
+userspace participation.
+
8. Other capabilities.
======================
diff --git a/Documentation/virt/kvm/cpuid.rst b/Documentation/virt/kvm/cpuid.rst
index 01b081f6e7ea..ff313f6827bf 100644
--- a/Documentation/virt/kvm/cpuid.rst
+++ b/Documentation/virt/kvm/cpuid.rst
@@ -86,6 +86,9 @@ KVM_FEATURE_PV_SCHED_YIELD 13 guest checks this feature bit
before using paravirtualized
sched yield.
+KVM_FEATURE_UCALL 14 guest checks this feature bit
+ before calling hypercall ucall.
+
KVM_FEATURE_CLOCSOURCE_STABLE_BIT 24 host will warn if no guest-side
per-cpu warps are expeced in
kvmclock
diff --git a/Documentation/virt/kvm/hypercalls.rst b/Documentation/virt/kvm/hypercalls.rst
index dbaf207e560d..ce3f30d5b2ee 100644
--- a/Documentation/virt/kvm/hypercalls.rst
+++ b/Documentation/virt/kvm/hypercalls.rst
@@ -169,3 +169,17 @@ a0: destination APIC ID
:Usage example: When sending a call-function IPI-many to vCPUs, yield if
any of the IPI target vCPUs was preempted.
+
+8. KVM_HC_UCALL
+---------------------
+
+:Architecture: x86
+:Status: active
+:Purpose: Hypercall used to exchange message between VM and hypervisor.
+
+a0: message type
+
+a1, a2, a3: dependent on message type
+
+:Usage example: A guest asks hypervisor to harden security by setting
+restricted access permission on guest SLAT entry.
diff --git a/arch/x86/include/asm/kvm_host.h b/arch/x86/include/asm/kvm_host.h
index 42a2d0d3984a..433e96c126a5 100644
--- a/arch/x86/include/asm/kvm_host.h
+++ b/arch/x86/include/asm/kvm_host.h
@@ -979,6 +979,7 @@ struct kvm_arch {
bool guest_can_read_msr_platform_info;
bool exception_payload_enabled;
+ bool hypercall_ucall_enabled;
struct kvm_pmu_event_filter *pmu_event_filter;
struct task_struct *nx_lpage_recovery_thread;
diff --git a/arch/x86/include/uapi/asm/kvm_para.h b/arch/x86/include/uapi/asm/kvm_para.h
index 2a8e0b6b9805..9524434463f2 100644
--- a/arch/x86/include/uapi/asm/kvm_para.h
+++ b/arch/x86/include/uapi/asm/kvm_para.h
@@ -31,6 +31,7 @@
#define KVM_FEATURE_PV_SEND_IPI 11
#define KVM_FEATURE_POLL_CONTROL 12
#define KVM_FEATURE_PV_SCHED_YIELD 13
+#define KVM_FEATURE_UCALL 14
#define KVM_HINTS_REALTIME 0
diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
index c5835f9cb9ad..388a4f89464d 100644
--- a/arch/x86/kvm/x86.c
+++ b/arch/x86/kvm/x86.c
@@ -3385,6 +3385,7 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
case KVM_CAP_GET_MSR_FEATURES:
case KVM_CAP_MSR_PLATFORM_INFO:
case KVM_CAP_EXCEPTION_PAYLOAD:
+ case KVM_CAP_UCALL:
r = 1;
break;
case KVM_CAP_SYNC_REGS:
@@ -4895,6 +4896,10 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
kvm->arch.exception_payload_enabled = cap->args[0];
r = 0;
break;
+ case KVM_CAP_UCALL:
+ kvm->arch.hypercall_ucall_enabled = cap->args[0];
+ r = 0;
+ break;
default:
r = -EINVAL;
break;
@@ -7554,6 +7559,19 @@ static void kvm_sched_yield(struct kvm *kvm, unsigned long dest_id)
kvm_vcpu_yield_to(target);
}
+static int complete_hypercall(struct kvm_vcpu *vcpu)
+{
+ u64 ret = vcpu->run->hypercall.ret;
+
+ if (!is_64_bit_mode(vcpu))
+ ret = (u32)ret;
+ kvm_rax_write(vcpu, ret);
+
+ ++vcpu->stat.hypercalls;
+
+ return kvm_skip_emulated_instruction(vcpu);
+}
+
int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
{
unsigned long nr, a0, a1, a2, a3, ret;
@@ -7605,17 +7623,26 @@ int kvm_emulate_hypercall(struct kvm_vcpu *vcpu)
kvm_sched_yield(vcpu->kvm, a0);
ret = 0;
break;
+ case KVM_HC_UCALL:
+ if (vcpu->kvm->arch.hypercall_ucall_enabled) {
+ vcpu->run->hypercall.nr = nr;
+ vcpu->run->hypercall.args[0] = a0;
+ vcpu->run->hypercall.args[1] = a1;
+ vcpu->run->hypercall.args[2] = a2;
+ vcpu->run->hypercall.args[3] = a3;
+ vcpu->run->exit_reason = KVM_EXIT_HYPERCALL;
+ vcpu->arch.complete_userspace_io = complete_hypercall;
+ return 0; // message is going to userspace
+ }
+ ret = -KVM_ENOSYS;
+ break;
default:
ret = -KVM_ENOSYS;
break;
}
out:
- if (!op_64_bit)
- ret = (u32)ret;
- kvm_rax_write(vcpu, ret);
-
- ++vcpu->stat.hypercalls;
- return kvm_skip_emulated_instruction(vcpu);
+ vcpu->run->hypercall.ret = ret;
+ return complete_hypercall(vcpu);
}
EXPORT_SYMBOL_GPL(kvm_emulate_hypercall);
diff --git a/include/uapi/linux/kvm.h b/include/uapi/linux/kvm.h
index 428c7dde6b4b..c1fcac311c76 100644
--- a/include/uapi/linux/kvm.h
+++ b/include/uapi/linux/kvm.h
@@ -1017,6 +1017,7 @@ struct kvm_ppc_resize_hpt {
#define KVM_CAP_S390_VCPU_RESETS 179
#define KVM_CAP_S390_PROTECTED 180
#define KVM_CAP_PPC_SECURE_GUEST 181
+#define KVM_CAP_UCALL 182
#ifdef KVM_CAP_IRQ_ROUTING
diff --git a/include/uapi/linux/kvm_para.h b/include/uapi/linux/kvm_para.h
index 8b86609849b9..4e5ad8dec801 100644
--- a/include/uapi/linux/kvm_para.h
+++ b/include/uapi/linux/kvm_para.h
@@ -29,6 +29,7 @@
#define KVM_HC_CLOCK_PAIRING 9
#define KVM_HC_SEND_IPI 10
#define KVM_HC_SCHED_YIELD 11
+#define KVM_HC_UCALL 12
/*
* hypercalls use architecture specific
diff --git a/tools/testing/selftests/kvm/.gitignore b/tools/testing/selftests/kvm/.gitignore
index a9b2b48947ff..c796c8efaa23 100644
--- a/tools/testing/selftests/kvm/.gitignore
+++ b/tools/testing/selftests/kvm/.gitignore
@@ -18,6 +18,7 @@
/x86_64/vmx_set_nested_state_test
/x86_64/vmx_tsc_adjust_test
/x86_64/xss_msr_test
+/x86_64/hypercall_ucall
/clear_dirty_log_test
/demand_paging_test
/dirty_log_test
diff --git a/tools/testing/selftests/kvm/Makefile b/tools/testing/selftests/kvm/Makefile
index 712a2ddd2a27..b3aeec375644 100644
--- a/tools/testing/selftests/kvm/Makefile
+++ b/tools/testing/selftests/kvm/Makefile
@@ -33,6 +33,7 @@ TEST_GEN_PROGS_x86_64 += demand_paging_test
TEST_GEN_PROGS_x86_64 += dirty_log_test
TEST_GEN_PROGS_x86_64 += kvm_create_max_vcpus
TEST_GEN_PROGS_x86_64 += steal_time
+TEST_GEN_PROGS_x86_64 += x86_64/hypercall_ucall
TEST_GEN_PROGS_aarch64 += clear_dirty_log_test
TEST_GEN_PROGS_aarch64 += demand_paging_test
diff --git a/tools/testing/selftests/kvm/x86_64/hypercall_ucall.c b/tools/testing/selftests/kvm/x86_64/hypercall_ucall.c
new file mode 100644
index 000000000000..132b6d1c98e2
--- /dev/null
+++ b/tools/testing/selftests/kvm/x86_64/hypercall_ucall.c
@@ -0,0 +1,195 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Hypercall KVM_HC_UCALL test
+ *
+ * Copyright (C) 2020, Google LLC.
+ *
+ * Author:
+ * Forrest Yuan Yu <yuanyu@google.com>
+ */
+
+#include <stdio.h>
+#include "linux/kernel.h"
+#include "linux/kvm_para.h"
+#include "linux/overflow.h"
+#include "test_util.h"
+#include "kvm_util.h"
+#include "processor.h"
+
+#define VCPU_ID 5
+#define HYPERCALL_RET 0xBEEF
+#define HYPERCALL_ARG0 1000
+#define HYPERCALL_ARG1 1001
+#define HYPERCALL_ARG2 1002
+#define HYPERCALL_ARG3 1003
+
+static inline bool is_feature_ucall_enabled(void)
+{
+ u32 eax, ebx, ecx, edx;
+
+ asm volatile("cpuid"
+ : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
+ : "a"(KVM_CPUID_FEATURES), "c"(0));
+
+ return eax & (1 << KVM_FEATURE_UCALL);
+}
+
+static inline void guest_info_to_host(u16 info)
+{
+ asm volatile("in %%dx, %%ax" : : "d" (info));
+}
+
+static inline long hypercall_ucall(void)
+{
+ long ret;
+
+ asm volatile("vmcall"
+ : "=a"(ret)
+ : "a"(KVM_HC_UCALL), "b"(HYPERCALL_ARG0),
+ "c"(HYPERCALL_ARG1), "d"(HYPERCALL_ARG2),
+ "S"(HYPERCALL_ARG3)
+ : "memory");
+
+ return ret;
+}
+
+void guest_code(void)
+{
+ long ret;
+
+ /* invoke ucall without it having been enabled */
+ ret = hypercall_ucall();
+ guest_info_to_host(ret);
+
+ /* check feature ucall the first time, host will see 0 */
+ guest_info_to_host(is_feature_ucall_enabled() ? 1 : 0);
+
+ /*
+ * check feature ucall the second time, host will see 1 because
+ * by now userspace should have enabled feature ucall
+ */
+ guest_info_to_host(is_feature_ucall_enabled() ? 1 : 0);
+
+ /*
+ * the following demonstrate the right way to make a hypercall ucall:
+ * check the existence of the feature then do ucall
+ */
+ if (is_feature_ucall_enabled()) {
+ /*
+ * now that ucall is enabled, kvm will hand control to userspace
+ * which will set the return value and finish it
+ */
+ ret = hypercall_ucall();
+ /*
+ * now we have received the return value set by userspace, send
+ * it back to userspace for double check
+ */
+ guest_info_to_host(ret);
+ } else {
+ /* this should not happen */
+ guest_info_to_host(-1);
+ }
+}
+
+void assert_guest_info(struct kvm_vm *vm, u16 expected, char *interpretation)
+{
+ struct kvm_run *state = vcpu_state(vm, VCPU_ID);
+
+ TEST_ASSERT(
+ state->exit_reason == KVM_EXIT_IO,
+ "Got exit_reason other than KVM_EXIT_IO: %u (%s).\n",
+ state->exit_reason, exit_reason_str(state->exit_reason));
+
+ TEST_ASSERT(
+ state->io.port == expected,
+ "Test failed: expecting %s 0x%x but got 0x%x.\n",
+ interpretation, expected, state->io.port);
+}
+
+static void set_ucall_enabled(struct kvm_vm *vm, bool enable)
+{
+ struct kvm_enable_cap cap = {};
+
+ cap.cap = KVM_CAP_UCALL;
+ cap.flags = 0;
+ cap.args[0] = (int)enable;
+ vm_enable_cap(vm, &cap);
+}
+
+int main(int argc, char *argv[])
+{
+ struct kvm_vm *vm;
+ struct kvm_run *state;
+ int hypercall_args[] = {
+ HYPERCALL_ARG0, HYPERCALL_ARG1, HYPERCALL_ARG2, HYPERCALL_ARG3
+ };
+ int arg_nr = ARRAY_SIZE(hypercall_args);
+ int i;
+ int expected_ucall_bit;
+ struct kvm_cpuid_entry2 *entry;
+
+ /* Create VM */
+ vm = vm_create_default(VCPU_ID, 0, guest_code);
+ vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
+
+ /*
+ * run VM which will do hypercall ucall, however, since the feature
+ * has not been enabled, the hypercall will do nothing and return
+ * -KVM_EOPNOTSUPP, which means an innocent userspace won't break even
+ * if the guest tries to invoke this hypercall
+ */
+ vcpu_run(vm, VCPU_ID);
+ assert_guest_info(vm, -KVM_EOPNOTSUPP, "hypercall return value");
+
+ /*
+ * continue to run VM which will check feature ucall, which of course
+ * hasn't been enabled yet
+ */
+ expected_ucall_bit = 0;
+ vcpu_run(vm, VCPU_ID);
+ assert_guest_info(vm, expected_ucall_bit, "ucall bit");
+
+ TEST_ASSERT(kvm_check_cap(KVM_CAP_UCALL), "CAP UCALL exists.");
+
+ set_ucall_enabled(vm, true);
+
+ /* enable feature ucall and let VM run to check it again */
+ entry = kvm_get_supported_cpuid_index(KVM_CPUID_FEATURES, 0);
+ entry->eax |= 1 << KVM_FEATURE_UCALL;
+ vcpu_set_cpuid(vm, VCPU_ID, kvm_get_supported_cpuid());
+ expected_ucall_bit = 1;
+ vcpu_run(vm, VCPU_ID);
+ assert_guest_info(vm, expected_ucall_bit, "ucall bit");
+
+ /*
+ * continue to run VM which will do hypercall ucall, which this time
+ * will give control to userspace because userspace is supposed to
+ * finish it
+ */
+ vcpu_run(vm, VCPU_ID);
+ state = vcpu_state(vm, VCPU_ID);
+ TEST_ASSERT(
+ state->exit_reason == KVM_EXIT_HYPERCALL,
+ "Got exit_reason other than KVM_EXIT_HYPERCALL: %u (%s).\n",
+ state->exit_reason, exit_reason_str(state->exit_reason));
+ for (i = 0; i < arg_nr; i++) {
+ TEST_ASSERT(
+ state->hypercall.args[i] == hypercall_args[i],
+ "Got unexpected hypercall argument [%d]: %lld.\n",
+ i, state->hypercall.args[i]);
+ }
+
+ /* userspace finishes it with HYPERCALL_RET */
+ state->hypercall.ret = (u16)HYPERCALL_RET;
+
+ /*
+ * continue VM which will see the finished ucall with a return value.
+ * verify the value guest sees is the one we set from userspace just now
+ */
+ vcpu_run(vm, VCPU_ID);
+ assert_guest_info(vm, HYPERCALL_RET, "hypercall return value");
+
+ kvm_vm_free(vm);
+
+ return 0;
+}
--
2.26.2.526.g744177e7f7-goog
next prev parent reply other threads:[~2020-05-01 18:52 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-05-01 18:51 [PATCH RFC 0/1] Hypercall UCALL for guest/userspace communication Forrest Yuan Yu
2020-05-01 18:51 ` Forrest Yuan Yu [this message]
2020-05-01 20:45 ` [PATCH RFC 1/1] KVM: x86: add KVM_HC_UCALL hypercall Sean Christopherson
2020-05-02 1:05 ` Liran Alon
2020-05-05 18:49 ` Jim Mattson
2020-05-05 23:53 ` Forrest Yuan Yu
2020-05-05 22:50 ` Forrest Yuan Yu
2020-05-01 20:23 ` [PATCH RFC 0/1] Hypercall UCALL for guest/userspace communication Sean Christopherson
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200501185147.208192-2-yuanyu@google.com \
--to=yuanyu@google.com \
--cc=kvm@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.