Skip to content

Commit 60e7dad

Browse files
ouptonMarc Zyngier
authored andcommitted
KVM: selftests: Add test for SMCCC filter
Add a selftest for the SMCCC filter, ensuring basic UAPI constraints (e.g. reserved ranges, non-overlapping ranges) are upheld. Additionally, test that the DENIED and FWD_TO_USER work as intended. Signed-off-by: Oliver Upton <oliver.upton@linux.dev> Signed-off-by: Marc Zyngier <maz@kernel.org> Link: https://lore.kernel.org/r/20230404154050.2270077-14-oliver.upton@linux.dev
1 parent fab1991 commit 60e7dad

2 files changed

Lines changed: 261 additions & 0 deletions

File tree

tools/testing/selftests/kvm/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ TEST_GEN_PROGS_aarch64 += aarch64/get-reg-list
141141
TEST_GEN_PROGS_aarch64 += aarch64/hypercalls
142142
TEST_GEN_PROGS_aarch64 += aarch64/page_fault_test
143143
TEST_GEN_PROGS_aarch64 += aarch64/psci_test
144+
TEST_GEN_PROGS_aarch64 += aarch64/smccc_filter
144145
TEST_GEN_PROGS_aarch64 += aarch64/vcpu_width_config
145146
TEST_GEN_PROGS_aarch64 += aarch64/vgic_init
146147
TEST_GEN_PROGS_aarch64 += aarch64/vgic_irq
Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
// SPDX-License-Identifier: GPL-2.0-only
2+
/*
3+
* smccc_filter - Tests for the SMCCC filter UAPI.
4+
*
5+
* Copyright (c) 2023 Google LLC
6+
*
7+
* This test includes:
8+
* - Tests that the UAPI constraints are upheld by KVM. For example, userspace
9+
* is prevented from filtering the architecture range of SMCCC calls.
10+
* - Test that the filter actions (DENIED, FWD_TO_USER) work as intended.
11+
*/
12+
13+
#include <linux/arm-smccc.h>
14+
#include <linux/psci.h>
15+
#include <stdint.h>
16+
17+
#include "processor.h"
18+
#include "test_util.h"
19+
20+
enum smccc_conduit {
21+
HVC_INSN,
22+
SMC_INSN,
23+
};
24+
25+
#define for_each_conduit(conduit) \
26+
for (conduit = HVC_INSN; conduit <= SMC_INSN; conduit++)
27+
28+
static void guest_main(uint32_t func_id, enum smccc_conduit conduit)
29+
{
30+
struct arm_smccc_res res;
31+
32+
if (conduit == SMC_INSN)
33+
smccc_smc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
34+
else
35+
smccc_hvc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
36+
37+
GUEST_SYNC(res.a0);
38+
}
39+
40+
static int __set_smccc_filter(struct kvm_vm *vm, uint32_t start, uint32_t nr_functions,
41+
enum kvm_smccc_filter_action action)
42+
{
43+
struct kvm_smccc_filter filter = {
44+
.base = start,
45+
.nr_functions = nr_functions,
46+
.action = action,
47+
};
48+
49+
return __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
50+
KVM_ARM_VM_SMCCC_FILTER, &filter);
51+
}
52+
53+
static void set_smccc_filter(struct kvm_vm *vm, uint32_t start, uint32_t nr_functions,
54+
enum kvm_smccc_filter_action action)
55+
{
56+
int ret = __set_smccc_filter(vm, start, nr_functions, action);
57+
58+
TEST_ASSERT(!ret, "failed to configure SMCCC filter: %d", ret);
59+
}
60+
61+
static struct kvm_vm *setup_vm(struct kvm_vcpu **vcpu)
62+
{
63+
struct kvm_vcpu_init init;
64+
struct kvm_vm *vm;
65+
66+
vm = vm_create(1);
67+
vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init);
68+
69+
/*
70+
* Enable in-kernel emulation of PSCI to ensure that calls are denied
71+
* due to the SMCCC filter, not because of KVM.
72+
*/
73+
init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
74+
75+
*vcpu = aarch64_vcpu_add(vm, 0, &init, guest_main);
76+
return vm;
77+
}
78+
79+
static void test_pad_must_be_zero(void)
80+
{
81+
struct kvm_vcpu *vcpu;
82+
struct kvm_vm *vm = setup_vm(&vcpu);
83+
struct kvm_smccc_filter filter = {
84+
.base = PSCI_0_2_FN_PSCI_VERSION,
85+
.nr_functions = 1,
86+
.action = KVM_SMCCC_FILTER_DENY,
87+
.pad = { -1 },
88+
};
89+
int r;
90+
91+
r = __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
92+
KVM_ARM_VM_SMCCC_FILTER, &filter);
93+
TEST_ASSERT(r < 0 && errno == EINVAL,
94+
"Setting filter with nonzero padding should return EINVAL");
95+
}
96+
97+
/* Ensure that userspace cannot filter the Arm Architecture SMCCC range */
98+
static void test_filter_reserved_range(void)
99+
{
100+
struct kvm_vcpu *vcpu;
101+
struct kvm_vm *vm = setup_vm(&vcpu);
102+
int r;
103+
104+
r = __set_smccc_filter(vm, ARM_SMCCC_ARCH_WORKAROUND_1,
105+
1, KVM_SMCCC_FILTER_DENY);
106+
TEST_ASSERT(r < 0 && errno == EEXIST,
107+
"Attempt to filter reserved range should return EEXIST");
108+
109+
kvm_vm_free(vm);
110+
}
111+
112+
static void test_invalid_nr_functions(void)
113+
{
114+
struct kvm_vcpu *vcpu;
115+
struct kvm_vm *vm = setup_vm(&vcpu);
116+
int r;
117+
118+
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 0, KVM_SMCCC_FILTER_DENY);
119+
TEST_ASSERT(r < 0 && errno == EINVAL,
120+
"Attempt to filter 0 functions should return EINVAL");
121+
122+
kvm_vm_free(vm);
123+
}
124+
125+
static void test_overflow_nr_functions(void)
126+
{
127+
struct kvm_vcpu *vcpu;
128+
struct kvm_vm *vm = setup_vm(&vcpu);
129+
int r;
130+
131+
r = __set_smccc_filter(vm, ~0, ~0, KVM_SMCCC_FILTER_DENY);
132+
TEST_ASSERT(r < 0 && errno == EINVAL,
133+
"Attempt to overflow filter range should return EINVAL");
134+
135+
kvm_vm_free(vm);
136+
}
137+
138+
static void test_reserved_action(void)
139+
{
140+
struct kvm_vcpu *vcpu;
141+
struct kvm_vm *vm = setup_vm(&vcpu);
142+
int r;
143+
144+
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, -1);
145+
TEST_ASSERT(r < 0 && errno == EINVAL,
146+
"Attempt to use reserved filter action should return EINVAL");
147+
148+
kvm_vm_free(vm);
149+
}
150+
151+
152+
/* Test that overlapping configurations of the SMCCC filter are rejected */
153+
static void test_filter_overlap(void)
154+
{
155+
struct kvm_vcpu *vcpu;
156+
struct kvm_vm *vm = setup_vm(&vcpu);
157+
int r;
158+
159+
set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
160+
161+
r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
162+
TEST_ASSERT(r < 0 && errno == EEXIST,
163+
"Attempt to filter already configured range should return EEXIST");
164+
165+
kvm_vm_free(vm);
166+
}
167+
168+
static void expect_call_denied(struct kvm_vcpu *vcpu)
169+
{
170+
struct ucall uc;
171+
172+
if (get_ucall(vcpu, &uc) != UCALL_SYNC)
173+
TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
174+
175+
TEST_ASSERT(uc.args[1] == SMCCC_RET_NOT_SUPPORTED,
176+
"Unexpected SMCCC return code: %lu", uc.args[1]);
177+
}
178+
179+
/* Denied SMCCC calls have a return code of SMCCC_RET_NOT_SUPPORTED */
180+
static void test_filter_denied(void)
181+
{
182+
enum smccc_conduit conduit;
183+
struct kvm_vcpu *vcpu;
184+
struct kvm_vm *vm;
185+
186+
for_each_conduit(conduit) {
187+
vm = setup_vm(&vcpu);
188+
189+
set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_DENY);
190+
vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
191+
192+
vcpu_run(vcpu);
193+
expect_call_denied(vcpu);
194+
195+
kvm_vm_free(vm);
196+
}
197+
}
198+
199+
static void expect_call_fwd_to_user(struct kvm_vcpu *vcpu, uint32_t func_id,
200+
enum smccc_conduit conduit)
201+
{
202+
struct kvm_run *run = vcpu->run;
203+
204+
TEST_ASSERT(run->exit_reason == KVM_EXIT_HYPERCALL,
205+
"Unexpected exit reason: %u", run->exit_reason);
206+
TEST_ASSERT(run->hypercall.nr == func_id,
207+
"Unexpected SMCCC function: %llu", run->hypercall.nr);
208+
209+
if (conduit == SMC_INSN)
210+
TEST_ASSERT(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC,
211+
"KVM_HYPERCALL_EXIT_SMC is not set");
212+
else
213+
TEST_ASSERT(!(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC),
214+
"KVM_HYPERCAL_EXIT_SMC is set");
215+
}
216+
217+
/* SMCCC calls forwarded to userspace cause KVM_EXIT_HYPERCALL exits */
218+
static void test_filter_fwd_to_user(void)
219+
{
220+
enum smccc_conduit conduit;
221+
struct kvm_vcpu *vcpu;
222+
struct kvm_vm *vm;
223+
224+
for_each_conduit(conduit) {
225+
vm = setup_vm(&vcpu);
226+
227+
set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_FWD_TO_USER);
228+
vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
229+
230+
vcpu_run(vcpu);
231+
expect_call_fwd_to_user(vcpu, PSCI_0_2_FN_PSCI_VERSION, conduit);
232+
233+
kvm_vm_free(vm);
234+
}
235+
}
236+
237+
static bool kvm_supports_smccc_filter(void)
238+
{
239+
struct kvm_vm *vm = vm_create_barebones();
240+
int r;
241+
242+
r = __kvm_has_device_attr(vm->fd, KVM_ARM_VM_SMCCC_CTRL, KVM_ARM_VM_SMCCC_FILTER);
243+
244+
kvm_vm_free(vm);
245+
return !r;
246+
}
247+
248+
int main(void)
249+
{
250+
TEST_REQUIRE(kvm_supports_smccc_filter());
251+
252+
test_pad_must_be_zero();
253+
test_invalid_nr_functions();
254+
test_overflow_nr_functions();
255+
test_reserved_action();
256+
test_filter_reserved_range();
257+
test_filter_overlap();
258+
test_filter_denied();
259+
test_filter_fwd_to_user();
260+
}

0 commit comments

Comments
 (0)