Fix: Disable IBT around indirect function calls
authorMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Mon, 8 Jan 2024 18:31:04 +0000 (13:31 -0500)
committerMathieu Desnoyers <mathieu.desnoyers@efficios.com>
Tue, 9 Jan 2024 16:26:03 +0000 (11:26 -0500)
When the Intel IBT feature is enabled, a CPU supporting this feature
validates that all indirect jumps/calls land on an ENDBR64 instruction.

The kernel seals functions which are not meant to be called indirectly,
which means that calling functions indirectly from their address fetched
using kallsyms or kprobes trigger a crash.

Use the MSR_IA32_S_CET CET_ENDBR_EN MSR bit to temporarily disable ENDBR
validation around indirect calls to kernel functions. Considering that
the main purpose of this feature is to prevent ROP-style attacks,
disabling the ENDBR validation temporarily around the call from a kernel
module does not affect the ROP protection.

Fixes #1408
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
Change-Id: I97f5d8efce093c1e956cede1f44de2fcebf30227

include/wrapper/ibt.h [new file with mode: 0644]
include/wrapper/kallsyms.h
src/lttng-context-callstack-legacy-impl.h
src/lttng-context-callstack-stackwalk-impl.h
src/wrapper/irqdesc.c
src/wrapper/kallsyms.c
src/wrapper/page_alloc.c

diff --git a/include/wrapper/ibt.h b/include/wrapper/ibt.h
new file mode 100644 (file)
index 0000000..e089a8f
--- /dev/null
@@ -0,0 +1,59 @@
+/* SPDX-License-Identifier: (GPL-2.0-only)
+ *
+ * wrapper/ibt.h
+ *
+ * Copyright (C) 2024 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
+ */
+
+#ifndef _LTTNG_WRAPPER_IBT_H
+#define _LTTNG_WRAPPER_IBT_H
+
+struct irq_ibt_state {
+       u64 msr;
+       unsigned long flags;
+};
+
+/*
+ * Save (disable) and restore interrupts around MSR bit change and indirect
+ * function call to make sure this thread is not migrated to another CPU which
+ * would not have the MSR bit cleared.
+ */
+
+#ifdef CONFIG_X86_KERNEL_IBT
+# include <asm/cpufeature.h>
+# include <asm/msr.h>
+static inline __attribute__((always_inline))
+struct irq_ibt_state wrapper_irq_ibt_save(void)
+{
+       struct irq_ibt_state state = { 0, 0 };
+       u64 msr;
+
+       if (!cpu_feature_enabled(X86_FEATURE_IBT))
+               goto end;
+       local_irq_save(state.flags);
+       rdmsrl(MSR_IA32_S_CET, msr);
+       wrmsrl(MSR_IA32_S_CET, msr & ~CET_ENDBR_EN);
+       state.msr = msr;
+end:
+       return state;
+}
+
+static inline __attribute__((always_inline))
+void wrapper_irq_ibt_restore(struct irq_ibt_state state)
+{
+       u64 msr;
+
+       if (!cpu_feature_enabled(X86_FEATURE_IBT))
+               return;
+       rdmsrl(MSR_IA32_S_CET, msr);
+       msr &= ~CET_ENDBR_EN;
+       msr |= (state.msr & CET_ENDBR_EN);
+       wrmsrl(MSR_IA32_S_CET, msr);
+       local_irq_restore(state.flags);
+}
+#else
+static inline struct irq_ibt_state wrapper_irq_ibt_save(void) { struct irq_ibt_state state = { 0, 0 }; return state; }
+static inline void wrapper_irq_ibt_restore(struct irq_ibt_state state) { }
+#endif
+
+#endif /* _LTTNG_WRAPPER_IBT_H */
index 0aac5a2d64ca28c19bc9abf2778413c4b04d370f..5be622272825db2ac7b84fb9c22ed14e1625ed95 100644 (file)
@@ -16,6 +16,8 @@
 #include <linux/kallsyms.h>
 #include <lttng/kernel-version.h>
 
+#include <wrapper/ibt.h>
+
 /* CONFIG_PPC64_ELF_ABI_V1/V2 were introduced in v5.19 */
 #if defined(CONFIG_PPC64_ELF_ABI_V2) || (defined(CONFIG_PPC64) && defined(CONFIG_CPU_LITTLE_ENDIAN))
 #define LTTNG_CONFIG_PPC64_ELF_ABI_V2
index 833443d612c73efe38389a32a24ee3d52e79bb43..ba176de9bab6fa65ea9ba1a2541807407368d358 100644 (file)
@@ -150,6 +150,7 @@ size_t lttng_callstack_sequence_get_size(void *priv, struct lttng_kernel_probe_c
        struct field_data *fdata = (struct field_data *) priv;
        size_t orig_offset = offset;
        int cpu = smp_processor_id();
+       struct irq_ibt_state irq_ibt_state;
 
        /* do not write data if no space is available */
        trace = stack_trace_context(fdata, cpu);
@@ -165,7 +166,9 @@ size_t lttng_callstack_sequence_get_size(void *priv, struct lttng_kernel_probe_c
                ++per_cpu(callstack_user_nesting, cpu);
 
        /* do the real work and reserve space */
+       irq_ibt_state = wrapper_irq_ibt_save();
        cs_types[fdata->mode].save_func(trace);
+       wrapper_irq_ibt_restore(irq_ibt_state);
 
        if (fdata->mode == CALLSTACK_USER)
                per_cpu(callstack_user_nesting, cpu)--;
index e73d1156ae7c0731e7918c194982e2446688f278..5075e9850d6742f320a89e2aa412807c256fdfd0 100644 (file)
@@ -152,6 +152,7 @@ size_t lttng_callstack_sequence_get_size(void *priv, struct lttng_kernel_probe_c
        struct field_data *fdata = (struct field_data *) priv;
        size_t orig_offset = offset;
        int cpu = smp_processor_id();
+       struct irq_ibt_state irq_ibt_state;
 
        /* do not write data if no space is available */
        trace = stack_trace_context(fdata, cpu);
@@ -166,14 +167,18 @@ size_t lttng_callstack_sequence_get_size(void *priv, struct lttng_kernel_probe_c
        switch (fdata->mode) {
        case CALLSTACK_KERNEL:
                /* do the real work and reserve space */
+               irq_ibt_state = wrapper_irq_ibt_save();
                trace->nr_entries = save_func_kernel(trace->entries,
                                                MAX_ENTRIES, 0);
+               wrapper_irq_ibt_restore(irq_ibt_state);
                break;
        case CALLSTACK_USER:
                ++per_cpu(callstack_user_nesting, cpu);
                /* do the real work and reserve space */
+               irq_ibt_state = wrapper_irq_ibt_save();
                trace->nr_entries = save_func_user(trace->entries,
                                                MAX_ENTRIES);
+               wrapper_irq_ibt_restore(irq_ibt_state);
                per_cpu(callstack_user_nesting, cpu)--;
                break;
        default:
index 34968df25c9183f8ee91854dde9abef4bed6c4aa..851d9227da547087612eb736749fb1fe60f8c154 100644 (file)
@@ -30,7 +30,13 @@ struct irq_desc *wrapper_irq_to_desc(unsigned int irq)
        if (!irq_to_desc_sym)
                irq_to_desc_sym = (void *) kallsyms_lookup_funcptr("irq_to_desc");
        if (irq_to_desc_sym) {
-               return irq_to_desc_sym(irq);
+               struct irq_ibt_state irq_ibt_state;
+               struct irq_desc *ret;
+
+               irq_ibt_state = wrapper_irq_ibt_save();
+               ret = irq_to_desc_sym(irq);
+               wrapper_irq_ibt_restore(irq_ibt_state);
+               return ret;
        } else {
                printk_once(KERN_WARNING "LTTng: irq_to_desc symbol lookup failed.\n");
                return NULL;
index e5b5e3101f42313b60fc2fd83bf507d6aaf44724..6293e07ea703f68c54e25e5b2f51b6d8a645c0a1 100644 (file)
@@ -103,9 +103,15 @@ unsigned long wrapper_kallsyms_lookup_name(const char *name)
        if (!kallsyms_lookup_name_sym) {
                kallsyms_lookup_name_sym = (void *)do_get_kallsyms();
        }
-       if (kallsyms_lookup_name_sym)
-               return kallsyms_lookup_name_sym(name);
-       else {
+       if (kallsyms_lookup_name_sym) {
+               struct irq_ibt_state irq_ibt_state;
+               unsigned long ret;
+
+               irq_ibt_state = wrapper_irq_ibt_save();
+               ret = kallsyms_lookup_name_sym(name);
+               wrapper_irq_ibt_restore(irq_ibt_state);
+               return ret;
+       } else {
                printk_once(KERN_WARNING "LTTng: requires kallsyms_lookup_name\n");
                return 0;
        }
index 81835cb2a04191fbd1aef3f45eae20cbc0a12cd6..8a05c62ee552cf31bebfc0b410f6a138397ac2ad 100644 (file)
@@ -37,7 +37,13 @@ unsigned long wrapper_get_pfnblock_flags_mask(struct page *page,
 {
        WARN_ON_ONCE(!get_pfnblock_flags_mask_sym);
        if (get_pfnblock_flags_mask_sym) {
-               return get_pfnblock_flags_mask_sym(page, pfn, end_bitidx, mask);
+               struct irq_ibt_state irq_ibt_state;
+               unsigned long ret;
+
+               irq_ibt_state = wrapper_irq_ibt_save();
+               ret = get_pfnblock_flags_mask_sym(page, pfn, end_bitidx, mask);
+               wrapper_irq_ibt_restore(irq_ibt_state);
+               return ret;
        } else {
                return -ENOSYS;
        }
This page took 0.030464 seconds and 4 git commands to generate.