160e7dadeSOliver Upton // SPDX-License-Identifier: GPL-2.0-only
260e7dadeSOliver Upton /*
360e7dadeSOliver Upton  * smccc_filter - Tests for the SMCCC filter UAPI.
460e7dadeSOliver Upton  *
560e7dadeSOliver Upton  * Copyright (c) 2023 Google LLC
660e7dadeSOliver Upton  *
760e7dadeSOliver Upton  * This test includes:
860e7dadeSOliver Upton  *  - Tests that the UAPI constraints are upheld by KVM. For example, userspace
960e7dadeSOliver Upton  *    is prevented from filtering the architecture range of SMCCC calls.
1060e7dadeSOliver Upton  *  - Test that the filter actions (DENIED, FWD_TO_USER) work as intended.
1160e7dadeSOliver Upton  */
1260e7dadeSOliver Upton 
1360e7dadeSOliver Upton #include <linux/arm-smccc.h>
1460e7dadeSOliver Upton #include <linux/psci.h>
1560e7dadeSOliver Upton #include <stdint.h>
1660e7dadeSOliver Upton 
1760e7dadeSOliver Upton #include "processor.h"
1860e7dadeSOliver Upton #include "test_util.h"
1960e7dadeSOliver Upton 
2060e7dadeSOliver Upton enum smccc_conduit {
2160e7dadeSOliver Upton 	HVC_INSN,
2260e7dadeSOliver Upton 	SMC_INSN,
2360e7dadeSOliver Upton };
2460e7dadeSOliver Upton 
2560e7dadeSOliver Upton #define for_each_conduit(conduit)					\
2660e7dadeSOliver Upton 	for (conduit = HVC_INSN; conduit <= SMC_INSN; conduit++)
2760e7dadeSOliver Upton 
guest_main(uint32_t func_id,enum smccc_conduit conduit)2860e7dadeSOliver Upton static void guest_main(uint32_t func_id, enum smccc_conduit conduit)
2960e7dadeSOliver Upton {
3060e7dadeSOliver Upton 	struct arm_smccc_res res;
3160e7dadeSOliver Upton 
3260e7dadeSOliver Upton 	if (conduit == SMC_INSN)
3360e7dadeSOliver Upton 		smccc_smc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
3460e7dadeSOliver Upton 	else
3560e7dadeSOliver Upton 		smccc_hvc(func_id, 0, 0, 0, 0, 0, 0, 0, &res);
3660e7dadeSOliver Upton 
3760e7dadeSOliver Upton 	GUEST_SYNC(res.a0);
3860e7dadeSOliver Upton }
3960e7dadeSOliver Upton 
__set_smccc_filter(struct kvm_vm * vm,uint32_t start,uint32_t nr_functions,enum kvm_smccc_filter_action action)4060e7dadeSOliver Upton static int __set_smccc_filter(struct kvm_vm *vm, uint32_t start, uint32_t nr_functions,
4160e7dadeSOliver Upton 			      enum kvm_smccc_filter_action action)
4260e7dadeSOliver Upton {
4360e7dadeSOliver Upton 	struct kvm_smccc_filter filter = {
4460e7dadeSOliver Upton 		.base		= start,
4560e7dadeSOliver Upton 		.nr_functions	= nr_functions,
4660e7dadeSOliver Upton 		.action		= action,
4760e7dadeSOliver Upton 	};
4860e7dadeSOliver Upton 
4960e7dadeSOliver Upton 	return __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
5060e7dadeSOliver Upton 				     KVM_ARM_VM_SMCCC_FILTER, &filter);
5160e7dadeSOliver Upton }
5260e7dadeSOliver Upton 
set_smccc_filter(struct kvm_vm * vm,uint32_t start,uint32_t nr_functions,enum kvm_smccc_filter_action action)5360e7dadeSOliver Upton static void set_smccc_filter(struct kvm_vm *vm, uint32_t start, uint32_t nr_functions,
5460e7dadeSOliver Upton 			     enum kvm_smccc_filter_action action)
5560e7dadeSOliver Upton {
5660e7dadeSOliver Upton 	int ret = __set_smccc_filter(vm, start, nr_functions, action);
5760e7dadeSOliver Upton 
5860e7dadeSOliver Upton 	TEST_ASSERT(!ret, "failed to configure SMCCC filter: %d", ret);
5960e7dadeSOliver Upton }
6060e7dadeSOliver Upton 
setup_vm(struct kvm_vcpu ** vcpu)6160e7dadeSOliver Upton static struct kvm_vm *setup_vm(struct kvm_vcpu **vcpu)
6260e7dadeSOliver Upton {
6360e7dadeSOliver Upton 	struct kvm_vcpu_init init;
6460e7dadeSOliver Upton 	struct kvm_vm *vm;
6560e7dadeSOliver Upton 
6660e7dadeSOliver Upton 	vm = vm_create(1);
6760e7dadeSOliver Upton 	vm_ioctl(vm, KVM_ARM_PREFERRED_TARGET, &init);
6860e7dadeSOliver Upton 
6960e7dadeSOliver Upton 	/*
7060e7dadeSOliver Upton 	 * Enable in-kernel emulation of PSCI to ensure that calls are denied
7160e7dadeSOliver Upton 	 * due to the SMCCC filter, not because of KVM.
7260e7dadeSOliver Upton 	 */
7360e7dadeSOliver Upton 	init.features[0] |= (1 << KVM_ARM_VCPU_PSCI_0_2);
7460e7dadeSOliver Upton 
7560e7dadeSOliver Upton 	*vcpu = aarch64_vcpu_add(vm, 0, &init, guest_main);
7660e7dadeSOliver Upton 	return vm;
7760e7dadeSOliver Upton }
7860e7dadeSOliver Upton 
test_pad_must_be_zero(void)7960e7dadeSOliver Upton static void test_pad_must_be_zero(void)
8060e7dadeSOliver Upton {
8160e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
8260e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
8360e7dadeSOliver Upton 	struct kvm_smccc_filter filter = {
8460e7dadeSOliver Upton 		.base		= PSCI_0_2_FN_PSCI_VERSION,
8560e7dadeSOliver Upton 		.nr_functions	= 1,
8660e7dadeSOliver Upton 		.action		= KVM_SMCCC_FILTER_DENY,
8760e7dadeSOliver Upton 		.pad		= { -1 },
8860e7dadeSOliver Upton 	};
8960e7dadeSOliver Upton 	int r;
9060e7dadeSOliver Upton 
9160e7dadeSOliver Upton 	r = __kvm_device_attr_set(vm->fd, KVM_ARM_VM_SMCCC_CTRL,
9260e7dadeSOliver Upton 				  KVM_ARM_VM_SMCCC_FILTER, &filter);
9360e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EINVAL,
9460e7dadeSOliver Upton 		    "Setting filter with nonzero padding should return EINVAL");
9560e7dadeSOliver Upton }
9660e7dadeSOliver Upton 
9760e7dadeSOliver Upton /* Ensure that userspace cannot filter the Arm Architecture SMCCC range */
test_filter_reserved_range(void)9860e7dadeSOliver Upton static void test_filter_reserved_range(void)
9960e7dadeSOliver Upton {
10060e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
10160e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
10200e0c947SOliver Upton 	uint32_t smc64_fn;
10360e7dadeSOliver Upton 	int r;
10460e7dadeSOliver Upton 
10560e7dadeSOliver Upton 	r = __set_smccc_filter(vm, ARM_SMCCC_ARCH_WORKAROUND_1,
10660e7dadeSOliver Upton 			       1, KVM_SMCCC_FILTER_DENY);
10760e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EEXIST,
10860e7dadeSOliver Upton 		    "Attempt to filter reserved range should return EEXIST");
10960e7dadeSOliver Upton 
11000e0c947SOliver Upton 	smc64_fn = ARM_SMCCC_CALL_VAL(ARM_SMCCC_FAST_CALL, ARM_SMCCC_SMC_64,
11100e0c947SOliver Upton 				      0, 0);
11200e0c947SOliver Upton 
11300e0c947SOliver Upton 	r = __set_smccc_filter(vm, smc64_fn, 1, KVM_SMCCC_FILTER_DENY);
11400e0c947SOliver Upton 	TEST_ASSERT(r < 0 && errno == EEXIST,
11500e0c947SOliver Upton 		    "Attempt to filter reserved range should return EEXIST");
11600e0c947SOliver Upton 
11760e7dadeSOliver Upton 	kvm_vm_free(vm);
11860e7dadeSOliver Upton }
11960e7dadeSOliver Upton 
test_invalid_nr_functions(void)12060e7dadeSOliver Upton static void test_invalid_nr_functions(void)
12160e7dadeSOliver Upton {
12260e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
12360e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
12460e7dadeSOliver Upton 	int r;
12560e7dadeSOliver Upton 
12660e7dadeSOliver Upton 	r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 0, KVM_SMCCC_FILTER_DENY);
12760e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EINVAL,
12860e7dadeSOliver Upton 		    "Attempt to filter 0 functions should return EINVAL");
12960e7dadeSOliver Upton 
13060e7dadeSOliver Upton 	kvm_vm_free(vm);
13160e7dadeSOliver Upton }
13260e7dadeSOliver Upton 
test_overflow_nr_functions(void)13360e7dadeSOliver Upton static void test_overflow_nr_functions(void)
13460e7dadeSOliver Upton {
13560e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
13660e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
13760e7dadeSOliver Upton 	int r;
13860e7dadeSOliver Upton 
13960e7dadeSOliver Upton 	r = __set_smccc_filter(vm, ~0, ~0, KVM_SMCCC_FILTER_DENY);
14060e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EINVAL,
14160e7dadeSOliver Upton 		    "Attempt to overflow filter range should return EINVAL");
14260e7dadeSOliver Upton 
14360e7dadeSOliver Upton 	kvm_vm_free(vm);
14460e7dadeSOliver Upton }
14560e7dadeSOliver Upton 
test_reserved_action(void)14660e7dadeSOliver Upton static void test_reserved_action(void)
14760e7dadeSOliver Upton {
14860e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
14960e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
15060e7dadeSOliver Upton 	int r;
15160e7dadeSOliver Upton 
15260e7dadeSOliver Upton 	r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, -1);
15360e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EINVAL,
15460e7dadeSOliver Upton 		    "Attempt to use reserved filter action should return EINVAL");
15560e7dadeSOliver Upton 
15660e7dadeSOliver Upton 	kvm_vm_free(vm);
15760e7dadeSOliver Upton }
15860e7dadeSOliver Upton 
15960e7dadeSOliver Upton 
16060e7dadeSOliver Upton /* Test that overlapping configurations of the SMCCC filter are rejected */
test_filter_overlap(void)16160e7dadeSOliver Upton static void test_filter_overlap(void)
16260e7dadeSOliver Upton {
16360e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
16460e7dadeSOliver Upton 	struct kvm_vm *vm = setup_vm(&vcpu);
16560e7dadeSOliver Upton 	int r;
16660e7dadeSOliver Upton 
16760e7dadeSOliver Upton 	set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
16860e7dadeSOliver Upton 
16960e7dadeSOliver Upton 	r = __set_smccc_filter(vm, PSCI_0_2_FN64_CPU_ON, 1, KVM_SMCCC_FILTER_DENY);
17060e7dadeSOliver Upton 	TEST_ASSERT(r < 0 && errno == EEXIST,
17160e7dadeSOliver Upton 		    "Attempt to filter already configured range should return EEXIST");
17260e7dadeSOliver Upton 
17360e7dadeSOliver Upton 	kvm_vm_free(vm);
17460e7dadeSOliver Upton }
17560e7dadeSOliver Upton 
expect_call_denied(struct kvm_vcpu * vcpu)17660e7dadeSOliver Upton static void expect_call_denied(struct kvm_vcpu *vcpu)
17760e7dadeSOliver Upton {
17860e7dadeSOliver Upton 	struct ucall uc;
17960e7dadeSOliver Upton 
18060e7dadeSOliver Upton 	if (get_ucall(vcpu, &uc) != UCALL_SYNC)
18160e7dadeSOliver Upton 		TEST_FAIL("Unexpected ucall: %lu\n", uc.cmd);
18260e7dadeSOliver Upton 
18360e7dadeSOliver Upton 	TEST_ASSERT(uc.args[1] == SMCCC_RET_NOT_SUPPORTED,
18460e7dadeSOliver Upton 		    "Unexpected SMCCC return code: %lu", uc.args[1]);
18560e7dadeSOliver Upton }
18660e7dadeSOliver Upton 
18760e7dadeSOliver Upton /* Denied SMCCC calls have a return code of SMCCC_RET_NOT_SUPPORTED */
test_filter_denied(void)18860e7dadeSOliver Upton static void test_filter_denied(void)
18960e7dadeSOliver Upton {
19060e7dadeSOliver Upton 	enum smccc_conduit conduit;
19160e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
19260e7dadeSOliver Upton 	struct kvm_vm *vm;
19360e7dadeSOliver Upton 
19460e7dadeSOliver Upton 	for_each_conduit(conduit) {
19560e7dadeSOliver Upton 		vm = setup_vm(&vcpu);
19660e7dadeSOliver Upton 
19760e7dadeSOliver Upton 		set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_DENY);
19860e7dadeSOliver Upton 		vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
19960e7dadeSOliver Upton 
20060e7dadeSOliver Upton 		vcpu_run(vcpu);
20160e7dadeSOliver Upton 		expect_call_denied(vcpu);
20260e7dadeSOliver Upton 
20360e7dadeSOliver Upton 		kvm_vm_free(vm);
20460e7dadeSOliver Upton 	}
20560e7dadeSOliver Upton }
20660e7dadeSOliver Upton 
expect_call_fwd_to_user(struct kvm_vcpu * vcpu,uint32_t func_id,enum smccc_conduit conduit)20760e7dadeSOliver Upton static void expect_call_fwd_to_user(struct kvm_vcpu *vcpu, uint32_t func_id,
20860e7dadeSOliver Upton 				    enum smccc_conduit conduit)
20960e7dadeSOliver Upton {
21060e7dadeSOliver Upton 	struct kvm_run *run = vcpu->run;
21160e7dadeSOliver Upton 
21260e7dadeSOliver Upton 	TEST_ASSERT(run->exit_reason == KVM_EXIT_HYPERCALL,
21360e7dadeSOliver Upton 		    "Unexpected exit reason: %u", run->exit_reason);
21460e7dadeSOliver Upton 	TEST_ASSERT(run->hypercall.nr == func_id,
21560e7dadeSOliver Upton 		    "Unexpected SMCCC function: %llu", run->hypercall.nr);
21660e7dadeSOliver Upton 
21760e7dadeSOliver Upton 	if (conduit == SMC_INSN)
21860e7dadeSOliver Upton 		TEST_ASSERT(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC,
21960e7dadeSOliver Upton 			    "KVM_HYPERCALL_EXIT_SMC is not set");
22060e7dadeSOliver Upton 	else
22160e7dadeSOliver Upton 		TEST_ASSERT(!(run->hypercall.flags & KVM_HYPERCALL_EXIT_SMC),
222*c5284f6dSColin Ian King 			    "KVM_HYPERCALL_EXIT_SMC is set");
22360e7dadeSOliver Upton }
22460e7dadeSOliver Upton 
22560e7dadeSOliver Upton /* SMCCC calls forwarded to userspace cause KVM_EXIT_HYPERCALL exits */
test_filter_fwd_to_user(void)22660e7dadeSOliver Upton static void test_filter_fwd_to_user(void)
22760e7dadeSOliver Upton {
22860e7dadeSOliver Upton 	enum smccc_conduit conduit;
22960e7dadeSOliver Upton 	struct kvm_vcpu *vcpu;
23060e7dadeSOliver Upton 	struct kvm_vm *vm;
23160e7dadeSOliver Upton 
23260e7dadeSOliver Upton 	for_each_conduit(conduit) {
23360e7dadeSOliver Upton 		vm = setup_vm(&vcpu);
23460e7dadeSOliver Upton 
23560e7dadeSOliver Upton 		set_smccc_filter(vm, PSCI_0_2_FN_PSCI_VERSION, 1, KVM_SMCCC_FILTER_FWD_TO_USER);
23660e7dadeSOliver Upton 		vcpu_args_set(vcpu, 2, PSCI_0_2_FN_PSCI_VERSION, conduit);
23760e7dadeSOliver Upton 
23860e7dadeSOliver Upton 		vcpu_run(vcpu);
23960e7dadeSOliver Upton 		expect_call_fwd_to_user(vcpu, PSCI_0_2_FN_PSCI_VERSION, conduit);
24060e7dadeSOliver Upton 
24160e7dadeSOliver Upton 		kvm_vm_free(vm);
24260e7dadeSOliver Upton 	}
24360e7dadeSOliver Upton }
24460e7dadeSOliver Upton 
kvm_supports_smccc_filter(void)24560e7dadeSOliver Upton static bool kvm_supports_smccc_filter(void)
24660e7dadeSOliver Upton {
24760e7dadeSOliver Upton 	struct kvm_vm *vm = vm_create_barebones();
24860e7dadeSOliver Upton 	int r;
24960e7dadeSOliver Upton 
25060e7dadeSOliver Upton 	r = __kvm_has_device_attr(vm->fd, KVM_ARM_VM_SMCCC_CTRL, KVM_ARM_VM_SMCCC_FILTER);
25160e7dadeSOliver Upton 
25260e7dadeSOliver Upton 	kvm_vm_free(vm);
25360e7dadeSOliver Upton 	return !r;
25460e7dadeSOliver Upton }
25560e7dadeSOliver Upton 
main(void)25660e7dadeSOliver Upton int main(void)
25760e7dadeSOliver Upton {
25860e7dadeSOliver Upton 	TEST_REQUIRE(kvm_supports_smccc_filter());
25960e7dadeSOliver Upton 
26060e7dadeSOliver Upton 	test_pad_must_be_zero();
26160e7dadeSOliver Upton 	test_invalid_nr_functions();
26260e7dadeSOliver Upton 	test_overflow_nr_functions();
26360e7dadeSOliver Upton 	test_reserved_action();
26460e7dadeSOliver Upton 	test_filter_reserved_range();
26560e7dadeSOliver Upton 	test_filter_overlap();
26660e7dadeSOliver Upton 	test_filter_denied();
26760e7dadeSOliver Upton 	test_filter_fwd_to_user();
26860e7dadeSOliver Upton }
269