lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [day] [month] [year] [list]
Date:   Mon, 3 Jul 2023 17:40:46 +0800
From:   Yan Zhao <yan.y.zhao@...el.com>
To:     Sean Christopherson <seanjc@...gle.com>
CC:     Reima Ishii <ishiir@...cc.u-tokyo.ac.jp>,
        <shina@....u-tokyo.ac.jp>, Paolo Bonzini <pbonzini@...hat.com>,
        Thomas Gleixner <tglx@...utronix.de>,
        Ingo Molnar <mingo@...hat.com>, Borislav Petkov <bp@...en8.de>,
        Dave Hansen <dave.hansen@...ux.intel.com>, <x86@...nel.org>,
        "H. Peter Anvin" <hpa@...or.com>, <kvm@...r.kernel.org>,
        <linux-kernel@...r.kernel.org>, <yuan.yao@...el.com>
Subject: Re: [PATCH] KVM: nVMX: Prevent vmlaunch with EPTP pointing outside
 assigned memory area

On Thu, Jun 29, 2023 at 01:30:31PM -0700, Sean Christopherson wrote:
> On Thu, Jun 29, 2023, Sean Christopherson wrote:
... 
> Turns out, not _that_ gross.  Exempting triple fault "works", but as I called out
> earlier, it generates the incorrect exit reason in KVM-Unit-Tests, e.g. when KUT
> stuffs a bad GUEST_RFLAGS to trigger a VM-Exit consistency check, because the
> consistency check has higher priority than anything that can lead to triple fault.
> And I couldn't bring myself to propagate the hack into testing code.
> 
> Lightly tested, but if this pans out, I'll post a series to (a) exempt triple
> fault so that there's a fix for stable@, (b) effect the below over several patches,
> and (c) revert the triple fault hack so that HEAD is architecturally less wrong.
> 
> ---
>  arch/x86/kvm/kvm_onhyperv.c     |  5 ++
>  arch/x86/kvm/mmu.h              |  7 +--
>  arch/x86/kvm/mmu/mmu.c          | 92 ++++++++++++++++-----------------
>  arch/x86/kvm/mmu/mmu_internal.h | 10 ++++
>  arch/x86/kvm/mmu/spte.h         | 12 +++++
>  arch/x86/kvm/mmu/tdp_iter.c     |  7 ++-
>  arch/x86/kvm/mmu/tdp_mmu.c      |  2 +-
>  7 files changed, 79 insertions(+), 56 deletions(-)
> 
> diff --git a/arch/x86/kvm/kvm_onhyperv.c b/arch/x86/kvm/kvm_onhyperv.c
> index ded0bd688c65..77add2afc92b 100644
> --- a/arch/x86/kvm/kvm_onhyperv.c
> +++ b/arch/x86/kvm/kvm_onhyperv.c
> @@ -113,6 +113,11 @@ void hv_track_root_tdp(struct kvm_vcpu *vcpu, hpa_t root_tdp)
>  {
>  	struct kvm_arch *kvm_arch = &vcpu->kvm->arch;
>  
> +	if (!kvm_mmu_is_usable_root(root_tdp)) {
> +		vcpu->arch.hv_root_tdp = INVALID_PAGE;
> +		return;
> +	}
> +
>  	if (kvm_x86_ops.flush_remote_tlbs == hv_flush_remote_tlbs) {
>  		spin_lock(&kvm_arch->hv_root_tdp_lock);
>  		vcpu->arch.hv_root_tdp = root_tdp;
> diff --git a/arch/x86/kvm/mmu.h b/arch/x86/kvm/mmu.h
> index 92d5a1924fc1..6dcc81582afa 100644
> --- a/arch/x86/kvm/mmu.h
> +++ b/arch/x86/kvm/mmu.h
> @@ -146,12 +146,7 @@ static inline unsigned long kvm_get_active_pcid(struct kvm_vcpu *vcpu)
>  
>  static inline void kvm_mmu_load_pgd(struct kvm_vcpu *vcpu)
>  {
> -	u64 root_hpa = vcpu->arch.mmu->root.hpa;
> -
> -	if (!VALID_PAGE(root_hpa))
> -		return;
> -
> -	static_call(kvm_x86_load_mmu_pgd)(vcpu, root_hpa,
> +	static_call(kvm_x86_load_mmu_pgd)(vcpu, vcpu->arch.mmu->root.hpa,
>  					  vcpu->arch.mmu->root_role.level);
>  }
>  
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 6e6078194ec7..64a918d89472 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -3574,11 +3574,7 @@ static void mmu_free_root_page(struct kvm *kvm, hpa_t *root_hpa,
>  	if (!VALID_PAGE(*root_hpa))
>  		return;
>  
> -	/*
> -	 * The "root" may be a special root, e.g. a PAE entry, treat it as a
> -	 * SPTE to ensure any non-PA bits are dropped.
> -	 */
> -	sp = spte_to_child_sp(*root_hpa);
> +	sp = root_to_sp(*root_hpa);
>  	if (WARN_ON(!sp))
>  		return;
>  
> @@ -3624,7 +3620,9 @@ void kvm_mmu_free_roots(struct kvm *kvm, struct kvm_mmu *mmu,
>  					   &invalid_list);
>  
>  	if (free_active_root) {
> -		if (to_shadow_page(mmu->root.hpa)) {
> +		if (kvm_mmu_is_dummy_root(mmu->root.hpa)) {
> +			/* Nothing to cleanup for dummy roots. */
> +		} else if (root_to_sp(mmu->root.hpa)) {
>  			mmu_free_root_page(kvm, &mmu->root.hpa, &invalid_list);
>  		} else if (mmu->pae_root) {
>  			for (i = 0; i < 4; ++i) {
> @@ -3648,6 +3646,7 @@ EXPORT_SYMBOL_GPL(kvm_mmu_free_roots);
>  void kvm_mmu_free_guest_mode_roots(struct kvm *kvm, struct kvm_mmu *mmu)
>  {
>  	unsigned long roots_to_free = 0;
> +	struct kvm_mmu_page *sp;
>  	hpa_t root_hpa;
>  	int i;
>  
> @@ -3662,8 +3661,8 @@ void kvm_mmu_free_guest_mode_roots(struct kvm *kvm, struct kvm_mmu *mmu)
>  		if (!VALID_PAGE(root_hpa))
>  			continue;
>  
> -		if (!to_shadow_page(root_hpa) ||
> -			to_shadow_page(root_hpa)->role.guest_mode)
> +		sp = root_to_sp(root_hpa);
> +		if (!sp || sp->role.guest_mode)
>  			roots_to_free |= KVM_MMU_ROOT_PREVIOUS(i);
>  	}
>  
> @@ -3671,19 +3670,6 @@ void kvm_mmu_free_guest_mode_roots(struct kvm *kvm, struct kvm_mmu *mmu)
>  }
>  EXPORT_SYMBOL_GPL(kvm_mmu_free_guest_mode_roots);
>  
> -
> -static int mmu_check_root(struct kvm_vcpu *vcpu, gfn_t root_gfn)
> -{
> -	int ret = 0;
> -
> -	if (!kvm_vcpu_is_visible_gfn(vcpu, root_gfn)) {
> -		kvm_make_request(KVM_REQ_TRIPLE_FAULT, vcpu);
> -		ret = 1;
> -	}
> -
> -	return ret;
> -}
> -
>  static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, int quadrant,
>  			    u8 level)
>  {
> @@ -3821,8 +3807,10 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
>  	root_pgd = kvm_mmu_get_guest_pgd(vcpu, mmu);
>  	root_gfn = root_pgd >> PAGE_SHIFT;
>  
> -	if (mmu_check_root(vcpu, root_gfn))
> -		return 1;
> +	if (!kvm_vcpu_is_visible_gfn(vcpu, root_gfn)) {
> +		mmu->root.hpa = kvm_mmu_get_dummy_root();

Also set mmu->root.pgd = root_pgd,
and reset mmu->root.hpa to INVALID_PAGE to trigger a reload after adding
corresponding memslot?

> +		return 0;
> +	}
>  
>  	/*
>  	 * On SVM, reading PDPTRs might access guest memory, which might fault
> @@ -3834,8 +3822,8 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
>  			if (!(pdptrs[i] & PT_PRESENT_MASK))
>  				continue;
>  
> -			if (mmu_check_root(vcpu, pdptrs[i] >> PAGE_SHIFT))
> -				return 1;
> +			if (kvm_vcpu_is_visible_gfn(vcpu, pdptrs[i] >> PAGE_SHIFT))
> +				pdptrs[i] = 0;
>  		}
>  	}
>  
> @@ -4002,7 +3990,7 @@ static bool is_unsync_root(hpa_t root)
>  {
>  	struct kvm_mmu_page *sp;
>  
> -	if (!VALID_PAGE(root))
> +	if (!VALID_PAGE(root) || kvm_mmu_is_dummy_root(root))
>  		return false;
>  
>  	/*
> @@ -4018,7 +4006,7 @@ static bool is_unsync_root(hpa_t root)
>  	 * requirement isn't satisfied.
>  	 */
>  	smp_rmb();
> -	sp = to_shadow_page(root);
> +	sp = root_to_sp(root);
>  
>  	/*
>  	 * PAE roots (somewhat arbitrarily) aren't backed by shadow pages, the
> @@ -4035,8 +4023,9 @@ static bool is_unsync_root(hpa_t root)
>  
>  void kvm_mmu_sync_roots(struct kvm_vcpu *vcpu)
>  {
> -	int i;
>  	struct kvm_mmu_page *sp;
> +	hpa_t root;
> +	int i;
>  
>  	if (vcpu->arch.mmu->root_role.direct)
>  		return;
> @@ -4047,12 +4036,12 @@ void kvm_mmu_sync_roots(struct kvm_vcpu *vcpu)
>  	vcpu_clear_mmio_info(vcpu, MMIO_GVA_ANY);
>  
>  	if (vcpu->arch.mmu->cpu_role.base.level >= PT64_ROOT_4LEVEL) {
> -		hpa_t root = vcpu->arch.mmu->root.hpa;
> -		sp = to_shadow_page(root);
> -
> +		root = vcpu->arch.mmu->root.hpa;
>  		if (!is_unsync_root(root))
>  			return;
>  
> +		sp = root_to_sp(root);
> +
>  		write_lock(&vcpu->kvm->mmu_lock);
>  		mmu_sync_children(vcpu, sp, true);
>  		write_unlock(&vcpu->kvm->mmu_lock);
> @@ -4062,8 +4051,7 @@ void kvm_mmu_sync_roots(struct kvm_vcpu *vcpu)
>  	write_lock(&vcpu->kvm->mmu_lock);
>  
>  	for (i = 0; i < 4; ++i) {
> -		hpa_t root = vcpu->arch.mmu->pae_root[i];
> -
> +		root = vcpu->arch.mmu->pae_root[i];
>  		if (IS_VALID_PAE_ROOT(root)) {
>  			sp = spte_to_child_sp(root);
>  			mmu_sync_children(vcpu, sp, true);
> @@ -4382,7 +4370,7 @@ static int kvm_faultin_pfn(struct kvm_vcpu *vcpu, struct kvm_page_fault *fault,
>  static bool is_page_fault_stale(struct kvm_vcpu *vcpu,
>  				struct kvm_page_fault *fault)
>  {
> -	struct kvm_mmu_page *sp = to_shadow_page(vcpu->arch.mmu->root.hpa);
> +	struct kvm_mmu_page *sp = root_to_sp(vcpu->arch.mmu->root.hpa);
>  
>  	/* Special roots, e.g. pae_root, are not backed by shadow pages. */
>  	if (sp && is_obsolete_sp(vcpu->kvm, sp))
> @@ -4562,9 +4550,16 @@ static void nonpaging_init_context(struct kvm_mmu *context)
>  static inline bool is_root_usable(struct kvm_mmu_root_info *root, gpa_t pgd,
>  				  union kvm_mmu_page_role role)
>  {
> -	return (role.direct || pgd == root->pgd) &&
> -	       VALID_PAGE(root->hpa) &&
> -	       role.word == to_shadow_page(root->hpa)->role.word;
> +	struct kvm_mmu_page *sp;
> +
> +	if (!VALID_PAGE(root->hpa))
> +		return false;
> +
> +	if (!role.direct && pgd != root->pgd)
> +		return false;
> +
> +	sp = root_to_sp(root->hpa);
> +	return sp && role.word == sp->role.word;
>  }
>  
>  /*
> @@ -4634,11 +4629,10 @@ static bool fast_pgd_switch(struct kvm *kvm, struct kvm_mmu *mmu,
>  			    gpa_t new_pgd, union kvm_mmu_page_role new_role)
>  {
>  	/*
> -	 * For now, limit the caching to 64-bit hosts+VMs in order to avoid
> -	 * having to deal with PDPTEs. We may add support for 32-bit hosts/VMs
> -	 * later if necessary.
> +	 * Limit reuse to 64-bit hosts+VMs without "special" roots in order to
> +	 * avoid having to deal with PDPTEs and other complexities.
>  	 */
> -	if (VALID_PAGE(mmu->root.hpa) && !to_shadow_page(mmu->root.hpa))
> +	if (VALID_PAGE(mmu->root.hpa) && !root_to_sp(mmu->root.hpa))
>  		kvm_mmu_free_roots(kvm, mmu, KVM_MMU_ROOT_CURRENT);
>  
>  	if (VALID_PAGE(mmu->root.hpa))
> @@ -4684,9 +4678,12 @@ void kvm_mmu_new_pgd(struct kvm_vcpu *vcpu, gpa_t new_pgd)
>  	 * If this is a direct root page, it doesn't have a write flooding
>  	 * count. Otherwise, clear the write flooding count.
>  	 */
> -	if (!new_role.direct)
> -		__clear_sp_write_flooding_count(
> -				to_shadow_page(vcpu->arch.mmu->root.hpa));
> +	if (!new_role.direct) {
> +		struct kvm_mmu_page *sp = root_to_sp(vcpu->arch.mmu->root.hpa);
> +
> +		if (!WARN_ON_ONCE(!sp))
> +			__clear_sp_write_flooding_count(sp);
> +	}
>  }
>  EXPORT_SYMBOL_GPL(kvm_mmu_new_pgd);
>  
> @@ -5536,12 +5533,13 @@ static bool is_obsolete_root(struct kvm *kvm, hpa_t root_hpa)
>  	 * positives and free roots that don't strictly need to be freed, but
>  	 * such false positives are relatively rare:
>  	 *
> -	 *  (a) only PAE paging and nested NPT has roots without shadow pages
> +	 *  (a) only PAE paging and nested NPT have roots without shadow pages
> +	 *      (or any shadow paging flavor with a dummy root)
>  	 *  (b) remote reloads due to a memslot update obsoletes _all_ roots
>  	 *  (c) KVM doesn't track previous roots for PAE paging, and the guest
>  	 *      is unlikely to zap an in-use PGD.
>  	 */
> -	sp = to_shadow_page(root_hpa);
> +	sp = root_to_sp(root_hpa);
>  	return !sp || is_obsolete_sp(kvm, sp);
>  }
>  
> @@ -5728,7 +5726,7 @@ int noinline kvm_mmu_page_fault(struct kvm_vcpu *vcpu, gpa_t cr2_or_gpa, u64 err
>  	int r, emulation_type = EMULTYPE_PF;
>  	bool direct = vcpu->arch.mmu->root_role.direct;
>  
> -	if (WARN_ON(!VALID_PAGE(vcpu->arch.mmu->root.hpa)))
> +	if (WARN_ON_ONCE(!VALID_PAGE(vcpu->arch.mmu->root.hpa)))
>  		return RET_PF_RETRY;
>  
>  	r = RET_PF_INVALID;
> diff --git a/arch/x86/kvm/mmu/mmu_internal.h b/arch/x86/kvm/mmu/mmu_internal.h
> index d39af5639ce9..3ca986450393 100644
> --- a/arch/x86/kvm/mmu/mmu_internal.h
> +++ b/arch/x86/kvm/mmu/mmu_internal.h
> @@ -44,6 +44,16 @@ extern bool dbg;
>  #define INVALID_PAE_ROOT	0
>  #define IS_VALID_PAE_ROOT(x)	(!!(x))
>  
> +static inline hpa_t kvm_mmu_get_dummy_root(void)
> +{
> +	return my_zero_pfn(0) << PAGE_SHIFT;
> +}
> +
> +static inline bool kvm_mmu_is_dummy_root(hpa_t shadow_page)
> +{
> +	return is_zero_pfn(shadow_page >> PAGE_SHIFT);
> +}
> +
>  typedef u64 __rcu *tdp_ptep_t;
>  
>  struct kvm_mmu_page {
> diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
> index 1279db2eab44..ac8ad12f9698 100644
> --- a/arch/x86/kvm/mmu/spte.h
> +++ b/arch/x86/kvm/mmu/spte.h
> @@ -236,6 +236,18 @@ static inline struct kvm_mmu_page *sptep_to_sp(u64 *sptep)
>  	return to_shadow_page(__pa(sptep));
>  }
>  
> +static inline struct kvm_mmu_page *root_to_sp(hpa_t root)
> +{
> +	if (kvm_mmu_is_dummy_root(root))
> +		return NULL;
> +
> +	/*
> +	 * The "root" may be a special root, e.g. a PAE entry, treat it as a
> +	 * SPTE to ensure any non-PA bits are dropped.
> +	 */
> +	return spte_to_child_sp(root);
> +}
> +
>  static inline bool is_mmio_spte(u64 spte)
>  {
>  	return (spte & shadow_mmio_mask) == shadow_mmio_value &&
> diff --git a/arch/x86/kvm/mmu/tdp_iter.c b/arch/x86/kvm/mmu/tdp_iter.c
> index d2eb0d4f8710..eda82a0e7fdb 100644
> --- a/arch/x86/kvm/mmu/tdp_iter.c
> +++ b/arch/x86/kvm/mmu/tdp_iter.c
> @@ -41,8 +41,11 @@ void tdp_iter_start(struct tdp_iter *iter, struct kvm_mmu_page *root,
>  {
>  	int root_level = root->role.level;
>  
> -	WARN_ON(root_level < 1);
> -	WARN_ON(root_level > PT64_ROOT_MAX_LEVEL);
> +	if (WARN_ON_ONCE(!root || (root_level < 1) ||
Though I think it's impossible for root to be NULL here, it's strange to
access the root->role.level and WARN !root.

> +			 (root_level > PT64_ROOT_MAX_LEVEL))) {
> +		iter->valid = false;
> +		return;
> +	}
>  
>  	iter->next_last_level_gfn = next_last_level_gfn;
>  	iter->root_level = root_level;
> diff --git a/arch/x86/kvm/mmu/tdp_mmu.c b/arch/x86/kvm/mmu/tdp_mmu.c
> index 512163d52194..046ac2589611 100644
> --- a/arch/x86/kvm/mmu/tdp_mmu.c
> +++ b/arch/x86/kvm/mmu/tdp_mmu.c
> @@ -689,7 +689,7 @@ static inline void tdp_mmu_iter_set_spte(struct kvm *kvm, struct tdp_iter *iter,
>  		else
>  
>  #define tdp_mmu_for_each_pte(_iter, _mmu, _start, _end)		\
> -	for_each_tdp_pte(_iter, to_shadow_page(_mmu->root.hpa), _start, _end)
> +	for_each_tdp_pte(_iter, root_to_sp(_mmu->root.hpa), _start, _end)
>  
>  /*
>   * Yield if the MMU lock is contended or this thread needs to return control
> 
> base-commit: 9bee9f1881ecd4eb68ba1ca88b56bff88e50fc8a
> -- 
> 

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ