KVM: fixes for CVE-2026-46113 and related issues
Subsystems (2)
v4 cover KVM: fixes for CVE-2026-46113 and related issues
This is a backport of the CVE-2026-46113 and CVE-2026-53359 fixes to 5.15.y.
v2: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/stable/[email protected]/
Changes from v3:
- Added patches 3-6 to backport the change from gpte_is_8_bytes to has_4_byte_gpte.
- Adjusted patches 7-8 (patches 3/7 and 4/7 in v3) accordingly.
A regression has been reported in the backport of commit 0cb2af2ea66a ("KVM: x86: Fix shadow paging use-after-free due to unexpected GFN") [1], but the corresponding fix has already been merged into 5.15.218 [2].
[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://lore.kernel.org/stable/[email protected]/
David Matlack (2):
KVM: x86/mmu: Use a bool for direct
KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root()
Lai Jiangshan (3):
KVM: X86: Remove useless code to set role.gpte_is_8_bytes when
role.direct
KVM: X86: Calculate quadrant when !role.gpte_is_8_bytes
KVM: X86: Rename gpte_is_8_bytes to has_4_byte_gpte and invert the
direction
Paolo Bonzini (5):
KVM: MMU: update comment on the number of page role combinations
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 (1):
KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
Documentation/virt/kvm/mmu.rst | 8 +- arch/x86/include/asm/kvm_host.h | 32 +++--- arch/x86/kvm/mmu/mmu.c | 184 ++++++++++++++++++++------------ arch/x86/kvm/mmu/mmutrace.h | 2 +- arch/x86/kvm/mmu/paging_tmpl.h | 32 +++--- arch/x86/kvm/mmu/spte.h | 5 + arch/x86/kvm/mmu/tdp_mmu.c | 2 +- 7 files changed, 159 insertions(+), 106 deletions(-)
Signature · 1 line
2.53.0
PATCH v4 1/11 KVM: x86/mmu: Use a bool for direct
From: David Matlack <[email protected]>
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.
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
arch/x86/kvm/mmu/mmu.c +2 -2
@@ -1738,7 +1738,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;
@@ -2079,7 +2079,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;
Signature · 1 line
2.53.0
PATCH v4 2/11 KVM: x86/mmu: Stop passing "direct" to mmu_alloc_root()
In reply to Kenta AkagiFrom: David Matlack <[email protected]>
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.
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-)
arch/x86/kvm/mmu/mmu.c +6 -5
@@ -3439,8 +3439,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 );
@@ -3466,7 +3467,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)) {
@@ -3478,7 +3479,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; }
@@ -3541,7 +3542,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; }
@@ -3587,7 +3588,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; }
Signature · 1 line
2.53.0
PATCH v4 3/11 KVM: MMU: update comment on the number of page role combinations
In reply to Kenta AkagiFrom: Paolo Bonzini <[email protected]>
commit dc1ce45575b3401102568dd60ba8894849d1d64b upstream.
Fix the number of bits in the role, and simplify the explanation of why several bits or combinations of bits are redundant.
--- arch/x86/include/asm/kvm_host.h | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-)
arch/x86/include/asm/kvm_host.h +18 -12
@@ -292,19 +292,25 @@ struct kvm_kernel_irq_routing_entry;* the number of unique SPs that can theoretically be created is 2^n, where n * is the number of bits that are used to compute the role. * -* But, even though there are 18 bits in the mask below, not all combinations -* of modes and flags are possible. The maximum number of possible upper-level -* shadow pages for a single gfn is in the neighborhood of 2^13. +* But, even though there are 19 bits in the mask below, not all combinations +* of modes and flags are possible: * -* - invalid shadow pages are not accounted. -* - level is effectively limited to four combinations, not 16 as the number -* bits would imply, as 4k SPs are not tracked (allowed to go unsync). -* - level is effectively unused for non-PAE paging because there is exactly -* one upper level (see 4k SP exception above). -* - quadrant is used only for non-PAE paging and is exclusive with -* gpte_is_8_bytes. -* - execonly and ad_disabled are used only for nested EPT, which makes it -* exclusive with quadrant. +* - invalid shadow pages are not accounted, so the bits are effectively 18 +* +* - quadrant will only be used if gpte_is_8_bytes=0 (non-PAE paging); +* execonly and ad_disabled are only used for nested EPT which has +* gpte_is_8_bytes=1. Therefore, 2 bits are always unused. +* +* - the 4 bits of level are effectively limited to the values 2/3/4/5, +* as 4k SPs are not tracked (allowed to go unsync). In addition non-PAE +* paging has exactly one upper level, making level completely redundant +* when gpte_is_8_bytes=0. +* +* - on top of this, smep_andnot_wp and smap_andnot_wp are only set if +* cr0_wp=0, therefore these three bits only give rise to 5 possibilities. +* +* Therefore, the maximum number of possible upper-level shadow pages for a +* single gfn is a bit less than 2^13. */ union kvm_mmu_page_role {u32 word;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH 5.15.y v4 03/11] KVM: MMU: update comment on the number of page role combinations
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Low] kvm: mmu: incorrect comment on page role bit count --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Low] kvm: mmu: incorrect comment on page role bit count
--
commit e43e6a4b81c3f6c9da7c094b704c3a172f16148a
Author: Paolo Bonzini <[email protected]>
KVM: MMU: update comment on the number of page role combinations
This commit updates the comment explaining the kvm_mmu_page_role bitmask to clarify why certain bit combinations are redundant.
Quoted 18 lines
arch/x86/include/asm/kvm_host.h +2
-7@@ -292,19 +292,25 @@ struct kvm_kernel_irq_routing_entry;* the number of unique SPs that can theoretically be created is 2^n, where n * is the number of bits that are used to compute the role. * -* But, even though there are 18 bits in the mask below, not all combinations -* of modes and flags are possible. The maximum number of possible upper-level -* shadow pages for a single gfn is in the neighborhood of 2^13. +* But, even though there are 19 bits in the mask below, not all combinations +* of modes and flags are possible: * -* - invalid shadow pages are not accounted. -* - level is effectively limited to four combinations, not 16 as the number -* bits would imply, as 4k SPs are not tracked (allowed to go unsync). -* - level is effectively unused for non-PAE paging because there is exactly
arch/x86/include/asm/kvm_host.h struct kvm_kernel_irq_routing_entry; · line 302
Quoted 10 lines
arch/x86/include/asm/kvm_host.h +5
-5-* one upper level (see 4k SP exception above). -* - quadrant is used only for non-PAE paging and is exclusive with -* gpte_is_8_bytes. -* - execonly and ad_disabled are used only for nested EPT, which makes it -* exclusive with quadrant. +* - invalid shadow pages are not accounted, so the bits are effectively 18 +* +* - quadrant will only be used if gpte_is_8_bytes=0 (non-PAE paging); +* execonly and ad_disabled are only used for nested EPT which has +* gpte_is_8_bytes=1. Therefore, 2 bits are always unused.
[Severity: Low]
Is this updated comment accurate for the current struct definition? The text claims there are 19 bits in the mask and references an execonly bit.
However, looking at the union kvm_mmu_page_role definition immediately following this comment:
union kvm_mmu_page_role {
u32 word;
struct {
unsigned level:4;
unsigned gpte_is_8_bytes:1;
unsigned quadrant:2;
unsigned direct:1;
unsigned access:3;
unsigned invalid:1;
unsigned efer_nx:1;
unsigned cr0_wp:1;
unsigned smep_andnot_wp:1;
unsigned smap_andnot_wp:1;
unsigned ad_disabled:1;
unsigned guest_mode:1;
...Summing these bitfields yields exactly 18 bits, and the execonly bit appears to be missing, having been moved to kvm_mmu_extended_role.
Could the mathematical breakdown in the comment be adjusted to reflect the actual 18 bits present in the structure?
[ ... ]
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=3
PATCH v4 4/11 KVM: X86: Remove useless code to set role.gpte_is_8_bytes when role.direct
In reply to Kenta AkagiFrom: Lai Jiangshan <[email protected]>
commit 41e35604eaff2266ba8523787ebe99c5ca4c4045 upstream.
role.gpte_is_8_bytes is unused when role.direct; there is no point in changing a bit in the role, the value that was set when the MMU is initialized is just fine.
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 2 -- 1 file changed, 2 deletions(-)
arch/x86/kvm/mmu/mmu.c +0 -2
@@ -2093,8 +2093,6 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, 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));
Signature · 1 line
2.53.0
PATCH v4 5/11 KVM: X86: Calculate quadrant when !role.gpte_is_8_bytes
In reply to Kenta AkagiFrom: Lai Jiangshan <[email protected]>
commit b46a13cb7ea1137b2e01dfaafcacd5cd79db8390 upstream.
role.quadrant is only valid when gpte size is 4 bytes and only be calculated when gpte size is 4 bytes.
Although "vcpu->arch.mmu->root_level <= PT32_ROOT_LEVEL" also means gpte size is 4 bytes, but using "!role.gpte_is_8_bytes" is clearer
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
arch/x86/kvm/mmu/mmu.c +1 -1
@@ -2094,7 +2094,7 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, role.level = level; role.direct = direct; role.access = access; -if (!direct_mmu && vcpu->arch.mmu->root_level <=PT32_ROOT_LEVEL ) { +if (!direct_mmu && !role.gpte_is_8_bytes) { quadrant = gaddr >> (PAGE_SHIFT + (PT64_PT_BITS * level)); quadrant &= (1 << ((PT32_PT_BITS -PT64_PT_BITS ) * level)) -1 ; role.quadrant = quadrant;
Signature · 1 line
2.53.0
PATCH v4 6/11 KVM: X86: Rename gpte_is_8_bytes to has_4_byte_gpte and invert the direction
In reply to Kenta AkagiFrom: Lai Jiangshan <[email protected]>
commit bb3b394d35e80d7a58ce015191e4960a13f54ba5 upstream.
This bit is very close to mean "role.quadrant is not in use", except that it is false also when the MMU is mapping guest physical addresses directly. In that case, role.quadrant is indeed not in use, but there are no guest PTEs at all.
Changing the name and direction of the bit removes the special case, since a guest with paging disabled, or not considering guest paging structures as is the case for two-dimensional paging, does not have to deal with 4-byte guest PTEs.
Message-Id: <[email protected]>
--- Documentation/virt/kvm/mmu.rst | 8 ++++---- arch/x86/include/asm/kvm_host.h | 8 ++++---- arch/x86/kvm/mmu/mmu.c | 12 ++++++------ arch/x86/kvm/mmu/mmutrace.h | 2 +- arch/x86/kvm/mmu/tdp_mmu.c | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-)
Documentation/virt/kvm/mmu.rst +4 -4
@@ -161,7 +161,7 @@ Shadow pages contain the following information: If clear, this page corresponds to a guest page table denoted by the gfn field. role.quadrant: - When role.gpte_is_8_bytes=0, the guest uses 32-bit gptes while the host uses 64-bit + When role.has_4_byte_gpte=1, the guest uses 32-bit gptes while the host uses 64-bit sptes. That means a guest page table contains more ptes than the host, so multiple shadow pages are needed to shadow one guest page. For first-level shadow pages, role.quadrant can be 0 or 1 and denotes the
@@ -177,9 +177,9 @@ Shadow pages contain the following information: The page is invalid and should not be used. It is a root page that is currently pinned (by a cpu hardware register pointing to it); once it is unpinned it will be destroyed. - role.gpte_is_8_bytes: - Reflects the size of the guest PTE for which the page is valid, i.e. '1' - if 64-bit gptes are in use, '0' if 32-bit gptes are in use. + role.has_4_byte_gpte: + Reflects the size of the guest PTE for which the page is valid, i.e. '0' + if direct map or 64-bit gptes are in use, '1' if 32-bit gptes are in use. role.efer_nx: Contains the value of efer.nx for which the page is valid. role.cr0_wp:
arch/x86/include/asm/kvm_host.h +4 -4
@@ -297,14 +297,14 @@ struct kvm_kernel_irq_routing_entry;* * - invalid shadow pages are not accounted, so the bits are effectively 18 * -* - quadrant will only be used if gpte_is_8_bytes=0 (non-PAE paging); +* - quadrant will only be used if has_4_byte_gpte=1 (non-PAE paging); * execonly and ad_disabled are only used for nested EPT which has -* gpte_is_8_bytes=1. Therefore, 2 bits are always unused. +* has_4_byte_gpte=0. Therefore, 2 bits are always unused. * * - the 4 bits of level are effectively limited to the values 2/3/4/5, * as 4k SPs are not tracked (allowed to go unsync). In addition non-PAE * paging has exactly one upper level, making level completely redundant -* when gpte_is_8_bytes=0. +* when has_4_byte_gpte=1. * * - on top of this, smep_andnot_wp and smap_andnot_wp are only set if * cr0_wp=0, therefore these three bits only give rise to 5 possibilities.
@@ -316,7 +316,7 @@ union kvm_mmu_page_role {u32 word;struct {unsigned level:4 ; -unsigned gpte_is_8_bytes:1 ; +unsigned has_4_byte_gpte:1 ;unsigned quadrant:2 ;unsigned direct:1 ;unsigned access:3 ;
arch/x86/kvm/mmu/mmu.c +6 -6
@@ -2094,7 +2094,7 @@ static struct kvm_mmu_page *kvm_mmu_get_page(struct kvm_vcpu *vcpu, role.level = level; role.direct = direct; role.access = access; -if (!direct_mmu && !role.gpte_is_8_bytes) { +if (role.has_4_byte_gpte) { quadrant = gaddr >> (PAGE_SHIFT + (PT64_PT_BITS * level)); quadrant &= (1 << ((PT32_PT_BITS -PT64_PT_BITS ) * level)) -1 ; role.quadrant = quadrant;
@@ -4840,7 +4840,7 @@ kvm_calc_tdp_mmu_root_page_role(struct kvm_vcpu *vcpu, role.base.ad_disabled = (shadow_accessed_mask ==0 ); role.base.level =kvm_mmu_get_tdp_level (vcpu); role.base.direct = true; - role.base.gpte_is_8_bytes = true; + role.base.has_4_byte_gpte = false;return role; }
@@ -4885,7 +4885,7 @@ kvm_calc_shadow_root_page_role_common(struct kvm_vcpu *vcpu, role.base.smep_andnot_wp = role.ext.cr4_smep && !____is_cr0_wp (regs); role.base.smap_andnot_wp = role.ext.cr4_smap && !____is_cr0_wp (regs); - role.base.gpte_is_8_bytes =____is_cr0_pg (regs) &&____is_cr4_pae (regs); + role.base.has_4_byte_gpte =____is_cr0_pg (regs) && !____is_cr4_pae (regs);return role; }
@@ -4984,7 +4984,7 @@ kvm_calc_shadow_ept_root_page_role(struct kvm_vcpu *vcpu, bool accessed_dirty, role.base.smm = vcpu->arch.root_mmu.mmu_role.base.smm; role.base.level = level; - role.base.gpte_is_8_bytes = true; + role.base.has_4_byte_gpte = false; role.base.direct = false; role.base.ad_disabled = !accessed_dirty; role.base.guest_mode = true;
@@ -5267,7 +5267,7 @@ static bool detect_write_misaligned(struct kvm_mmu_page *sp, gpa_t gpa, gpa, bytes, sp->role.word); offset =offset_in_page (gpa); - pte_size = sp->role.gpte_is_8_bytes ?8 :4 ; + pte_size = sp->role.has_4_byte_gpte ?4 :8 ;/* * Sometimes, the OS only writes the last one bytes to update status
@@ -5291,7 +5291,7 @@ static u64 *get_written_sptes(struct kvm_mmu_page *sp, gpa_t gpa, int *nspte) page_offset =offset_in_page (gpa); level = sp->role.level; *nspte =1 ; -if (!sp->role.gpte_is_8_bytes) { +if (sp->role.has_4_byte_gpte) { page_offset <<=1 ;/* 32->64 */ /* * A 32-bit pde maps 4MB while the shadow pdes map
arch/x86/kvm/mmu/mmutrace.h +1 -1
@@ -35,7 +35,7 @@" %snxe %sad root %u %s%c" , \ __entry->mmu_valid_gen, \ __entry->gfn, role.level, \ - role.gpte_is_8_bytes ?8 :4 , \ + role.has_4_byte_gpte ?4 :8 , \ role.quadrant, \ role.direct ?" direct" :"" , \ access_str[role.access], \
arch/x86/kvm/mmu/tdp_mmu.c +1 -1
@@ -179,7 +179,7 @@ static union kvm_mmu_page_role page_role_for_level(struct kvm_vcpu *vcpu, role = vcpu->arch.mmu->mmu_role.base; role.level = level; role.direct = true; - role.gpte_is_8_bytes = true; + role.has_4_byte_gpte = false; role.access =ACC_ALL ;return role;
Signature · 1 line
2.53.0
[email protected]
AI reviewRe: [PATCH 5.15.y v4 06/11] KVM: X86: Rename gpte_is_8_bytes to has_4_byte_gpte and invert the direction
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider: - [Medium] tools/kvm: inverted page role bit breaks plugin_kvm.c parsing --
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] tools/kvm: inverted page role bit breaks plugin_kvm.c parsing
--
commit e1e76187db8d1987f4d32cf661bdfabf00f858af
Author: Lai Jiangshan <[email protected]>
KVM: X86: Rename gpte_is_8_bytes to has_4_byte_gpte and invert the direction
The commit renames the gpte_is_8_bytes bit in the MMU page role to has_4_byte_gpte and inverts its logic. This removes a special case since guests with paging disabled do not use 4-byte PTEs.
Quoted 4 lines
arch/x86/include/asm/kvm_host.h +0
-0
[ ... ]
arch/x86/include/asm/kvm_host.h union kvm_mmu_page_role { · line 319
Quoted 6 lines
arch/x86/include/asm/kvm_host.h +1
-1@@ -316,7 +316,7 @@ union kvm_mmu_page_role {u32 word;struct {unsigned level:4 ; -unsigned gpte_is_8_bytes:1 ; +unsigned has_4_byte_gpte:1 ;
[Severity: Medium]
Does this logic inversion break userspace tracing tools?
Looking at tools/lib/traceevent/plugins/plugin_kvm.c, the in-tree plugin parses this raw bit from the role word under the assumption it represents PAE being enabled (when set to 1):
tools/lib/traceevent/plugins/plugin_kvm.c:union kvm_mmu_page_role {
unsigned word;
struct {
unsigned level:4;
unsigned cr4_pae:1;It then prints it as inverted if the bit is zero:
tools/lib/traceevent/plugins/plugin_kvm.c:kvm_mmu_print_role() {
trace_seq_printf(s, "%u q%u%s %s%s %spae %snxe %swp%s%s%s",
...
role.cr4_pae ? "" : "!",Because this patch inverts the logic in the kernel's role bit without updating the plugin, will tracing tools like trace-cmd and perf now display an inverted PAE state (printing 'pae' when disabled, and '!pae' when enabled)?
Quoted 3 lines
arch/x86/include/asm/kvm_host.h +0
-0unsigned quadrant:2; unsigned direct:1; unsigned access:3;
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=6
PATCH v4 7/11 KVM: x86/mmu: Derive shadow MMU page role from parent
In reply to Kenta AkagiFrom: Paolo Bonzini <[email protected]>
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.
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 97 +++++++++++++++++++++++----------- arch/x86/kvm/mmu/paging_tmpl.h | 9 ++-- 2 files changed, 71 insertions(+), 35 deletions(-)
arch/x86/kvm/mmu/mmu.c +66 -31
@@ -2075,31 +2075,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; - role.access = access; -if (role.has_4_byte_gpte) { - 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) {
@@ -2117,7 +2101,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 ;
@@ -2155,14 +2139,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);
@@ -2174,6 +2158,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.has_4_byte_gpte) { +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)
@@ -3041,8 +3073,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 &&
@@ -3436,13 +3467,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.has_4_byte_gpte) + role.quadrant = quadrant; + + sp =kvm_mmu_get_page (vcpu, gfn, role); ++sp->root_count;return __pa (sp->spt);
@@ -3476,8 +3512,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; }
@@ -3585,8 +3621,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; }
arch/x86/kvm/mmu/paging_tmpl.h +5 -4
@@ -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);
Signature · 1 line
2.53.0
PATCH v4 8/11 KVM: x86/mmu: Always pass 0 for @quadrant when gptes are 8 bytes
In reply to Kenta AkagiFrom: Paolo Bonzini <[email protected]>
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.
Message-Id: <[email protected]>
--- arch/x86/kvm/mmu/mmu.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-)
arch/x86/kvm/mmu/mmu.c +14 -6
@@ -3474,9 +3474,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.has_4_byte_gpte) - role.quadrant = quadrant; +WARN_ON_ONCE (quadrant && !role.has_4_byte_gpte); +WARN_ON_ONCE (role.direct && role.has_4_byte_gpte); sp =kvm_mmu_get_page (vcpu, gfn, role); ++sp->root_count;
@@ -3512,7 +3513,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;
@@ -3536,9 +3537,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 ;
@@ -3621,7 +3621,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.has_4_byte_gpte ? i :0 ; + + root =mmu_alloc_root (vcpu, root_gfn, quadrant,PT32_ROOT_LEVEL ); mmu->pae_root[i] = root | pm_mask; }
Signature · 1 line
2.53.0
PATCH v4 9/11 KVM: x86/mmu: pull call to drop_large_spte() into __link_shadow_page()
In reply to Kenta AkagiFrom: Paolo Bonzini <[email protected]>
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().
--- arch/x86/kvm/mmu/mmu.c | 49 +++++++++++++++++++--------------- arch/x86/kvm/mmu/paging_tmpl.h | 29 +++++++++----------- 2 files changed, 40 insertions(+), 38 deletions(-)
arch/x86/kvm/mmu/mmu.c +27 -22
@@ -1180,26 +1180,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; - } - -return false; -} +struct kvm_mmu_page *sp; -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); + sp =sptep_to_sp (sptep); +WARN_ON (sp->role.level ==PG_LEVEL_4K ); -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)); - } }/*
@@ -2202,6 +2192,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); }
@@ -2269,13 +2262,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);
@@ -2286,6 +2287,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) {
@@ -3069,11 +3076,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 &&
arch/x86/kvm/mmu/paging_tmpl.h +13 -16
@@ -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,
Signature · 1 line
2.53.0
PATCH v4 10/11 KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
In reply to Kenta AkagiFrom: Sean Christopherson <[email protected]>
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.
--- arch/x86/kvm/mmu/mmu.c | 33 ++++++++++++++------------------- arch/x86/kvm/mmu/spte.h | 5 +++++ 2 files changed, 19 insertions(+), 19 deletions(-)
arch/x86/kvm/mmu/mmu.c +14 -19
@@ -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;
@@ -1180,18 +1182,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.
@@ -2192,7 +2182,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);
@@ -2270,12 +2261,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));
arch/x86/kvm/mmu/spte.h +5 -0
@@ -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 ;
Signature · 1 line
2.53.0
PATCH v4 11/11 KVM: x86: Fix shadow paging use-after-free due to unexpected role
In reply to Kenta AkagiFrom: Paolo Bonzini <[email protected]>
[ Upstream commit 81ccda30b4e83d8f5cc4fd50503c44e3a33abfeb ]
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.
--- arch/x86/kvm/mmu/mmu.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
arch/x86/kvm/mmu/mmu.c +6 -4
@@ -2180,13 +2180,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); }
Signature · 1 line
2.53.0
Quoted 37 lines · Kenta Akagi
This is a backport of the CVE-2026-46113 and CVE-2026-53359 fixes to 5.15.y.
v2: https://lore.kernel.org/all/[email protected]/
v3: https://lore.kernel.org/stable/[email protected]/Changes from v3:
- Added patches 3-6 to backport the change fromgpte_is_8_bytestohas_4_byte_gpte.
- Adjusted patches 7-8 (patches 3/7 and 4/7 in v3) accordingly.A regression has been reported in the backport of commit
0cb2af2ea66a("KVM: x86: Fix shadow paging use-after-free due to unexpected GFN") [1], but the corresponding fix has already been merged into 5.15.218 [2].[1] https://lore.kernel.org/kvm/[email protected]/
[2] https://lore.kernel.org/stable/[email protected]/David Matlack (2):
KVM: x86/mmu: Use a bool for direct
KVM: x86/mmu: Stop passing "direct" tommu_alloc_root()Lai Jiangshan (3):
KVM: X86: Remove useless code to set role.gpte_is_8_bytes when
role.direct
KVM: X86: Calculate quadrant when !role.gpte_is_8_bytes
KVM: X86: Renamegpte_is_8_bytestohas_4_byte_gpteand invert the
directionPaolo Bonzini (5):
KVM: MMU: update comment on the number of page role combinations
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 todrop_large_spte()into__link_shadow_page()
KVM: x86: Fix shadow paging use-after-free due to unexpected roleSean Christopherson (1):
KVM: x86: Fix shadow paging use-after-free due to unexpected GFN
Quoted 8 lines
Documentation/virt/kvm/mmu.rst | 8 +- arch/x86/include/asm/kvm_host.h | 32 +++--- arch/x86/kvm/mmu/mmu.c | 184 ++++++++++++++++++++------------ arch/x86/kvm/mmu/mmutrace.h | 2 +- arch/x86/kvm/mmu/paging_tmpl.h | 32 +++--- arch/x86/kvm/mmu/spte.h | 5 + arch/x86/kvm/mmu/tdp_mmu.c | 2 +- 7 files changed, 159 insertions(+), 106 deletions(-)
With the disclaimer that I didn't do extensive testing...
Quoted 1 line
This is a backport of the CVE-2026-46113 and CVE-2026-53359 fixes to 5.15.y.
Queued the series for 5.15, thanks. Patches 07 and 08 are David Matlack's upstream, so I fixed the author on those two.
Signature · 2 lines
Thanks, Sasha