* [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues
@ 2026-06-26 11:25 Paolo Bonzini
2026-06-26 11:25 ` [PATCH 5.15.y 1/8] KVM: x86/mmu: Use a bool for direct Paolo Bonzini
` (7 more replies)
0 siblings, 8 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:25 UTC (permalink / raw)
To: linux-kernel, kvm, stable
Sasha, Greg,
this is the backport to 5.15 for the above CVE. The fix was relatively
simple upstream but only due to years of refactoring and cleaning up
of the code; fixing from scratch is not really feasible so start by
applying the patches that are needed.
Paolo
David Matlack (2):
KVM: x86/mmu: Use a bool for direct
KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root()
Paolo Bonzini (5):
KVM: x86/mmu: Derive shadow MMU page role from parent
KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes
KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page()
KVM: x86: Fix shadow paging use-after-free due to unexpected role
Sean Christopherson (2):
KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
KVM: x86/mmu: Ensure hugepage is in by slot before checking max
mapping level
arch/x86/kvm/mmu/mmu.c | 192 +++++++++++++++++++++------------
arch/x86/kvm/mmu/paging_tmpl.h | 30 +++---
arch/x86/kvm/mmu/spte.h | 5 +
arch/x86/kvm/vmx/vmx_ops.h | 3 +-
include/linux/kvm_host.h | 7 +-
5 files changed, 147 insertions(+), 90 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 1/8] KVM: x86/mmu: Use a bool for direct
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
@ 2026-06-26 11:25 ` Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 2/8] KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root() Paolo Bonzini
` (6 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:25 UTC (permalink / raw)
To: linux-kernel, kvm, stable
Cc: David Matlack, Lai Jiangshan, Sean Christopherson
From: David Matlack <dmatlack@google.com>
commit 27a59d57f073f21f029df1517c2c0a1abea5b0ce upstream.
The parameter "direct" can either be true or false, and all of the
callers pass in a bool variable or true/false literal, so just use the
type bool.
No functional change intended.
Reviewed-by: Lai Jiangshan <jiangshanlai@gmail.com>
Reviewed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Message-Id: <20220516232138.1783324-3-dmatlack@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e4813964bfa0..c03c4341a87f 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -1737,7 +1737,7 @@ static void drop_parent_pte(struct kvm_mmu_page *sp,
mmu_spte_clear_no_track(parent_pte);
}
-static struct kvm_mmu_page *kvm_mmu_alloc_page(struct kvm_vcpu *vcpu, int direct)
+static struct kvm_mmu_page *kvm_mmu_alloc_page(struct kvm_vcpu *vcpu, bool direct)
{
struct kvm_mmu_page *sp;
@@ -2074,7 +2074,7 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
gfn_t gfn,
gva_t gaddr,
unsigned level,
- int direct,
+ bool direct,
unsigned int access)
{
bool direct_mmu = vcpu->arch.mmu->direct_map;
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 2/8] KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root()
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
2026-06-26 11:25 ` [PATCH 5.15.y 1/8] KVM: x86/mmu: Use a bool for direct Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent Paolo Bonzini
` (5 subsequent siblings)
7 siblings, 0 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable; +Cc: David Matlack, Lai Jiangshan
From: David Matlack <dmatlack@google.com>
commit 86938ab6925b8fe174ca6abf397e6ea9d3c054a4 upstream.
The "direct" argument is vcpu->arch.mmu->root_role.direct,
because unlike non-root page tables, it's impossible to have
a direct root in an indirect MMU. So just use that.
Suggested-by: Lai Jiangshan <jiangshanlai@gmail.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Message-Id: <20220516232138.1783324-4-dmatlack@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index c03c4341a87f..bd7650380ad9 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -3409,8 +3409,9 @@ static int mmu_check_root(struct kvm_vcpu *vcpu, gfn_t root_gfn)
}
static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, gva_t gva,
- u8 level, bool direct)
+ u8 level)
{
+ bool direct = vcpu->arch.mmu->mmu_role.base.direct;
struct kvm_mmu_page *sp;
sp = kvm_mmu_get_page(vcpu, gfn, gva, level, direct, ACC_ALL);
@@ -3436,7 +3437,7 @@ static int mmu_alloc_direct_roots(struct kvm_vcpu *vcpu)
root = kvm_tdp_mmu_get_vcpu_root_hpa(vcpu);
mmu->root_hpa = root;
} else if (shadow_root_level >= PT64_ROOT_4LEVEL) {
- root = mmu_alloc_root(vcpu, 0, 0, shadow_root_level, true);
+ root = mmu_alloc_root(vcpu, 0, 0, shadow_root_level);
mmu->root_hpa = root;
} else if (shadow_root_level == PT32E_ROOT_LEVEL) {
if (WARN_ON_ONCE(!mmu->pae_root)) {
@@ -3448,7 +3449,7 @@ static int mmu_alloc_direct_roots(struct kvm_vcpu *vcpu)
WARN_ON_ONCE(IS_VALID_PAE_ROOT(mmu->pae_root[i]));
root = mmu_alloc_root(vcpu, i << (30 - PAGE_SHIFT),
- i << 30, PT32_ROOT_LEVEL, true);
+ i << 30, PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | PT_PRESENT_MASK |
shadow_me_mask;
}
@@ -3511,7 +3512,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
*/
if (mmu->root_level >= PT64_ROOT_4LEVEL) {
root = mmu_alloc_root(vcpu, root_gfn, 0,
- mmu->shadow_root_level, false);
+ mmu->shadow_root_level);
mmu->root_hpa = root;
goto set_root_pgd;
}
@@ -3557,7 +3558,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
}
root = mmu_alloc_root(vcpu, root_gfn, i << 30,
- PT32_ROOT_LEVEL, false);
+ PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | pm_mask;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
2026-06-26 11:25 ` [PATCH 5.15.y 1/8] KVM: x86/mmu: Use a bool for direct Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 2/8] KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root() Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 12:01 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes Paolo Bonzini
` (4 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable; +Cc: Peter Xu, David Matlack
commit 2e65e842c57d72e9a573ba42bc2055b7f626ea1f upstream.
Instead of computing the shadow page role from scratch for every new
page, derive most of the information from the parent shadow page. This
eliminates the dependency on the vCPU root role to allocate shadow page
tables, and reduces the number of parameters to kvm_mmu_get_page().
Preemptively split out the role calculation to a separate function for
use in a following commit.
Note that when calculating the MMU root role, we can take
@role.passthrough, @role.direct, and @role.access directly from
@vcpu->arch.mmu->root_role. Only @role.level and @role.quadrant still
must be overridden for PAE page directories, when shadowing 32-bit
guest page tables with PAE page tables.
No functional change intended.
Reviewed-by: Peter Xu <peterx@redhat.com>
Signed-off-by: David Matlack <dmatlack@google.com>
Message-Id: <20220516232138.1783324-5-dmatlack@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 99 ++++++++++++++++++++++------------
arch/x86/kvm/mmu/paging_tmpl.h | 9 ++--
2 files changed, 71 insertions(+), 37 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index bd7650380ad9..3a5ed9670377 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2070,33 +2070,15 @@ static void clear_sp_write_flooding_count(u64 *spte)
__clear_sp_write_flooding_count(sptep_to_sp(spte));
}
-static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
- gfn_t gfn,
- gva_t gaddr,
- unsigned level,
- bool direct,
- unsigned int access)
+static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, gfn_t gfn,
+ union kvm_mmu_page_role role)
{
bool direct_mmu = vcpu->arch.mmu->direct_map;
- union kvm_mmu_page_role role;
struct hlist_head *sp_list;
- unsigned quadrant;
struct kvm_mmu_page *sp;
int collisions = 0;
LIST_HEAD(invalid_list);
- role = vcpu->arch.mmu->mmu_role.base;
- role.level = level;
- role.direct = direct;
- if (role.direct)
- role.gpte_is_8_bytes = true;
- role.access = access;
- if (!direct_mmu && vcpu->arch.mmu->root_level <= PT32_ROOT_LEVEL) {
- quadrant = gaddr >> (PAGE_SHIFT + (PT64_PT_BITS * level));
- quadrant &= (1 << ((PT32_PT_BITS - PT64_PT_BITS) * level)) - 1;
- role.quadrant = quadrant;
- }
-
sp_list = &vcpu->kvm->arch.mmu_page_hash[kvm_page_table_hashfn(gfn)];
for_each_valid_sp(vcpu->kvm, sp, sp_list) {
if (sp->gfn != gfn) {
@@ -2114,7 +2096,7 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
* Unsync pages must not be left as is, because the new
* upper-level page will be write-protected.
*/
- if (level > PG_LEVEL_4K && sp->unsync)
+ if (role.level > PG_LEVEL_4K && sp->unsync)
kvm_mmu_prepare_zap_page(vcpu->kvm, sp,
&invalid_list);
continue;
@@ -2152,14 +2134,14 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
++vcpu->kvm->stat.mmu_cache_miss;
- sp = kvm_mmu_alloc_page(vcpu, direct);
+ sp = kvm_mmu_alloc_page(vcpu, role.direct);
sp->gfn = gfn;
sp->role = role;
hlist_add_head(&sp->hash_link, sp_list);
- if (!direct) {
+ if (!role.direct) {
account_shadowed(vcpu->kvm, sp);
- if (level == PG_LEVEL_4K && rmap_write_protect(vcpu, gfn))
+ if (role.level == PG_LEVEL_4K && rmap_write_protect(vcpu, gfn))
kvm_flush_remote_tlbs_with_address(vcpu->kvm, gfn, 1);
}
trace_kvm_mmu_get_page(sp, true);
@@ -2171,6 +2153,54 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
return sp;
}
+static union kvm_mmu_page_role kvm_mmu_child_role(u64 *sptep, bool direct, unsigned int access)
+{
+ struct kvm_mmu_page *parent_sp = sptep_to_sp(sptep);
+ union kvm_mmu_page_role role;
+
+ role = parent_sp->role;
+ role.level--;
+ role.access = access;
+ role.direct = direct;
+
+ /*
+ * If the guest has 4-byte PTEs then that means it's using 32-bit,
+ * 2-level, non-PAE paging. KVM shadows such guests with PAE paging
+ * (i.e. 8-byte PTEs). The difference in PTE size means that KVM must
+ * shadow each guest page table with multiple shadow page tables, which
+ * requires extra bookkeeping in the role.
+ *
+ * Specifically, to shadow the guest's page directory (which covers a
+ * 4GiB address space), KVM uses 4 PAE page directories, each mapping
+ * 1GiB of the address space. @role.quadrant encodes which quarter of
+ * the address space each maps.
+ *
+ * To shadow the guest's page tables (which each map a 4MiB region), KVM
+ * uses 2 PAE page tables, each mapping a 2MiB region. For these,
+ * @role.quadrant encodes which half of the region they map.
+ *
+ * Note, the 4 PAE page directories are pre-allocated and the quadrant
+ * assigned in mmu_alloc_root(). So only page tables need to be handled
+ * here.
+ */
+ if (!role.gpte_is_8_bytes) {
+ WARN_ON_ONCE(role.level != PG_LEVEL_4K);
+ role.quadrant = (sptep - parent_sp->spt) % 2;
+ }
+
+ return role;
+}
+
+static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
+ u64 *sptep, gfn_t gfn,
+ bool direct, unsigned int access)
+{
+ union kvm_mmu_page_role role;
+
+ role = kvm_mmu_child_role(sptep, direct, access);
+ return kvm_mmu_get_page(vcpu, gfn, role);
+}
+
static void shadow_walk_init_using_root(struct kvm_shadow_walk_iterator *iterator,
struct kvm_vcpu *vcpu, hpa_t root,
u64 addr)
@@ -3013,8 +3043,7 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, u32 error_code,
if (is_shadow_present_pte(*it.sptep))
continue;
- sp = kvm_mmu_get_page(vcpu, base_gfn, it.addr,
- it.level - 1, true, ACC_ALL);
+ sp = kvm_mmu_get_child_sp(vcpu, it.sptep, base_gfn, true, ACC_ALL);
link_shadow_page(vcpu, it.sptep, sp);
if (is_tdp && huge_page_disallowed &&
@@ -3408,13 +3437,18 @@ static int mmu_check_root(struct kvm_vcpu *vcpu, gfn_t root_gfn)
return ret;
}
-static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, gva_t gva,
+static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, int quadrant,
u8 level)
{
- bool direct = vcpu->arch.mmu->mmu_role.base.direct;
+ union kvm_mmu_page_role role = vcpu->arch.mmu->mmu_role.base;
struct kvm_mmu_page *sp;
- sp = kvm_mmu_get_page(vcpu, gfn, gva, level, direct, ACC_ALL);
+ role.level = level;
+
+ if (!role.gpte_is_8_bytes)
+ role.quadrant = quadrant;
+
+ sp = kvm_mmu_get_page(vcpu, gfn, role);
++sp->root_count;
return __pa(sp->spt);
@@ -3448,8 +3482,8 @@ static int mmu_alloc_direct_roots(struct kvm_vcpu *vcpu)
for (i = 0; i < 4; ++i) {
WARN_ON_ONCE(IS_VALID_PAE_ROOT(mmu->pae_root[i]));
- root = mmu_alloc_root(vcpu, i << (30 - PAGE_SHIFT),
- i << 30, PT32_ROOT_LEVEL);
+ root = mmu_alloc_root(vcpu, i << (30 - PAGE_SHIFT), i,
+ PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | PT_PRESENT_MASK |
shadow_me_mask;
}
@@ -3557,8 +3591,7 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
root_gfn = pdptrs[i] >> PAGE_SHIFT;
}
- root = mmu_alloc_root(vcpu, root_gfn, i << 30,
- PT32_ROOT_LEVEL);
+ root = mmu_alloc_root(vcpu, root_gfn, i, PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | pm_mask;
}
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index a1811f51eda9..cc70cbb3f261 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -704,8 +704,9 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
if (!is_shadow_present_pte(*it.sptep)) {
table_gfn = gw->table_gfn[it.level - 2];
access = gw->pt_access[it.level - 2];
- sp = kvm_mmu_get_page(vcpu, table_gfn, addr,
- it.level-1, false, access);
+ sp = kvm_mmu_get_child_sp(vcpu, it.sptep, table_gfn,
+ false, access);
+
/*
* We must synchronize the pagetable before linking it
* because the guest doesn't need to flush tlb when
@@ -763,8 +764,8 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
drop_large_spte(vcpu, it.sptep);
if (!is_shadow_present_pte(*it.sptep)) {
- sp = kvm_mmu_get_page(vcpu, base_gfn, addr,
- it.level - 1, true, direct_access);
+ sp = kvm_mmu_get_child_sp(vcpu, it.sptep, base_gfn,
+ true, direct_access);
link_shadow_page(vcpu, it.sptep, sp);
if (huge_page_disallowed && req_level >= it.level)
account_huge_nx_page(vcpu->kvm, sp);
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
` (2 preceding siblings ...)
2026-06-26 11:26 ` [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 12:13 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page() Paolo Bonzini
` (3 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable; +Cc: David Matlack
commit 7f49777550e55a7d6832cbb0873f48f91c175b9c upstream.
The quadrant is only used when gptes are 4 bytes, but
mmu_alloc_{direct,shadow}_roots() pass in a non-zero quadrant for PAE
page directories regardless. Make this less confusing by only passing in
a non-zero quadrant when it is actually necessary.
Signed-off-by: David Matlack <dmatlack@google.com>
Message-Id: <20220516232138.1783324-6-dmatlack@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 20 ++++++++++++++------
1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 3a5ed9670377..dbc18d4cc572 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -3444,9 +3444,10 @@ static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, int quadrant,
struct kvm_mmu_page *sp;
role.level = level;
+ role.quadrant = quadrant;
- if (!role.gpte_is_8_bytes)
- role.quadrant = quadrant;
+ WARN_ON_ONCE(quadrant && role.gpte_is_8_bytes);
+ WARN_ON_ONCE(role.direct && !role.gpte_is_8_bytes);
sp = kvm_mmu_get_page(vcpu, gfn, role);
++sp->root_count;
@@ -3482,7 +3483,7 @@ static int mmu_alloc_direct_roots(struct kvm_vcpu *vcpu)
for (i = 0; i < 4; ++i) {
WARN_ON_ONCE(IS_VALID_PAE_ROOT(mmu->pae_root[i]));
- root = mmu_alloc_root(vcpu, i << (30 - PAGE_SHIFT), i,
+ root = mmu_alloc_root(vcpu, i << (30 - PAGE_SHIFT), 0,
PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | PT_PRESENT_MASK |
shadow_me_mask;
@@ -3506,9 +3507,8 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
struct kvm_mmu *mmu = vcpu->arch.mmu;
u64 pdptrs[4], pm_mask;
gfn_t root_gfn, root_pgd;
+ int quadrant, i, r;
hpa_t root;
- unsigned i;
- int r;
root_pgd = mmu->get_guest_pgd(vcpu);
root_gfn = root_pgd >> PAGE_SHIFT;
@@ -3591,7 +3591,15 @@ static int mmu_alloc_shadow_roots(struct kvm_vcpu *vcpu)
root_gfn = pdptrs[i] >> PAGE_SHIFT;
}
- root = mmu_alloc_root(vcpu, root_gfn, i, PT32_ROOT_LEVEL);
+ /*
+ * If shadowing 32-bit non-PAE page tables, each PAE page
+ * directory maps one quarter of the guest's non-PAE page
+ * directory. Othwerise each PAE page direct shadows one guest
+ * PAE page directory so that quadrant should be 0.
+ */
+ quadrant = !mmu->mmu_role.base.gpte_is_8_bytes ? i : 0;
+
+ root = mmu_alloc_root(vcpu, root_gfn, quadrant, PT32_ROOT_LEVEL);
mmu->pae_root[i] = root | pm_mask;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page()
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
` (3 preceding siblings ...)
2026-06-26 11:26 ` [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 12:28 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 6/8] KVM: x86: Fix shadow paging use-after-free due to unexpected GFN Paolo Bonzini
` (2 subsequent siblings)
7 siblings, 1 reply; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable; +Cc: Sean Christopherson
commit 0cd8dc739833080aa0813cbd94d907a93e3a14c3 upstream.
Before allocating a child shadow page table, all callers check
whether the parent already points to a huge page and, if so, they
drop that SPTE. This is done by drop_large_spte().
However, dropping the large SPTE is really only necessary before the
sp is installed. While the sp is returned by kvm_mmu_get_child_sp(),
installing it happens later in __link_shadow_page(). Move the call
there instead of having it in each and every caller.
To ensure that the shadow page is not linked twice if it was present,
do _not_ opportunistically make kvm_mmu_get_child_sp() idempotent:
instead, return an error value if the shadow page already existed.
This is a bit more verbose, but clearer than NULL.
Finally, now that the drop_large_spte() name is not taken anymore,
remove the two underscores in front of __drop_large_spte().
Reviewed-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 49 +++++++++++++++++++---------------
arch/x86/kvm/mmu/paging_tmpl.h | 29 +++++++++-----------
2 files changed, 40 insertions(+), 38 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index dbc18d4cc572..d58be2e698f7 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -1179,26 +1179,16 @@ static void drop_spte(struct kvm *kvm, u64 *sptep)
rmap_remove(kvm, sptep);
}
-
-static bool __drop_large_spte(struct kvm *kvm, u64 *sptep)
+static void drop_large_spte(struct kvm *kvm, u64 *sptep)
{
- if (is_large_pte(*sptep)) {
- WARN_ON(sptep_to_sp(sptep)->role.level == PG_LEVEL_4K);
- drop_spte(kvm, sptep);
- return true;
- }
+ struct kvm_mmu_page *sp;
- return false;
-}
+ sp = sptep_to_sp(sptep);
+ WARN_ON(sp->role.level == PG_LEVEL_4K);
-static void drop_large_spte(struct kvm_vcpu *vcpu, u64 *sptep)
-{
- if (__drop_large_spte(vcpu->kvm, sptep)) {
- struct kvm_mmu_page *sp = sptep_to_sp(sptep);
-
- kvm_flush_remote_tlbs_with_address(vcpu->kvm, sp->gfn,
+ drop_spte(kvm, sptep);
+ kvm_flush_remote_tlbs_with_address(kvm, sp->gfn,
KVM_PAGES_PER_HPAGE(sp->role.level));
- }
}
/*
@@ -2197,6 +2187,9 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
{
union kvm_mmu_page_role role;
+ if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep))
+ return ERR_PTR(-EEXIST);
+
role = kvm_mmu_child_role(sptep, direct, access);
return kvm_mmu_get_page(vcpu, gfn, role);
}
@@ -2264,13 +2257,21 @@ static void shadow_walk_next(struct kvm_shadow_walk_iterator *iterator)
__shadow_walk_next(iterator, *iterator->sptep);
}
-static void link_shadow_page(struct kvm_vcpu *vcpu, u64 *sptep,
- struct kvm_mmu_page *sp)
+static void __link_shadow_page(struct kvm_vcpu *vcpu,
+ struct kvm_mmu_memory_cache *cache, u64 *sptep,
+ struct kvm_mmu_page *sp)
{
u64 spte;
BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != PT_WRITABLE_MASK);
+ /*
+ * If an SPTE is present already, it must be a leaf and therefore
+ * a large one. Drop it and flush the TLB before installing sp.
+ */
+ if (is_shadow_present_pte(*sptep))
+ drop_large_spte(vcpu->kvm, sptep);
+
spte = make_nonleaf_spte(sp->spt, sp_ad_disabled(sp));
mmu_spte_set(sptep, spte);
@@ -2281,6 +2282,12 @@ static void link_shadow_page(struct kvm_vcpu *vcpu, u64 *sptep,
mark_unsync(sptep);
}
+static void link_shadow_page(struct kvm_vcpu *vcpu, u64 *sptep,
+ struct kvm_mmu_page *sp)
+{
+ __link_shadow_page(vcpu, &vcpu->arch.mmu_pte_list_desc_cache, sptep, sp);
+}
+
static void validate_direct_spte(struct kvm_vcpu *vcpu, u64 *sptep,
unsigned direct_access)
{
@@ -3039,11 +3046,9 @@ static int __direct_map(struct kvm_vcpu *vcpu, gpa_t gpa, u32 error_code,
if (it.level == level)
break;
- drop_large_spte(vcpu, it.sptep);
- if (is_shadow_present_pte(*it.sptep))
- continue;
-
sp = kvm_mmu_get_child_sp(vcpu, it.sptep, base_gfn, true, ACC_ALL);
+ if (sp == ERR_PTR(-EEXIST))
+ continue;
link_shadow_page(vcpu, it.sptep, sp);
if (is_tdp && huge_page_disallowed &&
diff --git a/arch/x86/kvm/mmu/paging_tmpl.h b/arch/x86/kvm/mmu/paging_tmpl.h
index cc70cbb3f261..0f68f5afa642 100644
--- a/arch/x86/kvm/mmu/paging_tmpl.h
+++ b/arch/x86/kvm/mmu/paging_tmpl.h
@@ -698,15 +698,13 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
gfn_t table_gfn;
clear_sp_write_flooding_count(it.sptep);
- drop_large_spte(vcpu, it.sptep);
- sp = NULL;
- if (!is_shadow_present_pte(*it.sptep)) {
- table_gfn = gw->table_gfn[it.level - 2];
- access = gw->pt_access[it.level - 2];
- sp = kvm_mmu_get_child_sp(vcpu, it.sptep, table_gfn,
- false, access);
+ table_gfn = gw->table_gfn[it.level - 2];
+ access = gw->pt_access[it.level - 2];
+ sp = kvm_mmu_get_child_sp(vcpu, it.sptep, table_gfn,
+ false, access);
+ if (sp != ERR_PTR(-EEXIST)) {
/*
* We must synchronize the pagetable before linking it
* because the guest doesn't need to flush tlb when
@@ -735,7 +733,7 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
if (FNAME(gpte_changed)(vcpu, gw, it.level - 1))
goto out_gpte_changed;
- if (sp)
+ if (sp != ERR_PTR(-EEXIST))
link_shadow_page(vcpu, it.sptep, sp);
}
@@ -761,15 +759,14 @@ static int FNAME(fetch)(struct kvm_vcpu *vcpu, gpa_t addr,
validate_direct_spte(vcpu, it.sptep, direct_access);
- drop_large_spte(vcpu, it.sptep);
+ sp = kvm_mmu_get_child_sp(vcpu, it.sptep, base_gfn,
+ true, direct_access);
+ if (sp == ERR_PTR(-EEXIST))
+ continue;
- if (!is_shadow_present_pte(*it.sptep)) {
- sp = kvm_mmu_get_child_sp(vcpu, it.sptep, base_gfn,
- true, direct_access);
- link_shadow_page(vcpu, it.sptep, sp);
- if (huge_page_disallowed && req_level >= it.level)
- account_huge_nx_page(vcpu->kvm, sp);
- }
+ link_shadow_page(vcpu, it.sptep, sp);
+ if (huge_page_disallowed && req_level >= it.level)
+ account_huge_nx_page(vcpu->kvm, sp);
}
ret = mmu_set_spte(vcpu, it.sptep, gw->pte_access, write_fault,
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 6/8] KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
` (4 preceding siblings ...)
2026-06-26 11:26 ` [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page() Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 7/8] KVM: x86: Fix shadow paging use-after-free due to unexpected role Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level Paolo Bonzini
7 siblings, 0 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable
Cc: Sean Christopherson, Alexander Bulekov, Fred Griffoul
From: Sean Christopherson <seanjc@google.com>
commit 0cb2af2ea66ad8ff195c156ea690f11216285bdf upstream.
The shadow MMU computes GFNs for direct shadow pages using sp->gfn plus
the SPTE index. This assumption breaks for shadow paging if the guest
page tables are modified between VM entries (similar to commit
aad885e77496, "KVM: x86/mmu: Drop/zap existing present SPTE even
when creating an MMIO SPTE", 2026-03-27). The flow is as follows:
- a PDE is installed for a 2MB mapping, and a page in that area is
accessed. KVM creates a kvm_mmu_page consisting of 512 4KB pages;
the kvm_mmu_page is marked by FNAME(fetch) as direct-mapped because
the guest's mapping is a huge page (and thus contiguous).
- the PDE mapping is changed from outside the guest.
- the guest accesses another page in the same 2MB area. KVM installs
a new leaf SPTE and rmap entry; the SPTE uses the "correct" GFN
(i.e. based on the new mapping, as changed in the previous step) but
that GFN is outside of the [sp->gfn, sp->gfn + 511] range; therefore
the rmap entry cannot be found and removed when the kvm_mmu_page
is zapped.
- the memslot that covers the first 2MB mapping is deleted, and the
kvm_mmu_page for the now-invalid GPA is zapped. However, rmap_remove()
only looks at the [sp->gfn, sp->gfn + 511] range established in step 1,
and fails to find the rmap entry that was recorded by step 3.
- any operation that causes an rmap walk for the same page accessed
by step 3 then walks a stale rmap and dereferences a freed kvm_mmu_page.
This includes dirty logging or MMU notifier invalidations (e.g., from
MADV_DONTNEED).
The underlying issue is that KVM's walking of shadow PTEs assumes that
if a SPTE is present when KVM wants to install a non-leaf SPTE, then the
existing kvm_mmu_page must be for the correct gfn. Because the only way
for the gfn to be wrong is if KVM messed up and failed to zap a SPTE...
which shouldn't happen, but *actually* only happens in response to a
guest write.
That bug dates back literally forever, as even the first version of KVM
assumes that the GFN matches and walks into the "wrong" shadow page.
However, that was only an imprecision until 2032a93d66fa ("KVM: MMU:
Don't allocate gfns page for direct mmu pages") came along.
Fix it by checking for a target gfn mismatch and zapping the existing
SPTE. That way the old SP and rmap entries are gone, KVM installs
the rmap in the right location, and everyone is happy.
Fixes: 2032a93d66fa ("KVM: MMU: Don't allocate gfns page for direct mmu pages")
Fixes: 6aa8b732ca01 ("kvm: userspace interface")
Reported-by: Alexander Bulekov <bkov@amazon.com>
Reported-by: Fred Griffoul <fgriffo@amazon.co.uk>
Cc: stable@vger.kernel.org
Signed-off-by: Sean Christopherson <seanjc@google.com>
Link: https://patch.msgid.link/20260503201029.106481-1-pbonzini@redhat.com/
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 33 ++++++++++++++-------------------
arch/x86/kvm/mmu/spte.h | 5 +++++
2 files changed, 19 insertions(+), 19 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index d58be2e698f7..6c9656b8062e 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -188,6 +188,8 @@ static struct percpu_counter kvm_total_used_mmu_pages;
static void mmu_spte_set(u64 *sptep, u64 spte);
static union kvm_mmu_page_role
kvm_mmu_calc_root_page_role(struct kvm_vcpu *vcpu);
+static int mmu_page_zap_pte(struct kvm *kvm, struct kvm_mmu_page *sp,
+ u64 *spte, struct list_head *invalid_list);
struct kvm_mmu_role_regs {
const unsigned long cr0;
@@ -1179,18 +1181,6 @@ static void drop_spte(struct kvm *kvm, u64 *sptep)
rmap_remove(kvm, sptep);
}
-static void drop_large_spte(struct kvm *kvm, u64 *sptep)
-{
- struct kvm_mmu_page *sp;
-
- sp = sptep_to_sp(sptep);
- WARN_ON(sp->role.level == PG_LEVEL_4K);
-
- drop_spte(kvm, sptep);
- kvm_flush_remote_tlbs_with_address(kvm, sp->gfn,
- KVM_PAGES_PER_HPAGE(sp->role.level));
-}
-
/*
* Write-protect on the specified @sptep, @pt_protect indicates whether
* spte write-protection is caused by protecting shadow page table.
@@ -2187,7 +2177,8 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
{
union kvm_mmu_page_role role;
- if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep))
+ if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep) &&
+ spte_to_child_sp(*sptep) && spte_to_child_sp(*sptep)->gfn == gfn)
return ERR_PTR(-EEXIST);
role = kvm_mmu_child_role(sptep, direct, access);
@@ -2265,12 +2256,16 @@ static void __link_shadow_page(struct kvm_vcpu *vcpu,
BUILD_BUG_ON(VMX_EPT_WRITABLE_MASK != PT_WRITABLE_MASK);
- /*
- * If an SPTE is present already, it must be a leaf and therefore
- * a large one. Drop it and flush the TLB before installing sp.
- */
- if (is_shadow_present_pte(*sptep))
- drop_large_spte(vcpu->kvm, sptep);
+ if (is_shadow_present_pte(*sptep)) {
+ struct kvm_mmu_page *parent_sp;
+ LIST_HEAD(invalid_list);
+
+ parent_sp = sptep_to_sp(sptep);
+ WARN_ON_ONCE(parent_sp->role.level == PG_LEVEL_4K);
+
+ mmu_page_zap_pte(vcpu->kvm, parent_sp, sptep, &invalid_list);
+ kvm_mmu_remote_flush_or_zap(vcpu->kvm, &invalid_list, true);
+ }
spte = make_nonleaf_spte(sp->spt, sp_ad_disabled(sp));
diff --git a/arch/x86/kvm/mmu/spte.h b/arch/x86/kvm/mmu/spte.h
index 31d6456d8ac3..31d03d15415c 100644
--- a/arch/x86/kvm/mmu/spte.h
+++ b/arch/x86/kvm/mmu/spte.h
@@ -267,6 +267,11 @@ static inline bool is_executable_pte(u64 spte)
return (spte & (shadow_x_mask | shadow_nx_mask)) == shadow_x_mask;
}
+static inline struct kvm_mmu_page *spte_to_child_sp(u64 spte)
+{
+ return to_shadow_page(spte & PT64_BASE_ADDR_MASK);
+}
+
static inline kvm_pfn_t spte_to_pfn(u64 pte)
{
return (pte & PT64_BASE_ADDR_MASK) >> PAGE_SHIFT;
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 7/8] KVM: x86: Fix shadow paging use-after-free due to unexpected role
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
` (5 preceding siblings ...)
2026-06-26 11:26 ` [PATCH 5.15.y 6/8] KVM: x86: Fix shadow paging use-after-free due to unexpected GFN Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level Paolo Bonzini
7 siblings, 0 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable; +Cc: Hyunwoo Kim
Commit 0cb2af2ea66ad ("KVM: x86: Fix shadow paging use-after-free due
to unexpected GFN") fixed a shadow paging mismatch between stored and
computed GFNs; the bug could be triggered by changing a PDE mapping from
outside the guest, and then deleting a memslot. The rmap_remove()
call would miss entries created after the PDE change because the GFN
of the leaf SPTE does not match the GFN of the struct kvm_mmu_page.
A similar hole however remains if the modified PDE points to a non-leaf
page. In this case the gfn can be made to match, but the role does not
match: the original large 2MB page creates a kvm_mmu_page with direct=1,
while the new 4KB needs a kvm_mmu_page with direct=0. However,
kvm_mmu_get_child_sp() does not compare the role, and therefore reuses
the page.
The next step is installing a leaf (4KB) SPTE on the new path which
records an rmap entry under the gfn resolved by the walk. But when
that child is zapped its parent kvm_mmu_page has direct=1 and
kvm_mmu_page_get_gfn() computes the gfn for the 4KB page as
sp->gfn + index instead of using sp->shadowed_translation[] (or sp->gfns[]
in older kernels). It therefore fails to remove the recorded entry.
When the memslot is dropped the shadow page is freed but the rmap
entry survives, as in the scenario that was already fixed. Code that
later walks that gfn (dirty logging, MMU notifier invalidation, and
so on) dereferences an sptep that lies in the freed page, causing the
use-after-free.
Fixes: 2032a93d66fa ("KVM: MMU: Don't allocate gfns page for direct mmu pages")
Reported-by: Hyunwoo Kim <imv4bel@gmail.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index 6c9656b8062e..e9dbe3e7ec62 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -2175,13 +2175,15 @@ static struct kvm_mmu_page *kvm_mmu_get_child_sp(struct kvm_vcpu *vcpu,
u64 *sptep, gfn_t gfn,
bool direct, unsigned int access)
{
- union kvm_mmu_page_role role;
+ union kvm_mmu_page_role role = kvm_mmu_child_role(sptep, direct, access);
- if (is_shadow_present_pte(*sptep) && !is_large_pte(*sptep) &&
- spte_to_child_sp(*sptep) && spte_to_child_sp(*sptep)->gfn == gfn)
+ if (is_shadow_present_pte(*sptep) &&
+ !is_large_pte(*sptep) &&
+ spte_to_child_sp(*sptep) &&
+ spte_to_child_sp(*sptep)->gfn == gfn &&
+ spte_to_child_sp(*sptep)->role.word == role.word)
return ERR_PTR(-EEXIST);
- role = kvm_mmu_child_role(sptep, direct, access);
return kvm_mmu_get_page(vcpu, gfn, role);
}
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
` (6 preceding siblings ...)
2026-06-26 11:26 ` [PATCH 5.15.y 7/8] KVM: x86: Fix shadow paging use-after-free due to unexpected role Paolo Bonzini
@ 2026-06-26 11:26 ` Paolo Bonzini
2026-06-26 12:57 ` sashiko-bot
2026-06-26 17:54 ` Sasha Levin
7 siblings, 2 replies; 15+ messages in thread
From: Paolo Bonzini @ 2026-06-26 11:26 UTC (permalink / raw)
To: linux-kernel, kvm, stable
Cc: Sean Christopherson, David Matlack, James Houghton,
Alexander Bulekov, Fred Griffoul, Alexander Graf, David Woodhouse,
Filippo Sironi, Ivan Orlov
From: Sean Christopherson <seanjc@google.com>
commit ef057cbf825e03b63f6edf5980f96abf3c53089d upstream.
When recovering hugepages in the shadow MMU, verify that the base gfn of
the shadow page is actually contained within the target memslot, *before*
querying the max mapping level given the shadow page's gfn. Failure to
pre-check the validity of the gfn can lead to an out-of-bounds access to
the slot's lpage_info (which typically manifests as a host #PF because the
lpage_info is vmalloc'd) if the guest creates a hugepage mapping (in its
PTEs) that extends "below" the bounds of a memslot.
When faulting in memory for a guest, and the size of the guest mapping is
greater than KVM's (current) max mapping, then KVM will create a "direct"
shadow page (direct in that there are no gPTEs to shadow, and so the target
gfn is a direct calculation given the base gfn of the shadow page). The
hugepage recovery flow looks for such direct shadow pages, as forcing 4KiB
mappings when dirty logging generates the guest > host mapping size case.
When the 4KiB restriction is lifted, then KVM can replace the shadow page
with a hugepage.
But if KVM originally used a smaller mapping than the guest because the
range of memory covered by the guest hugepage exceeds the bounds of a
memslot, then KVM will link a direct shadow page with a gfn that is outside
the bounds of the memslot being used to fault in memory. The rmap entry
added for the leaf mapping is correct and within bounds, but the gfn of the
leaf SPTE's parent shadow page will be out of bounds.
BUG: unable to handle page fault for address: ffffc90000806ffc
#PF: supervisor read access in kernel mode
#PF: error_code(0x0000) - not-present page
PGD 100000067 P4D 100000067 PUD 1002a7067 PMD 10612f067 PTE 0
Oops: Oops: 0000 [#1] SMP
CPU: 13 UID: 1000 PID: 757 Comm: mmu_stress_test Not tainted 7.1.0-rc1-48ce1e26eace-x86_pir_to_irr_comments-vm #341 PREEMPT
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 0.0.0 02/06/2015
RIP: 0010:kvm_mmu_max_mapping_level+0x79/0x2b0 [kvm]
Call Trace:
<TASK>
kvm_mmu_recover_huge_pages+0x21b/0x320 [kvm]
kvm_set_memslot+0x1ee/0x590 [kvm]
kvm_set_memory_region.part.0+0x3a1/0x4d0 [kvm]
kvm_vm_ioctl+0x9bf/0x15d0 [kvm]
__x64_sys_ioctl+0x8a/0xd0
do_syscall_64+0xb7/0xbb0
entry_SYSCALL_64_after_hwframe+0x4b/0x53
RIP: 0033:0x7f21c0f1a9bf
</TASK>
Don't bother pre-checking the bounds of the potential hugepage, i.e. don't
check that e.g. sp->gfn + KVM_PAGES_PER_HPAGE(sp->role.level + 1) is also
within the memslot, as the checks performed by kvm_mmu_max_mapping_level()
are a superset of the basic bounds checks. I.e. pre-checking the full
range would be a dubious micro-optimization.
Fixes: 9eba50f8d7fc ("KVM: x86/mmu: Consult max mapping level when zapping collapsible SPTEs")
Cc: stable@vger.kernel.org
Cc: David Matlack <dmatlack@google.com>
Cc: James Houghton <jthoughton@google.com>
Cc: Alexander Bulekov <bkov@amazon.com>
Cc: Fred Griffoul <fgriffo@amazon.co.uk>
Cc: Alexander Graf <graf@amazon.de>
Cc: David Woodhouse <dwmw@amazon.co.uk>
Cc: Filippo Sironi <sironi@amazon.de>
Cc: Ivan Orlov <iorlov@amazon.co.uk>
Signed-off-by: Sean Christopherson <seanjc@google.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
arch/x86/kvm/mmu/mmu.c | 18 ++++++++++++------
include/linux/kvm_host.h | 7 ++++++-
2 files changed, 18 insertions(+), 7 deletions(-)
diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
index e9dbe3e7ec62..9a3020648c8b 100644
--- a/arch/x86/kvm/mmu/mmu.c
+++ b/arch/x86/kvm/mmu/mmu.c
@@ -5883,13 +5883,19 @@ static bool kvm_mmu_zap_collapsible_spte(struct kvm *kvm,
pfn = spte_to_pfn(*sptep);
/*
- * We cannot do huge page mapping for indirect shadow pages,
- * which are found on the last rmap (level = 1) when not using
- * tdp; such shadow pages are synced with the page table in
- * the guest, and the guest page table is using 4K page size
- * mapping if the indirect sp has level = 1.
+ * Direct shadow page can be replaced by a hugepage if the host
+ * mapping level allows it and the memslot maps all of the host
+ * hugepage. Note! If the memslot maps only part of the
+ * hugepage, sp->gfn may be below slot->base_gfn, and querying
+ * the max mapping level would cause an out-of-bounds lpage_info
+ * access. So the gfn bounds check *must* be done first.
+ *
+ * Indirect shadow pages are created when the guest page tables
+ * are using 4K pages. Since the host mapping is always
+ * constrained by the page size in the guest, indirect shadow
+ * pages are never collapsible.
*/
- if (sp->role.direct && !kvm_is_reserved_pfn(pfn) &&
+ if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) &&
sp->role.level < kvm_mmu_max_mapping_level(kvm, slot, sp->gfn,
pfn, PG_LEVEL_NUM)) {
pte_list_remove(kvm, rmap_head, sptep);
diff --git a/include/linux/kvm_host.h b/include/linux/kvm_host.h
index 956a568c2dc2..559564bb6af7 100644
--- a/include/linux/kvm_host.h
+++ b/include/linux/kvm_host.h
@@ -1311,6 +1311,11 @@ int kvm_request_irq_source_id(struct kvm *kvm);
void kvm_free_irq_source_id(struct kvm *kvm, int irq_source_id);
bool kvm_arch_irqfd_allowed(struct kvm *kvm, struct kvm_irqfd *args);
+static inline bool is_gfn_in_memslot(const struct kvm_memory_slot *slot, gfn_t gfn)
+{
+ return gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages;
+}
+
/*
* Returns a pointer to the memslot at slot_index if it contains gfn.
* Otherwise returns NULL.
@@ -1331,7 +1336,7 @@ try_get_memslot(struct kvm_memslots *slots, int slot_index, gfn_t gfn)
slot_index = array_index_nospec(slot_index, slots->used_slots);
slot = &slots->memslots[slot_index];
- if (gfn >= slot->base_gfn && gfn < slot->base_gfn + slot->npages)
+ if (is_gfn_in_memslot(slot, gfn))
return slot;
else
return NULL;
--
2.54.0
^ permalink raw reply related [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent
2026-06-26 11:26 ` [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent Paolo Bonzini
@ 2026-06-26 12:01 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-26 12:01 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Missing `gpte_is_8_bytes = true` override for direct pages causes KVM invariant violations, memory bloat, and guest-triggerable host panics (DoS).
--
commit 9e9bc7152f4bea9d4c30ffc781d2f28f82a2c951
Author: Paolo Bonzini <pbonzini@redhat.com>
Subject: KVM: x86/mmu: Derive shadow MMU page role from parent
This commit refactors how the shadow MMU page role is computed by
deriving it from the parent shadow page. This removes the reliance on
the vCPU root role when allocating shadow page tables.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -2070,33 +2070,15 @@ static void clear_sp_write_flooding_count(u64 *spte)
[ ... ]
> - role.direct = direct;
> - if (role.direct)
> - role.gpte_is_8_bytes = true;
[ ... ]
> @@ -2171,6 +2153,54 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu,
> +static union kvm_mmu_page_role kvm_mmu_child_role(u64 *sptep, bool direct, unsigned int access)
> +{
[ ... ]
> + if (!role.gpte_is_8_bytes) {
> + WARN_ON_ONCE(role.level != PG_LEVEL_4K);
> + role.quadrant = (sptep - parent_sp->spt) % 2;
> + }
> +
> + return role;
> +}
[Severity: High]
Does this missing gpte_is_8_bytes override cause issues for direct maps
when a guest has paging disabled?
Since we no longer set gpte_is_8_bytes = true when role.direct is true,
if a guest runs with CR0.PG=0, mmu_role.base.gpte_is_8_bytes will be false.
In kvm_mmu_child_role(), child direct pages inherit this false value
from their parent. When building non-leaf shadow pages for direct maps,
this will enter the if (!role.gpte_is_8_bytes) block.
Won't this trigger the WARN_ON_ONCE(role.level != PG_LEVEL_4K) assertion
for non-leaf direct maps, and also calculate a bogus quadrant? This
could fragment the cache keys and cause duplicate shadow page allocations
for the same GFN.
[ ... ]
> @@ -3408,13 +3437,18 @@ static int mmu_check_root(struct kvm_vcpu *vcpu, gfn_t root_gfn)
> -static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, gva_t gva,
> +static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, int quadrant,
> u8 level)
> {
> - bool direct = vcpu->arch.mmu->mmu_role.base.direct;
> + union kvm_mmu_page_role role = vcpu->arch.mmu->mmu_role.base;
[ ... ]
> + if (!role.gpte_is_8_bytes)
> + role.quadrant = quadrant;
> +
> + sp = kvm_mmu_get_page(vcpu, gfn, role);
[Severity: High]
Could the lack of the direct override here cause the root page to have
invalid role properties?
When mmu_alloc_root() is called by mmu_alloc_direct_roots() for a guest
with paging disabled, the role is extracted from mmu_role.base. If we
don't override gpte_is_8_bytes to true for direct pages, the root page
will have gpte_is_8_bytes = false.
Because of this, sequentially all children will inherit these invalid
properties, leading to the problems mentioned above in
kvm_mmu_child_role(). Should we ensure gpte_is_8_bytes is set correctly
for direct pages during root allocation?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626112606.1778248-1-pbonzini@redhat.com?part=3
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes
2026-06-26 11:26 ` [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes Paolo Bonzini
@ 2026-06-26 12:13 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-26 12:13 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] The newly added WARN_ON_ONCE(role.direct && !role.gpte_is_8_bytes) in mmu_alloc_root incorrectly triggers when KVM uses the shadow MMU for a guest with paging disabled (CR0.PG=0), enabling a guest-triggerable host Denial of Service.
--
commit f4ad49b2fb4e69e5a09cb3c4ffc7f0aaaa670e2f
Author: Paolo Bonzini <pbonzini@redhat.com>
KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes
The quadrant is only used when gptes are 4 bytes, but
mmu_alloc_{direct,shadow}_roots() pass in a non-zero quadrant for PAE
page directories regardless. Make this less confusing by only passing in
a non-zero quadrant when it is actually necessary.
Message-Id: <20220516232138.1783324-6-dmatlack@google.com>
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index 3a5ed96703776..dbc18d4cc572c 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
> @@ -3444,9 +3444,10 @@ static hpa_t mmu_alloc_root(struct kvm_vcpu *vcpu, gfn_t gfn, int quadrant,
> struct kvm_mmu_page *sp;
>
> role.level = level;
> + role.quadrant = quadrant;
>
> - if (!role.gpte_is_8_bytes)
> - role.quadrant = quadrant;
> + WARN_ON_ONCE(quadrant && role.gpte_is_8_bytes);
> + WARN_ON_ONCE(role.direct && !role.gpte_is_8_bytes);
[Severity: High]
Could this warning in mmu_alloc_root() inadvertently trigger a
guest-triggerable host denial of service?
If KVM uses the shadow MMU (for example, ept=0 or nested without TDP) and
the guest disables paging (CR0.PG=0), KVM falls back to direct mapping mode.
In this state, kvm_calc_shadow_mmu_root_page_role() in arch/x86/kvm/mmu/mmu.c
sets role.base.direct = true, but kvm_calc_shadow_root_page_role_common()
leaves role.base.gpte_is_8_bytes as false since paging is disabled.
When handling the next memory access, wouldn't the call chain
kvm_mmu_load() -> mmu_alloc_direct_roots() -> mmu_alloc_root()
cause this warning to unconditionally evaluate to true and trigger a kernel
panic if the host has panic_on_warn enabled?
> sp = kvm_mmu_get_page(vcpu, gfn, role);
> ++sp->root_count;
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626112606.1778248-1-pbonzini@redhat.com?part=4
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page()
2026-06-26 11:26 ` [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page() Paolo Bonzini
@ 2026-06-26 12:28 ` sashiko-bot
0 siblings, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-26 12:28 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] The newly introduced `__link_shadow_page` function declares a `cache` parameter that is never used.
Pre-existing issues:
- [High] `drop_large_spte` flushes the wrong guest physical address range when dropping a large SPTE, potentially leaving stale hardware TLB entries for the unmapped guest memory.
--
commit 9820244be6c2ac65ed2abfdc79a98053af6c7970
Author: Paolo Bonzini <pbonzini@redhat.com>
KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page()
This patch consolidates the removal of existing large SPTEs by moving the
drop_large_spte() call from multiple caller sites directly into
__link_shadow_page(). It also updates kvm_mmu_get_child_sp() to return
-EEXIST if a non-large shadow page is already present, avoiding double
linking.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
[ ... ]
> -static void drop_large_spte(struct kvm_vcpu *vcpu, u64 *sptep)
> +static void drop_large_spte(struct kvm *kvm, u64 *sptep)
> {
> - if (__drop_large_spte(vcpu->kvm, sptep)) {
> - struct kvm_mmu_page *sp = sptep_to_sp(sptep);
> + struct kvm_mmu_page *sp;
>
> - kvm_flush_remote_tlbs_with_address(vcpu->kvm, sp->gfn,
> + sp = sptep_to_sp(sptep);
> + WARN_ON(sp->role.level == PG_LEVEL_4K);
>
> - }
> + drop_spte(kvm, sptep);
> + kvm_flush_remote_tlbs_with_address(kvm, sp->gfn,
> + KVM_PAGES_PER_HPAGE(sp->role.level));
> }
[Severity: High]
This is a pre-existing issue, but does drop_large_spte() flush the wrong
guest physical address range here?
By using sp->gfn, the flush targets the base GFN of the entire parent page
table, or the guest page directory itself for indirect mappings.
If the dropped SPTE is at an index greater than 0, wouldn't this flush the
0th index instead of the actual memory mapped by the dropped SPTE?
Could this leave stale hardware TLB entries for the unmapped guest memory
on environments supporting precise range-based TLB flushes? Should this
retrieve the exact GFN using the offset of sptep within sp->spt instead?
[ ... ]
> -static void link_shadow_page(struct kvm_vcpu *vcpu, u64 *sptep,
> - struct kvm_mmu_page *sp)
> +static void __link_shadow_page(struct kvm_vcpu *vcpu,
> + struct kvm_mmu_memory_cache *cache, u64 *sptep,
> + struct kvm_mmu_page *sp)
[Severity: Low]
Is the new cache parameter actually used in this function?
It appears the cache parameter is passed in but never referenced. The
underlying code seems to use the cache from the vcpu struct directly
when adding a parent PTE.
Could this parameter be removed to keep the signature clean?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626112606.1778248-1-pbonzini@redhat.com?part=5
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level
2026-06-26 11:26 ` [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level Paolo Bonzini
@ 2026-06-26 12:57 ` sashiko-bot
2026-06-26 17:54 ` Sasha Levin
1 sibling, 0 replies; 15+ messages in thread
From: sashiko-bot @ 2026-06-26 12:57 UTC (permalink / raw)
To: Paolo Bonzini; +Cc: kvm
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] The patch accidentally removes the `!kvm_is_reserved_pfn(pfn)` check while adding the `is_gfn_in_memslot(slot, sp->gfn)` bounds check. This allows reserved PFNs (e.g., from VM_PFNMAP device memory mappings) to be processed for hugepage collapsing, resulting in a host kernel panic.
--
commit 2d19b4db9edc2ca668a2a822466a8d07744d71f3
Author: Sean Christopherson <seanjc@google.com>
KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level
This patch adds a bounds check in the shadow MMU to verify the base gfn of
a shadow page is within the target memslot before querying the max mapping
level. This prevents out-of-bounds access to the slot's lpage_info when
guest hugepage mappings extend below the bounds of a memslot.
> diff --git a/arch/x86/kvm/mmu/mmu.c b/arch/x86/kvm/mmu/mmu.c
> index e9dbe3e7ec622..9a3020648c8be 100644
> --- a/arch/x86/kvm/mmu/mmu.c
> +++ b/arch/x86/kvm/mmu/mmu.c
[ ... ]
> @@ -5883,13 +5883,19 @@ static bool kvm_mmu_zap_collapsible_spte(struct kvm *kvm,
[ ... ]
> - if (sp->role.direct && !kvm_is_reserved_pfn(pfn) &&
> + if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) &&
> sp->role.level < kvm_mmu_max_mapping_level(kvm, slot, sp->gfn,
> pfn, PG_LEVEL_NUM)) {
[Severity: Critical]
Does this code accidentally drop the check for reserved PFNs?
If a memslot maps device memory (VM_PFNMAP), the SPTE contains a reserved
PFN with no backing struct page. Since the !kvm_is_reserved_pfn(pfn) check
was removed, this loop can now pass these reserved PFNs down into
kvm_mmu_max_mapping_level(), which takes the following path:
kvm_mmu_zap_collapsible_spte()
kvm_mmu_max_mapping_level()
host_pfn_mapping_level()
Inside host_pfn_mapping_level(), the code executes:
if (!PageCompound(pfn_to_page(pfn)) && !kvm_is_zone_device_pfn(pfn))
Could calling pfn_to_page() on a reserved PFN here result in dereferencing
an unmapped vmemmap pointer, causing a host kernel panic?
> pte_list_remove(kvm, rmap_head, sptep);
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260626112606.1778248-1-pbonzini@redhat.com?part=8
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level
2026-06-26 11:26 ` [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level Paolo Bonzini
2026-06-26 12:57 ` sashiko-bot
@ 2026-06-26 17:54 ` Sasha Levin
2026-06-26 19:11 ` Sean Christopherson
1 sibling, 1 reply; 15+ messages in thread
From: Sasha Levin @ 2026-06-26 17:54 UTC (permalink / raw)
To: linux-kernel, kvm, stable
Cc: Sasha Levin, Sean Christopherson, David Matlack, James Houghton,
Alexander Bulekov, Fred Griffoul, Alexander Graf, David Woodhouse,
Filippo Sironi, Ivan Orlov, Paolo Bonzini
> - if (sp->role.direct && !kvm_is_reserved_pfn(pfn) &&
> + if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) &&
This drops the !kvm_is_reserved_pfn(pfn) guard instead of adding
is_gfn_in_memslot() alongside it. I think upstream could drop it only because
a8ac499bb6ab ("KVM: x86/mmu: Don't require refcounted "struct page" to create
huge SPTEs") rewrote host_pfn_mapping_level() to stop touching the struct page
but that commit isn't in 5.15. Does it make sense?
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 15+ messages in thread
* Re: [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level
2026-06-26 17:54 ` Sasha Levin
@ 2026-06-26 19:11 ` Sean Christopherson
0 siblings, 0 replies; 15+ messages in thread
From: Sean Christopherson @ 2026-06-26 19:11 UTC (permalink / raw)
To: Sasha Levin
Cc: linux-kernel, kvm, stable, David Matlack, James Houghton,
Alexander Bulekov, Fred Griffoul, Alexander Graf, David Woodhouse,
Filippo Sironi, Ivan Orlov, Paolo Bonzini
On Fri, Jun 26, 2026, Sasha Levin wrote:
> > - if (sp->role.direct && !kvm_is_reserved_pfn(pfn) &&
> > + if (sp->role.direct && is_gfn_in_memslot(slot, sp->gfn) &&
>
> This drops the !kvm_is_reserved_pfn(pfn) guard instead of adding
> is_gfn_in_memslot() alongside it. I think upstream could drop it only because
> a8ac499bb6ab ("KVM: x86/mmu: Don't require refcounted "struct page" to create
> huge SPTEs") rewrote host_pfn_mapping_level() to stop touching the struct page
> but that commit isn't in 5.15. Does it make sense?
Agreed, the kvm_is_reserved_pfn() should be kept for 5.15.
^ permalink raw reply [flat|nested] 15+ messages in thread
end of thread, other threads:[~2026-06-26 19:11 UTC | newest]
Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-26 11:25 [PATCH 5.15.y 0/8] KVM: fixes for CVE-2026-46113 and related issues Paolo Bonzini
2026-06-26 11:25 ` [PATCH 5.15.y 1/8] KVM: x86/mmu: Use a bool for direct Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 2/8] KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root() Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 3/8] KVM: x86/mmu: Derive shadow MMU page role from parent Paolo Bonzini
2026-06-26 12:01 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 4/8] KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes Paolo Bonzini
2026-06-26 12:13 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 5/8] KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page() Paolo Bonzini
2026-06-26 12:28 ` sashiko-bot
2026-06-26 11:26 ` [PATCH 5.15.y 6/8] KVM: x86: Fix shadow paging use-after-free due to unexpected GFN Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 7/8] KVM: x86: Fix shadow paging use-after-free due to unexpected role Paolo Bonzini
2026-06-26 11:26 ` [PATCH 5.15.y 8/8] KVM: x86/mmu: Ensure hugepage is in by slot before checking max mapping level Paolo Bonzini
2026-06-26 12:57 ` sashiko-bot
2026-06-26 17:54 ` Sasha Levin
2026-06-26 19:11 ` Sean Christopherson
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox