From ccefd718c8b67a6697b516af82de4abbdf0d4102 Mon Sep 17 00:00:00 2001 From: Salvatore Bonaccorso Date: Fri, 13 Oct 2017 18:09:11 +0200 Subject: [PATCH] [x86] KVM: MMU: always terminate page walks at level 1 (CVE-2017-12188) --- debian/changelog | 1 + ...ways-terminate-page-walks-at-level-1.patch | 83 +++++++++++++++++++ debian/patches/series | 1 + 3 files changed, 85 insertions(+) create mode 100644 debian/patches/bugfix/x86/KVM-MMU-always-terminate-page-walks-at-level-1.patch diff --git a/debian/changelog b/debian/changelog index aef371ea7..b53f32431 100644 --- a/debian/changelog +++ b/debian/changelog @@ -16,6 +16,7 @@ linux (4.13.4-2) UNRELEASED; urgency=medium * ALSA: seq: Fix use-after-free at creating a port (CVE-2017-15265) * [x86] KVM: nVMX: update last_nonleaf_level when initializing nested EPT (CVE-2017-12188) + * [x86] KVM: MMU: always terminate page walks at level 1 (CVE-2017-12188) -- Ben Hutchings Wed, 04 Oct 2017 23:14:54 +0100 diff --git a/debian/patches/bugfix/x86/KVM-MMU-always-terminate-page-walks-at-level-1.patch b/debian/patches/bugfix/x86/KVM-MMU-always-terminate-page-walks-at-level-1.patch new file mode 100644 index 000000000..47cbead06 --- /dev/null +++ b/debian/patches/bugfix/x86/KVM-MMU-always-terminate-page-walks-at-level-1.patch @@ -0,0 +1,83 @@ +From: Ladi Prosek +Date: Thu, 5 Oct 2017 11:10:23 +0200 +Subject: KVM: MMU: always terminate page walks at level 1 +Origin: https://git.kernel.org/linus/829ee279aed43faa5cb1e4d65c0cad52f2426c53 +Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-12188 + +is_last_gpte() is not equivalent to the pseudo-code given in commit +6bb69c9b69c31 ("KVM: MMU: simplify last_pte_bitmap") because an incorrect +value of last_nonleaf_level may override the result even if level == 1. + +It is critical for is_last_gpte() to return true on level == 1 to +terminate page walks. Otherwise memory corruption may occur as level +is used as an index to various data structures throughout the page +walking code. Even though the actual bug would be wherever the MMU is +initialized (as in the previous patch), be defensive and ensure here +that is_last_gpte() returns the correct value. + +This patch is also enough to fix CVE-2017-12188. + +Fixes: 6bb69c9b69c315200ddc2bc79aee14c0184cf5b2 +Cc: stable@vger.kernel.org +Cc: Andy Honig +Signed-off-by: Ladi Prosek +[Panic if walk_addr_generic gets an incorrect level; this is a serious + bug and it's not worth a WARN_ON where the recovery path might hide + further exploitable issues; suggested by Andrew Honig. - Paolo] +Signed-off-by: Paolo Bonzini +--- + arch/x86/kvm/mmu.c | 14 +++++++------- + arch/x86/kvm/paging_tmpl.h | 3 ++- + 2 files changed, 9 insertions(+), 8 deletions(-) + +diff --git a/arch/x86/kvm/mmu.c b/arch/x86/kvm/mmu.c +index 3c25f20115bc..7a69cf053711 100644 +--- a/arch/x86/kvm/mmu.c ++++ b/arch/x86/kvm/mmu.c +@@ -3974,19 +3974,19 @@ static inline bool is_last_gpte(struct kvm_mmu *mmu, + unsigned level, unsigned gpte) + { + /* +- * PT_PAGE_TABLE_LEVEL always terminates. The RHS has bit 7 set +- * iff level <= PT_PAGE_TABLE_LEVEL, which for our purpose means +- * level == PT_PAGE_TABLE_LEVEL; set PT_PAGE_SIZE_MASK in gpte then. +- */ +- gpte |= level - PT_PAGE_TABLE_LEVEL - 1; +- +- /* + * The RHS has bit 7 set iff level < mmu->last_nonleaf_level. + * If it is clear, there are no large pages at this level, so clear + * PT_PAGE_SIZE_MASK in gpte if that is the case. + */ + gpte &= level - mmu->last_nonleaf_level; + ++ /* ++ * PT_PAGE_TABLE_LEVEL always terminates. The RHS has bit 7 set ++ * iff level <= PT_PAGE_TABLE_LEVEL, which for our purpose means ++ * level == PT_PAGE_TABLE_LEVEL; set PT_PAGE_SIZE_MASK in gpte then. ++ */ ++ gpte |= level - PT_PAGE_TABLE_LEVEL - 1; ++ + return gpte & PT_PAGE_SIZE_MASK; + } + +diff --git a/arch/x86/kvm/paging_tmpl.h b/arch/x86/kvm/paging_tmpl.h +index 86b68dc5a649..f18d1f8d332b 100644 +--- a/arch/x86/kvm/paging_tmpl.h ++++ b/arch/x86/kvm/paging_tmpl.h +@@ -334,10 +334,11 @@ static int FNAME(walk_addr_generic)(struct guest_walker *walker, + --walker->level; + + index = PT_INDEX(addr, walker->level); +- + table_gfn = gpte_to_gfn(pte); + offset = index * sizeof(pt_element_t); + pte_gpa = gfn_to_gpa(table_gfn) + offset; ++ ++ BUG_ON(walker->level < 1); + walker->table_gfn[walker->level - 1] = table_gfn; + walker->pte_gpa[walker->level - 1] = pte_gpa; + +-- +2.11.0 + diff --git a/debian/patches/series b/debian/patches/series index 09ca22143..408d18370 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -127,6 +127,7 @@ bugfix/all/KEYS-prevent-KEYCTL_READ-on-negative-key.patch bugfix/all/waitid-Add-missing-access_ok-checks.patch bugfix/all/ALSA-seq-Fix-use-after-free-at-creating-a-port.patch bugfix/x86/KVM-nVMX-update-last_nonleaf_level-when-initializing.patch +bugfix/x86/KVM-MMU-always-terminate-page-walks-at-level-1.patch # Fix exported symbol versions bugfix/alpha/alpha-restore-symbol-versions-for-symbols-exported-f.patch