Hi all,
This is v4 of the series previously posted here:
https://lore.kernel.org/kvmarm/20211201170411.1561936-1-qperret@google.com/
This series implements an unshare hypercall at EL2 in nVHE protected
mode, and makes use of it to unmmap guest-specific data-structures from
EL2 stage-1 during guest tear-down. Crucially, the implementation of the
share and unshare routines use page refcounts in the host kernel to
avoid accidentally unmapping data-structures that overlap a common page.
This series has two main benefits. Firstly it allows EL2 to track the
state of shared pages cleanly, as they can now transition from SHARED
back to OWNED. This will simplify permission checks once e.g. pkvm
implements a donation hcall to provide memory to protected guests, as
there should then be no reason for the host to donate a page that is
currently marked shared. And secondly, it avoids having dangling
mappings in the hypervisor's stage-1, which should be a good idea from
a security perspective as the hypervisor is obviously running with
elevated privileges. And perhaps worth noting is that this also
refactors the EL2 page-tracking checks in a more scalable way, which
should allow to implement other memory transitions (host donating memory
to a guest, a guest sharing back with the host, ...) much more easily in
the future.
Changes since v3:
- fixed refcount of hyp stage-1 page-table pages when only changing SW
bits (Will)
- misc minor cleanups (Will, Andrew)
- rebased on kvmarm/next
Quentin Perret (6):
KVM: arm64: Provide {get,put}_page() stubs for early hyp allocator
KVM: arm64: Refcount hyp stage-1 pgtable pages
KVM: arm64: Fixup hyp stage-1 refcount
KVM: arm64: Introduce kvm_share_hyp()
KVM: arm64: pkvm: Refcount the pages shared with EL2
KVM: arm64: pkvm: Unshare guest structs during teardown
Will Deacon (8):
KVM: arm64: Hook up ->page_count() for hypervisor stage-1 page-table
KVM: arm64: Implement kvm_pgtable_hyp_unmap() at EL2
KVM: arm64: Extend pkvm_page_state enumeration to handle absent pages
KVM: arm64: Introduce wrappers for host and hyp spin lock accessors
KVM: arm64: Implement do_share() helper for sharing memory
KVM: arm64: Implement __pkvm_host_share_hyp() using do_share()
KVM: arm64: Implement do_unshare() helper for unsharing memory
KVM: arm64: Expose unshare hypercall to the host
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/include/asm/kvm_host.h | 2 +
arch/arm64/include/asm/kvm_mmu.h | 2 +
arch/arm64/include/asm/kvm_pgtable.h | 21 +
arch/arm64/kvm/arm.c | 6 +-
arch/arm64/kvm/fpsimd.c | 36 +-
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 6 +
arch/arm64/kvm/hyp/nvhe/early_alloc.c | 5 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 8 +
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 500 +++++++++++++++---
arch/arm64/kvm/hyp/nvhe/setup.c | 22 +-
arch/arm64/kvm/hyp/pgtable.c | 102 +++-
arch/arm64/kvm/mmu.c | 137 ++++-
arch/arm64/kvm/reset.c | 10 +-
14 files changed, 739 insertions(+), 119 deletions(-)
--
2.34.1.173.g76aa8bc2d0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
In nVHE protected mode, the EL2 code uses a temporary allocator during
boot while re-creating its stage-1 page-table. Unfortunately, the
hyp_vmmemap is not ready to use at this stage, so refcounting pages
is not possible. That is not currently a problem because hyp stage-1
mappings are never removed, which implies refcounting of page-table
pages is unnecessary.
In preparation for allowing hypervisor stage-1 mappings to be removed,
provide stub implementations for {get,put}_page() in the early allocator.
Acked-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/early_alloc.c | 5 +++++
1 file changed, 5 insertions(+)
To prepare the ground for allowing hyp stage-1 mappings to be removed at
run-time, update the KVM page-table code to maintain a correct refcount
using the ->{get,put}_page() function callbacks.
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/pgtable.c | 39 ++++++++++++++++++------------------
1 file changed, 19 insertions(+), 20 deletions(-)
In nVHE-protected mode, the hyp stage-1 page-table refcount is broken
due to the lack of refcount support in the early allocator. Fix-up the
refcount in the finalize walker, once the 'hyp_vmemmap' is up and running.
Acked-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/setup.c | 21 ++++++++++++++++-----
1 file changed, 16 insertions(+), 5 deletions(-)
From: Will Deacon <will@kernel.org>
kvm_pgtable_hyp_unmap() relies on the ->page_count() function callback
being provided by the memory-management operations for the page-table.
Wire up this callback for the hypervisor stage-1 page-table.
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/setup.c | 1 +
1 file changed, 1 insertion(+)
From: Will Deacon <will@kernel.org>
Implement kvm_pgtable_hyp_unmap() which can be used to remove hypervisor
stage-1 mappings at EL2.
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/include/asm/kvm_pgtable.h | 21 ++++++++++
arch/arm64/kvm/hyp/pgtable.c | 63 ++++++++++++++++++++++++++++
2 files changed, 84 insertions(+)
The create_hyp_mappings() function can currently be called at any point
in time. However, its behaviour in protected mode changes widely
depending on when it is being called. Prior to KVM init, it is used to
create the temporary page-table used to bring-up the hypervisor, and
later on it is transparently turned into a 'share' hypercall when the
kernel has lost control over the hypervisor stage-1. In order to prepare
the ground for also unsharing pages with the hypervisor during guest
teardown, introduce a kvm_share_hyp() function to make it clear in which
places a share hypercall should be expected, as we will soon need a
matching unshare hypercall in all those places.
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/include/asm/kvm_mmu.h | 1 +
arch/arm64/kvm/arm.c | 4 ++--
arch/arm64/kvm/fpsimd.c | 2 +-
arch/arm64/kvm/mmu.c | 27 +++++++++++++++++++++------
arch/arm64/kvm/reset.c | 2 +-
5 files changed, 26 insertions(+), 10 deletions(-)
@@ -30,7 +30,7 @@ int kvm_arch_vcpu_run_map_fp(struct kvm_vcpu *vcpu)structuser_fpsimd_state*fpsimd=¤t->thread.uw.fpsimd_state;/* Make sure the host task fpsimd state is visible to hyp: */-ret=create_hyp_mappings(fpsimd,fpsimd+1,PAGE_HYP);+ret=kvm_share_hyp(fpsimd,fpsimd+1);if(!ret)vcpu->arch.host_fpsimd_state=kern_hyp_va(fpsimd);
In order to simplify the page tracking infrastructure at EL2 in nVHE
protected mode, move the responsibility of refcounting pages that are
shared multiple times on the host. In order to do so, let's create a
red-black tree tracking all the PFNs that have been shared, along with
a refcount.
Acked-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/mmu.c | 78 ++++++++++++++++++++++++++++++++++++++------
1 file changed, 68 insertions(+), 10 deletions(-)
From: Will Deacon <will@kernel.org>
Explicitly name the combination of SW0 | SW1 as reserved in the pte and
introduce a new PKVM_NOPAGE meta-state which, although not directly
stored in the software bits of the pte, can be used to represent an
entry for which there is no underlying page. This is distinct from an
invalid pte, as stage-2 identity mappings for the host are created
lazily and so an invalid pte there is the same as a valid mapping for
the purposes of ownership information.
This state will be used for permission checking during page transitions
in later patches.
Reviewed-by: Andrew Walbran <redacted>
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 5 +++++
1 file changed, 5 insertions(+)
From: Will Deacon <will@kernel.org>
In preparation for adding additional locked sections for manipulating
page-tables at EL2, introduce some simple wrappers around the host and
hypervisor locks so that it's a bit easier to read and bit more difficult
to take the wrong lock (or even take them in the wrong order).
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 32 ++++++++++++++++++++++-----
1 file changed, 26 insertions(+), 6 deletions(-)
From: Will Deacon <will@kernel.org>
By default, protected KVM isolates memory pages so that they are
accessible only to their owner: be it the host kernel, the hypervisor
at EL2 or (in future) the guest. Establishing shared-memory regions
between these components therefore involves a transition for each page
so that the owner can share memory with a borrower under a certain set
of permissions.
Introduce a do_share() helper for safely sharing a memory region between
two components. Currently, only host-to-hyp sharing is implemented, but
the code is easily extended to handle other combinations and the
permission checks for each component are reusable.
Reviewed-by: Andrew Walbran <redacted>
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 237 ++++++++++++++++++++++++++
1 file changed, 237 insertions(+)
@@ -472,3 +472,240 @@ void handle_host_mem_abort(struct kvm_cpu_context *host_ctxt)ret=host_stage2_idmap(addr);BUG_ON(ret&&ret!=-EAGAIN);}++/* This corresponds to locking order */+enumpkvm_component_id{+PKVM_ID_HOST,+PKVM_ID_HYP,+};++structpkvm_mem_transition{+u64nr_pages;++struct{+enumpkvm_component_idid;+/* Address in the initiator's address space */+u64addr;++union{+struct{+/* Address in the completer's address space */+u64completer_addr;+}host;+};+}initiator;++struct{+enumpkvm_component_idid;+}completer;+};++structpkvm_mem_share{+conststructpkvm_mem_transitiontx;+constenumkvm_pgtable_protcompleter_prot;+};++structcheck_walk_data{+enumpkvm_page_statedesired;+enumpkvm_page_state(*get_page_state)(kvm_pte_tpte);+};++staticint__check_page_state_visitor(u64addr,u64end,u32level,+kvm_pte_t*ptep,+enumkvm_pgtable_walk_flagsflag,+void*constarg)+{+structcheck_walk_data*d=arg;+kvm_pte_tpte=*ptep;++if(kvm_pte_valid(pte)&&!addr_is_memory(kvm_pte_to_phys(pte)))+return-EINVAL;++returnd->get_page_state(pte)==d->desired?0:-EPERM;+}++staticintcheck_page_state_range(structkvm_pgtable*pgt,u64addr,u64size,+structcheck_walk_data*data)+{+structkvm_pgtable_walkerwalker={+.cb=__check_page_state_visitor,+.arg=data,+.flags=KVM_PGTABLE_WALK_LEAF,+};++returnkvm_pgtable_walk(pgt,addr,size,&walker);+}++staticenumpkvm_page_statehost_get_page_state(kvm_pte_tpte)+{+if(!kvm_pte_valid(pte)&&pte)+returnPKVM_NOPAGE;++returnpkvm_getstate(kvm_pgtable_stage2_pte_prot(pte));+}++staticint__host_check_page_state_range(u64addr,u64size,+enumpkvm_page_statestate)+{+structcheck_walk_datad={+.desired=state,+.get_page_state=host_get_page_state,+};++hyp_assert_lock_held(&host_kvm.lock);+returncheck_page_state_range(&host_kvm.pgt,addr,size,&d);+}++staticint__host_set_page_state_range(u64addr,u64size,+enumpkvm_page_statestate)+{+enumkvm_pgtable_protprot=pkvm_mkstate(PKVM_HOST_MEM_PROT,state);++returnhost_stage2_idmap_locked(addr,size,prot);+}++staticinthost_request_owned_transition(u64*completer_addr,+conststructpkvm_mem_transition*tx)+{+u64size=tx->nr_pages*PAGE_SIZE;+u64addr=tx->initiator.addr;++*completer_addr=tx->initiator.host.completer_addr;+return__host_check_page_state_range(addr,size,PKVM_PAGE_OWNED);+}++staticinthost_initiate_share(u64*completer_addr,+conststructpkvm_mem_transition*tx)+{+u64size=tx->nr_pages*PAGE_SIZE;+u64addr=tx->initiator.addr;++*completer_addr=tx->initiator.host.completer_addr;+return__host_set_page_state_range(addr,size,PKVM_PAGE_SHARED_OWNED);+}++staticenumpkvm_page_statehyp_get_page_state(kvm_pte_tpte)+{+if(!kvm_pte_valid(pte))+returnPKVM_NOPAGE;++returnpkvm_getstate(kvm_pgtable_stage2_pte_prot(pte));+}++staticint__hyp_check_page_state_range(u64addr,u64size,+enumpkvm_page_statestate)+{+structcheck_walk_datad={+.desired=state,+.get_page_state=hyp_get_page_state,+};++hyp_assert_lock_held(&pkvm_pgd_lock);+returncheck_page_state_range(&pkvm_pgtable,addr,size,&d);+}++staticbool__hyp_ack_skip_pgtable_check(conststructpkvm_mem_transition*tx)+{+return!(IS_ENABLED(CONFIG_NVHE_EL2_DEBUG)||+tx->initiator.id!=PKVM_ID_HOST);+}++staticinthyp_ack_share(u64addr,conststructpkvm_mem_transition*tx,+enumkvm_pgtable_protperms)+{+u64size=tx->nr_pages*PAGE_SIZE;++if(perms!=PAGE_HYP)+return-EPERM;++if(__hyp_ack_skip_pgtable_check(tx))+return0;++return__hyp_check_page_state_range(addr,size,PKVM_NOPAGE);+}++staticinthyp_complete_share(u64addr,conststructpkvm_mem_transition*tx,+enumkvm_pgtable_protperms)+{+void*start=(void*)addr,*end=start+(tx->nr_pages*PAGE_SIZE);+enumkvm_pgtable_protprot;++prot=pkvm_mkstate(perms,PKVM_PAGE_SHARED_BORROWED);+returnpkvm_create_mappings_locked(start,end,prot);+}++staticintcheck_share(structpkvm_mem_share*share)+{+conststructpkvm_mem_transition*tx=&share->tx;+u64completer_addr;+intret;++switch(tx->initiator.id){+casePKVM_ID_HOST:+ret=host_request_owned_transition(&completer_addr,tx);+break;+default:+ret=-EINVAL;+}++if(ret)+returnret;++switch(tx->completer.id){+casePKVM_ID_HYP:+ret=hyp_ack_share(completer_addr,tx,share->completer_prot);+break;+default:+ret=-EINVAL;+}++returnret;+}++staticint__do_share(structpkvm_mem_share*share)+{+conststructpkvm_mem_transition*tx=&share->tx;+u64completer_addr;+intret;++switch(tx->initiator.id){+casePKVM_ID_HOST:+ret=host_initiate_share(&completer_addr,tx);+break;+default:+ret=-EINVAL;+}++if(ret)+returnret;++switch(tx->completer.id){+casePKVM_ID_HYP:+ret=hyp_complete_share(completer_addr,tx,share->completer_prot);+break;+default:+ret=-EINVAL;+}++returnret;+}++/*+*do_share():+*+*Thepageownergrantsaccesstoanothercomponentwithagivenset+*ofpermissions.+*+*Initiator:OWNED=>SHARED_OWNED+*Completer:NOPAGE=>SHARED_BORROWED+*/+staticintdo_share(structpkvm_mem_share*share)+{+intret;++ret=check_share(share);+if(ret)+returnret;++returnWARN_ON(__do_share(share));+}
--
2.34.1.173.g76aa8bc2d0-goog
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
From: Will Deacon <will@kernel.org>
__pkvm_host_share_hyp() shares memory between the host and the
hypervisor so implement it as an invocation of the new do_share()
mechanism.
Note that double-sharing is no longer permitted (as this allows us to
reduce the number of page-table walks significantly), but is thankfully
no longer relied upon by the host.
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 121 +++++++-------------------
1 file changed, 33 insertions(+), 88 deletions(-)
From: Will Deacon <will@kernel.org>
Tearing down a previously shared memory region results in the borrower
losing access to the underlying pages and returning them to the "owned"
state in the owner.
Implement a do_unshare() helper, along the same lines as do_share(), to
provide this functionality for the host-to-hyp case.
Reviewed-by: Andrew Walbran <redacted>
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 115 ++++++++++++++++++++++++++
1 file changed, 115 insertions(+)
From: Will Deacon <will@kernel.org>
Introduce an unshare hypercall which can be used to unmap memory from
the hypervisor stage-1 in nVHE protected mode. This will be useful to
update the EL2 ownership state of pages during guest teardown, and
avoids keeping dangling mappings to unreferenced portions of memory.
Signed-off-by: Will Deacon <will@kernel.org>
Signed-off-by: Quentin Perret <redacted>
---
arch/arm64/include/asm/kvm_asm.h | 1 +
arch/arm64/kvm/hyp/include/nvhe/mem_protect.h | 1 +
arch/arm64/kvm/hyp/nvhe/hyp-main.c | 8 +++++
arch/arm64/kvm/hyp/nvhe/mem_protect.c | 33 +++++++++++++++++++
4 files changed, 43 insertions(+)
@@ -29,12 +42,27 @@ int kvm_arch_vcpu_run_map_fp(struct kvm_vcpu *vcpu)structuser_fpsimd_state*fpsimd=¤t->thread.uw.fpsimd_state;+kvm_vcpu_unshare_task_fp(vcpu);+/* Make sure the host task fpsimd state is visible to hyp: */ret=kvm_share_hyp(fpsimd,fpsimd+1);-if(!ret)-vcpu->arch.host_fpsimd_state=kern_hyp_va(fpsimd);+if(ret)+returnret;++vcpu->arch.host_fpsimd_state=kern_hyp_va(fpsimd);++/*+*Weneedtokeepcurrent'stask_structpinneduntilitsdatahasbeen+*unsharedwiththehypervisortomakesureitisnotre-usedbythe+*kernelanddonatedtosomeoneelsewhilealreadyshared--see+*kvm_vcpu_unshare_task_fp()forthematchingput_task_struct().+*/+if(is_protected_kvm_enabled()){+get_task_struct(current);+vcpu->arch.parent_task=current;+}-returnret;+return0;}/*
From: Marc Zyngier <maz@kernel.org> Date: 2021-12-16 13:07:56
On Wed, 15 Dec 2021 16:12:17 +0000, Quentin Perret wrote:
This is v4 of the series previously posted here:
https://lore.kernel.org/kvmarm/20211201170411.1561936-1-qperret@google.com/
This series implements an unshare hypercall at EL2 in nVHE protected
mode, and makes use of it to unmmap guest-specific data-structures from
EL2 stage-1 during guest tear-down. Crucially, the implementation of the
share and unshare routines use page refcounts in the host kernel to
avoid accidentally unmapping data-structures that overlap a common page.
[...]
Applied to next, thanks!
[01/14] KVM: arm64: Provide {get,put}_page() stubs for early hyp allocator
commit: 1fac3cfb9cc60d71b66ee5127b2bc5b5f9f79df8
[02/14] KVM: arm64: Refcount hyp stage-1 pgtable pages
commit: 2ea2ff91e82293909d4879b0b4c6c94b02d52b7e
[03/14] KVM: arm64: Fixup hyp stage-1 refcount
commit: d6b4bd3f4897f3b60ac9e8c9e2f0300e739b3392
[04/14] KVM: arm64: Hook up ->page_count() for hypervisor stage-1 page-table
commit: 34ec7cbf1ee0c45e66a0c24311bcd5b83b7109f5
[05/14] KVM: arm64: Implement kvm_pgtable_hyp_unmap() at EL2
commit: 82bb02445de57bb3072052705f6f5dea9465592e
[06/14] KVM: arm64: Introduce kvm_share_hyp()
commit: 3f868e142c0bb052a1c15fd3ceca1391604e2e69
[07/14] KVM: arm64: pkvm: Refcount the pages shared with EL2
commit: a83e2191b7f1894dd0b4b3816ceb9caf4e0cd7e5
[08/14] KVM: arm64: Extend pkvm_page_state enumeration to handle absent pages
commit: 3d467f7b8c0a179a10aa4e9f17cd2d3c3b7e5403
[09/14] KVM: arm64: Introduce wrappers for host and hyp spin lock accessors
commit: 61d99e33e757a21b47b8b130e49dcbdfaa5d2b1c
[10/14] KVM: arm64: Implement do_share() helper for sharing memory
commit: e82edcc75c4e2389a3d7223c4ef1737bd9a07e5d
[11/14] KVM: arm64: Implement __pkvm_host_share_hyp() using do_share()
commit: 1ee32109fd78720259f7431740897d37ebcd84f6
[12/14] KVM: arm64: Implement do_unshare() helper for unsharing memory
commit: 376a240f037959c2b9a2486e53bcd8d388cbec17
[13/14] KVM: arm64: Expose unshare hypercall to the host
commit: b8cc6eb5bded7078f796b2ebf548f79850281eb6
[14/14] KVM: arm64: pkvm: Unshare guest structs during teardown
commit: 52b28657ebd7cd20e931ce71190f235d0fa018a6
Cheers,
M.
--
Without deviation from the norm, progress is not possible.
_______________________________________________
linux-arm-kernel mailing list
linux-arm-kernel@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-arm-kernel