Merge branch 'x86-asm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull x86 asm updates from Ingo Molnar: "Most of the changes relate to Peter Zijlstra's cleanup of ptregs handling, in particular the i386 part is now much simplified and standardized - no more partial ptregs stack frames via the esp/ss oddity. This simplifies ftrace, kprobes, the unwinder, ptrace, kdump and kgdb. There's also a CR4 hardening enhancements by Kees Cook, to make the generic platform functions such as native_write_cr4() less useful as ROP gadgets that disable SMEP/SMAP. Also protect the WP bit of CR0 against similar attacks. The rest is smaller cleanups/fixes" * 'x86-asm-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: x86/alternatives: Add int3_emulate_call() selftest x86/stackframe/32: Allow int3_emulate_push() x86/stackframe/32: Provide consistent pt_regs x86/stackframe, x86/ftrace: Add pt_regs frame annotations x86/stackframe, x86/kprobes: Fix frame pointer annotations x86/stackframe: Move ENCODE_FRAME_POINTER to asm/frame.h x86/entry/32: Clean up return from interrupt preemption path x86/asm: Pin sensitive CR0 bits x86/asm: Pin sensitive CR4 bits Documentation/x86: Fix path to entry_32.S x86/asm: Remove unused TASK_TI_flags from asm-offsets.c
This commit is contained in:
@@ -22,6 +22,35 @@
|
||||
pop %_ASM_BP
|
||||
.endm
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
/*
|
||||
* This is a sneaky trick to help the unwinder find pt_regs on the stack. The
|
||||
* frame pointer is replaced with an encoded pointer to pt_regs. The encoding
|
||||
* is just setting the LSB, which makes it an invalid stack address and is also
|
||||
* a signal to the unwinder that it's a pt_regs pointer in disguise.
|
||||
*
|
||||
* NOTE: This macro must be used *after* PUSH_AND_CLEAR_REGS because it corrupts
|
||||
* the original rbp.
|
||||
*/
|
||||
.macro ENCODE_FRAME_POINTER ptregs_offset=0
|
||||
leaq 1+\ptregs_offset(%rsp), %rbp
|
||||
.endm
|
||||
#else /* !CONFIG_X86_64 */
|
||||
/*
|
||||
* This is a sneaky trick to help the unwinder find pt_regs on the stack. The
|
||||
* frame pointer is replaced with an encoded pointer to pt_regs. The encoding
|
||||
* is just clearing the MSB, which makes it an invalid stack address and is also
|
||||
* a signal to the unwinder that it's a pt_regs pointer in disguise.
|
||||
*
|
||||
* NOTE: This macro must be used *after* SAVE_ALL because it corrupts the
|
||||
* original ebp.
|
||||
*/
|
||||
.macro ENCODE_FRAME_POINTER
|
||||
mov %esp, %ebp
|
||||
andl $0x7fffffff, %ebp
|
||||
.endm
|
||||
#endif /* CONFIG_X86_64 */
|
||||
|
||||
#else /* !__ASSEMBLY__ */
|
||||
|
||||
#define FRAME_BEGIN \
|
||||
@@ -30,12 +59,32 @@
|
||||
|
||||
#define FRAME_END "pop %" _ASM_BP "\n"
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
#define ENCODE_FRAME_POINTER \
|
||||
"lea 1(%rsp), %rbp\n\t"
|
||||
#else /* !CONFIG_X86_64 */
|
||||
#define ENCODE_FRAME_POINTER \
|
||||
"movl %esp, %ebp\n\t" \
|
||||
"andl $0x7fffffff, %ebp\n\t"
|
||||
#endif /* CONFIG_X86_64 */
|
||||
|
||||
#endif /* __ASSEMBLY__ */
|
||||
|
||||
#define FRAME_OFFSET __ASM_SEL(4, 8)
|
||||
|
||||
#else /* !CONFIG_FRAME_POINTER */
|
||||
|
||||
#ifdef __ASSEMBLY__
|
||||
|
||||
.macro ENCODE_FRAME_POINTER ptregs_offset=0
|
||||
.endm
|
||||
|
||||
#else /* !__ASSEMBLY */
|
||||
|
||||
#define ENCODE_FRAME_POINTER
|
||||
|
||||
#endif
|
||||
|
||||
#define FRAME_BEGIN
|
||||
#define FRAME_END
|
||||
#define FRAME_OFFSET 0
|
||||
|
@@ -70,22 +70,6 @@ struct kimage;
|
||||
#define KEXEC_BACKUP_SRC_START (0UL)
|
||||
#define KEXEC_BACKUP_SRC_END (640 * 1024UL - 1) /* 640K */
|
||||
|
||||
/*
|
||||
* CPU does not save ss and sp on stack if execution is already
|
||||
* running in kernel mode at the time of NMI occurrence. This code
|
||||
* fixes it.
|
||||
*/
|
||||
static inline void crash_fixup_ss_esp(struct pt_regs *newregs,
|
||||
struct pt_regs *oldregs)
|
||||
{
|
||||
#ifdef CONFIG_X86_32
|
||||
newregs->sp = (unsigned long)&(oldregs->sp);
|
||||
asm volatile("xorl %%eax, %%eax\n\t"
|
||||
"movw %%ss, %%ax\n\t"
|
||||
:"=a"(newregs->ss));
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
* This function is responsible for capturing register states if coming
|
||||
* via panic otherwise just fix up the ss and sp if coming via kernel
|
||||
@@ -96,7 +80,6 @@ static inline void crash_setup_regs(struct pt_regs *newregs,
|
||||
{
|
||||
if (oldregs) {
|
||||
memcpy(newregs, oldregs, sizeof(*newregs));
|
||||
crash_fixup_ss_esp(newregs, oldregs);
|
||||
} else {
|
||||
#ifdef CONFIG_X86_32
|
||||
asm volatile("movl %%ebx,%0" : "=m"(newregs->bx));
|
||||
|
@@ -166,14 +166,10 @@ static inline bool user_64bit_mode(struct pt_regs *regs)
|
||||
#define compat_user_stack_pointer() current_pt_regs()->sp
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_X86_32
|
||||
extern unsigned long kernel_stack_pointer(struct pt_regs *regs);
|
||||
#else
|
||||
static inline unsigned long kernel_stack_pointer(struct pt_regs *regs)
|
||||
{
|
||||
return regs->sp;
|
||||
}
|
||||
#endif
|
||||
|
||||
#define GET_IP(regs) ((regs)->ip)
|
||||
#define GET_FP(regs) ((regs)->bp)
|
||||
@@ -201,14 +197,6 @@ static inline unsigned long regs_get_register(struct pt_regs *regs,
|
||||
if (unlikely(offset > MAX_REG_OFFSET))
|
||||
return 0;
|
||||
#ifdef CONFIG_X86_32
|
||||
/*
|
||||
* Traps from the kernel do not save sp and ss.
|
||||
* Use the helper function to retrieve sp.
|
||||
*/
|
||||
if (offset == offsetof(struct pt_regs, sp) &&
|
||||
regs->cs == __KERNEL_CS)
|
||||
return kernel_stack_pointer(regs);
|
||||
|
||||
/* The selector fields are 16-bit. */
|
||||
if (offset == offsetof(struct pt_regs, cs) ||
|
||||
offset == offsetof(struct pt_regs, ss) ||
|
||||
@@ -234,8 +222,7 @@ static inline unsigned long regs_get_register(struct pt_regs *regs,
|
||||
static inline int regs_within_kernel_stack(struct pt_regs *regs,
|
||||
unsigned long addr)
|
||||
{
|
||||
return ((addr & ~(THREAD_SIZE - 1)) ==
|
||||
(kernel_stack_pointer(regs) & ~(THREAD_SIZE - 1)));
|
||||
return ((addr & ~(THREAD_SIZE - 1)) == (regs->sp & ~(THREAD_SIZE - 1)));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,7 +236,7 @@ static inline int regs_within_kernel_stack(struct pt_regs *regs,
|
||||
*/
|
||||
static inline unsigned long *regs_get_kernel_stack_nth_addr(struct pt_regs *regs, unsigned int n)
|
||||
{
|
||||
unsigned long *addr = (unsigned long *)kernel_stack_pointer(regs);
|
||||
unsigned long *addr = (unsigned long *)regs->sp;
|
||||
|
||||
addr += n;
|
||||
if (regs_within_kernel_stack(regs, (unsigned long)addr))
|
||||
|
@@ -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;
|
||||
@@ -25,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)
|
||||
@@ -74,7 +93,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
|
||||
|
@@ -78,7 +78,7 @@ static inline unsigned long *
|
||||
get_stack_pointer(struct task_struct *task, struct pt_regs *regs)
|
||||
{
|
||||
if (regs)
|
||||
return (unsigned long *)kernel_stack_pointer(regs);
|
||||
return (unsigned long *)regs->sp;
|
||||
|
||||
if (task == current)
|
||||
return __builtin_frame_address(0);
|
||||
|
@@ -66,7 +66,6 @@ static inline void int3_emulate_jmp(struct pt_regs *regs, unsigned long ip)
|
||||
#define INT3_INSN_SIZE 1
|
||||
#define CALL_INSN_SIZE 5
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
static inline void int3_emulate_push(struct pt_regs *regs, unsigned long val)
|
||||
{
|
||||
/*
|
||||
@@ -84,7 +83,6 @@ static inline void int3_emulate_call(struct pt_regs *regs, unsigned long func)
|
||||
int3_emulate_push(regs, regs->ip - INT3_INSN_SIZE + CALL_INSN_SIZE);
|
||||
int3_emulate_jmp(regs, func);
|
||||
}
|
||||
#endif /* CONFIG_X86_64 */
|
||||
#endif /* !CONFIG_UML_X86 */
|
||||
|
||||
#endif /* _ASM_X86_TEXT_PATCHING_H */
|
||||
|
Reference in New Issue
Block a user