Home Home > GIT Browse
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNicolai Stange <nstange@suse.de>2018-05-14 08:30:02 +0200
committerMiroslav Benes <mbenes@suse.cz>2018-05-14 14:47:25 +0200
commitdcfa955e570989176ce0f4e25c9397786189174d (patch)
treea4219e70f757d35f16278967cf725d186750ebe8
parente5e103a70f7bb713a825516a9aaf75c8ea6bf1a0 (diff)
Fix CVE-2018-8897 (kernel: "POP SS")
Live patch for CVE-2018-8897. Upstream commit d8ba61ba58c8 ("x86/entry/64: Don't use IST entry for #BP stack"). KLP: CVE-2018-8897 References: bsc#1090368 CVE-2018-8897 Signed-off-by: Nicolai Stange <nstange@suse.de> Signed-off-by: Miroslav Benes <mbenes@suse.cz>
-rw-r--r--bsc1090368/kgr_patch_bsc1090368.c271
-rw-r--r--bsc1090368/kgr_patch_bsc1090368.h20
2 files changed, 291 insertions, 0 deletions
diff --git a/bsc1090368/kgr_patch_bsc1090368.c b/bsc1090368/kgr_patch_bsc1090368.c
new file mode 100644
index 0000000..43ad9e4
--- /dev/null
+++ b/bsc1090368/kgr_patch_bsc1090368.c
@@ -0,0 +1,271 @@
+/*
+ * kgraft_patch_bsc1090368
+ *
+ * Fix for CVE-2018-8897, bsc#1090368
+ *
+ * Upstream commit:
+ * d8ba61ba58c8 ("x86/entry/64: Don't use IST entry for #BP stack")
+ *
+ * SLE12 commit:
+ * d82457ccb4d94672995489eb1cb0b087510cbf9e
+ *
+ * SLE12-SP1 commit
+ * ed7901eb499f3092bad6285f51228627ba901e2b
+ *
+ * SLE12-SP2 commit:
+ * 409b5f7eb20038c99154633dccb691d8ffc2bf63
+ *
+ * SLE12-SP3 commit:
+ * 95d3e6c3bd7ee22b1bf33b4e4f91eedd3dc26f0a ("stable 4.4.125")
+ *
+ * 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)
+
+#include <linux/kernel.h>
+#include <linux/kallsyms.h>
+#include <linux/slab.h>
+#include <asm/desc.h>
+#include <asm/irq_vectors.h>
+#include <asm/traps.h>
+#include <asm/hypervisor.h>
+#include <xen/xen.h>
+#include <asm/xen/events.h>
+
+#include "patch_state.h"
+#include "kgr_patch_bsc1090368.h"
+
+static gate_desc (*kgr_idt_table)[NR_VECTORS];
+static gate_desc (*kgr_trace_idt_table)[NR_VECTORS];
+static asmlinkage void (*kgr_int3)(void);
+
+static struct {
+ char *name;
+ char **addr;
+} kgr_funcs[] = {
+ { "idt_table", (void *)&kgr_idt_table },
+ { "trace_idt_table", (void *)&kgr_trace_idt_table },
+ { "int3", (void *)&kgr_int3 },
+};
+
+
+#define BSC1090368_SUBPATCH_ID KGR_SUBPATCH_ID_BSC(1090368)
+#define BSC1090368_DATA_VERSION 0
+
+struct bsc1090368_data0
+{
+ struct kgr_subpatch_data header;
+
+ bool replaced_bp_gate_ist;
+};
+
+static inline struct bsc1090368_data0*
+to_bsc1090368_data(struct kgr_subpatch_data *header)
+{
+ return container_of(header, struct bsc1090368_data0,
+ header);
+}
+
+static struct kgr_subpatch_data* bsc1090368_alloc_data(void)
+{
+ struct bsc1090368_data0 *data;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+
+ if (!data)
+ return NULL;
+
+ /* Not really necessary, but be explicit. */
+ data->header.version = BSC1090368_DATA_VERSION;
+ data->replaced_bp_gate_ist = false;
+
+ return &data->header;
+}
+
+static void bsc1090368_free_data(struct kgr_subpatch_data *header)
+{
+ struct bsc1090368_data0 *data = to_bsc1090368_data(header);
+
+ kfree(data);
+}
+
+static bool bsc1090368_prepare_migration(struct kgr_subpatch *self,
+ struct kgr_subpatch_data *prev_data)
+{
+ if (prev_data->version != BSC1090368_DATA_VERSION)
+ return false;
+
+ self->data = prev_data;
+ return true;
+}
+
+static void change_ist(int entry, unsigned int new_ist)
+{
+ gate_desc g;
+
+ /*
+ * native_write_idt() is a plain memcpy().
+ *
+ * Note that none of gate_desc's bitfields spans a byte
+ * boundary and thus, atomic writes are guaranteed for ->ist.
+ * Also, IDTs are not modified after boot (except by us)
+ * and no locking is needed.
+ *
+ * idt_table is in .bss and thus, WB memory. It's cached in
+ * data caches as usual and thus, tagged with physical
+ * addresses. In particular, no IDTR reload or any cache
+ * flushing is needed.
+ */
+ g = (*kgr_idt_table)[entry];
+ g.ist = new_ist;
+ write_idt_entry(*kgr_idt_table, entry, &g);
+
+ g = (*kgr_trace_idt_table)[entry];
+ g.ist = new_ist;
+ write_idt_entry(*kgr_trace_idt_table, entry, &g);
+}
+
+static void bsc1090368_post_patch(struct kgr_subpatch_data *header)
+{
+ struct bsc1090368_data0 *data = to_bsc1090368_data(header);
+ gate_desc expected_bp_gate;
+
+ if (data->replaced_bp_gate_ist) {
+ /*
+ * This can't happen and there's something really
+ * fishy going on. Better don't touch anything.
+ */
+ return;
+ }
+
+ /*
+ * On XEN PV, the kernel doesn't communicate any IST info
+ * to the kernel, c.f xen_write_idt_entry() and
+ * xen_load_idt().
+ *
+ * OTOH, on XEN PVH, the xen_cpu_ops are not installed,
+ * c.f. xen_start_kernel().
+ */
+ if (x86_hyper == &x86_hyper_xen && !xen_pvh_domain())
+ return;
+
+ /*
+ * Safety check: verify that the current IDT entry is what
+ * it's expected to be.
+ *
+ * The X86_TRAP_BP IDT entry is expected to have been setup by
+ * trap_init()
+ * ->set_system_intr_gate_ist(X86_TRAP_BP, &int3, DEBUG_STACK)
+ * ->_set_gate(n, GATE_INTERRUPT, addr, 0x3, ist, __KERNEL_CS);
+ * ->pack_gate(&s, type, (unsigned long)addr, dpl, ist, seg);
+ * Verify that this has indeed been the case.
+ */
+ pack_gate(&expected_bp_gate, GATE_INTERRUPT,
+ (unsigned long)kgr_int3, 0x3, DEBUG_STACK, __KERNEL_CS);
+
+ if (memcmp(&(*kgr_idt_table)[X86_TRAP_BP], &expected_bp_gate,
+ sizeof(expected_bp_gate)) ||
+ memcmp(&(*kgr_trace_idt_table)[X86_TRAP_BP], &expected_bp_gate,
+ sizeof(expected_bp_gate))) {
+ pr_warn("kgraft-patch: BP IDT entry has unexpected value, NOT patching");
+ return;
+ }
+
+ change_ist(X86_TRAP_BP, 0);
+ data->replaced_bp_gate_ist = true;
+}
+
+static void bsc1090368_pre_revert(struct kgr_subpatch_data *header)
+{
+ struct bsc1090368_data0 *data = to_bsc1090368_data(header);
+ gate_desc expected_bp_gate;
+
+ if (!data->replaced_bp_gate_ist)
+ return;
+
+ /*
+ * Safety check: verify that the current IDT entry is what
+ * it's expected to be, i.e. the original BP gate but with
+ * ->ist set to zero.
+ */
+ pack_gate(&expected_bp_gate, GATE_INTERRUPT,
+ (unsigned long)kgr_int3, 0x3, 0, __KERNEL_CS);
+
+ if (memcmp(&(*kgr_idt_table)[X86_TRAP_BP], &expected_bp_gate,
+ sizeof(expected_bp_gate)) ||
+ memcmp(&(*kgr_trace_idt_table)[X86_TRAP_BP], &expected_bp_gate,
+ sizeof(expected_bp_gate))) {
+ pr_warn("kgraft-patch: BP IDT entry has unexpected value, NOT unpatching");
+ return;
+ }
+
+ change_ist(X86_TRAP_BP, DEBUG_STACK);
+ data->replaced_bp_gate_ist = false;
+}
+
+static struct kgr_subpatch bsc1090368_subpatch = {
+ .mod = THIS_MODULE,
+ .alloc_data = bsc1090368_alloc_data,
+ .free_data = bsc1090368_free_data,
+
+ .prepare_migration = bsc1090368_prepare_migration,
+
+ .post_patch = bsc1090368_post_patch,
+ .pre_revert = bsc1090368_pre_revert,
+};
+
+static int kgr_patch_bsc1090368_kallsyms(void)
+{
+ unsigned long addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(kgr_funcs); i++) {
+ 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;
+}
+
+int kgr_patch_bsc1090368_init(void)
+{
+ int ret;
+
+ ret = kgr_patch_bsc1090368_kallsyms();
+ if (ret)
+ return ret;
+
+ ret = kgr_subpatch_register(BSC1090368_SUBPATCH_ID,
+ &bsc1090368_subpatch);
+ return ret;
+}
+
+void kgr_patch_bsc1090368_cleanup(void)
+{
+ kgr_subpatch_unregister(&bsc1090368_subpatch);
+}
+
+#endif /* IS_ENABLED(CONFIG_X86_64) */
diff --git a/bsc1090368/kgr_patch_bsc1090368.h b/bsc1090368/kgr_patch_bsc1090368.h
new file mode 100644
index 0000000..5e31c67
--- /dev/null
+++ b/bsc1090368/kgr_patch_bsc1090368.h
@@ -0,0 +1,20 @@
+#ifndef _KGR_PATCH_BSC1080368_H
+#define _KGR_PATCH_BSC1080368_H
+
+#if IS_ENABLED(CONFIG_X86_64)
+
+int kgr_patch_bsc1090368_init(void);
+void kgr_patch_bsc1090368_cleanup(void);
+
+#define KGR_PATCH_BSC1090368_FUNCS
+
+#else /* !IS_ENABLED(CONFIG_X86_64) */
+
+static inline int kgr_patch_bsc1090368_init(void) { return 0; }
+static inline void kgr_patch_bsc1090368_cleanup(void) {}
+
+#define KGR_PATCH_BSC1090368_FUNCS
+
+#endif /* IS_ENABLED(CONFIG_X86_64) */
+
+#endif