From: Jinrong Liang <[email protected]>
Tests have been added to this commit to check if setting
MSR_IA32_DS_AREA with a non-classical address causes a fault. By
verifying that KVM is correctly faulting non-classical addresses
in MSR_IA32_DS_AREA, it helps ensure the accuracy and stability
of the KVM subsystem.
Signed-off-by: Jinrong Liang <[email protected]>
---
.../selftests/kvm/x86_64/vmx_pmu_caps_test.c | 100 ++++++++++++++++++
1 file changed, 100 insertions(+)
diff --git a/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c b/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
index 4c90f76930f9..02903084598f 100644
--- a/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
+++ b/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
@@ -19,6 +19,9 @@
#include "kvm_util.h"
#include "vmx.h"
+#define MAX_LINEAR_ADDR_MASK GENMASK_ULL(15, 8)
+#define ADDR_OFS_BIT 8
+
union perf_capabilities {
struct {
u64 lbr_format:6;
@@ -232,6 +235,102 @@ static void test_lbr_perf_capabilities(union perf_capabilities host_cap)
kvm_vm_free(vm);
}
+/*
+ * Generate a non-canonical address for a given number of address bits.
+ * @addr_bits: The number of address bits used in the system.
+ *
+ * This function calculates a non-canonical address by setting the most
+ * significant bit to 1 and adding an offset equal to the maximum value
+ * that can be represented by the remaining bits. This ensures that the
+ * generated address is outside the valid address range and is consistent.
+ */
+static inline uint64_t non_canonical_address(unsigned int addr_bits)
+{
+ return (1ULL << (addr_bits - 1)) + ((1ULL << (addr_bits - 1)) - 1);
+}
+
+static uint64_t get_addr_bits(struct kvm_vcpu *vcpu)
+{
+ const struct kvm_cpuid_entry2 *kvm_entry;
+ unsigned int addr_bits;
+ struct kvm_sregs sregs;
+
+ kvm_entry = get_cpuid_entry(kvm_get_supported_cpuid(), 0x80000008, 0);
+ addr_bits = (kvm_entry->eax & MAX_LINEAR_ADDR_MASK) >> ADDR_OFS_BIT;
+ /*
+ * Get the size of the virtual address space by checking the LA57 bit
+ * in the CR4 control register. If the LA57 bit is set, then the virtual
+ * address space is 57 bits. Otherwise, it's 48 bits.
+ */
+ if (addr_bits != 32) {
+ vcpu_sregs_get(vcpu, &sregs);
+ addr_bits = (sregs.cr4 & X86_CR4_LA57) ? 57 : 48;
+ }
+
+ return addr_bits;
+}
+
+static void test_ds_guest_code(uint64_t bad_addr)
+{
+ uint8_t vector = 0;
+
+ vector = wrmsr_safe(MSR_IA32_DS_AREA, bad_addr);
+ GUEST_SYNC(vector);
+}
+
+/* Check if setting MSR_IA32_DS_AREA in guest and kvm userspace will fail. */
+static void test_ds_area_noncanonical_address(union perf_capabilities host_cap)
+{
+ struct kvm_vm *vm;
+ struct kvm_vcpu *vcpu;
+ unsigned int r, addr_bits;
+ uint64_t bad_addr, without_pebs_fmt_caps;
+ struct ucall uc;
+
+ vm = vm_create_with_one_vcpu(&vcpu, test_ds_guest_code);
+ vm_init_descriptor_tables(vm);
+ vcpu_init_descriptor_tables(vcpu);
+
+ /* Check that Setting MSR_IA32_DS_AREA with 0 should succeed. */
+ r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, 0);
+ TEST_ASSERT(r, "Setting MSR_IA32_DS_AREA with 0 should succeed.");
+
+ /*
+ * Check that if PEBS_FMT is not set setting MSR_IA32_DS_AREA will
+ * succeed.
+ */
+ without_pebs_fmt_caps = host_cap.capabilities & ~PERF_CAP_PEBS_FORMAT;
+ vcpu_set_msr(vcpu, MSR_IA32_PERF_CAPABILITIES, without_pebs_fmt_caps);
+ r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, 1);
+ TEST_ASSERT(r, "Setting MSR_IA32_DS_AREA with bad addr should fail.");
+
+ /*
+ * Check that setting MSR_IA32_DS_AREA in kvm userspace to use a
+ * non-canonical address should fail.
+ */
+ vcpu_set_msr(vcpu, MSR_IA32_PERF_CAPABILITIES, host_cap.capabilities);
+ addr_bits = get_addr_bits(vcpu);
+ bad_addr = non_canonical_address(addr_bits);
+ r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, bad_addr);
+ TEST_ASSERT(!r, "Setting MSR_IA32_DS_AREA with bad addr should fail.");
+
+ /*
+ * Check that setting MSR_IA32_DS_AREA in guest to use a non-canonical
+ * address should cause the #GP.
+ */
+ vcpu_args_set(vcpu, 1, bad_addr);
+ vcpu_run(vcpu);
+
+ TEST_ASSERT_KVM_EXIT_REASON(vcpu, KVM_EXIT_IO);
+ get_ucall(vcpu, &uc);
+ TEST_ASSERT(uc.cmd == UCALL_SYNC,
+ "Received ucall other than UCALL_SYNC: %lu", uc.cmd);
+ TEST_ASSERT(uc.args[1] == GP_VECTOR,
+ "Setting MSR_IA32_DS_AREA with bad addr should fail.");
+
+ kvm_vm_free(vm);
+}
+
int main(int argc, char *argv[])
{
union perf_capabilities host_cap;
@@ -252,4 +351,5 @@ int main(int argc, char *argv[])
test_immutable_perf_capabilities(host_cap);
test_guest_wrmsr_perf_capabilities(host_cap);
test_lbr_perf_capabilities(host_cap);
+ test_ds_area_noncanonical_address(host_cap);
}
--
2.31.1
On Thu, Jun 08, 2023, Jinrong Liang wrote:
> From: Jinrong Liang <[email protected]>
>
> Tests have been added to this commit to check if setting
> MSR_IA32_DS_AREA with a non-classical address causes a fault. By
> verifying that KVM is correctly faulting non-classical addresses
> in MSR_IA32_DS_AREA, it helps ensure the accuracy and stability
> of the KVM subsystem.
>
> Signed-off-by: Jinrong Liang <[email protected]>
> ---
> .../selftests/kvm/x86_64/vmx_pmu_caps_test.c | 100 ++++++++++++++++++
> 1 file changed, 100 insertions(+)
>
> diff --git a/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c b/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
> index 4c90f76930f9..02903084598f 100644
> --- a/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
> +++ b/tools/testing/selftests/kvm/x86_64/vmx_pmu_caps_test.c
> @@ -19,6 +19,9 @@
> #include "kvm_util.h"
> #include "vmx.h"
>
> +#define MAX_LINEAR_ADDR_MASK GENMASK_ULL(15, 8)
> +#define ADDR_OFS_BIT 8
Similar to other comments, please spell this out. Whatever "OFS" stands for, it
isn't common knowledge (unless I'm way out of the loop).
> +
> union perf_capabilities {
> struct {
> u64 lbr_format:6;
> @@ -232,6 +235,102 @@ static void test_lbr_perf_capabilities(union perf_capabilities host_cap)
> kvm_vm_free(vm);
> }
>
> +/*
> + * Generate a non-canonical address for a given number of address bits.
> + * @addr_bits: The number of address bits used in the system.
> + *
> + * This function calculates a non-canonical address by setting the most
> + * significant bit to 1 and adding an offset equal to the maximum value
> + * that can be represented by the remaining bits. This ensures that the
> + * generated address is outside the valid address range and is consistent.
> + */
> +static inline uint64_t non_canonical_address(unsigned int addr_bits)
> +{
> + return (1ULL << (addr_bits - 1)) + ((1ULL << (addr_bits - 1)) - 1);
> +}
Eh, I don't know that I would
> +
> +static uint64_t get_addr_bits(struct kvm_vcpu *vcpu)
> +{
> + const struct kvm_cpuid_entry2 *kvm_entry;
> + unsigned int addr_bits;
> + struct kvm_sregs sregs;
> +
> + kvm_entry = get_cpuid_entry(kvm_get_supported_cpuid(), 0x80000008, 0);
> + addr_bits = (kvm_entry->eax & MAX_LINEAR_ADDR_MASK) >> ADDR_OFS_BIT;
*sigh*
X86_PROPERTY_MAX_VIRT_ADDR, or even better, vcpu->vm->va_bits.
> + /*
> + * Get the size of the virtual address space by checking the LA57 bit
> + * in the CR4 control register. If the LA57 bit is set, then the virtual
> + * address space is 57 bits. Otherwise, it's 48 bits.
> + */
> + if (addr_bits != 32) {
> + vcpu_sregs_get(vcpu, &sregs);
> + addr_bits = (sregs.cr4 & X86_CR4_LA57) ? 57 : 48;
> + }
> +
> + return addr_bits;
(a) None of this is PMU specific. (b) Selftests don't support LA57 (yet).
(c) IMO there's no reason to get this fancy. Just hardcode a value that's
guaranteed to be non-canonical and call it good.
E.g.
#define NONCANONICAL (0xaaaULL << 48)
That way the "bad" value can be used from the guest directly.
Or if you want to handle LA57 right now and verify that the guest can set values
that are canonical for LA57 but not for LA48, something like
static inline vm_vaddr_t get_noncanonical_address(bool is_la57)
{
return is_la57 ? BIT(57) : BIT(48);
}
and then the guest can invoke it by reading and passing in the current CR4, i.e.
still doesn't require splitting logic across the guest and host.
> +}
> +
> +static void test_ds_guest_code(uint64_t bad_addr)
> +{
> + uint8_t vector = 0;
> +
> + vector = wrmsr_safe(MSR_IA32_DS_AREA, bad_addr);
> + GUEST_SYNC(vector);
GUEST_ASSERT(vector == GP_VECTOR);
Aaron's fancy printf() stuff is coming, and even without that, splitting logic
across the guest and host is generally a bad idea as it makes it much harder to
understand what the test does.
> +}
> +
> +/* Check if setting MSR_IA32_DS_AREA in guest and kvm userspace will fail. */
> +static void test_ds_area_noncanonical_address(union perf_capabilities host_cap)
> +{
> + struct kvm_vm *vm;
> + struct kvm_vcpu *vcpu;
> + unsigned int r, addr_bits;
> + uint64_t bad_addr, without_pebs_fmt_caps;
> + struct ucall uc;
Reverse xmas tree.
> +
> + vm = vm_create_with_one_vcpu(&vcpu, test_ds_guest_code);
> + vm_init_descriptor_tables(vm);
> + vcpu_init_descriptor_tables(vcpu);
> +
> + /* Check that Setting MSR_IA32_DS_AREA with 0 should succeed. */
Drop the comment, the assert covers everything.
> + r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, 0);
> + TEST_ASSERT(r, "Setting MSR_IA32_DS_AREA with 0 should succeed.");
Eh, I would just do vcpu_set_msr() and let it assert for you.
> + /*
> + * Check that if PEBS_FMT is not set setting MSR_IA32_DS_AREA will
> + * succeed.
I don't understand what this comment is trying to say. The below tests that
writing MSR_IA32_DS_AREA *fails*, not succeeds.
> + */
> + without_pebs_fmt_caps = host_cap.capabilities & ~PERF_CAP_PEBS_FORMAT;
> + vcpu_set_msr(vcpu, MSR_IA32_PERF_CAPABILITIES, without_pebs_fmt_caps);
> + r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, 1);
> + TEST_ASSERT(r, "Setting MSR_IA32_DS_AREA with bad addr should fail.");
Print the actual "bad" value, and be more descriptive, i.e. state *why* '1' is
a bad value.
> +
> + /*
> + * Check that setting MSR_IA32_DS_AREA in kvm userspace to use a
> + * non-canonical address should fail.
> + */
> + vcpu_set_msr(vcpu, MSR_IA32_PERF_CAPABILITIES, host_cap.capabilities);
> + addr_bits = get_addr_bits(vcpu);
> + bad_addr = non_canonical_address(addr_bits);
> + r = _vcpu_set_msr(vcpu, MSR_IA32_DS_AREA, bad_addr);
> + TEST_ASSERT(!r, "Setting MSR_IA32_DS_AREA with bad addr should fail.");
Same comment here.