[PATCH] x86/traps: Enable UBSAN traps on x86
Bring x86 to parity with arm64, similar to commit 25b84002afb9 ("arm64: Support Clang UBSAN trap codes for better reporting"). Enable the output of UBSAN type information on x86 architectures compiled with clang when CONFIG_UBSAN_TRAP=y. Currently ARM architectures output which specific sanitizer caused the trap, via the encoded data in the trap instruction. Clang on x86 currently encodes the same data in ud1 instructions but the x86 handle_bug() and is_valid_bugaddr() functions currently only look at ud2s. Signed-off-by: Gatlin Newhouse --- MAINTAINERS | 2 ++ arch/x86/include/asm/bug.h | 8 arch/x86/include/asm/ubsan.h | 21 + arch/x86/kernel/Makefile | 1 + arch/x86/kernel/traps.c | 34 -- arch/x86/kernel/ubsan.c | 32 6 files changed, 92 insertions(+), 6 deletions(-) create mode 100644 arch/x86/include/asm/ubsan.h create mode 100644 arch/x86/kernel/ubsan.c diff --git a/MAINTAINERS b/MAINTAINERS index 28e20975c26f..b8512887ffb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22635,6 +22635,8 @@ L: kasan-...@googlegroups.com L: linux-hardening@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening +F: arch/x86/include/asm/ubsan.h +F: arch/x86/kernel/ubsan.c F: Documentation/dev-tools/ubsan.rst F: include/linux/ubsan.h F: lib/Kconfig.ubsan diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index a3ec87d198ac..e3fbed9073f8 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -13,6 +13,14 @@ #define INSN_UD2 0x0b0f #define LEN_UD22 +/* + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. + */ +#define INSN_UD1 0xb90f +#define LEN_UD12 +#define INSN_REX 0x67 +#define LEN_REX1 + #ifdef CONFIG_GENERIC_BUG #ifdef CONFIG_X86_32 diff --git a/arch/x86/include/asm/ubsan.h b/arch/x86/include/asm/ubsan.h new file mode 100644 index ..5235822eb4ae --- /dev/null +++ b/arch/x86/include/asm/ubsan.h @@ -0,0 +1,21 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UBSAN_H +#define _ASM_X86_UBSAN_H + +/* + * Clang Undefined Behavior Sanitizer trap mode support. + */ +#include +#include +#include + +#ifdef CONFIG_UBSAN_TRAP +enum bug_trap_type handle_ubsan_failure(struct pt_regs *regs, int insn); +#else +static inline enum bug_trap_type handle_ubsan_failure(struct pt_regs *regs, int insn) +{ + return BUG_TRAP_TYPE_NONE; +} +#endif /* CONFIG_UBSAN_TRAP */ + +#endif /* _ASM_X86_UBSAN_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 74077694da7d..fe1d9db27500 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_UNWINDER_GUESS)+= unwind_guess.o obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o obj-$(CONFIG_CFI_CLANG)+= cfi.o +obj-$(CONFIG_UBSAN_TRAP) += ubsan.o obj-$(CONFIG_CALL_THUNKS) += callthunks.o diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4fa0b17e5043..7876449e97a0 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -67,6 +67,7 @@ #include #include #include +#include #ifdef CONFIG_X86_64 #include @@ -79,6 +80,9 @@ DECLARE_BITMAP(system_vectors, NR_VECTORS); +/* + * Check for UD1, UD2, with or without REX instructions. + */ __always_inline int is_valid_bugaddr(unsigned long addr) { if (addr < TASK_SIZE_MAX) @@ -88,7 +92,13 @@ __always_inline int is_valid_bugaddr(unsigned long addr) * We got #UD, if the text isn't readable we'd have gotten * a different exception. */ - return *(unsigned short *)addr == INSN_UD2; + if (*(u16 *)addr == INSN_UD2) + return INSN_UD2; + if (*(u16 *)addr == INSN_UD1) + return INSN_UD1; + if (*(u8 *)addr == INSN_REX && *(u16 *)(addr + 1) == INSN_UD1) + return INSN_REX; + return 0; } static nokprobe_inline int @@ -216,6 +226,7 @@ static inline void handle_invalid_op(struct pt_regs *regs) static noinstr bool handle_bug(struct pt_regs *regs) { bool handled = false; + int insn; /* * Normally @regs are unpoisoned by irqentry_enter(), but handle_bug() @@ -223,7 +234,8 @@ static noinstr bool handle_bug(struct pt_regs *regs) * irqentry_enter(). */ kmsan_unpoison_entry_regs(regs); - if (!is_valid_bugaddr(regs->ip)) + insn = is_valid_bugaddr(regs->ip); + if (insn == 0) return handled; /* @@ -236,10 +248,20 @@ static noinstr bool handle_bug(struct pt_regs *regs) */ if (regs->flags & X86_EFLAGS_IF) raw_local_i
Re: [PATCH] x86/traps: Enable UBSAN traps on x86
On Wed, May 29, 2024 at 09:25:21AM UTC, Marco Elver wrote: > On Wed, 29 May 2024 at 04:20, Gatlin Newhouse > wrote: > [...] > > if (regs->flags & X86_EFLAGS_IF) > > raw_local_irq_enable(); > > - if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || > > - handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) { > > - regs->ip += LEN_UD2; > > - handled = true; > > + > > + if (insn == INSN_UD2) { > > + if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || > > + handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) { > > + regs->ip += LEN_UD2; > > + handled = true; > > + } > > + } else { > > + if (handle_ubsan_failure(regs, insn) == BUG_TRAP_TYPE_WARN) > > { > > handle_ubsan_failure currently only returns BUG_TRAP_TYPE_NONE? > > > + if (insn == INSN_REX) > > + regs->ip += LEN_REX; > > + regs->ip += LEN_UD1; > > + handled = true; > > + } > > } > > if (regs->flags & X86_EFLAGS_IF) > > raw_local_irq_disable(); > > diff --git a/arch/x86/kernel/ubsan.c b/arch/x86/kernel/ubsan.c > > new file mode 100644 > > index ..6cae11f4fe23 > > --- /dev/null > > +++ b/arch/x86/kernel/ubsan.c > > @@ -0,0 +1,32 @@ > > +// SPDX-License-Identifier: GPL-2.0 > > +/* > > + * Clang Undefined Behavior Sanitizer trap mode support. > > + */ > > +#include > > +#include > > +#include > > +#include > > +#include > > +#include > > + > > +/* > > + * Checks for the information embedded in the UD1 trap instruction > > + * for the UB Sanitizer in order to pass along debugging output. > > + */ > > +enum bug_trap_type handle_ubsan_failure(struct pt_regs *regs, int insn) > > +{ > > + u32 type = 0; > > + > > + if (insn == INSN_REX) { > > + type = (*(u16 *)(regs->ip + LEN_REX + LEN_UD1)); > > + if ((type & 0xFF) == 0x40) > > + type = (type >> 8) & 0xFF; > > + } else { > > + type = (*(u16 *)(regs->ip + LEN_UD1)); > > + if ((type & 0xFF) == 0x40) > > + type = (type >> 8) & 0xFF; > > + } > > + pr_crit("%s at %pS\n", report_ubsan_failure(regs, type), (void > > *)regs->ip); > > + > > + return BUG_TRAP_TYPE_NONE; > > +} > > Shouldn't this return BUG_TRAP_TYPE_WARN? So as far as I understand, UBSAN trap mode never warns. Perhaps it does on arm64, although it calls die() so I am unsure. Maybe the condition in handle_bug() should be rewritten in the case of UBSAN ud1s? Do you have any suggestions?
Re: [PATCH] x86/traps: Enable UBSAN traps on x86
On Wed, May 29, 2024 at 08:30:20PM UTC, Marco Elver wrote: > On Wed, 29 May 2024 at 20:17, Gatlin Newhouse > wrote: > > > > On Wed, May 29, 2024 at 09:25:21AM UTC, Marco Elver wrote: > > > On Wed, 29 May 2024 at 04:20, Gatlin Newhouse > > > wrote: > > > [...] > > > > if (regs->flags & X86_EFLAGS_IF) > > > > raw_local_irq_enable(); > > > > - if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || > > > > - handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) { > > > > - regs->ip += LEN_UD2; > > > > - handled = true; > > > > + > > > > + if (insn == INSN_UD2) { > > > > + if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || > > > > + handle_cfi_failure(regs) == BUG_TRAP_TYPE_WARN) { > > > > + regs->ip += LEN_UD2; > > > > + handled = true; > > > > + } > > > > + } else { > > > > + if (handle_ubsan_failure(regs, insn) == > > > > BUG_TRAP_TYPE_WARN) { > > > > > > handle_ubsan_failure currently only returns BUG_TRAP_TYPE_NONE? > > > > > > > + if (insn == INSN_REX) > > > > + regs->ip += LEN_REX; > > > > + regs->ip += LEN_UD1; > > > > + handled = true; > > > > + } > > > > } > > > > if (regs->flags & X86_EFLAGS_IF) > > > > raw_local_irq_disable(); > > > > diff --git a/arch/x86/kernel/ubsan.c b/arch/x86/kernel/ubsan.c > > > > new file mode 100644 > > > > index ..6cae11f4fe23 > > > > --- /dev/null > > > > +++ b/arch/x86/kernel/ubsan.c > > > > @@ -0,0 +1,32 @@ > > > > +// SPDX-License-Identifier: GPL-2.0 > > > > +/* > > > > + * Clang Undefined Behavior Sanitizer trap mode support. > > > > + */ > > > > +#include > > > > +#include > > > > +#include > > > > +#include > > > > +#include > > > > +#include > > > > + > > > > +/* > > > > + * Checks for the information embedded in the UD1 trap instruction > > > > + * for the UB Sanitizer in order to pass along debugging output. > > > > + */ > > > > +enum bug_trap_type handle_ubsan_failure(struct pt_regs *regs, int insn) > > > > +{ > > > > + u32 type = 0; > > > > + > > > > + if (insn == INSN_REX) { > > > > + type = (*(u16 *)(regs->ip + LEN_REX + LEN_UD1)); > > > > + if ((type & 0xFF) == 0x40) > > > > + type = (type >> 8) & 0xFF; > > > > + } else { > > > > + type = (*(u16 *)(regs->ip + LEN_UD1)); > > > > + if ((type & 0xFF) == 0x40) > > > > + type = (type >> 8) & 0xFF; > > > > + } > > > > + pr_crit("%s at %pS\n", report_ubsan_failure(regs, type), (void > > > > *)regs->ip); > > > > + > > > > + return BUG_TRAP_TYPE_NONE; > > > > +} > > > > > > Shouldn't this return BUG_TRAP_TYPE_WARN? > > > > So as far as I understand, UBSAN trap mode never warns. Perhaps it does on > > arm64, although it calls die() so I am unsure. Maybe the condition in > > handle_bug() should be rewritten in the case of UBSAN ud1s? Do you have any > > suggestions? > > AFAIK on arm64 it's basically a kernel OOPS. > > The main thing I just wanted to point out though is that your newly added > branch > > > if (handle_ubsan_failure(regs, insn) == BUG_TRAP_TYPE_WARN) { > > will never be taken, because I don't see where handle_ubsan_failure() > returns BUG_TRAP_TYPE_WARN. > Initially I wrote this with some symmetry to the KCFI checks nearby, but I was unsure if this would be considered handled or not. > > That means 'handled' will be false, and the code in exc_invalid_op > will proceed to call handle_invalid_op() which is probably not what > you intended - i.e. it's definitely not BUG_TRAP_TYPE_NONE, but one of > TYPE_WARN of TYPE_BUG. > This remains a question to me as to whether it should be considered handled or not. Which is why I'm happy to change this branch which is never taken to something else that still outputs the UBSAN type information before calling handle_invalid_op(). > > Did you test it and you got the behaviour you expected? > Testing with LKDTM provided the output I expected. The UBSAN type information along with file and offsets are output before an illegal op and trace.
Re: [PATCH] x86/traps: Enable UBSAN traps on x86
On Thu, May 30, 2024 at 01:24:56AM UTC, Andrew Cooper wrote: > On 29/05/2024 3:20 am, Gatlin Newhouse wrote: > > diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h > > index a3ec87d198ac..e3fbed9073f8 100644 > > --- a/arch/x86/include/asm/bug.h > > +++ b/arch/x86/include/asm/bug.h > > @@ -13,6 +13,14 @@ > > #define INSN_UD2 0x0b0f > > #define LEN_UD22 > > > > +/* > > + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. > > + */ > > +#define INSN_UD1 0xb90f > > +#define LEN_UD12 > > +#define INSN_REX 0x67 > > +#define LEN_REX1 > > That's an address size override prefix, not a REX prefix. Good to know, thanks. > What information is actually encoded in this UD1 instruction? I can't > find anything any documentation which actually discusses how the ModRM > byte is encoded. lib/ubsan.h has a comment before the ubsan_checks enum which links to line 113 in LLVM's clang/lib/CodeGen/CodeGenFunction.h which defines the values for the ModRM byte. I think the Undefined Behavior Sanitizer pass does the actual encoding of UB type to values but I'm not an expert in LLVM. > ~Andrew
[PATCH v2] x86/traps: Enable UBSAN traps on x86
Bring x86 to parity with arm64, similar to commit 25b84002afb9 ("arm64: Support Clang UBSAN trap codes for better reporting"). Enable the output of UBSAN type information on x86 architectures compiled with clang when CONFIG_UBSAN_TRAP=y. Currently ARM architectures output which specific sanitizer caused the trap, via the encoded data in the trap instruction. Clang on x86 currently encodes the same data in ud1 instructions but the x86 handle_bug() and is_valid_bugaddr() functions currently only look at ud2s. Signed-off-by: Gatlin Newhouse --- Changes in v2: - Name the new constants 'LEN_ASOP' and 'INSN_ASOP' instead of 'LEN_REX' and 'INSN_REX' - Change handle_ubsan_failure() from enum bug_trap_type to void function v1: https://lore.kernel.org/linux-hardening/20240529022043.3661757-1-gatlin.newho...@gmail.com/ --- MAINTAINERS | 2 ++ arch/x86/include/asm/bug.h | 8 arch/x86/include/asm/ubsan.h | 18 ++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/traps.c | 29 +++-- arch/x86/kernel/ubsan.c | 30 ++ 6 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 arch/x86/include/asm/ubsan.h create mode 100644 arch/x86/kernel/ubsan.c diff --git a/MAINTAINERS b/MAINTAINERS index 28e20975c26f..b8512887ffb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22635,6 +22635,8 @@ L: kasan-...@googlegroups.com L: linux-hardening@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening +F: arch/x86/include/asm/ubsan.h +F: arch/x86/kernel/ubsan.c F: Documentation/dev-tools/ubsan.rst F: include/linux/ubsan.h F: lib/Kconfig.ubsan diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index a3ec87d198ac..1023c149f93d 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -13,6 +13,14 @@ #define INSN_UD2 0x0b0f #define LEN_UD22 +/* + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. + */ +#define INSN_UD1 0xb90f +#define LEN_UD12 +#define INSN_ASOP 0x67 +#define LEN_ASOP 1 + #ifdef CONFIG_GENERIC_BUG #ifdef CONFIG_X86_32 diff --git a/arch/x86/include/asm/ubsan.h b/arch/x86/include/asm/ubsan.h new file mode 100644 index ..896ad7bf587f --- /dev/null +++ b/arch/x86/include/asm/ubsan.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UBSAN_H +#define _ASM_X86_UBSAN_H + +/* + * Clang Undefined Behavior Sanitizer trap mode support. + */ +#include +#include +#include + +#ifdef CONFIG_UBSAN_TRAP +void handle_ubsan_failure(struct pt_regs *regs, int insn); +#else +static inline void handle_ubsan_failure(struct pt_regs *regs, int insn) { return; } +#endif /* CONFIG_UBSAN_TRAP */ + +#endif /* _ASM_X86_UBSAN_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 74077694da7d..fe1d9db27500 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_UNWINDER_GUESS)+= unwind_guess.o obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o obj-$(CONFIG_CFI_CLANG)+= cfi.o +obj-$(CONFIG_UBSAN_TRAP) += ubsan.o obj-$(CONFIG_CALL_THUNKS) += callthunks.o diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4fa0b17e5043..ee77c868090a 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -67,6 +67,7 @@ #include #include #include +#include #ifdef CONFIG_X86_64 #include @@ -79,6 +80,9 @@ DECLARE_BITMAP(system_vectors, NR_VECTORS); +/* + * Check for UD1, UD2, with or without Address Size Override Prefixes instructions. + */ __always_inline int is_valid_bugaddr(unsigned long addr) { if (addr < TASK_SIZE_MAX) @@ -88,7 +92,13 @@ __always_inline int is_valid_bugaddr(unsigned long addr) * We got #UD, if the text isn't readable we'd have gotten * a different exception. */ - return *(unsigned short *)addr == INSN_UD2; + if (*(u16 *)addr == INSN_UD2) + return INSN_UD2; + if (*(u16 *)addr == INSN_UD1) + return INSN_UD1; + if (*(u8 *)addr == INSN_ASOP && *(u16 *)(addr + 1) == INSN_UD1) + return INSN_ASOP; + return 0; } static nokprobe_inline int @@ -216,6 +226,7 @@ static inline void handle_invalid_op(struct pt_regs *regs) static noinstr bool handle_bug(struct pt_regs *regs) { bool handled = false; + int insn; /* * Normally @regs are unpoisoned by irqentry_enter(), but handle_bug() @@ -223,7 +234,8 @@ static noinstr bool handle_bug(struct pt_regs *regs) * irqentry_enter(). */ kmsan_unpoison_entry_regs(regs); - if (!is_valid_bugaddr(regs
Re: [PATCH v2] x86/traps: Enable UBSAN traps on x86
On Mon, Jun 03, 2024 at 06:13:53PM UTC, Thomas Gleixner wrote: > On Sat, Jun 01 2024 at 03:10, Gatlin Newhouse wrote: > > > Bring x86 to parity with arm64, similar to commit 25b84002afb9 > > ("arm64: Support Clang UBSAN trap codes for better reporting"). > > Enable the output of UBSAN type information on x86 architectures > > compiled with clang when CONFIG_UBSAN_TRAP=y. Currently ARM > > architectures output which specific sanitizer caused the trap, > > via the encoded data in the trap instruction. Clang on x86 > > currently encodes the same data in ud1 instructions but the x86 > > handle_bug() and is_valid_bugaddr() functions currently only look > > at ud2s. > > Please structure your change log properly instead of one paragraph of > unstructured word salad. See: > > https://www.kernel.org/doc/html/latest/process/maintainer-tip.html#changelog > > > +/* > > + * Check for UD1, UD2, with or without Address Size Override Prefixes > > instructions. > > + */ > > __always_inline int is_valid_bugaddr(unsigned long addr) > > { > > if (addr < TASK_SIZE_MAX) > > @@ -88,7 +92,13 @@ __always_inline int is_valid_bugaddr(unsigned long addr) > > * We got #UD, if the text isn't readable we'd have gotten > > * a different exception. > > */ > > - return *(unsigned short *)addr == INSN_UD2; > > + if (*(u16 *)addr == INSN_UD2) > > + return INSN_UD2; > > + if (*(u16 *)addr == INSN_UD1) > > + return INSN_UD1; > > + if (*(u8 *)addr == INSN_ASOP && *(u16 *)(addr + 1) == INSN_UD1) > > s/1/LEN_ASOP/ ? > > > + return INSN_ASOP; > > + return 0; > > I'm not really a fan of the reuse of the INSN defines here. Especially > not about INSN_ASOP. Also 0 is just lame. > > Neither does the function name make sense anymore. is_valid_bugaddr() is > clearly telling that it's a boolean check (despite the return value > being int for hysterical raisins). But now you turn it into a > non-boolean integer which returns a instruction encoding. That's > hideous. Programming should result in obvious code and that should be > pretty obvious to people who create tools to validate code. > > Also all UBSAN cares about is the actual failure type and not the > instruction itself: > > #define INSN_UD_MASK 0x > #define INSN_ASOP_MASK0x00FF > > #define BUG_UD_NONE 0x > #define BUG_UD2 0xFFFE > > __always_inline u16 get_ud_type(unsigned long addr) > { > u16 insn; > > if (addr < TASK_SIZE_MAX) > return BUD_UD_NONE; > > insn = *(u16 *)addr; > if ((insn & INSN_UD_MASK) == INSN_UD2) > return BUG_UD2; > > if ((insn & INSN_ASOP_MASK) == INSN_ASOP) > insn = *(u16 *)(++addr); > > // UBSAN encodes the failure type in the two bytes after UD1 > if ((insn & INSN_UD_MASK) == INSN_UD1) > return *(u16 *)(addr + LEN_UD1); > > return BUG_UD_NONE; > } > > No? Thanks for the feedback. It seems that is_valid_bugaddr() needs to be implemented on all architectures and the function get_ud_type() replaces it here. So how should the patch handle is_valid_bugaddr()? Should the function remain as-is in traps.c despite no longer being used? > > > static nokprobe_inline int > > @@ -216,6 +226,7 @@ static inline void handle_invalid_op(struct pt_regs > > *regs) > > static noinstr bool handle_bug(struct pt_regs *regs) > > { > > bool handled = false; > > + int insn; > > > > /* > > * Normally @regs are unpoisoned by irqentry_enter(), but handle_bug() > > @@ -223,7 +234,8 @@ static noinstr bool handle_bug(struct pt_regs *regs) > > * irqentry_enter(). > > */ > > kmsan_unpoison_entry_regs(regs); > > - if (!is_valid_bugaddr(regs->ip)) > > + insn = is_valid_bugaddr(regs->ip); > > + if (insn == 0) > > Sigh. > > But with the above sanitized (pun intended) this becomes obvious by > itself: > > ud_type = get_ud_type(regs->ip); > if (ud_type == BUG_UD_NONE) > return false; > > See? > > > return handled; > > > > /* > > @@ -236,10 +248,15 @@ static noinstr bool handle_bug(struct pt_regs *regs) > > */ > > if (regs->flags & X86_EFLAGS_IF) > > raw_local_irq_enable(); > > - if (report_bug(regs->ip, regs) == BUG_TRAP_TYPE_WARN || > > -
[PATCH v3] x86/traps: Enable UBSAN traps on x86
Currently ARM architectures output which specific sanitizer caused the trap, via the encoded data in the trap instruction. Clang on x86 currently encodes the same data in ud1 instructions but the x86 handle_bug() and is_valid_bugaddr() functions currently only look at ud2s. Bring x86 to parity with arm64, similar to commit 25b84002afb9 ("arm64: Support Clang UBSAN trap codes for better reporting"). Enable the output of UBSAN type information on x86 architectures compiled with clang when CONFIG_UBSAN_TRAP=y. Signed-off-by: Gatlin Newhouse --- Changes in v3: - Address Thomas's remarks about: change log structure, get_ud_type() instead of is_valid_bugaddr(), handle_bug() changes, and handle_ubsan_failure(). Changes in v2: - Name the new constants 'LEN_ASOP' and 'INSN_ASOP' instead of 'LEN_REX' and 'INSN_REX' - Change handle_ubsan_failure() from enum bug_trap_type to void function v1: https://lore.kernel.org/linux-hardening/20240529022043.3661757-1-gatlin.newho...@gmail.com/ v2: https://lore.kernel.org/linux-hardening/20240601031019.3708758-1-gatlin.newho...@gmail.com/ --- MAINTAINERS | 2 ++ arch/x86/include/asm/bug.h | 11 ++ arch/x86/include/asm/ubsan.h | 23 + arch/x86/kernel/Makefile | 1 + arch/x86/kernel/traps.c | 40 +++- arch/x86/kernel/ubsan.c | 21 +++ 6 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 arch/x86/include/asm/ubsan.h create mode 100644 arch/x86/kernel/ubsan.c diff --git a/MAINTAINERS b/MAINTAINERS index 28e20975c26f..b8512887ffb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22635,6 +22635,8 @@ L: kasan-...@googlegroups.com L: linux-hardening@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening +F: arch/x86/include/asm/ubsan.h +F: arch/x86/kernel/ubsan.c F: Documentation/dev-tools/ubsan.rst F: include/linux/ubsan.h F: lib/Kconfig.ubsan diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index a3ec87d198ac..a363d13c263b 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -13,6 +13,17 @@ #define INSN_UD2 0x0b0f #define LEN_UD22 +/* + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. + */ +#define INSN_UD1 0xb90f +#define INSN_UD_MASK 0x +#define LEN_UD12 +#define INSN_ASOP 0x67 +#define INSN_ASOP_MASK 0x00FF +#define BUG_UD_NONE0x +#define BUG_UD20xFFFE + #ifdef CONFIG_GENERIC_BUG #ifdef CONFIG_X86_32 diff --git a/arch/x86/include/asm/ubsan.h b/arch/x86/include/asm/ubsan.h new file mode 100644 index ..ac2080984e83 --- /dev/null +++ b/arch/x86/include/asm/ubsan.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UBSAN_H +#define _ASM_X86_UBSAN_H + +/* + * Clang Undefined Behavior Sanitizer trap mode support. + */ +#include +#include +#include + +/* + * UBSAN uses the EAX register to encode its type in the ModRM byte. + */ +#define UBSAN_REG 0x40 + +#ifdef CONFIG_UBSAN_TRAP +void handle_ubsan_failure(struct pt_regs *regs, u16 insn); +#else +static inline void handle_ubsan_failure(struct pt_regs *regs, u16 insn) { return; } +#endif /* CONFIG_UBSAN_TRAP */ + +#endif /* _ASM_X86_UBSAN_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 74077694da7d..fe1d9db27500 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_UNWINDER_GUESS)+= unwind_guess.o obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o obj-$(CONFIG_CFI_CLANG)+= cfi.o +obj-$(CONFIG_UBSAN_TRAP) += ubsan.o obj-$(CONFIG_CALL_THUNKS) += callthunks.o diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4fa0b17e5043..aef21287e7ed 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -67,6 +67,7 @@ #include #include #include +#include #ifdef CONFIG_X86_64 #include @@ -91,6 +92,29 @@ __always_inline int is_valid_bugaddr(unsigned long addr) return *(unsigned short *)addr == INSN_UD2; } +/* + * Check for UD1, UD2, with or without Address Size Override Prefixes instructions. + */ +__always_inline u16 get_ud_type(unsigned long addr) +{ + u16 insn; + + if (addr < TASK_SIZE_MAX) + return BUG_UD_NONE; + insn = *(u16 *)addr; + if ((insn & INSN_UD_MASK) == INSN_UD2) + return BUG_UD2; + if ((insn & INSN_ASOP_MASK) == INSN_ASOP) + insn = *(u16 *)(++addr); + + // UBSAN encode the failure type in the two bytes after UD1 + if ((insn & INSN_UD_MASK) == INSN_UD1) + return *(u16 *)(addr + LEN_UD1); + + return BUG_UD_NONE; +} + +
[PATCH v4] x86/traps: Enable UBSAN traps on x86
Currently ARM architectures extract which specific sanitizer has caused a trap via encoded data in the trap instruction. Clang on x86 currently encodes the same data in ud1 instructions but the x86 handle_bug() and is_valid_bugaddr() functions currently only look at ud2s. Bring x86 to parity with arm64, similar to commit 25b84002afb9 ("arm64: Support Clang UBSAN trap codes for better reporting"). Enable the reporting of UBSAN sanitizer detail on x86 architectures compiled with clang when CONFIG_UBSAN_TRAP=y. Signed-off-by: Gatlin Newhouse --- Changes in v4: - Implement Peter's suggestions for decode_bug(), and fix inconsistent capitalization in hex values. Changes in v3: - Address Thomas's remarks about: change log structure, get_ud_type() instead of is_valid_bugaddr(), handle_bug() changes, and handle_ubsan_failure(). Changes in v2: - Name the new constants 'LEN_ASOP' and 'INSN_ASOP' instead of 'LEN_REX' and 'INSN_REX' - Change handle_ubsan_failure() from enum bug_trap_type to void function v1: https://lore.kernel.org/linux-hardening/20240529022043.3661757-1-gatlin.newho...@gmail.com/ v2: https://lore.kernel.org/linux-hardening/20240601031019.3708758-1-gatlin.newho...@gmail.com/ v3: https://lore.kernel.org/linux-hardening/20240625032509.4155839-1-gatlin.newho...@gmail.com/ --- MAINTAINERS | 2 ++ arch/x86/include/asm/bug.h | 11 +++ arch/x86/include/asm/ubsan.h | 23 +++ arch/x86/kernel/Makefile | 1 + arch/x86/kernel/traps.c | 57 arch/x86/kernel/ubsan.c | 21 + 6 files changed, 110 insertions(+), 5 deletions(-) create mode 100644 arch/x86/include/asm/ubsan.h create mode 100644 arch/x86/kernel/ubsan.c diff --git a/MAINTAINERS b/MAINTAINERS index 28e20975c26f..b8512887ffb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22635,6 +22635,8 @@ L: kasan-...@googlegroups.com L: linux-hardening@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening +F: arch/x86/include/asm/ubsan.h +F: arch/x86/kernel/ubsan.c F: Documentation/dev-tools/ubsan.rst F: include/linux/ubsan.h F: lib/Kconfig.ubsan diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index a3ec87d198ac..ccd573d58edb 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -13,6 +13,17 @@ #define INSN_UD2 0x0b0f #define LEN_UD22 +/* + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. + */ +#define INSN_ASOP 0x67 +#define OPCODE_PREFIX 0x0f +#define OPCODE_UD1 0xb9 +#define OPCODE_UD2 0x0b +#define BUG_NONE 0x +#define BUG_UD10xfffe +#define BUG_UD20xfffd + #ifdef CONFIG_GENERIC_BUG #ifdef CONFIG_X86_32 diff --git a/arch/x86/include/asm/ubsan.h b/arch/x86/include/asm/ubsan.h new file mode 100644 index ..ac2080984e83 --- /dev/null +++ b/arch/x86/include/asm/ubsan.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UBSAN_H +#define _ASM_X86_UBSAN_H + +/* + * Clang Undefined Behavior Sanitizer trap mode support. + */ +#include +#include +#include + +/* + * UBSAN uses the EAX register to encode its type in the ModRM byte. + */ +#define UBSAN_REG 0x40 + +#ifdef CONFIG_UBSAN_TRAP +void handle_ubsan_failure(struct pt_regs *regs, u16 insn); +#else +static inline void handle_ubsan_failure(struct pt_regs *regs, u16 insn) { return; } +#endif /* CONFIG_UBSAN_TRAP */ + +#endif /* _ASM_X86_UBSAN_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 74077694da7d..fe1d9db27500 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_UNWINDER_GUESS)+= unwind_guess.o obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o obj-$(CONFIG_CFI_CLANG)+= cfi.o +obj-$(CONFIG_UBSAN_TRAP) += ubsan.o obj-$(CONFIG_CALL_THUNKS) += callthunks.o diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4fa0b17e5043..b6664016622a 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -67,6 +67,7 @@ #include #include #include +#include #ifdef CONFIG_X86_64 #include @@ -91,6 +92,45 @@ __always_inline int is_valid_bugaddr(unsigned long addr) return *(unsigned short *)addr == INSN_UD2; } +/* + * Check for UD1 or UD2, accounting for Address Size Override Prefixes. + * If it's a UD1, get the ModRM byte to pass along to UBSan. + */ +__always_inline int decode_bug(unsigned long addr, u32 *imm) +{ + u8 v; + + if (addr < TASK_SIZE_MAX) + return BUG_NONE; + + v = *(u8 *)(addr++); + if (v == INSN_ASOP) + v = *(u8 *)(addr++); + if (v != OPCODE_PREFIX) +
[PATCH v5] x86/traps: Enable UBSAN traps on x86
Currently ARM architectures extract which specific sanitizer has caused a trap via encoded data in the trap instruction.[1] Clang on x86 currently encodes the same data in ud1 instructions but the x86 handle_bug() and is_valid_bugaddr() functions currently only look at ud2s. Bring x86 to parity with arm64, similar to commit 25b84002afb9 ("arm64: Support Clang UBSAN trap codes for better reporting"). Enable the reporting of UBSAN sanitizer detail on x86 architectures compiled with clang when CONFIG_UBSAN_TRAP=y. [1] Details are in llvm/lib/Target/X86/X86MCInstLower.cpp. See: https://github.com/llvm/llvm-project/commit/c5978f42ec8e9#diff-bb68d7cd885f41cfc35843998b0f9f534adb60b415f647109e597ce448e92d9f EmitAndCountInstruction() uses the UD1Lm template, which uses a OpSize32. See: https://github.com/llvm/llvm-project/blob/main/llvm/lib/Target/X86/X86InstrSystem.td#L27 Signed-off-by: Gatlin Newhouse --- Changes in v5: - Added references to the LLVM commits in the commit message from Kees and Marco's feedback - Renamed incorrect defines, and removed handle_ubsan_failure()'s duplicated work per Peter's feedback Changes in v4: - Implement Peter's suggestions for decode_bug(), and fix inconsistent capitalization in hex values. Changes in v3: - Address Thomas's remarks about: change log structure, get_ud_type() instead of is_valid_bugaddr(), handle_bug() changes, and handle_ubsan_failure(). Changes in v2: - Name the new constants 'LEN_ASOP' and 'INSN_ASOP' instead of 'LEN_REX' and 'INSN_REX' - Change handle_ubsan_failure() from enum bug_trap_type to void function v1: https://lore.kernel.org/linux-hardening/20240529022043.3661757-1-gatlin.newho...@gmail.com/ v2: https://lore.kernel.org/linux-hardening/20240601031019.3708758-1-gatlin.newho...@gmail.com/ v3: https://lore.kernel.org/linux-hardening/20240625032509.4155839-1-gatlin.newho...@gmail.com/ v4: https://lore.kernel.org/linux-hardening/20240710203250.238782-1-gatlin.newho...@gmail.com/ --- MAINTAINERS | 2 ++ arch/x86/include/asm/bug.h | 12 arch/x86/include/asm/ubsan.h | 18 arch/x86/kernel/Makefile | 1 + arch/x86/kernel/traps.c | 57 arch/x86/kernel/ubsan.c | 19 6 files changed, 104 insertions(+), 5 deletions(-) create mode 100644 arch/x86/include/asm/ubsan.h create mode 100644 arch/x86/kernel/ubsan.c diff --git a/MAINTAINERS b/MAINTAINERS index 28e20975c26f..b8512887ffb1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -22635,6 +22635,8 @@ L: kasan-...@googlegroups.com L: linux-hardening@vger.kernel.org S: Supported T: git git://git.kernel.org/pub/scm/linux/kernel/git/kees/linux.git for-next/hardening +F: arch/x86/include/asm/ubsan.h +F: arch/x86/kernel/ubsan.c F: Documentation/dev-tools/ubsan.rst F: include/linux/ubsan.h F: lib/Kconfig.ubsan diff --git a/arch/x86/include/asm/bug.h b/arch/x86/include/asm/bug.h index a3ec87d198ac..751e45ea27ca 100644 --- a/arch/x86/include/asm/bug.h +++ b/arch/x86/include/asm/bug.h @@ -13,6 +13,18 @@ #define INSN_UD2 0x0b0f #define LEN_UD22 +/* + * In clang we have UD1s reporting UBSAN failures on X86, 64 and 32bit. + */ +#define INSN_ASOP 0x67 +#define OPCODE_ESCAPE 0x0f +#define SECOND_BYTE_OPCODE_UD1 0xb9 +#define SECOND_BYTE_OPCODE_UD2 0x0b + +#define BUG_NONE 0x +#define BUG_UD10xfffe +#define BUG_UD20xfffd + #ifdef CONFIG_GENERIC_BUG #ifdef CONFIG_X86_32 diff --git a/arch/x86/include/asm/ubsan.h b/arch/x86/include/asm/ubsan.h new file mode 100644 index ..1d7c2b4129de --- /dev/null +++ b/arch/x86/include/asm/ubsan.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _ASM_X86_UBSAN_H +#define _ASM_X86_UBSAN_H + +/* + * Clang Undefined Behavior Sanitizer trap mode support. + */ +#include +#include +#include + +#ifdef CONFIG_UBSAN_TRAP +void handle_ubsan_failure(struct pt_regs *regs, u32 type); +#else +static inline void handle_ubsan_failure(struct pt_regs *regs, u32 type) { return; } +#endif /* CONFIG_UBSAN_TRAP */ + +#endif /* _ASM_X86_UBSAN_H */ diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile index 74077694da7d..fe1d9db27500 100644 --- a/arch/x86/kernel/Makefile +++ b/arch/x86/kernel/Makefile @@ -145,6 +145,7 @@ obj-$(CONFIG_UNWINDER_GUESS)+= unwind_guess.o obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o obj-$(CONFIG_CFI_CLANG)+= cfi.o +obj-$(CONFIG_UBSAN_TRAP) += ubsan.o obj-$(CONFIG_CALL_THUNKS) += callthunks.o diff --git a/arch/x86/kernel/traps.c b/arch/x86/kernel/traps.c index 4fa0b17e5043..6350d00a6555 100644 --- a/arch/x86/kernel/traps.c +++ b/arch/x86/kernel/traps.c @@ -67,6 +67,7 @@ #include #include #include +#include
[RFC v1 00/17] Add Safefetch double-fetch protection
SafeFetch is a patch that provides a caching mechanism to user syscalls to prevent time-of-check to time-of-use bugs in the kernel. SafeFetch was originally created by Victor Duta, Mitchel Josephus Aloserij, and Cristiano Giuffrida. Their original research publication and appendix can be found here [1]. Their patch for v5.11 can be found here [2]. The testing scripts can be found here [3]. This patchset is not currently intended for production but rather for use in testing. I have been forward porting this patchset for about one year now, from v5.11 to v6.16-rc6. I branched and merged in my own fork the kernel to keep track of my progress [4]. I have been able to test the patchset with the same CVE used by the original paper authors for each new version I ported the patchset to confirm its functionality. I have not tested the performance of the patchset in different versions in order to verify the original claims of minimal overheads. I have done limited testing with different compiler versions to confirm it can compile across clang and gcc versions. I have not tested recommended kernel configuration variations. I would love some help testing this patchset more rigorously in addition to cleaning up any checkpatch warnings or errors that I was unsure of. Specifically I was hesitant to try to fix macro warnings as some seemed wrong to me in my limited knowledge. I also need help fixing up or formatting the dmesg output to be more consistent with other messages. [1] https://www.usenix.org/conference/usenixsecurity24/presentation/duta [2] https://github.com/vusec/safefetch [3] https://github.com/vusec/safefetch-ae [4] https://github.com/gatlinnewhouse/linux Gatlin Newhouse (17): Add SafeFetch double-fetch protection to the kernel x86: syscall: support caching in do_syscall_64() x86: asm: support caching in do_get_user_call() sched: add protection to task_struct uaccess: add non-caching copy_from_user functions futex: add get_user_no_dfcache() functions gup: add non-caching get_user call to fault_in_readable() init: add caching startup and initialization to start_kernel() exit: add destruction of SafeFetch caches and debug info to do_exit() iov_iter: add SafeFetch pinning call to copy_from_user_iter() kernel: add SafeFetch cache handling to dup_task_struct() bug: add SafeFetch statistics tracking to __report_bug() calls softirq: add SafeFetch statistics to irq_enter_rc() and irq_exit() makefile: add SafeFetch support to makefiles kconfig: debug: add SafeFetch to debug kconfig x86: enable SafeFetch on x86_64 builds vfs: ioctl: add logging to ioctl_file_dedupe_range() for testing Makefile |3 +- arch/x86/Kconfig |5 +- arch/x86/entry/syscall_64.c | 76 + arch/x86/include/asm/uaccess.h| 211 ++- arch/x86/include/asm/uaccess_64.h | 54 + fs/ioctl.c|6 + include/linux/dfcache_measuring.h | 72 + include/linux/mem_range.h | 302 include/linux/region_allocator.h | 188 +++ include/linux/safefetch.h | 222 +++ include/linux/safefetch_static_keys.h | 22 + include/linux/sched.h | 11 + include/linux/uaccess.h | 30 + init/Kconfig |2 +- init/init_task.c | 11 + init/main.c |7 + kernel/exit.c | 16 + kernel/fork.c | 17 + kernel/futex/core.c |5 + kernel/futex/futex.h |4 + kernel/futex/pi.c |5 + kernel/futex/requeue.c|5 +- kernel/futex/waitwake.c |4 + kernel/softirq.c |8 + lib/Kconfig.debug |1 + lib/Kconfig.safefetch | 36 + lib/bug.c | 10 + lib/iov_iter.c| 12 + mm/Makefile |1 + mm/gup.c |4 + mm/safefetch/Makefile | 11 + mm/safefetch/mem_range.c | 1882 + mm/safefetch/page_cache.c | 129 ++ mm/safefetch/page_cache.h | 141 ++ mm/safefetch/region_allocator.c | 584 mm/safefetch/safefetch.c | 487 +++ mm/safefetch/safefetch_debug.c| 110 ++ mm/safefetch/safefetch_debug.h| 86 ++ mm/safefetch/safefetch_static_keys.c | 299 scripts/Makefile.lib |4 + scripts/Makefile.safefetch| 10 + 41 files changed, 5077 insertions(+), 16 deletions(-) create mode 100644 include/linux/dfcache_measuring.h create mode 100644 include/linux/mem_range.h create mode 100644 include/linux/region_allocator.h create mode 100644 include/linux/safefetch.h create mode 100644 include/linux/safefetch_static_keys.h
[RFC v1 08/17] init: add caching startup and initialization to start_kernel()
--- init/main.c | 7 +++ 1 file changed, 7 insertions(+) diff --git a/init/main.c b/init/main.c index 225a58279acd..72e55704ce2f 100644 --- a/init/main.c +++ b/init/main.c @@ -958,6 +958,10 @@ void start_kernel(void) trap_init(); mm_core_init(); poking_init(); +#ifdef CONFIG_SAFEFETCH + #include + df_startup(); +#endif ftrace_init(); /* trace_printk can be enabled here */ @@ -1098,6 +1102,9 @@ void start_kernel(void) arch_post_acpi_subsys_init(); kcsan_init(); +#if defined(SAFEFETCH_DEBUG) || defined(SAFEFETCH_STATIC_KEYS) + df_sysfs_init(); +#endif /* Do the rest non-__init'ed, we're now alive */ rest_init(); -- 2.25.1
[RFC v1 11/17] kernel: add SafeFetch cache handling to dup_task_struct()
--- kernel/fork.c | 17 + 1 file changed, 17 insertions(+) diff --git a/kernel/fork.c b/kernel/fork.c index 1ee8eb11f38b..379dcf5626e9 100644 --- a/kernel/fork.c +++ b/kernel/fork.c @@ -122,6 +122,12 @@ #include +#ifdef CONFIG_SAFEFETCH +#include +#include +#include +#endif + /* * Minimum number of threads to boot the kernel */ @@ -955,6 +961,17 @@ static struct task_struct *dup_task_struct(struct task_struct *orig, int node) tsk->last_mm_cid = -1; tsk->mm_cid_active = 0; tsk->migrate_from_cpu = -1; +#endif + +#ifdef CONFIG_SAFEFETCH + IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_hooks_key) { + df_task_dup(tsk); + } +#ifdef SAFEFETCH_DEBUG + WARN_ON(SAFEFETCH_TASK_MEM_RANGE_INIT_FLAG(tsk)); + WARN_ON(tsk->df_prot_struct_head.df_metadata_allocator.extended); + WARN_ON(tsk->df_prot_struct_head.df_storage_allocator.extended); +#endif #endif return tsk; -- 2.25.1
[RFC v1 10/17] iov_iter: add SafeFetch pinning call to copy_from_user_iter()
--- lib/iov_iter.c | 12 1 file changed, 12 insertions(+) diff --git a/lib/iov_iter.c b/lib/iov_iter.c index f9193f952f49..8997272481c3 100644 --- a/lib/iov_iter.c +++ b/lib/iov_iter.c @@ -41,6 +41,10 @@ size_t copy_to_user_iter_nofault(void __user *iter_to, size_t progress, return res < 0 ? len : res; } +#ifndef PIN_BUDDY_PAGES_WATERMARK +#define PIN_BUDDY_PAGES_WATERMARK PAGE_SIZE +#endif + static __always_inline size_t copy_from_user_iter(void __user *iter_from, size_t progress, size_t len, void *to, void *priv2) @@ -52,7 +56,15 @@ size_t copy_from_user_iter(void __user *iter_from, size_t progress, if (access_ok(iter_from, len)) { to += progress; instrument_copy_from_user_before(to, iter_from, len); +#ifdef SAFEFETCH_PIN_BUDDY_PAGES + /* #warning "Using Page_pinning for copyin calls" */ + if (len >= PIN_BUDDY_PAGES_WATERMARK) + res = raw_copy_from_user_pinning(to, iter_from, len); + else + res = raw_copy_from_user(to, iter_from, len); +#else res = raw_copy_from_user(to, iter_from, len); +#endif instrument_copy_from_user_after(to, iter_from, len, res); } return res; -- 2.25.1
[RFC v1 09/17] exit: add destruction of SafeFetch caches and debug info to do_exit()
--- kernel/exit.c | 16 1 file changed, 16 insertions(+) diff --git a/kernel/exit.c b/kernel/exit.c index bb184a67ac73..c712cd11a2c7 100644 --- a/kernel/exit.c +++ b/kernel/exit.c @@ -951,6 +951,22 @@ void __noreturn do_exit(long code) exit_mm(); +#ifdef CONFIG_SAFEFETCH + #include + #include + #include +//if (!(tsk->flags & PF_KTHREAD)) +//df_task_destroy(tsk); + IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_hooks_key) { + if (!(tsk->flags & PF_KTHREAD)) { + destroy_regions(); +#ifdef SAFEFETCH_DEBUG + df_debug_task_destroy(tsk); +#endif + } + } +#endif + if (group_dead) acct_process(); -- 2.25.1
[RFC v1 12/17] bug: add SafeFetch statistics tracking to __report_bug() calls
--- lib/bug.c | 10 ++ 1 file changed, 10 insertions(+) diff --git a/lib/bug.c b/lib/bug.c index b1f07459c2ee..d1007c1b3dda 100644 --- a/lib/bug.c +++ b/lib/bug.c @@ -155,6 +155,9 @@ static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *re struct bug_entry *bug; const char *file; unsigned line, warning, once, done; +#if defined(SAFEFETCH_DEBUG) + current->df_stats.traced = 1; +#endif if (!is_valid_bugaddr(bugaddr)) return BUG_TRAP_TYPE_NONE; @@ -194,6 +197,9 @@ static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *re /* this is a WARN_ON rather than BUG/BUG_ON */ __warn(file, line, (void *)bugaddr, BUG_GET_TAINT(bug), regs, NULL); +#if defined(SAFEFETCH_DEBUG) + current->df_stats.traced = 0; +#endif return BUG_TRAP_TYPE_WARN; } @@ -203,6 +209,10 @@ static enum bug_trap_type __report_bug(unsigned long bugaddr, struct pt_regs *re pr_crit("Kernel BUG at %pB [verbose debug info unavailable]\n", (void *)bugaddr); +#if defined(SAFEFETCH_DEBUG) + current->df_stats.traced = 0; +#endif + return BUG_TRAP_TYPE_BUG; } -- 2.25.1
[RFC v1 07/17] gup: add non-caching get_user call to fault_in_readable()
Adds non-caching call to fault_in_readable() for configurations with SafeFetch enabled and disabled readable pages. --- mm/gup.c | 4 1 file changed, 4 insertions(+) diff --git a/mm/gup.c b/mm/gup.c index 3c39cbbeebef..69d2d110da3f 100644 --- a/mm/gup.c +++ b/mm/gup.c @@ -2224,7 +2224,11 @@ size_t fault_in_readable(const char __user *uaddr, size_t size) /* Stop once we overflow to 0. */ for (cur = start; cur && cur < end; cur = PAGE_ALIGN_DOWN(cur + PAGE_SIZE)) +#if defined(CONFIG_SAFEFETCH) && !defined(SAFEFETCH_PROTECT_PAGES_READABLE) + unsafe_get_user_no_dfcache(c, (const char __user *)cur, out); +#else unsafe_get_user(c, (const char __user *)cur, out); +#endif out: user_read_access_end(); (void)c; -- 2.25.1
[RFC v1 02/17] x86: syscall: support caching in do_syscall_64()
Include safefetch and support caching strategy in syscalls to protect against time of check to time of use bugs. --- arch/x86/entry/syscall_64.c | 76 + 1 file changed, 76 insertions(+) diff --git a/arch/x86/entry/syscall_64.c b/arch/x86/entry/syscall_64.c index b6e68ea98b83..0d5665e096a6 100644 --- a/arch/x86/entry/syscall_64.c +++ b/arch/x86/entry/syscall_64.c @@ -20,6 +20,30 @@ #undef __SYSCALL_NORETURN #define __SYSCALL_NORETURN __SYSCALL +#ifdef CONFIG_SAFEFETCH +#include +#include +#include +#include +#ifdef SAFEFETCH_WHITELISTING +#warning "Using DFCACHER whitelisting" +static noinline void should_whitelist(unsigned long syscall_nr) +{ + switch (syscall_nr) { + case __NR_futex: + case __NR_execve: + case __NR_writev: + case __NR_pwritev2: + case __NR_pwrite64: + case __NR_write: + current->df_prot_struct_head.is_whitelisted = 1; + return; + } + current->df_prot_struct_head.is_whitelisted = 0; +} +#endif +#endif + /* * The sys_call_table[] is no longer used for system calls, but * kernel/trace/trace_syscalls.c still wants to know the system @@ -87,8 +111,46 @@ static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr) __visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr) { add_random_kstack_offset(); + // If interrupts using current execute prior to the next syscall + // then we will enter the syscall with the mem_range initialized + // we could chose to clean this info (shrink_region) or simply + // trust that the interrupt doesn't fetch something nasty and just + // operate the next syscall on the interrupt state (happens for + // sigaction calls mostly during IPI's that save the signal frame + // prior to executing a sigaction call). Or simply clear state + // on irq end (might slow down irqs so avoid this). +#if defined(CONFIG_SAFEFETCH) + IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_hooks_key) { + if (unlikely(SAFEFETCH_MEM_RANGE_INIT_FLAG)) { + // An IPI probably sent us a signal and the signal + // enabled the defense in interrupt context. Reset + // dfcache interrupt state. +#ifndef SAFEFETCH_DEBUG + // If in debug mode, we actually reset the range in + // df_debug_syscall_entry. + SAFEFETCH_RESET_MEM_RANGE(); +#endif + shrink_region(DF_CUR_STORAGE_REGION_ALLOCATOR); + shrink_region(DF_CUR_METADATA_REGION_ALLOCATOR); + } + } +#endif + +#ifdef SAFEFETCH_MEASURE_DEFENSE + // We only use this for measuring so execute this without the static key + // else we get into nasty scenarios if we miss this initialization step. + df_init_measure_structs(current); +#endif nr = syscall_enter_from_user_mode(regs, nr); +#if defined(CONFIG_SAFEFETCH) && defined(SAFEFETCH_WHITELISTING) + should_whitelist(nr); +#endif +#if defined(CONFIG_SAFEFETCH) && defined(SAFEFETCH_DEBUG) + IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_hooks_key) { + df_debug_syscall_entry(nr, regs); + } +#endif instrumentation_begin(); if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr) && nr != -1) { @@ -99,6 +161,20 @@ __visible noinstr bool do_syscall_64(struct pt_regs *regs, int nr) instrumentation_end(); syscall_exit_to_user_mode(regs); +#ifdef CONFIG_SAFEFETCH + // Note, we might have rseq regions executing in syscall_exit_to_user_mode + // and irqs so delay resetting region after this. + IF_SAFEFETCH_STATIC_BRANCH_UNLIKELY_WRAPPER(safefetch_hooks_key) { +#ifdef SAFEFETCH_DEBUG + df_debug_syscall_exit(); +#endif +#ifdef SAFEFETCH_MEASURE_DEFENSE + df_destroy_measure_structs(); +#endif + reset_regions(); + } +#endif + /* * Check that the register state is valid for using SYSRET to exit * to userspace. Otherwise use the slower but fully capable IRET -- 2.25.1
[RFC v1 05/17] uaccess: add non-caching copy_from_user functions
--- include/linux/uaccess.h | 30 ++ 1 file changed, 30 insertions(+) diff --git a/include/linux/uaccess.h b/include/linux/uaccess.h index 7c06f4795670..d6d80bb9e0fa 100644 --- a/include/linux/uaccess.h +++ b/include/linux/uaccess.h @@ -186,6 +186,26 @@ _inline_copy_from_user(void *to, const void __user *from, unsigned long n) extern __must_check unsigned long _copy_from_user(void *, const void __user *, unsigned long); +#ifdef CONFIG_SAFEFETCH +static inline __must_check unsigned long +_copy_from_user_no_dfcache(void *to, const void __user *from, unsigned long n) +{ + unsigned long res = n; + + might_fault(); + if (!should_fail_usercopy() && likely(access_ok(from, n))) { + instrument_copy_from_user_before(to, from, n); + res = raw_copy_from_user_no_dfcache(to, from, n); + instrument_copy_from_user_after(to, from, n, res); + } + if (unlikely(res)) + memset(to + (n - res), 0, res); + return res; +} +extern __must_check unsigned long +_copy_from_user_no_dfcache(void *, const void __user *, unsigned long); +#endif + static inline __must_check unsigned long _inline_copy_to_user(void __user *to, const void *from, unsigned long n) { @@ -213,6 +233,16 @@ copy_from_user(void *to, const void __user *from, unsigned long n) #endif } +#ifdef CONFIG_SAFEFETCH +static __always_inline unsigned long __must_check +copy_from_user_no_dfcache(void *to, const void __user *from, unsigned long n) +{ + if (likely(check_copy_size(to, n, false))) + n = _copy_from_user_no_dfcache(to, from, n); + return n; +} +#endif + static __always_inline unsigned long __must_check copy_to_user(void __user *to, const void *from, unsigned long n) { -- 2.25.1
[RFC v1 04/17] sched: add protection to task_struct
Adds caching data structure for every task structure and optionally adds a statistics structure to each as well. --- include/linux/sched.h | 11 +++ init/init_task.c | 11 +++ 2 files changed, 22 insertions(+) diff --git a/include/linux/sched.h b/include/linux/sched.h index 4f78a64beb52..f2de0e565696 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -48,6 +48,10 @@ #include #include +#ifdef CONFIG_SAFEFETCH +#include +#endif + /* task_struct member predeclarations (sorted alphabetically): */ struct audit_context; struct bio_list; @@ -1654,6 +1658,13 @@ struct task_struct { struct user_event_mm*user_event_mm; #endif +#ifdef CONFIG_SAFEFETCH + struct df_prot_struct df_prot_struct_head; +#ifdef SAFEFETCH_DEBUG + struct df_stats_struct df_stats; +#endif +#endif + /* CPU-specific state of this task: */ struct thread_structthread; diff --git a/init/init_task.c b/init/init_task.c index e557f622bd90..a378271cf3a2 100644 --- a/init/init_task.c +++ b/init/init_task.c @@ -17,6 +17,10 @@ #include +#ifdef CONFIG_SAFEFETCH +#include +#endif + static struct signal_struct init_signals = { .nr_threads = 1, .thread_head= LIST_HEAD_INIT(init_task.thread_node), @@ -220,6 +224,13 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = { #ifdef CONFIG_SECCOMP_FILTER .seccomp= { .filter_count = ATOMIC_INIT(0) }, #endif +#ifdef CONFIG_SAFEFETCH +#ifndef SAFEFETCH_MEASURE_DEFENSE + .df_prot_struct_head = { .df_mem_range_allocator = { .initialized = 0 }, .df_metadata_allocator = {.first = 0, .initialized = 0, .extended = 0}, .df_storage_allocator = {.first = 0, .initialized = 0, .extended = 0}}, +#else + .df_prot_struct_head = { .df_mem_range_allocator = { .initialized = 0 }, .df_metadata_allocator = {.first = 0, .initialized = 0, .extended = 0}, .df_storage_allocator = {.first = 0, .initialized = 0, .extended = 0}, .df_measures = {.search_time = 0, .insert_time = 0, .counter = 0}}, +#endif +#endif }; EXPORT_SYMBOL(init_task); -- 2.25.1
[RFC v1 15/17] kconfig: debug: add SafeFetch to debug kconfig
--- lib/Kconfig.debug | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index ebe33181b6e6..d4b4214164a5 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -1040,6 +1040,7 @@ config MEM_ALLOC_PROFILING_DEBUG source "lib/Kconfig.kasan" source "lib/Kconfig.kfence" source "lib/Kconfig.kmsan" +source "lib/Kconfig.safefetch" endmenu # "Memory Debugging" -- 2.25.1
[RFC v1 17/17] vfs: ioctl: add logging to ioctl_file_dedupe_range() for testing
This adds a message indicating a double-fetch bug trigger for testing the SafeFetch patchset. It add the message right before the fix for CVE-2016-6516 [1][2] introduced by Scott Bauer [3]. Which can be tested by first compiling the double-fetch program from [4], and running a shell script similar to the one provided by the SafeFetch paper authors in their artifacts repository (see: run_security_artifact.sh) [5]. In summary, you can compile the sample from [4], then clear dmesg, run the sample with `./a.out 7 65534 100 0`. Then remove both files used in the sample /tmp/test.txt and /tmp/test2.txt. Now count the bug warning messages in dmesg before clearing dmesg again. Then enable safefetch with `./safefetch_control.sh -hooks` followed by `./safefetch_control.sh -adaptive 4096 4096 0` or `./safefetch_control.sh -rbtree 4096 4096 0` where safefetch_control.sh can be found in [5]. Now run the compiled sample again and count the bug warning messages in dmesg. This was my method of testing the patchset as I forward ported it from v5.11 after fixing any merge conflicts or compiler errors. [1] https://nvd.nist.gov/vuln/detail/CVE-2016-6516 [2] https://www.openwall.com/lists/oss-security/2016/07/31/6 [3] 10eec60ce79187686e052092e5383c99b4420a20 [4] https://github.com/wpengfei/CVE-2016-6516-exploit/tree/master/Scott%20Bauer [5] https://github.com/vusec/safefetch-ae/ --- fs/ioctl.c | 6 ++ 1 file changed, 6 insertions(+) diff --git a/fs/ioctl.c b/fs/ioctl.c index 69107a245b4c..db8df94d4caa 100644 --- a/fs/ioctl.c +++ b/fs/ioctl.c @@ -439,6 +439,12 @@ static int ioctl_file_dedupe_range(struct file *file, goto out; } + // Add an extra check before the bug fix to check whether a double-fetch occurred + // With SafeFetch enabled this check will never get triggered because we correct + // the second fetch from the cache. + if (same->dest_count != count) + pr_warn("[Bug-Warning] Bug triggered\n"); + same->dest_count = count; ret = vfs_dedupe_file_range(file, same); if (ret) -- 2.25.1
[RFC v1 13/17] softirq: add SafeFetch statistics to irq_enter_rc() and irq_exit()
--- kernel/softirq.c | 8 1 file changed, 8 insertions(+) diff --git a/kernel/softirq.c b/kernel/softirq.c index 513b1945987c..cfed8419b6c5 100644 --- a/kernel/softirq.c +++ b/kernel/softirq.c @@ -632,6 +632,10 @@ void irq_enter_rcu(void) */ void irq_enter(void) { +#ifdef SAFEFETCH_DEBUG + /* #warning IRQ_DEFENSE */ + current->df_stats.in_irq = 1; +#endif ct_irq_enter(); irq_enter_rcu(); } @@ -708,7 +712,11 @@ void irq_exit(void) __irq_exit_rcu(); ct_irq_exit(); /* must be last! */ +#ifdef SAFEFETCH_DEBUG + current->df_stats.in_irq = 0; +#endif lockdep_hardirq_exit(); + } /* -- 2.25.1
[RFC v1 14/17] makefile: add SafeFetch support to makefiles
Also update version string for differentiating testing kernels from host kernels. --- Makefile | 3 ++- mm/Makefile | 1 + scripts/Makefile.lib | 4 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 7eea2a41c905..6a3bf5849270 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ VERSION = 6 PATCHLEVEL = 16 SUBLEVEL = 0 -EXTRAVERSION = -rc5 +EXTRAVERSION = -rc5-safefetch NAME = Baby Opossum Posse # *DOCUMENTATION* @@ -1088,6 +1088,7 @@ include-$(CONFIG_KCOV)+= scripts/Makefile.kcov include-$(CONFIG_RANDSTRUCT) += scripts/Makefile.randstruct include-$(CONFIG_AUTOFDO_CLANG)+= scripts/Makefile.autofdo include-$(CONFIG_PROPELLER_CLANG) += scripts/Makefile.propeller +include-$(CONFIG_SAFEFETCH)+= scripts/Makefile.safefetch include-$(CONFIG_GCC_PLUGINS) += scripts/Makefile.gcc-plugins include $(addprefix $(srctree)/, $(include-y)) diff --git a/mm/Makefile b/mm/Makefile index 1a7a11d4933d..36826aaea1c2 100644 --- a/mm/Makefile +++ b/mm/Makefile @@ -92,6 +92,7 @@ obj-$(CONFIG_PAGE_POISONING) += page_poison.o obj-$(CONFIG_KASAN)+= kasan/ obj-$(CONFIG_KFENCE) += kfence/ obj-$(CONFIG_KMSAN)+= kmsan/ +obj-$(CONFIG_SAFEFETCH) += safefetch/ obj-$(CONFIG_FAILSLAB) += failslab.o obj-$(CONFIG_FAIL_PAGE_ALLOC) += fail_page_alloc.o obj-$(CONFIG_MEMTEST) += memtest.o diff --git a/scripts/Makefile.lib b/scripts/Makefile.lib index 1d581ba5df66..d227eed3c6ed 100644 --- a/scripts/Makefile.lib +++ b/scripts/Makefile.lib @@ -90,6 +90,10 @@ _rust_flags += $(if $(patsubst n%,, \ $(RUSTFLAGS_KCOV)) endif +ifeq ($(CONFIG_SAFEFETCH),y) +_c_flags += $(CFLAGS_SAFEFETCH) +endif + # # Enable KCSAN flags except some files or directories we don't want to check # (depends on variables KCSAN_SANITIZE_obj.o, KCSAN_SANITIZE) -- 2.25.1
[RFC v1 06/17] futex: add get_user_no_dfcache() functions
Add disabled cache get_user function calls to futex functions to disable using the SafeFetch cache on any fast userspace mutexes. --- kernel/futex/core.c | 5 + kernel/futex/futex.h| 4 kernel/futex/pi.c | 5 + kernel/futex/requeue.c | 5 - kernel/futex/waitwake.c | 4 5 files changed, 22 insertions(+), 1 deletion(-) diff --git a/kernel/futex/core.c b/kernel/futex/core.c index 90d53fb0ee9e..0ad5e0dba881 100644 --- a/kernel/futex/core.c +++ b/kernel/futex/core.c @@ -1023,8 +1023,13 @@ static int handle_futex_death(u32 __user *uaddr, struct task_struct *curr, return -1; retry: +#ifdef CONFIG_SAFEFETCH + if (get_user_no_dfcache(uval, uaddr)) + return -1; +#else if (get_user(uval, uaddr)) return -1; +#endif /* * Special case for regular (non PI) futexes. The unlock path in diff --git a/kernel/futex/futex.h b/kernel/futex/futex.h index fcd1617212ee..515338cf4289 100644 --- a/kernel/futex/futex.h +++ b/kernel/futex/futex.h @@ -308,7 +308,11 @@ static __always_inline int futex_get_value(u32 *dest, u32 __user *from) from = masked_user_access_begin(from); else if (!user_read_access_begin(from, sizeof(*from))) return -EFAULT; +#ifdef CONFIG_SAFEFETCH + unsafe_get_user_no_dfcache(val, from, Efault); +#else unsafe_get_user(val, from, Efault); +#endif user_read_access_end(); *dest = val; return 0; diff --git a/kernel/futex/pi.c b/kernel/futex/pi.c index dacb2330f1fb..f9f4ac192338 100644 --- a/kernel/futex/pi.c +++ b/kernel/futex/pi.c @@ -1140,8 +1140,13 @@ int futex_unlock_pi(u32 __user *uaddr, unsigned int flags) return -ENOSYS; retry: +#ifdef CONFIG_SAFEFETCH + if (get_user_no_dfcache(uval, uaddr)) + return -EFAULT; +#else if (get_user(uval, uaddr)) return -EFAULT; +#endif /* * We release only a lock we actually own: */ diff --git a/kernel/futex/requeue.c b/kernel/futex/requeue.c index c716a66f8692..3ebc08a9a8e8 100644 --- a/kernel/futex/requeue.c +++ b/kernel/futex/requeue.c @@ -468,8 +468,11 @@ int futex_requeue(u32 __user *uaddr1, unsigned int flags1, if (unlikely(ret)) { futex_hb_waiters_dec(hb2); double_unlock_hb(hb1, hb2); - +#ifdef CONFIG_SAFEFETCH + ret = get_user_no_dfcache(curval, uaddr1); +#else ret = get_user(curval, uaddr1); +#endif if (ret) return ret; diff --git a/kernel/futex/waitwake.c b/kernel/futex/waitwake.c index e2bbe5509ec2..bf4ed107aff8 100644 --- a/kernel/futex/waitwake.c +++ b/kernel/futex/waitwake.c @@ -629,7 +629,11 @@ int futex_wait_setup(u32 __user *uaddr, u32 val, unsigned int flags, if (ret) { futex_q_unlock(hb); +#ifdef CONFIG_SAFEFETCH + ret = get_user_no_dfcache(uval, uaddr); +#else ret = get_user(uval, uaddr); +#endif if (ret) return ret; -- 2.25.1
[RFC v1 16/17] x86: enable SafeFetch on x86_64 builds
Disable HAVE_ARCH_AUDITSYSCALL and HAVE_ARCH_SOFT_DIRTY. Both options are untested with SafeFetch enabled as of right now. --- arch/x86/Kconfig | 5 +++-- init/Kconfig | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig index 71019b3b54ea..b31a8a2dea71 100644 --- a/arch/x86/Kconfig +++ b/arch/x86/Kconfig @@ -31,7 +31,7 @@ config X86_64 select ARCH_SUPPORTS_INT128 if CC_HAS_INT128 select ARCH_SUPPORTS_PER_VMA_LOCK select ARCH_SUPPORTS_HUGE_PFNMAP if TRANSPARENT_HUGEPAGE - select HAVE_ARCH_SOFT_DIRTY + # select HAVE_ARCH_SOFT_DIRTY select MODULES_USE_ELF_RELA select NEED_DMA_MAP_STATE select SWIOTLB @@ -194,7 +194,7 @@ config X86 select HAVE_ACPI_APEI if ACPI select HAVE_ACPI_APEI_NMI if ACPI select HAVE_ALIGNED_STRUCT_PAGE - select HAVE_ARCH_AUDITSYSCALL + # select HAVE_ARCH_AUDITSYSCALL select HAVE_ARCH_HUGE_VMAP if X86_64 || X86_PAE select HAVE_ARCH_HUGE_VMALLOC if X86_64 select HAVE_ARCH_JUMP_LABEL @@ -203,6 +203,7 @@ config X86 select HAVE_ARCH_KASAN_VMALLOC if X86_64 select HAVE_ARCH_KFENCE select HAVE_ARCH_KMSAN if X86_64 + select HAVE_ARCH_SAFEFETCH if X86_64 select HAVE_ARCH_KGDB select HAVE_ARCH_MMAP_RND_BITS if MMU select HAVE_ARCH_MMAP_RND_COMPAT_BITS if MMU && COMPAT diff --git a/init/Kconfig b/init/Kconfig index 666783eb50ab..5f365fa06fe8 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -494,7 +494,7 @@ config HAVE_ARCH_AUDITSYSCALL bool config AUDITSYSCALL - def_bool y + def_bool n depends on AUDIT && HAVE_ARCH_AUDITSYSCALL select FSNOTIFY -- 2.25.1
[RFC v1 03/17] x86: asm: support caching in do_get_user_call()
Adds various caching functions with different sizes alongside a macro to select the smallest possible caching function to enable caching of user calls to protect against time-of-check to time-of-use bugs. --- arch/x86/include/asm/uaccess.h| 211 -- arch/x86/include/asm/uaccess_64.h | 54 2 files changed, 254 insertions(+), 11 deletions(-) diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h index 3a7755c1a441..9096aaec5482 100644 --- a/arch/x86/include/asm/uaccess.h +++ b/arch/x86/include/asm/uaccess.h @@ -73,27 +73,215 @@ extern int __get_user_bad(void); * Clang/LLVM cares about the size of the register, but still wants * the base register for something that ends up being a pair. */ + +#ifdef CONFIG_SAFEFETCH +#include +#include + +extern int df_get_user1(unsigned long long user_src, unsigned char user_val, + unsigned long long kern_dst); +extern int df_get_user2(unsigned long long user_src, unsigned short user_val, + unsigned long long kern_dst); +extern int df_get_user4(unsigned long long user_src, unsigned int user_val, + unsigned long long kern_dst); +extern int df_get_user8(unsigned long long user_src, unsigned long user_val, + unsigned long long kern_dst); +extern int df_get_useru8(unsigned long long user_src, unsigned long user_val, +unsigned long long kern_dst); + +// This macro returns the smallest possible get_user function based on value x +#define __dfgetuserfunc(x) \ + __dfgetuserfuncfits(x, char, df_get_user1, \ + __dfgetuserfuncfits(x, short, df_get_user2, \ + __dfgetuserfuncfits(x, int, df_get_user4, \ + __dfgetuserfuncfits(x, long, df_get_user8, \ + df_get_useru8 + +// This macro will deduce the best double fetch get_user protection function, +// based on the register content +#define __dfgetuserfuncfits(x, type, func, not) \ + __builtin_choose_expr(sizeof(x) <= sizeof(type), func, not) + + +//#define GET_USER_CALL_CHECK(x) (likely(!x) && !IS_WHITELISTED(current)) +#define GET_USER_CALL_CHECK(x) likely(!x) + + +// fn = get_user function name template +// x = destination +// ptr = source +#define do_get_user_call(fn, x, ptr)\ +({ \ + /* __ret_gu = the return value from the copy from user function */ \ + int __ret_gu; \ + /* register = compiler hint to store it into a register instead of RAM +* __inttype = func that gets the smallest variable type that fits the source +* __val_gu = intermediate storage of user obtained variable +* Obtain a register with a size equal to *ptr and store the user data pointer inside it +*/ \ + register __inttype(*(ptr)) __val_gu asm("%"_ASM_DX); \ + /* Sparse integrity check, checks is a ptr is in fact a pointer to user space */ \ + __chk_user_ptr(ptr); \ + /* asm := assembly instruction +* volatile := no optimizations +* +* Assembler template: +* "call" := issue a call assembly instruction +* "__" #fn "_%P4" := stringbuilder that creates the right __get_user_X function name +*based on size of ptr +* %P4 := Take fourth variable value as literal string +* +* Output operands: +* "=a" (__ret_gu) := overwrite (=) the address register (a) __ret_gu +* "=r" (__val_gu) := overwrite (=) the general register (r) __val_gu +* ASM_CALL_CONSTRAINT := Constraint that forces the right execution order of inline asm +* +* Input operands: +* "0" (ptr):= first argument, the user space source address +* "i" (sizeof(*(ptr))) := second argument, the size of user space data that must be copied +* +* This function calls one of the __get_user_X functions based on the size of the ptr data +* This copies the data from user space into the temporary variable __val_gu +* The result of this operation is stored in the variable __ret_gu +*/ \ + asm volatile("call __" #fn "_%P4" \ +: "=a" (__ret_gu), "=r" (__val_gu), \ + ASM_CALL_CONSTRAINT \ +: "0" (ptr), "i" (sizeof(*(ptr; \ + in