Skip to content

Commit 6c2d4c3

Browse files
Quentin PerretMarc Zyngier
authored andcommitted
KVM: arm64: Selftest for pKVM transitions
We have recently found a bug [1] in the pKVM memory ownership transitions by code inspection, but it could have been caught with a test. Introduce a boot-time selftest exercising all the known pKVM memory transitions and importantly checks the rejection of illegal transitions. The new test is hidden behind a new Kconfig option separate from CONFIG_EL2_NVHE_DEBUG on purpose as that has side effects on the transition checks ([1] doesn't reproduce with EL2 debug enabled). [1] https://lore.kernel.org/kvmarm/[email protected]/ Suggested-by: Will Deacon <[email protected]> Signed-off-by: Quentin Perret <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Marc Zyngier <[email protected]>
1 parent 845f126 commit 6c2d4c3

File tree

3 files changed

+119
-0
lines changed

3 files changed

+119
-0
lines changed

arch/arm64/kvm/hyp/include/nvhe/mem_protect.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,4 +67,10 @@ static __always_inline void __load_host_stage2(void)
6767
else
6868
write_sysreg(0, vttbr_el2);
6969
}
70+
71+
#ifdef CONFIG_NVHE_EL2_DEBUG
72+
void pkvm_ownership_selftest(void);
73+
#else
74+
static inline void pkvm_ownership_selftest(void) { }
75+
#endif
7076
#endif /* __KVM_NVHE_MEM_PROTECT__ */

arch/arm64/kvm/hyp/nvhe/mem_protect.c

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1090,3 +1090,114 @@ int __pkvm_host_mkyoung_guest(u64 gfn, struct pkvm_hyp_vcpu *vcpu)
10901090

10911091
return 0;
10921092
}
1093+
1094+
#ifdef CONFIG_NVHE_EL2_DEBUG
1095+
struct pkvm_expected_state {
1096+
enum pkvm_page_state host;
1097+
enum pkvm_page_state hyp;
1098+
};
1099+
1100+
static struct pkvm_expected_state selftest_state;
1101+
static struct hyp_page *selftest_page;
1102+
1103+
static void assert_page_state(void)
1104+
{
1105+
void *virt = hyp_page_to_virt(selftest_page);
1106+
u64 size = PAGE_SIZE << selftest_page->order;
1107+
u64 phys = hyp_virt_to_phys(virt);
1108+
1109+
host_lock_component();
1110+
WARN_ON(__host_check_page_state_range(phys, size, selftest_state.host));
1111+
host_unlock_component();
1112+
1113+
hyp_lock_component();
1114+
WARN_ON(__hyp_check_page_state_range(phys, size, selftest_state.hyp));
1115+
hyp_unlock_component();
1116+
}
1117+
1118+
#define assert_transition_res(res, fn, ...) \
1119+
do { \
1120+
WARN_ON(fn(__VA_ARGS__) != res); \
1121+
assert_page_state(); \
1122+
} while (0)
1123+
1124+
void pkvm_ownership_selftest(void)
1125+
{
1126+
void *virt = hyp_alloc_pages(&host_s2_pool, 0);
1127+
u64 phys, size, pfn;
1128+
1129+
WARN_ON(!virt);
1130+
selftest_page = hyp_virt_to_page(virt);
1131+
selftest_page->refcount = 0;
1132+
1133+
size = PAGE_SIZE << selftest_page->order;
1134+
phys = hyp_virt_to_phys(virt);
1135+
pfn = hyp_phys_to_pfn(phys);
1136+
1137+
selftest_state.host = PKVM_NOPAGE;
1138+
selftest_state.hyp = PKVM_PAGE_OWNED;
1139+
assert_page_state();
1140+
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
1141+
assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
1142+
assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
1143+
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
1144+
assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
1145+
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
1146+
1147+
selftest_state.host = PKVM_PAGE_OWNED;
1148+
selftest_state.hyp = PKVM_NOPAGE;
1149+
assert_transition_res(0, __pkvm_hyp_donate_host, pfn, 1);
1150+
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
1151+
assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
1152+
assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
1153+
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
1154+
1155+
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
1156+
selftest_state.hyp = PKVM_PAGE_SHARED_BORROWED;
1157+
assert_transition_res(0, __pkvm_host_share_hyp, pfn);
1158+
assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
1159+
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
1160+
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
1161+
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
1162+
1163+
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
1164+
assert_transition_res(0, hyp_pin_shared_mem, virt, virt + size);
1165+
hyp_unpin_shared_mem(virt, virt + size);
1166+
WARN_ON(hyp_page_count(virt) != 1);
1167+
assert_transition_res(-EBUSY, __pkvm_host_unshare_hyp, pfn);
1168+
assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
1169+
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
1170+
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
1171+
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
1172+
1173+
hyp_unpin_shared_mem(virt, virt + size);
1174+
assert_page_state();
1175+
WARN_ON(hyp_page_count(virt));
1176+
1177+
selftest_state.host = PKVM_PAGE_OWNED;
1178+
selftest_state.hyp = PKVM_NOPAGE;
1179+
assert_transition_res(0, __pkvm_host_unshare_hyp, pfn);
1180+
1181+
selftest_state.host = PKVM_PAGE_SHARED_OWNED;
1182+
selftest_state.hyp = PKVM_NOPAGE;
1183+
assert_transition_res(0, __pkvm_host_share_ffa, pfn, 1);
1184+
assert_transition_res(-EPERM, __pkvm_host_share_ffa, pfn, 1);
1185+
assert_transition_res(-EPERM, __pkvm_host_donate_hyp, pfn, 1);
1186+
assert_transition_res(-EPERM, __pkvm_host_share_hyp, pfn);
1187+
assert_transition_res(-EPERM, __pkvm_host_unshare_hyp, pfn);
1188+
assert_transition_res(-EPERM, __pkvm_hyp_donate_host, pfn, 1);
1189+
assert_transition_res(-EPERM, hyp_pin_shared_mem, virt, virt + size);
1190+
1191+
selftest_state.host = PKVM_PAGE_OWNED;
1192+
selftest_state.hyp = PKVM_NOPAGE;
1193+
assert_transition_res(0, __pkvm_host_unshare_ffa, pfn, 1);
1194+
assert_transition_res(-EPERM, __pkvm_host_unshare_ffa, pfn, 1);
1195+
1196+
selftest_state.host = PKVM_NOPAGE;
1197+
selftest_state.hyp = PKVM_PAGE_OWNED;
1198+
assert_transition_res(0, __pkvm_host_donate_hyp, pfn, 1);
1199+
1200+
selftest_page->refcount = 1;
1201+
hyp_put_page(&host_s2_pool, virt);
1202+
}
1203+
#endif

arch/arm64/kvm/hyp/nvhe/setup.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,8 @@ void __noreturn __pkvm_init_finalise(void)
308308
goto out;
309309

310310
pkvm_hyp_vm_table_init(vm_table_base);
311+
312+
pkvm_ownership_selftest();
311313
out:
312314
/*
313315
* We tail-called to here from handle___pkvm_init() and will not return,

0 commit comments

Comments
 (0)