Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2018-07-11 13:50:51 +0200
committerMiroslav Benes <mbenes@suse.cz>2018-07-11 16:15:10 +0200
commite30fc399d8e210ae52fc1f9474c66cf571e49ae7 (patch)
tree7fd033108bedf2d0c573ca10e7149599118032b7
parent58fddd5116b88802afb88ae66a6cd03998c79595 (diff)
Fix CVE-2018-10853 ("kvm: guest userspace to guest kernel write")
Live patch for CVE-2018-10853. Upstream commit 3c9fa24ca7c9 ("kvm: x86: use correct privilege level for sgdt/sidt/fxsave/fxrstor access"). KLP: CVE-2018-10853 References: bsc#1097108 CVE-2018-10853 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1097108/kgr_patch_bsc1097108.c560
-rw-r--r--bsc1097108/kgr_patch_bsc1097108.h38
2 files changed, 598 insertions, 0 deletions
diff --git a/bsc1097108/kgr_patch_bsc1097108.c b/bsc1097108/kgr_patch_bsc1097108.c
new file mode 100644
index 0000000..6750034
--- /dev/null
+++ b/bsc1097108/kgr_patch_bsc1097108.c
@@ -0,0 +1,560 @@
+/*
+ * kgraft_patch_bsc1097108
+ *
+ * Fix for CVE-2018-10853, bsc#1097108
+ *
+ * Upstream commits:
+ * 79367a657439 ("KVM: x86: introduce linear_{read,write}_system")
+ * ce14e868a54e ("KVM: x86: pass kvm_vcpu to kvm_read_guest_virt and
+ * kvm_write_guest_virt_system")
+ * 3c9fa24ca7c9 ("kvm: x86: use correct privilege level for
+ * sgdt/sidt/fxsave/fxrstor access")
+ *
+ * SLE12 commit:
+ * none yet
+ *
+ * SLE12-SP1 commit
+ * none yet
+ *
+ * SLE12-SP2 commit:
+ * none yet
+ *
+ * SLE12-SP3 commit:
+ * 06238a187fb7aafb7088fc617cb0a0efd6259d80 (stable 4.4.138)
+ *
+ * SLE15 commit:
+ * none yet
+ *
+ * Copyright (c) 2018 SUSE
+ * Author: Nicolai Stange <nstange@suse.de>
+ *
+ * Based on the original Linux kernel code. Other copyrights apply.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#if IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM)
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/kallsyms.h>
+#include <linux/kvm_host.h>
+#include <asm/kvm_emulate.h>
+#include <asm/fpu/types.h>
+#include <asm/desc_defs.h>
+#include "kgr_patch_bsc1097108.h"
+
+#if !IS_MODULE(CONFIG_KVM)
+#error "Live patch supports only CONFIG_KVM=m"
+#endif
+
+#define KGR_PATCHED_MODULE "kvm"
+
+
+static struct kvm_x86_ops **kgr_kvm_x86_ops;
+
+static int (*kgr_kvm_read_guest_virt_system)(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception);
+static int (*kgr_kvm_write_guest_virt_system)(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val,
+ unsigned int bytes,
+ struct x86_exception *exception);
+static int (*kgr_kvm_read_guest_virt_helper)(gva_t addr, void *val,
+ unsigned int bytes,
+ struct kvm_vcpu *vcpu, u32 access,
+ struct x86_exception *exception);
+static int (*kgr_kvm_vcpu_write_guest)(struct kvm_vcpu *vcpu, gpa_t gpa,
+ const void *data, unsigned long len);
+static unsigned long (*kgr_seg_base)(struct x86_emulate_ctxt *ctxt, int seg);
+static u32 (*kgr_desc_limit_scaled)(struct desc_struct *desc);
+static int (*kgr_check_fxsr)(struct x86_emulate_ctxt *ctxt);
+static int (*kgr_fxrstor_fixup)(struct x86_emulate_ctxt *ctxt,
+ struct fxregs_state *new);
+
+static struct {
+ char *name;
+ void **addr;
+} kgr_funcs[] = {
+ { "kvm:kvm_x86_ops", (void *)&kgr_kvm_x86_ops },
+ { "kvm:kvm_read_guest_virt_system",
+ (void *)&kgr_kvm_read_guest_virt_system },
+ { "kvm:kvm_write_guest_virt_system",
+ (void *)&kgr_kvm_write_guest_virt_system },
+ { "kvm:kvm_read_guest_virt_helper",
+ (void *)&kgr_kvm_read_guest_virt_helper },
+ { "kvm:kvm_vcpu_write_guest", (void *)&kgr_kvm_vcpu_write_guest },
+ { "kvm:seg_base", (void *)&kgr_seg_base },
+ { "kvm:desc_limit_scaled", (void *)&kgr_desc_limit_scaled },
+ { "kvm:check_fxsr", (void *)&kgr_check_fxsr },
+ { "kvm:fxrstor_fixup", (void *)&kgr_fxrstor_fixup },
+};
+
+
+
+/* from arch/x86/kvm/x86.c */
+#define kgr_emul_to_vcpu(ctxt) \
+ container_of(ctxt, struct kvm_vcpu, arch.emulate_ctxt)
+
+
+/* from arch/x86/kvm/emulate.c */
+#define KGR_Aligned ((u64)1 << 41) /* Explicitly aligned (e.g. MOVDQA) */
+#define KGR_Unaligned ((u64)1 << 42) /* Explicitly unaligned (e.g. MOVDQU) */
+#define KGR_Avx ((u64)1 << 43) /* Advanced Vector Extensions */
+#define KGR_Aligned16 ((u64)1 << 55) /* Aligned to 16 byte boundary (e.g. FXSAVE) */
+
+#define kgr_asm_safe(insn, inoutclob...) \
+({ \
+ int _fault = 0; \
+ \
+ asm volatile("1:" insn "\n" \
+ "2:\n" \
+ ".pushsection .fixup, \"ax\"\n" \
+ "3: movl $1, %[_fault]\n" \
+ " jmp 2b\n" \
+ ".popsection\n" \
+ _ASM_EXTABLE(1b, 3b) \
+ : [_fault] "+qm"(_fault) inoutclob ); \
+ \
+ _fault ? X86EMUL_UNHANDLEABLE : X86EMUL_CONTINUE; \
+})
+
+/* inlined */
+int kgr_emulate_exception(struct x86_emulate_ctxt *ctxt, int vec,
+ u32 error, bool valid)
+{
+ WARN_ON(vec > 0x1f);
+ ctxt->exception.vector = vec;
+ ctxt->exception.error_code = error;
+ ctxt->exception.error_code_valid = valid;
+ return X86EMUL_PROPAGATE_FAULT;
+}
+
+/* inlined */
+static int kgr_emulate_gp(struct x86_emulate_ctxt *ctxt, int err)
+{
+ return kgr_emulate_exception(ctxt, GP_VECTOR, err, true);
+}
+
+/* inlined */
+static int kgr_emulate_ss(struct x86_emulate_ctxt *ctxt, int err)
+{
+ return kgr_emulate_exception(ctxt, SS_VECTOR, err, true);
+}
+
+/* optimized */
+static unsigned kgr_insn_alignment(struct x86_emulate_ctxt *ctxt, unsigned size)
+{
+ if (likely(size < 16))
+ return 1;
+
+ if (ctxt->d & KGR_Aligned)
+ return size;
+ else if (ctxt->d & KGR_Unaligned)
+ return 1;
+ else if (ctxt->d & KGR_Avx)
+ return 1;
+ else if (ctxt->d & KGR_Aligned16)
+ return 16;
+ else
+ return size;
+}
+
+/* inlined */
+static __always_inline int kgr__linearize(struct x86_emulate_ctxt *ctxt,
+ struct segmented_address addr,
+ unsigned *max_size, unsigned size,
+ bool write, bool fetch,
+ enum x86emul_mode mode, ulong *linear)
+{
+ struct desc_struct desc;
+ bool usable;
+ ulong la;
+ u32 lim;
+ u16 sel;
+
+ la = kgr_seg_base(ctxt, addr.seg) + addr.ea;
+ *max_size = 0;
+ switch (mode) {
+ case X86EMUL_MODE_PROT64:
+ *linear = la;
+ if (is_noncanonical_address(la))
+ goto bad;
+
+ *max_size = min_t(u64, ~0u, (1ull << 48) - la);
+ if (size > *max_size)
+ goto bad;
+ break;
+ default:
+ *linear = la = (u32)la;
+ usable = ctxt->ops->get_segment(ctxt, &sel, &desc, NULL,
+ addr.seg);
+ if (!usable)
+ goto bad;
+ /* code segment in protected mode or read-only data segment */
+ if ((((ctxt->mode != X86EMUL_MODE_REAL) && (desc.type & 8))
+ || !(desc.type & 2)) && write)
+ goto bad;
+ /* unreadable code segment */
+ if (!fetch && (desc.type & 8) && !(desc.type & 2))
+ goto bad;
+ lim = kgr_desc_limit_scaled(&desc);
+ if (!(desc.type & 8) && (desc.type & 4)) {
+ /* expand-down segment */
+ if (addr.ea <= lim)
+ goto bad;
+ lim = desc.d ? 0xffffffff : 0xffff;
+ }
+ if (addr.ea > lim)
+ goto bad;
+ if (lim == 0xffffffff)
+ *max_size = ~0u;
+ else {
+ *max_size = (u64)lim + 1 - addr.ea;
+ if (size > *max_size)
+ goto bad;
+ }
+ break;
+ }
+ if (la & (kgr_insn_alignment(ctxt, size) - 1))
+ return kgr_emulate_gp(ctxt, 0);
+ return X86EMUL_CONTINUE;
+bad:
+ if (addr.seg == VCPU_SREG_SS)
+ return kgr_emulate_ss(ctxt, 0);
+ else
+ return kgr_emulate_gp(ctxt, 0);
+}
+
+/* optimized */
+static int kgr_linearize(struct x86_emulate_ctxt *ctxt,
+ struct segmented_address addr,
+ unsigned size, bool write,
+ ulong *linear)
+{
+ unsigned max_size;
+ return kgr__linearize(ctxt, addr, &max_size, size, write, false,
+ ctxt->mode, linear);
+}
+
+
+
+/* New */
+static int kgr_emulator_read_std(struct x86_emulate_ctxt *ctxt,
+ gva_t addr, void *val, unsigned int bytes,
+ struct x86_exception *exception, bool system)
+{
+ struct kvm_vcpu *vcpu = kgr_emul_to_vcpu(ctxt);
+ u32 access = 0;
+
+ if (!system && (*kgr_kvm_x86_ops)->get_cpl(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ return kgr_kvm_read_guest_virt_helper(addr, val, bytes, vcpu, access,
+ exception);
+}
+
+/* New */
+static int kgr_kvm_write_guest_virt_helper(gva_t addr, void *val,
+ unsigned int bytes,
+ struct kvm_vcpu *vcpu, u32 access,
+ struct x86_exception *exception)
+{
+ void *data = val;
+ int r = X86EMUL_CONTINUE;
+
+ while (bytes) {
+ gpa_t gpa = vcpu->arch.walk_mmu->gva_to_gpa(vcpu, addr,
+ access,
+ exception);
+ unsigned offset = addr & (PAGE_SIZE-1);
+ unsigned towrite = min(bytes, (unsigned)PAGE_SIZE - offset);
+ int ret;
+
+ if (gpa == UNMAPPED_GVA)
+ return X86EMUL_PROPAGATE_FAULT;
+ ret = kgr_kvm_vcpu_write_guest(vcpu, gpa, data, towrite);
+ if (ret < 0) {
+ r = X86EMUL_IO_NEEDED;
+ goto out;
+ }
+
+ bytes -= towrite;
+ data += towrite;
+ addr += towrite;
+ }
+out:
+ return r;
+}
+
+/* New */
+static int kgr_emulator_write_std(struct x86_emulate_ctxt *ctxt, gva_t addr,
+ void *val, unsigned int bytes,
+ struct x86_exception *exception, bool system)
+{
+ struct kvm_vcpu *vcpu = kgr_emul_to_vcpu(ctxt);
+ u32 access = PFERR_WRITE_MASK;
+
+ if (!system && (*kgr_kvm_x86_ops)->get_cpl(vcpu) == 3)
+ access |= PFERR_USER_MASK;
+
+ return kgr_kvm_write_guest_virt_helper(addr, val, bytes, vcpu,
+ access, exception);
+}
+
+/* patched, optimized */
+static int kgr_segmented_read_std(struct x86_emulate_ctxt *ctxt,
+ struct segmented_address addr,
+ void *data,
+ unsigned size)
+{
+ int rc;
+ ulong linear;
+
+ rc = kgr_linearize(ctxt, addr, size, false, &linear);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+ /*
+ * Fix CVE-2018-10853
+ * +5 lines
+ *
+ * Note that ->read_std should always equal
+ * kvm_read_guest_virt_system and that the check below is
+ * only there as a safety measure.
+ */
+ if (likely(ctxt->ops->read_std == kgr_kvm_read_guest_virt_system)) {
+ return kgr_emulator_read_std(ctxt, linear, data, size,
+ &ctxt->exception, false);
+ }
+
+ return ctxt->ops->read_std(ctxt, linear, data, size, &ctxt->exception);
+}
+
+/* patched, optimized */
+static int kgr_segmented_write_std(struct x86_emulate_ctxt *ctxt,
+ struct segmented_address addr,
+ void *data,
+ unsigned int size)
+{
+ int rc;
+ ulong linear;
+
+ rc = kgr_linearize(ctxt, addr, size, true, &linear);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+ /*
+ * Fix CVE-2018-10853
+ * +5 lines
+ *
+ * Note that ->write_std should always equal
+ * kvm_write_guest_virt_system and that the check below is
+ * only there as a safety measure.
+ */
+ if (likely(ctxt->ops->write_std == kgr_kvm_write_guest_virt_system)) {
+ return kgr_emulator_write_std(ctxt, linear, data, size,
+ &ctxt->exception, false);
+ }
+
+ return ctxt->ops->write_std(ctxt, linear, data, size, &ctxt->exception);
+}
+
+
+/* patched, inlined, calls segmented_read_std() */
+static int kgr_read_descriptor(struct x86_emulate_ctxt *ctxt,
+ struct segmented_address addr,
+ u16 *size, unsigned long *address, int op_bytes)
+{
+ int rc;
+
+ if (op_bytes == 2)
+ op_bytes = 3;
+ *address = 0;
+ rc = kgr_segmented_read_std(ctxt, addr, size, 2);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+ addr.ea += 2;
+ rc = kgr_segmented_read_std(ctxt, addr, address, op_bytes);
+ return rc;
+}
+
+/* patched, calls read_descriptor() */
+int kgr_em_lgdt_lidt(struct x86_emulate_ctxt *ctxt, bool lgdt)
+{
+ struct desc_ptr desc_ptr;
+ int rc;
+
+ if (ctxt->mode == X86EMUL_MODE_PROT64)
+ ctxt->op_bytes = 8;
+ rc = kgr_read_descriptor(ctxt, ctxt->src.addr.mem,
+ &desc_ptr.size, &desc_ptr.address,
+ ctxt->op_bytes);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+ if (ctxt->mode == X86EMUL_MODE_PROT64 &&
+ is_noncanonical_address(desc_ptr.address))
+ return kgr_emulate_gp(ctxt, 0);
+ if (lgdt)
+ ctxt->ops->set_gdt(ctxt, &desc_ptr);
+ else
+ ctxt->ops->set_idt(ctxt, &desc_ptr);
+ /* Disable writeback. */
+ ctxt->dst.type = OP_NONE;
+ return X86EMUL_CONTINUE;
+}
+
+/* patched, calls segmented_read_std() */
+int kgr_em_fxrstor(struct x86_emulate_ctxt *ctxt)
+{
+ struct fxregs_state fx_state;
+ int rc;
+
+ rc = kgr_check_fxsr(ctxt);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+
+ rc = kgr_segmented_read_std(ctxt, ctxt->memop.addr.mem, &fx_state, 512);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+
+ if (fx_state.mxcsr >> 16)
+ return kgr_emulate_gp(ctxt, 0);
+
+ ctxt->ops->get_fpu(ctxt);
+
+ if (ctxt->mode < X86EMUL_MODE_PROT64)
+ rc = kgr_fxrstor_fixup(ctxt, &fx_state);
+
+ if (rc == X86EMUL_CONTINUE)
+ rc = kgr_asm_safe("fxrstor %[fx]", : [fx] "m"(fx_state));
+
+ ctxt->ops->put_fpu(ctxt);
+
+ return rc;
+}
+
+/* patched, calls segmented_write_std() */
+int kgr_emulate_store_desc_ptr(struct x86_emulate_ctxt *ctxt,
+ void (*get)(struct x86_emulate_ctxt *ctxt,
+ struct desc_ptr *ptr))
+{
+ struct desc_ptr desc_ptr;
+
+ if (ctxt->mode == X86EMUL_MODE_PROT64)
+ ctxt->op_bytes = 8;
+ get(ctxt, &desc_ptr);
+ if (ctxt->op_bytes == 2) {
+ ctxt->op_bytes = 4;
+ desc_ptr.address &= 0x00ffffff;
+ }
+ /* Disable writeback. */
+ ctxt->dst.type = OP_NONE;
+ return kgr_segmented_write_std(ctxt, ctxt->dst.addr.mem,
+ &desc_ptr, 2 + ctxt->op_bytes);
+}
+
+/* patched, calls segmented_write_std() */
+ int kgr_em_fxsave(struct x86_emulate_ctxt *ctxt)
+{
+ struct fxregs_state fx_state;
+ size_t size;
+ int rc;
+
+ rc = kgr_check_fxsr(ctxt);
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+
+ ctxt->ops->get_fpu(ctxt);
+
+ rc = kgr_asm_safe("fxsave %[fx]", , [fx] "+m"(fx_state));
+
+ ctxt->ops->put_fpu(ctxt);
+
+ if (rc != X86EMUL_CONTINUE)
+ return rc;
+
+ if (ctxt->ops->get_cr(ctxt, 4) & X86_CR4_OSFXSR)
+ size = offsetof(struct fxregs_state, xmm_space[8 * 16/4]);
+ else
+ size = offsetof(struct fxregs_state, xmm_space[0]);
+
+ return kgr_segmented_write_std(ctxt, ctxt->memop.addr.mem, &fx_state, size);
+}
+
+
+
+static int kgr_patch_bsc1097108_kallsyms(void)
+{
+ unsigned long addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(kgr_funcs); i++) {
+ /* mod_find_symname would be nice, but it is not exported */
+ addr = kallsyms_lookup_name(kgr_funcs[i].name);
+ if (!addr) {
+ pr_err("kgraft-patch: symbol %s not resolved\n",
+ kgr_funcs[i].name);
+ return -ENOENT;
+ }
+
+ *(kgr_funcs[i].addr) = (void *)addr;
+ }
+
+ return 0;
+}
+
+static int kgr_patch_bsc1097108_module_notify(struct notifier_block *nb,
+ unsigned long action, void *data)
+{
+ struct module *mod = data;
+ int ret;
+
+ if (action != MODULE_STATE_COMING || strcmp(mod->name, KGR_PATCHED_MODULE))
+ return 0;
+
+ ret = kgr_patch_bsc1097108_kallsyms();
+ WARN(ret, "kgraft-patch: delayed kallsyms lookup failed. System is broken and can crash.\n");
+
+ return ret;
+}
+
+static struct notifier_block kgr_patch_bsc1097108_module_nb = {
+ .notifier_call = kgr_patch_bsc1097108_module_notify,
+ .priority = INT_MIN+1,
+};
+
+int kgr_patch_bsc1097108_init(void)
+{
+ int ret;
+
+ mutex_lock(&module_mutex);
+ if (find_module(KGR_PATCHED_MODULE)) {
+ ret = kgr_patch_bsc1097108_kallsyms();
+ if (ret)
+ goto out;
+ }
+
+ ret = register_module_notifier(&kgr_patch_bsc1097108_module_nb);
+out:
+ mutex_unlock(&module_mutex);
+ return ret;
+}
+
+void kgr_patch_bsc1097108_cleanup(void)
+{
+ unregister_module_notifier(&kgr_patch_bsc1097108_module_nb);
+}
+
+#endif /* IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM) */
diff --git a/bsc1097108/kgr_patch_bsc1097108.h b/bsc1097108/kgr_patch_bsc1097108.h
new file mode 100644
index 0000000..8636a6e
--- /dev/null
+++ b/bsc1097108/kgr_patch_bsc1097108.h
@@ -0,0 +1,38 @@
+#ifndef _KGR_PATCH_BSC1097108_H
+#define _KGR_PATCH_BSC1097108_H
+
+#if IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM)
+
+int kgr_patch_bsc1097108_init(void);
+void kgr_patch_bsc1097108_cleanup(void);
+
+
+struct x86_emulate_ctxt;
+struct desc_ptr;
+
+int kgr_em_lgdt_lidt(struct x86_emulate_ctxt *ctxt, bool lgdt);
+int kgr_em_fxrstor(struct x86_emulate_ctxt *ctxt);
+int kgr_emulate_store_desc_ptr(struct x86_emulate_ctxt *ctxt,
+ void (*get)(struct x86_emulate_ctxt *ctxt,
+ struct desc_ptr *ptr));
+int kgr_em_fxsave(struct x86_emulate_ctxt *ctxt);
+
+
+#define KGR_PATCH_BSC1097108_FUNCS \
+ KGR_PATCH_OBJ(em_lgdt_lidt, kgr_em_lgdt_lidt, "kvm"), \
+ KGR_PATCH_OBJ(em_fxrstor, kgr_em_fxrstor, "kvm"), \
+ KGR_PATCH_OBJ(emulate_store_desc_ptr, \
+ kgr_emulate_store_desc_ptr, "kvm"), \
+ KGR_PATCH_OBJ(em_fxsave, kgr_em_fxsave, "kvm"), \
+
+#else /* !(IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM)) */
+
+static inline int kgr_patch_bsc1097108_init(void) { return 0; }
+static inline void kgr_patch_bsc1097108_cleanup(void) {}
+
+#define KGR_PATCH_BSC1097108_FUNCS
+
+
+#endif /* IS_ENABLED(CONFIG_X86_64) && IS_ENABLED(CONFIG_KVM) */
+
+#endif /* _KGR_PATCH_BSC1097108 */