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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox