linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v3 0/3] x86/asm: Pin sensitive CR4 and CR0 bits
@ 2019-06-18  4:55 Kees Cook
  2019-06-18  4:55 ` [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections Kees Cook
                   ` (2 more replies)
  0 siblings, 3 replies; 11+ messages in thread
From: Kees Cook @ 2019-06-18  4:55 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Kees Cook, Linus Torvalds, x86, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

Hi,

This updates the cr pinning series to allow for no silent papering-over
of pinning bugs (thanks to tglx to pointing out where I needed to poke
cr4 harder). I've tested with under normal boot and hibernation.

-Kees

Kees Cook (3):
  lkdtm: Check for SMEP clearing protections
  x86/asm: Pin sensitive CR4 bits
  x86/asm: Pin sensitive CR0 bits

 arch/x86/include/asm/special_insns.h | 37 +++++++++++++++-
 arch/x86/kernel/cpu/common.c         | 20 +++++++++
 arch/x86/kernel/smpboot.c            |  8 +++-
 drivers/misc/lkdtm/bugs.c            | 66 ++++++++++++++++++++++++++++
 drivers/misc/lkdtm/core.c            |  1 +
 drivers/misc/lkdtm/lkdtm.h           |  1 +
 6 files changed, 130 insertions(+), 3 deletions(-)

--
2.17.1


^ permalink raw reply	[flat|nested] 11+ messages in thread

* [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections
  2019-06-18  4:55 [PATCH v3 0/3] x86/asm: Pin sensitive CR4 and CR0 bits Kees Cook
@ 2019-06-18  4:55 ` Kees Cook
  2019-06-18  7:10   ` Rasmus Villemoes
  2019-06-18  4:55 ` [PATCH v3 2/3] x86/asm: Pin sensitive CR4 bits Kees Cook
  2019-06-18  4:55 ` [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits Kees Cook
  2 siblings, 1 reply; 11+ messages in thread
From: Kees Cook @ 2019-06-18  4:55 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Kees Cook, Linus Torvalds, x86, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

This adds an x86-specific test for pinned cr4 bits. A successful test
will validate pinning and check the ROP-style call-middle-of-function
defense, if needed. For example, in the case of native_write_cr4()
looking like this:

ffffffff8171bce0 <native_write_cr4>:
ffffffff8171bce0:       48 8b 35 79 46 f2 00    mov    0xf24679(%rip),%rsi
ffffffff8171bce7:       48 09 f7                or     %rsi,%rdi
ffffffff8171bcea:       0f 22 e7                mov    %rdi,%cr4
...
ffffffff8171bd5a:       c3                      retq

The UNSET_SMEP test will jump to ffffffff8171bcea (the mov to cr4)
instead of ffffffff8171bce0 (native_write_cr4() entry) to simulate a
direct-call bypass attempt.

Expected successful results:

  # echo UNSET_SMEP > /sys/kernel/debug/provoke-crash/DIRECT
  # dmesg
  [   79.594433] lkdtm: Performing direct entry UNSET_SMEP
  [   79.596459] lkdtm: trying to clear SMEP normally
  [   79.601810] ------------[ cut here ]------------
  [   79.603421] Attempt to unpin cr4 bits: 100000; bypass attack?!
  ...
  [   79.650170] ---[ end trace 2452ca0f6126242e ]---
  [   79.652281] lkdtm: ok: SMEP did not get cleared
  [   79.654344] lkdtm: trying to clear SMEP with call gadget
  [   79.655937] lkdtm: ok: SMEP removal was reverted

Signed-off-by: Kees Cook <keescook@chromium.org>
---
 drivers/misc/lkdtm/bugs.c  | 66 ++++++++++++++++++++++++++++++++++++++
 drivers/misc/lkdtm/core.c  |  1 +
 drivers/misc/lkdtm/lkdtm.h |  1 +
 3 files changed, 68 insertions(+)

diff --git a/drivers/misc/lkdtm/bugs.c b/drivers/misc/lkdtm/bugs.c
index 7eebbdfbcacd..3edf4464b9fc 100644
--- a/drivers/misc/lkdtm/bugs.c
+++ b/drivers/misc/lkdtm/bugs.c
@@ -255,3 +255,69 @@ void lkdtm_STACK_GUARD_PAGE_TRAILING(void)
 
 	pr_err("FAIL: accessed page after stack!\n");
 }
+
+void lkdtm_UNSET_SMEP(void)
+{
+#ifdef CONFIG_X86_64
+#define MOV_CR4_DEPTH	64
+	void (*direct_write_cr4)(unsigned long val);
+	unsigned char *insn;
+	unsigned long cr4;
+	int i;
+
+	cr4 = native_read_cr4();
+
+	if ((cr4 & X86_CR4_SMEP) != X86_CR4_SMEP) {
+		pr_err("FAIL: SMEP not in use\n");
+		return;
+	}
+	cr4 &= ~(X86_CR4_SMEP);
+
+	pr_info("trying to clear SMEP normally\n");
+	native_write_cr4(cr4);
+	if (cr4 == native_read_cr4()) {
+		pr_err("FAIL: pinning SMEP failed!\n");
+		cr4 |= X86_CR4_SMEP;
+		pr_info("restoring SMEP\n");
+		native_write_cr4(cr4);
+		return;
+	}
+	pr_info("ok: SMEP did not get cleared\n");
+
+	/*
+	 * To test the post-write pinning verification we need to call
+	 * directly into the the middle of native_write_cr4() where the
+	 * cr4 write happens, skipping the pinning. This searches for
+	 * the cr4 writing instruction.
+	 */
+	insn = (unsigned char *)native_write_cr4;
+	for (i = 0; i < MOV_CR4_DEPTH; i++) {
+		/* mov %rdi, %cr4 */
+		if (insn[i] == 0x0f && insn[i+1] == 0x22 && insn[i+2] == 0xe7)
+			break;
+		/* mov %rdi,%rax; mov %rax, %cr4 */
+		if (insn[i]   == 0x48 && insn[i+1] == 0x89 &&
+		    insn[i+2] == 0xf8 && insn[i+3] == 0x0f &&
+		    insn[i+4] == 0x22 && insn[i+5] == 0xe0)
+			break;
+	}
+	if (i >= MOV_CR4_DEPTH) {
+		pr_info("ok: cannot locate cr4 writing call gadget\n");
+		return;
+	}
+	direct_write_cr4 = (void *)(insn + i);
+
+	pr_info("trying to clear SMEP with call gadget\n");
+	direct_write_cr4(cr4);
+	if (native_read_cr4() & X86_CR4_SMEP) {
+		pr_info("ok: SMEP removal was reverted\n");
+	} else {
+		pr_err("FAIL: cleared SMEP not detected!\n");
+		cr4 |= X86_CR4_SMEP;
+		pr_info("restoring SMEP\n");
+		native_write_cr4(cr4);
+	}
+#else
+	pr_err("FAIL: this test is x86_64-only\n");
+#endif
+}
diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
index b51cf182b031..58cfd713f8dc 100644
--- a/drivers/misc/lkdtm/core.c
+++ b/drivers/misc/lkdtm/core.c
@@ -123,6 +123,7 @@ static const struct crashtype crashtypes[] = {
 	CRASHTYPE(CORRUPT_LIST_ADD),
 	CRASHTYPE(CORRUPT_LIST_DEL),
 	CRASHTYPE(CORRUPT_USER_DS),
+	CRASHTYPE(UNSET_SMEP),
 	CRASHTYPE(CORRUPT_STACK),
 	CRASHTYPE(CORRUPT_STACK_STRONG),
 	CRASHTYPE(STACK_GUARD_PAGE_LEADING),
diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
index b69ee004a3f7..d7eb5a8f1da4 100644
--- a/drivers/misc/lkdtm/lkdtm.h
+++ b/drivers/misc/lkdtm/lkdtm.h
@@ -26,6 +26,7 @@ void lkdtm_CORRUPT_LIST_DEL(void);
 void lkdtm_CORRUPT_USER_DS(void);
 void lkdtm_STACK_GUARD_PAGE_LEADING(void);
 void lkdtm_STACK_GUARD_PAGE_TRAILING(void);
+void lkdtm_UNSET_SMEP(void);
 
 /* lkdtm_heap.c */
 void lkdtm_OVERWRITE_ALLOCATION(void);
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 2/3] x86/asm: Pin sensitive CR4 bits
  2019-06-18  4:55 [PATCH v3 0/3] x86/asm: Pin sensitive CR4 and CR0 bits Kees Cook
  2019-06-18  4:55 ` [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections Kees Cook
@ 2019-06-18  4:55 ` Kees Cook
  2019-06-22  9:58   ` [tip:x86/asm] " tip-bot for Kees Cook
  2019-06-18  4:55 ` [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits Kees Cook
  2 siblings, 1 reply; 11+ messages in thread
From: Kees Cook @ 2019-06-18  4:55 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Kees Cook, Linus Torvalds, x86, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

Several recent exploits have used direct calls to the native_write_cr4()
function to disable SMEP and SMAP before then continuing their exploits
using userspace memory access. This pins bits of CR4 so that they cannot
be changed through a common function. This is not intended to be general
ROP protection (which would require CFI to defend against properly), but
rather a way to avoid trivial direct function calling (or CFI bypasses
via a matching function prototype) as seen in:

https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html
(https://github.com/xairy/kernel-exploits/tree/master/CVE-2017-7308)

The goals of this change:
 - pin specific bits (SMEP, SMAP, and UMIP) when writing CR4.
 - avoid setting the bits too early (they must become pinned only after
   CPU feature detection and selection has finished).
 - pinning mask needs to be read-only during normal runtime.
 - pinning needs to be checked after write to validate the cr4 state

Using __ro_after_init on the mask is done so it can't be first disabled
with a malicious write.

Since these bits are global state (once established by the boot CPU
and kernel boot parameters), they are safe to write to secondary CPUs
before those CPUs have finished feature detection. As such, the bits
are set at the first cr4 write, so that cr4 write bugs can be detected
(instead of silently papered over). This uses a few bytes less storage
of a location we don't have: read-only per-CPU data.

A check is performed after the register write because an attack could
just skip directly to the register write. Such a direct jump is possible
because of how this function may be built by the compiler (especially
due to the removal of frame pointers) where it doesn't add a stack frame
(function exit may only be a retq without pops) which is sufficient for
trivial exploitation like in the timer overwrites mentioned above).

The asm argument constraints gain the "+" modifier to convince the
compiler that it shouldn't make ordering assumptions about the arguments
or memory, and treat them as changed.

Signed-off-by: Kees Cook <keescook@chromium.org>
---
v3:
- added missing EXPORT_SYMBOL()s
- remove always-OR, instead doing an early OR in secondary startup (tglx)
v2:
- move setup until after CPU feature detection and selection.
- refactor to use static branches to have atomic enabling.
- only perform the "or" after a failed check.
---
 arch/x86/include/asm/special_insns.h | 22 +++++++++++++++++++++-
 arch/x86/kernel/cpu/common.c         | 20 ++++++++++++++++++++
 arch/x86/kernel/smpboot.c            |  8 +++++++-
 3 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
index 0a3c4cab39db..c8c8143ab27b 100644
--- a/arch/x86/include/asm/special_insns.h
+++ b/arch/x86/include/asm/special_insns.h
@@ -6,6 +6,8 @@
 #ifdef __KERNEL__
 
 #include <asm/nops.h>
+#include <asm/processor-flags.h>
+#include <linux/jump_label.h>
 
 /*
  * Volatile isn't enough to prevent the compiler from reordering the
@@ -16,6 +18,10 @@
  */
 extern unsigned long __force_order;
 
+/* Starts false and gets enabled once CPU feature detection is done. */
+DECLARE_STATIC_KEY_FALSE(cr_pinning);
+extern unsigned long cr4_pinned_bits;
+
 static inline unsigned long native_read_cr0(void)
 {
 	unsigned long val;
@@ -74,7 +80,21 @@ static inline unsigned long native_read_cr4(void)
 
 static inline void native_write_cr4(unsigned long val)
 {
-	asm volatile("mov %0,%%cr4": : "r" (val), "m" (__force_order));
+	unsigned long bits_missing = 0;
+
+set_register:
+	asm volatile("mov %0,%%cr4": "+r" (val), "+m" (cr4_pinned_bits));
+
+	if (static_branch_likely(&cr_pinning)) {
+		if (unlikely((val & cr4_pinned_bits) != cr4_pinned_bits)) {
+			bits_missing = ~val & cr4_pinned_bits;
+			val |= bits_missing;
+			goto set_register;
+		}
+		/* Warn after we've set the missing bits. */
+		WARN_ONCE(bits_missing, "CR4 bits went missing: %lx!?\n",
+			  bits_missing);
+	}
 }
 
 #ifdef CONFIG_X86_64
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index 2c57fffebf9b..c578addfcf8a 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -366,6 +366,25 @@ static __always_inline void setup_umip(struct cpuinfo_x86 *c)
 	cr4_clear_bits(X86_CR4_UMIP);
 }
 
+DEFINE_STATIC_KEY_FALSE_RO(cr_pinning);
+EXPORT_SYMBOL(cr_pinning);
+unsigned long cr4_pinned_bits __ro_after_init;
+EXPORT_SYMBOL(cr4_pinned_bits);
+
+/*
+ * Once CPU feature detection is finished (and boot params have been
+ * parsed), record any of the sensitive CR bits that are set, and
+ * enable CR pinning.
+ */
+static void __init setup_cr_pinning(void)
+{
+	unsigned long mask;
+
+	mask = (X86_CR4_SMEP | X86_CR4_SMAP | X86_CR4_UMIP);
+	cr4_pinned_bits = this_cpu_read(cpu_tlbstate.cr4) & mask;
+	static_key_enable(&cr_pinning.key);
+}
+
 /*
  * Protection Keys are not available in 32-bit mode.
  */
@@ -1464,6 +1483,7 @@ void __init identify_boot_cpu(void)
 	enable_sep_cpu();
 #endif
 	cpu_detect_tlb(&boot_cpu_data);
+	setup_cr_pinning();
 }
 
 void identify_secondary_cpu(struct cpuinfo_x86 *c)
diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c
index 362dd8953f48..1af7a2d89419 100644
--- a/arch/x86/kernel/smpboot.c
+++ b/arch/x86/kernel/smpboot.c
@@ -205,13 +205,19 @@ static int enable_start_cpu0;
  */
 static void notrace start_secondary(void *unused)
 {
+	unsigned long cr4 = __read_cr4();
+
 	/*
 	 * Don't put *anything* except direct CPU state initialization
 	 * before cpu_init(), SMP booting is too fragile that we want to
 	 * limit the things done here to the most necessary things.
 	 */
 	if (boot_cpu_has(X86_FEATURE_PCID))
-		__write_cr4(__read_cr4() | X86_CR4_PCIDE);
+		cr4 |= X86_CR4_PCIDE;
+	if (static_branch_likely(&cr_pinning))
+		cr4 |= cr4_pinned_bits;
+
+	__write_cr4(cr4);
 
 #ifdef CONFIG_X86_32
 	/* switch away from the initial page table */
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits
  2019-06-18  4:55 [PATCH v3 0/3] x86/asm: Pin sensitive CR4 and CR0 bits Kees Cook
  2019-06-18  4:55 ` [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections Kees Cook
  2019-06-18  4:55 ` [PATCH v3 2/3] x86/asm: Pin sensitive CR4 bits Kees Cook
@ 2019-06-18  4:55 ` Kees Cook
  2019-06-18  9:38   ` Jann Horn
  2019-06-22  9:58   ` [tip:x86/asm] " tip-bot for Kees Cook
  2 siblings, 2 replies; 11+ messages in thread
From: Kees Cook @ 2019-06-18  4:55 UTC (permalink / raw)
  To: Thomas Gleixner
  Cc: Kees Cook, Linus Torvalds, x86, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

With sensitive CR4 bits pinned now, it's possible that the WP bit for
CR0 might become a target as well. Following the same reasoning for
the CR4 pinning, this pins CR0's WP bit (but this can be done with a
static value).

Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
---
 arch/x86/include/asm/special_insns.h | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
index c8c8143ab27b..b2e84d113f2a 100644
--- a/arch/x86/include/asm/special_insns.h
+++ b/arch/x86/include/asm/special_insns.h
@@ -31,7 +31,20 @@ static inline unsigned long native_read_cr0(void)
 
 static inline void native_write_cr0(unsigned long val)
 {
-	asm volatile("mov %0,%%cr0": : "r" (val), "m" (__force_order));
+	unsigned long bits_missing = 0;
+
+set_register:
+	asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order));
+
+	if (static_branch_likely(&cr_pinning)) {
+		if (unlikely((val & X86_CR0_WP) != X86_CR0_WP)) {
+			bits_missing = X86_CR0_WP;
+			val |= bits_missing;
+			goto set_register;
+		}
+		/* Warn after we've set the missing bits. */
+		WARN_ONCE(bits_missing, "CR0 WP bit went missing!?\n");
+	}
 }
 
 static inline unsigned long native_read_cr2(void)
-- 
2.17.1


^ permalink raw reply related	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections
  2019-06-18  4:55 ` [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections Kees Cook
@ 2019-06-18  7:10   ` Rasmus Villemoes
  2019-06-18  7:23     ` Kees Cook
  0 siblings, 1 reply; 11+ messages in thread
From: Rasmus Villemoes @ 2019-06-18  7:10 UTC (permalink / raw)
  To: Kees Cook
  Cc: Thomas Gleixner, Linus Torvalds, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

On 18/06/2019 06.55, Kees Cook wrote:

> +#else
> +	pr_err("FAIL: this test is x86_64-only\n");
> +#endif
> +}

Why expose it at all on all other architectures? If you wrap the
CRASHTYPE() in an #ifdef, you can also guard the whole lkdtm_UNSET_SMEP
definition (the declaration in lkdtm.h can stay, possibly with a comment
saying /* x86-64 only */).

> diff --git a/drivers/misc/lkdtm/core.c b/drivers/misc/lkdtm/core.c
> index b51cf182b031..58cfd713f8dc 100644
> --- a/drivers/misc/lkdtm/core.c
> +++ b/drivers/misc/lkdtm/core.c
> @@ -123,6 +123,7 @@ static const struct crashtype crashtypes[] = {
>  	CRASHTYPE(CORRUPT_LIST_ADD),
>  	CRASHTYPE(CORRUPT_LIST_DEL),
>  	CRASHTYPE(CORRUPT_USER_DS),
> +	CRASHTYPE(UNSET_SMEP),
>  	CRASHTYPE(CORRUPT_STACK),
>  	CRASHTYPE(CORRUPT_STACK_STRONG),
>  	CRASHTYPE(STACK_GUARD_PAGE_LEADING),
> diff --git a/drivers/misc/lkdtm/lkdtm.h b/drivers/misc/lkdtm/lkdtm.h
> index b69ee004a3f7..d7eb5a8f1da4 100644
> --- a/drivers/misc/lkdtm/lkdtm.h
> +++ b/drivers/misc/lkdtm/lkdtm.h
> @@ -26,6 +26,7 @@ void lkdtm_CORRUPT_LIST_DEL(void);
>  void lkdtm_CORRUPT_USER_DS(void);
>  void lkdtm_STACK_GUARD_PAGE_LEADING(void);
>  void lkdtm_STACK_GUARD_PAGE_TRAILING(void);
> +void lkdtm_UNSET_SMEP(void);
>  
>  /* lkdtm_heap.c */
>  void lkdtm_OVERWRITE_ALLOCATION(void);
Rasmus

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections
  2019-06-18  7:10   ` Rasmus Villemoes
@ 2019-06-18  7:23     ` Kees Cook
  0 siblings, 0 replies; 11+ messages in thread
From: Kees Cook @ 2019-06-18  7:23 UTC (permalink / raw)
  To: Rasmus Villemoes
  Cc: Thomas Gleixner, Linus Torvalds, Peter Zijlstra, Dave Hansen,
	linux-kernel, kernel-hardening

On Tue, Jun 18, 2019 at 09:10:13AM +0200, Rasmus Villemoes wrote:
> On 18/06/2019 06.55, Kees Cook wrote:
> 
> > +#else
> > +	pr_err("FAIL: this test is x86_64-only\n");
> > +#endif
> > +}
> 
> Why expose it at all on all other architectures? If you wrap the
> CRASHTYPE() in an #ifdef, you can also guard the whole lkdtm_UNSET_SMEP
> definition (the declaration in lkdtm.h can stay, possibly with a comment
> saying /* x86-64 only */).

My preference for LKDTM is for all the tests to be visible regardless
of architecture so that the testing "environment" doesn't have to change
depending on architecture. I've found it easier to deal with when I ran
test harnesses on Chrome OS where I had cross-architectural scripts.
Doing a side-by-side with a PASS and an XFAIL was more sensible to
compare than a PASS and a missing test.

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits
  2019-06-18  4:55 ` [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits Kees Cook
@ 2019-06-18  9:38   ` Jann Horn
  2019-06-18 12:24     ` Peter Zijlstra
  2019-06-22  9:58   ` [tip:x86/asm] " tip-bot for Kees Cook
  1 sibling, 1 reply; 11+ messages in thread
From: Jann Horn @ 2019-06-18  9:38 UTC (permalink / raw)
  To: Kees Cook
  Cc: Thomas Gleixner, Linus Torvalds, the arch/x86 maintainers,
	Peter Zijlstra, Dave Hansen, kernel list, Kernel Hardening

On Tue, Jun 18, 2019 at 6:55 AM Kees Cook <keescook@chromium.org> wrote:
> With sensitive CR4 bits pinned now, it's possible that the WP bit for
> CR0 might become a target as well. Following the same reasoning for
> the CR4 pinning, this pins CR0's WP bit (but this can be done with a
> static value).
>
> Suggested-by: Peter Zijlstra <peterz@infradead.org>
> Signed-off-by: Kees Cook <keescook@chromium.org>
> ---
>  arch/x86/include/asm/special_insns.h | 15 ++++++++++++++-
>  1 file changed, 14 insertions(+), 1 deletion(-)
>
> diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
> index c8c8143ab27b..b2e84d113f2a 100644
> --- a/arch/x86/include/asm/special_insns.h
> +++ b/arch/x86/include/asm/special_insns.h
> @@ -31,7 +31,20 @@ static inline unsigned long native_read_cr0(void)
>
>  static inline void native_write_cr0(unsigned long val)
>  {

So, assuming a legitimate call to native_write_cr0(), we come in here...

> -       asm volatile("mov %0,%%cr0": : "r" (val), "m" (__force_order));
> +       unsigned long bits_missing = 0;
> +
> +set_register:
> +       asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order));

... here we've updated CR0...

> +       if (static_branch_likely(&cr_pinning)) {

... this branch is taken, since cr_pinning is set to true after boot...

> +               if (unlikely((val & X86_CR0_WP) != X86_CR0_WP)) {

... this branch isn't taken, because a legitimate update preserves the WP bit...

> +                       bits_missing = X86_CR0_WP;
> +                       val |= bits_missing;
> +                       goto set_register;
> +               }
> +               /* Warn after we've set the missing bits. */
> +               WARN_ONCE(bits_missing, "CR0 WP bit went missing!?\n");

... and we reach this WARN_ONCE()? Am I missing something, or does
every legitimate CR0 write after early boot now trigger a warning?

> +       }
>  }

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits
  2019-06-18  9:38   ` Jann Horn
@ 2019-06-18 12:24     ` Peter Zijlstra
  2019-06-18 17:12       ` Kees Cook
  0 siblings, 1 reply; 11+ messages in thread
From: Peter Zijlstra @ 2019-06-18 12:24 UTC (permalink / raw)
  To: Jann Horn
  Cc: Kees Cook, Thomas Gleixner, Linus Torvalds,
	the arch/x86 maintainers, Dave Hansen, kernel list,
	Kernel Hardening

On Tue, Jun 18, 2019 at 11:38:02AM +0200, Jann Horn wrote:
> On Tue, Jun 18, 2019 at 6:55 AM Kees Cook <keescook@chromium.org> wrote:
> > With sensitive CR4 bits pinned now, it's possible that the WP bit for
> > CR0 might become a target as well. Following the same reasoning for
> > the CR4 pinning, this pins CR0's WP bit (but this can be done with a
> > static value).
> >
> > Suggested-by: Peter Zijlstra <peterz@infradead.org>
> > Signed-off-by: Kees Cook <keescook@chromium.org>
> > ---
> >  arch/x86/include/asm/special_insns.h | 15 ++++++++++++++-
> >  1 file changed, 14 insertions(+), 1 deletion(-)
> >
> > diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
> > index c8c8143ab27b..b2e84d113f2a 100644
> > --- a/arch/x86/include/asm/special_insns.h
> > +++ b/arch/x86/include/asm/special_insns.h
> > @@ -31,7 +31,20 @@ static inline unsigned long native_read_cr0(void)
> >
> >  static inline void native_write_cr0(unsigned long val)
> >  {
> 
> So, assuming a legitimate call to native_write_cr0(), we come in here...
> 
> > -       asm volatile("mov %0,%%cr0": : "r" (val), "m" (__force_order));
> > +       unsigned long bits_missing = 0;

^^^

> > +
> > +set_register:
> > +       asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order));
> 
> ... here we've updated CR0...
> 
> > +       if (static_branch_likely(&cr_pinning)) {
> 
> ... this branch is taken, since cr_pinning is set to true after boot...
> 
> > +               if (unlikely((val & X86_CR0_WP) != X86_CR0_WP)) {
> 
> ... this branch isn't taken, because a legitimate update preserves the WP bit...
> 
> > +                       bits_missing = X86_CR0_WP;

^^^

> > +                       val |= bits_missing;
> > +                       goto set_register;
> > +               }
> > +               /* Warn after we've set the missing bits. */
> > +               WARN_ONCE(bits_missing, "CR0 WP bit went missing!?\n");
> 
> ... and we reach this WARN_ONCE()? Am I missing something, or does
> every legitimate CR0 write after early boot now trigger a warning?

bits_missing will be 0 and WARN will not be issued.

> > +       }
> >  }

^ permalink raw reply	[flat|nested] 11+ messages in thread

* Re: [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits
  2019-06-18 12:24     ` Peter Zijlstra
@ 2019-06-18 17:12       ` Kees Cook
  0 siblings, 0 replies; 11+ messages in thread
From: Kees Cook @ 2019-06-18 17:12 UTC (permalink / raw)
  To: Peter Zijlstra
  Cc: Jann Horn, Thomas Gleixner, Linus Torvalds,
	the arch/x86 maintainers, Dave Hansen, kernel list,
	Kernel Hardening

On Tue, Jun 18, 2019 at 02:24:30PM +0200, Peter Zijlstra wrote:
> On Tue, Jun 18, 2019 at 11:38:02AM +0200, Jann Horn wrote:
> > On Tue, Jun 18, 2019 at 6:55 AM Kees Cook <keescook@chromium.org> wrote:
> > > With sensitive CR4 bits pinned now, it's possible that the WP bit for
> > > CR0 might become a target as well. Following the same reasoning for
> > > the CR4 pinning, this pins CR0's WP bit (but this can be done with a
> > > static value).
> > >
> > > Suggested-by: Peter Zijlstra <peterz@infradead.org>
> > > Signed-off-by: Kees Cook <keescook@chromium.org>
> > > ---
> > >  arch/x86/include/asm/special_insns.h | 15 ++++++++++++++-
> > >  1 file changed, 14 insertions(+), 1 deletion(-)
> > >
> > > diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
> > > index c8c8143ab27b..b2e84d113f2a 100644
> > > --- a/arch/x86/include/asm/special_insns.h
> > > +++ b/arch/x86/include/asm/special_insns.h
> > > @@ -31,7 +31,20 @@ static inline unsigned long native_read_cr0(void)
> > >
> > >  static inline void native_write_cr0(unsigned long val)
> > >  {
> > 
> > So, assuming a legitimate call to native_write_cr0(), we come in here...
> > 
> > > -       asm volatile("mov %0,%%cr0": : "r" (val), "m" (__force_order));
> > > +       unsigned long bits_missing = 0;
> 
> ^^^
> 
> > > +
> > > +set_register:
> > > +       asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order));
> > 
> > ... here we've updated CR0...
> > 
> > > +       if (static_branch_likely(&cr_pinning)) {
> > 
> > ... this branch is taken, since cr_pinning is set to true after boot...
> > 
> > > +               if (unlikely((val & X86_CR0_WP) != X86_CR0_WP)) {
> > 
> > ... this branch isn't taken, because a legitimate update preserves the WP bit...
> > 
> > > +                       bits_missing = X86_CR0_WP;
> 
> ^^^
> 
> > > +                       val |= bits_missing;
> > > +                       goto set_register;
> > > +               }
> > > +               /* Warn after we've set the missing bits. */
> > > +               WARN_ONCE(bits_missing, "CR0 WP bit went missing!?\n");
> > 
> > ... and we reach this WARN_ONCE()? Am I missing something, or does
> > every legitimate CR0 write after early boot now trigger a warning?
> 
> bits_missing will be 0 and WARN will not be issued.
> 
> > > +       }
> > >  }

Yup, as Peter points out, bits_missing is only non-zero when bits went
missing. The normal case will skip the WARN_ONCE() (which is also
internally wrapped in unlikely()). And I would have noticed the very
loud WARN at every boot if this wasn't true. ;)

-- 
Kees Cook

^ permalink raw reply	[flat|nested] 11+ messages in thread

* [tip:x86/asm] x86/asm: Pin sensitive CR4 bits
  2019-06-18  4:55 ` [PATCH v3 2/3] x86/asm: Pin sensitive CR4 bits Kees Cook
@ 2019-06-22  9:58   ` tip-bot for Kees Cook
  0 siblings, 0 replies; 11+ messages in thread
From: tip-bot for Kees Cook @ 2019-06-22  9:58 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: keescook, hpa, mingo, torvalds, dave.hansen, linux-kernel, tglx, peterz

Commit-ID:  873d50d58f67ef15d2777b5e7f7a5268bb1fbae2
Gitweb:     https://git.kernel.org/tip/873d50d58f67ef15d2777b5e7f7a5268bb1fbae2
Author:     Kees Cook <keescook@chromium.org>
AuthorDate: Mon, 17 Jun 2019 21:55:02 -0700
Committer:  Thomas Gleixner <tglx@linutronix.de>
CommitDate: Sat, 22 Jun 2019 11:55:22 +0200

x86/asm: Pin sensitive CR4 bits

Several recent exploits have used direct calls to the native_write_cr4()
function to disable SMEP and SMAP before then continuing their exploits
using userspace memory access.

Direct calls of this form can be mitigate by pinning bits of CR4 so that
they cannot be changed through a common function. This is not intended to
be a general ROP protection (which would require CFI to defend against
properly), but rather a way to avoid trivial direct function calling (or
CFI bypasses via a matching function prototype) as seen in:

https://googleprojectzero.blogspot.com/2017/05/exploiting-linux-kernel-via-packet.html
(https://github.com/xairy/kernel-exploits/tree/master/CVE-2017-7308)

The goals of this change:

 - Pin specific bits (SMEP, SMAP, and UMIP) when writing CR4.

 - Avoid setting the bits too early (they must become pinned only after
   CPU feature detection and selection has finished).

 - Pinning mask needs to be read-only during normal runtime.

 - Pinning needs to be checked after write to validate the cr4 state

Using __ro_after_init on the mask is done so it can't be first disabled
with a malicious write.

Since these bits are global state (once established by the boot CPU and
kernel boot parameters), they are safe to write to secondary CPUs before
those CPUs have finished feature detection. As such, the bits are set at
the first cr4 write, so that cr4 write bugs can be detected (instead of
silently papered over). This uses a few bytes less storage of a location we
don't have: read-only per-CPU data.

A check is performed after the register write because an attack could just
skip directly to the register write. Such a direct jump is possible because
of how this function may be built by the compiler (especially due to the
removal of frame pointers) where it doesn't add a stack frame (function
exit may only be a retq without pops) which is sufficient for trivial
exploitation like in the timer overwrites mentioned above).

The asm argument constraints gain the "+" modifier to convince the compiler
that it shouldn't make ordering assumptions about the arguments or memory,
and treat them as changed.

Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Dave Hansen <dave.hansen@intel.com>
Cc: kernel-hardening@lists.openwall.com
Link: https://lkml.kernel.org/r/20190618045503.39105-3-keescook@chromium.org

---
 arch/x86/include/asm/special_insns.h | 22 +++++++++++++++++++++-
 arch/x86/kernel/cpu/common.c         | 20 ++++++++++++++++++++
 arch/x86/kernel/smpboot.c            |  8 +++++++-
 3 files changed, 48 insertions(+), 2 deletions(-)

diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
index 0a3c4cab39db..c8c8143ab27b 100644
--- a/arch/x86/include/asm/special_insns.h
+++ b/arch/x86/include/asm/special_insns.h
@@ -6,6 +6,8 @@
 #ifdef __KERNEL__
 
 #include <asm/nops.h>
+#include <asm/processor-flags.h>
+#include <linux/jump_label.h>
 
 /*
  * Volatile isn't enough to prevent the compiler from reordering the
@@ -16,6 +18,10 @@
  */
 extern unsigned long __force_order;
 
+/* Starts false and gets enabled once CPU feature detection is done. */
+DECLARE_STATIC_KEY_FALSE(cr_pinning);
+extern unsigned long cr4_pinned_bits;
+
 static inline unsigned long native_read_cr0(void)
 {
 	unsigned long val;
@@ -74,7 +80,21 @@ static inline unsigned long native_read_cr4(void)
 
 static inline void native_write_cr4(unsigned long val)
 {
-	asm volatile("mov %0,%%cr4": : "r" (val), "m" (__force_order));
+	unsigned long bits_missing = 0;
+
+set_register:
+	asm volatile("mov %0,%%cr4": "+r" (val), "+m" (cr4_pinned_bits));
+
+	if (static_branch_likely(&cr_pinning)) {
+		if (unlikely((val & cr4_pinned_bits) != cr4_pinned_bits)) {
+			bits_missing = ~val & cr4_pinned_bits;
+			val |= bits_missing;
+			goto set_register;
+		}
+		/* Warn after we've set the missing bits. */
+		WARN_ONCE(bits_missing, "CR4 bits went missing: %lx!?\n",
+			  bits_missing);
+	}
 }
 
 #ifdef CONFIG_X86_64
diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
index 2c57fffebf9b..c578addfcf8a 100644
--- a/arch/x86/kernel/cpu/common.c
+++ b/arch/x86/kernel/cpu/common.c
@@ -366,6 +366,25 @@ out:
 	cr4_clear_bits(X86_CR4_UMIP);
 }
 
+DEFINE_STATIC_KEY_FALSE_RO(cr_pinning);
+EXPORT_SYMBOL(cr_pinning);
+unsigned long cr4_pinned_bits __ro_after_init;
+EXPORT_SYMBOL(cr4_pinned_bits);
+
+/*
+ * Once CPU feature detection is finished (and boot params have been
+ * parsed), record any of the sensitive CR bits that are set, and
+ * enable CR pinning.
+ */
+static void __init setup_cr_pinning(void)
+{
+	unsigned long mask;
+
+	mask = (X86_CR4_SMEP | X86_CR4_SMAP | X86_CR4_UMIP);
+	cr4_pinned_bits = this_cpu_read(cpu_tlbstate.cr4) & mask;
+	static_key_enable(&cr_pinning.key);
+}
+
 /*
  * Protection Keys are not available in 32-bit mode.
  */
@@ -1464,6 +1483,7 @@ void __init identify_boot_cpu(void)
 	enable_sep_cpu();
 #endif
 	cpu_detect_tlb(&boot_cpu_data);
+	setup_cr_pinning();
 }
 
 void identify_secondary_cpu(struct cpuinfo_x86 *c)
diff --git a/arch/x86/kernel/smpboot.c b/arch/x86/kernel/smpboot.c
index 362dd8953f48..1af7a2d89419 100644
--- a/arch/x86/kernel/smpboot.c
+++ b/arch/x86/kernel/smpboot.c
@@ -205,13 +205,19 @@ static int enable_start_cpu0;
  */
 static void notrace start_secondary(void *unused)
 {
+	unsigned long cr4 = __read_cr4();
+
 	/*
 	 * Don't put *anything* except direct CPU state initialization
 	 * before cpu_init(), SMP booting is too fragile that we want to
 	 * limit the things done here to the most necessary things.
 	 */
 	if (boot_cpu_has(X86_FEATURE_PCID))
-		__write_cr4(__read_cr4() | X86_CR4_PCIDE);
+		cr4 |= X86_CR4_PCIDE;
+	if (static_branch_likely(&cr_pinning))
+		cr4 |= cr4_pinned_bits;
+
+	__write_cr4(cr4);
 
 #ifdef CONFIG_X86_32
 	/* switch away from the initial page table */

^ permalink raw reply related	[flat|nested] 11+ messages in thread

* [tip:x86/asm] x86/asm: Pin sensitive CR0 bits
  2019-06-18  4:55 ` [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits Kees Cook
  2019-06-18  9:38   ` Jann Horn
@ 2019-06-22  9:58   ` tip-bot for Kees Cook
  1 sibling, 0 replies; 11+ messages in thread
From: tip-bot for Kees Cook @ 2019-06-22  9:58 UTC (permalink / raw)
  To: linux-tip-commits
  Cc: keescook, dave.hansen, tglx, mingo, linux-kernel, hpa, peterz, torvalds

Commit-ID:  8dbec27a242cd3e2816eeb98d3237b9f57cf6232
Gitweb:     https://git.kernel.org/tip/8dbec27a242cd3e2816eeb98d3237b9f57cf6232
Author:     Kees Cook <keescook@chromium.org>
AuthorDate: Mon, 17 Jun 2019 21:55:03 -0700
Committer:  Thomas Gleixner <tglx@linutronix.de>
CommitDate: Sat, 22 Jun 2019 11:55:22 +0200

x86/asm: Pin sensitive CR0 bits

With sensitive CR4 bits pinned now, it's possible that the WP bit for
CR0 might become a target as well.

Following the same reasoning for the CR4 pinning, pin CR0's WP
bit. Contrary to the cpu feature dependend CR4 pinning this can be done
with a constant value.

Suggested-by: Peter Zijlstra <peterz@infradead.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Linus Torvalds <torvalds@linux-foundation.org>
Cc: Dave Hansen <dave.hansen@intel.com>
Cc: kernel-hardening@lists.openwall.com
Link: https://lkml.kernel.org/r/20190618045503.39105-4-keescook@chromium.org

---
 arch/x86/include/asm/special_insns.h | 15 ++++++++++++++-
 1 file changed, 14 insertions(+), 1 deletion(-)

diff --git a/arch/x86/include/asm/special_insns.h b/arch/x86/include/asm/special_insns.h
index c8c8143ab27b..b2e84d113f2a 100644
--- a/arch/x86/include/asm/special_insns.h
+++ b/arch/x86/include/asm/special_insns.h
@@ -31,7 +31,20 @@ static inline unsigned long native_read_cr0(void)
 
 static inline void native_write_cr0(unsigned long val)
 {
-	asm volatile("mov %0,%%cr0": : "r" (val), "m" (__force_order));
+	unsigned long bits_missing = 0;
+
+set_register:
+	asm volatile("mov %0,%%cr0": "+r" (val), "+m" (__force_order));
+
+	if (static_branch_likely(&cr_pinning)) {
+		if (unlikely((val & X86_CR0_WP) != X86_CR0_WP)) {
+			bits_missing = X86_CR0_WP;
+			val |= bits_missing;
+			goto set_register;
+		}
+		/* Warn after we've set the missing bits. */
+		WARN_ONCE(bits_missing, "CR0 WP bit went missing!?\n");
+	}
 }
 
 static inline unsigned long native_read_cr2(void)

^ permalink raw reply related	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2019-06-22  9:59 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-06-18  4:55 [PATCH v3 0/3] x86/asm: Pin sensitive CR4 and CR0 bits Kees Cook
2019-06-18  4:55 ` [PATCH v3 1/3] lkdtm: Check for SMEP clearing protections Kees Cook
2019-06-18  7:10   ` Rasmus Villemoes
2019-06-18  7:23     ` Kees Cook
2019-06-18  4:55 ` [PATCH v3 2/3] x86/asm: Pin sensitive CR4 bits Kees Cook
2019-06-22  9:58   ` [tip:x86/asm] " tip-bot for Kees Cook
2019-06-18  4:55 ` [PATCH v3 3/3] x86/asm: Pin sensitive CR0 bits Kees Cook
2019-06-18  9:38   ` Jann Horn
2019-06-18 12:24     ` Peter Zijlstra
2019-06-18 17:12       ` Kees Cook
2019-06-22  9:58   ` [tip:x86/asm] " tip-bot for Kees Cook

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).