Merge branch 'core-objtool-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull objtool updates from Ingo Molnar: "This is a series from Peter Zijlstra that adds x86 build-time uaccess validation of SMAP to objtool, which will detect and warn about the following uaccess API usage bugs and weirdnesses: - call to %s() with UACCESS enabled - return with UACCESS enabled - return with UACCESS disabled from a UACCESS-safe function - recursive UACCESS enable - redundant UACCESS disable - UACCESS-safe disables UACCESS As it turns out not leaking uaccess permissions outside the intended uaccess functionality is hard when the interfaces are complex and when such bugs are mostly dormant. As a bonus we now also check the DF flag. We had at least one high-profile bug in that area in the early days of Linux, and the checking is fairly simple. The checks performed and warnings emitted are: - call to %s() with DF set - return with DF set - return with modified stack frame - recursive STD - redundant CLD It's all x86-only for now, but later on this can also be used for PAN on ARM and objtool is fairly cross-platform in principle. While all warnings emitted by this new checking facility that got reported to us were fixed, there might be GCC version dependent warnings that were not reported yet - which we'll address, should they trigger. The warnings are non-fatal build warnings" * 'core-objtool-for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: (27 commits) mm/uaccess: Use 'unsigned long' to placate UBSAN warnings on older GCC versions x86/uaccess: Dont leak the AC flag into __put_user() argument evaluation sched/x86_64: Don't save flags on context switch objtool: Add Direction Flag validation objtool: Add UACCESS validation objtool: Fix sibling call detection objtool: Rewrite alt->skip_orig objtool: Add --backtrace support objtool: Rewrite add_ignores() objtool: Handle function aliases objtool: Set insn->func for alternatives x86/uaccess, kcov: Disable stack protector x86/uaccess, ftrace: Fix ftrace_likely_update() vs. SMAP x86/uaccess, ubsan: Fix UBSAN vs. SMAP x86/uaccess, kasan: Fix KASAN vs SMAP x86/smap: Ditch __stringify() x86/uaccess: Introduce user_access_{save,restore}() x86/uaccess, signal: Fix AC=1 bloat x86/uaccess: Always inline user_access_begin() x86/uaccess, xen: Suppress SMAP warnings ...
This commit is contained in:
commit
6ec62961e6
|
@ -650,6 +650,7 @@ ENTRY(__switch_to_asm)
|
|||
pushl %ebx
|
||||
pushl %edi
|
||||
pushl %esi
|
||||
pushfl
|
||||
|
||||
/* switch stack */
|
||||
movl %esp, TASK_threadsp(%eax)
|
||||
|
@ -672,6 +673,7 @@ ENTRY(__switch_to_asm)
|
|||
#endif
|
||||
|
||||
/* restore callee-saved registers */
|
||||
popfl
|
||||
popl %esi
|
||||
popl %edi
|
||||
popl %ebx
|
||||
|
|
|
@ -61,9 +61,8 @@
|
|||
} while (0)
|
||||
|
||||
#define RELOAD_SEG(seg) { \
|
||||
unsigned int pre = GET_SEG(seg); \
|
||||
unsigned int pre = (seg) | 3; \
|
||||
unsigned int cur = get_user_seg(seg); \
|
||||
pre |= 3; \
|
||||
if (pre != cur) \
|
||||
set_user_seg(seg, pre); \
|
||||
}
|
||||
|
@ -72,6 +71,7 @@ static int ia32_restore_sigcontext(struct pt_regs *regs,
|
|||
struct sigcontext_32 __user *sc)
|
||||
{
|
||||
unsigned int tmpflags, err = 0;
|
||||
u16 gs, fs, es, ds;
|
||||
void __user *buf;
|
||||
u32 tmp;
|
||||
|
||||
|
@ -79,16 +79,10 @@ static int ia32_restore_sigcontext(struct pt_regs *regs,
|
|||
current->restart_block.fn = do_no_restart_syscall;
|
||||
|
||||
get_user_try {
|
||||
/*
|
||||
* Reload fs and gs if they have changed in the signal
|
||||
* handler. This does not handle long fs/gs base changes in
|
||||
* the handler, but does not clobber them at least in the
|
||||
* normal case.
|
||||
*/
|
||||
RELOAD_SEG(gs);
|
||||
RELOAD_SEG(fs);
|
||||
RELOAD_SEG(ds);
|
||||
RELOAD_SEG(es);
|
||||
gs = GET_SEG(gs);
|
||||
fs = GET_SEG(fs);
|
||||
ds = GET_SEG(ds);
|
||||
es = GET_SEG(es);
|
||||
|
||||
COPY(di); COPY(si); COPY(bp); COPY(sp); COPY(bx);
|
||||
COPY(dx); COPY(cx); COPY(ip); COPY(ax);
|
||||
|
@ -106,6 +100,17 @@ static int ia32_restore_sigcontext(struct pt_regs *regs,
|
|||
buf = compat_ptr(tmp);
|
||||
} get_user_catch(err);
|
||||
|
||||
/*
|
||||
* Reload fs and gs if they have changed in the signal
|
||||
* handler. This does not handle long fs/gs base changes in
|
||||
* the handler, but does not clobber them at least in the
|
||||
* normal case.
|
||||
*/
|
||||
RELOAD_SEG(gs);
|
||||
RELOAD_SEG(fs);
|
||||
RELOAD_SEG(ds);
|
||||
RELOAD_SEG(es);
|
||||
|
||||
err |= fpu__restore_sig(buf, 1);
|
||||
|
||||
force_iret();
|
||||
|
|
|
@ -19,6 +19,17 @@
|
|||
.endm
|
||||
#endif
|
||||
|
||||
/*
|
||||
* objtool annotation to ignore the alternatives and only consider the original
|
||||
* instruction(s).
|
||||
*/
|
||||
.macro ANNOTATE_IGNORE_ALTERNATIVE
|
||||
.Lannotate_\@:
|
||||
.pushsection .discard.ignore_alts
|
||||
.long .Lannotate_\@ - .
|
||||
.popsection
|
||||
.endm
|
||||
|
||||
/*
|
||||
* Issue one struct alt_instr descriptor entry (need to put it into
|
||||
* the section .altinstructions, see below). This entry contains
|
||||
|
|
|
@ -45,6 +45,16 @@
|
|||
#define LOCK_PREFIX ""
|
||||
#endif
|
||||
|
||||
/*
|
||||
* objtool annotation to ignore the alternatives and only consider the original
|
||||
* instruction(s).
|
||||
*/
|
||||
#define ANNOTATE_IGNORE_ALTERNATIVE \
|
||||
"999:\n\t" \
|
||||
".pushsection .discard.ignore_alts\n\t" \
|
||||
".long 999b - .\n\t" \
|
||||
".popsection\n\t"
|
||||
|
||||
struct alt_instr {
|
||||
s32 instr_offset; /* original instruction */
|
||||
s32 repl_offset; /* offset to replacement instruction */
|
||||
|
|
|
@ -148,30 +148,6 @@
|
|||
_ASM_PTR (entry); \
|
||||
.popsection
|
||||
|
||||
.macro ALIGN_DESTINATION
|
||||
/* check for bad alignment of destination */
|
||||
movl %edi,%ecx
|
||||
andl $7,%ecx
|
||||
jz 102f /* already aligned */
|
||||
subl $8,%ecx
|
||||
negl %ecx
|
||||
subl %ecx,%edx
|
||||
100: movb (%rsi),%al
|
||||
101: movb %al,(%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decl %ecx
|
||||
jnz 100b
|
||||
102:
|
||||
.section .fixup,"ax"
|
||||
103: addl %ecx,%edx /* ecx is zerorest also */
|
||||
jmp copy_user_handle_tail
|
||||
.previous
|
||||
|
||||
_ASM_EXTABLE_UA(100b, 103b)
|
||||
_ASM_EXTABLE_UA(101b, 103b)
|
||||
.endm
|
||||
|
||||
#else
|
||||
# define _EXPAND_EXTABLE_HANDLE(x) #x
|
||||
# define _ASM_EXTABLE_HANDLE(from, to, handler) \
|
||||
|
|
|
@ -10,6 +10,15 @@
|
|||
#include <asm/cpufeatures.h>
|
||||
#include <asm/msr-index.h>
|
||||
|
||||
/*
|
||||
* This should be used immediately before a retpoline alternative. It tells
|
||||
* objtool where the retpolines are so that it can make sense of the control
|
||||
* flow by just reading the original instruction(s) and ignoring the
|
||||
* alternatives.
|
||||
*/
|
||||
#define ANNOTATE_NOSPEC_ALTERNATIVE \
|
||||
ANNOTATE_IGNORE_ALTERNATIVE
|
||||
|
||||
/*
|
||||
* Fill the CPU return stack buffer.
|
||||
*
|
||||
|
@ -56,19 +65,6 @@
|
|||
|
||||
#ifdef __ASSEMBLY__
|
||||
|
||||
/*
|
||||
* This should be used immediately before a retpoline alternative. It tells
|
||||
* objtool where the retpolines are so that it can make sense of the control
|
||||
* flow by just reading the original instruction(s) and ignoring the
|
||||
* alternatives.
|
||||
*/
|
||||
.macro ANNOTATE_NOSPEC_ALTERNATIVE
|
||||
.Lannotate_\@:
|
||||
.pushsection .discard.nospec
|
||||
.long .Lannotate_\@ - .
|
||||
.popsection
|
||||
.endm
|
||||
|
||||
/*
|
||||
* This should be used immediately before an indirect jump/call. It tells
|
||||
* objtool the subsequent indirect jump/call is vouched safe for retpoline
|
||||
|
@ -152,12 +148,6 @@
|
|||
|
||||
#else /* __ASSEMBLY__ */
|
||||
|
||||
#define ANNOTATE_NOSPEC_ALTERNATIVE \
|
||||
"999:\n\t" \
|
||||
".pushsection .discard.nospec\n\t" \
|
||||
".long 999b - .\n\t" \
|
||||
".popsection\n\t"
|
||||
|
||||
#define ANNOTATE_RETPOLINE_SAFE \
|
||||
"999:\n\t" \
|
||||
".pushsection .discard.retpoline_safe\n\t" \
|
||||
|
|
|
@ -13,13 +13,12 @@
|
|||
#ifndef _ASM_X86_SMAP_H
|
||||
#define _ASM_X86_SMAP_H
|
||||
|
||||
#include <linux/stringify.h>
|
||||
#include <asm/nops.h>
|
||||
#include <asm/cpufeatures.h>
|
||||
|
||||
/* "Raw" instruction opcodes */
|
||||
#define __ASM_CLAC .byte 0x0f,0x01,0xca
|
||||
#define __ASM_STAC .byte 0x0f,0x01,0xcb
|
||||
#define __ASM_CLAC ".byte 0x0f,0x01,0xca"
|
||||
#define __ASM_STAC ".byte 0x0f,0x01,0xcb"
|
||||
|
||||
#ifdef __ASSEMBLY__
|
||||
|
||||
|
@ -28,10 +27,10 @@
|
|||
#ifdef CONFIG_X86_SMAP
|
||||
|
||||
#define ASM_CLAC \
|
||||
ALTERNATIVE "", __stringify(__ASM_CLAC), X86_FEATURE_SMAP
|
||||
ALTERNATIVE "", __ASM_CLAC, X86_FEATURE_SMAP
|
||||
|
||||
#define ASM_STAC \
|
||||
ALTERNATIVE "", __stringify(__ASM_STAC), X86_FEATURE_SMAP
|
||||
ALTERNATIVE "", __ASM_STAC, X86_FEATURE_SMAP
|
||||
|
||||
#else /* CONFIG_X86_SMAP */
|
||||
|
||||
|
@ -49,26 +48,46 @@
|
|||
static __always_inline void clac(void)
|
||||
{
|
||||
/* Note: a barrier is implicit in alternative() */
|
||||
alternative("", __stringify(__ASM_CLAC), X86_FEATURE_SMAP);
|
||||
alternative("", __ASM_CLAC, X86_FEATURE_SMAP);
|
||||
}
|
||||
|
||||
static __always_inline void stac(void)
|
||||
{
|
||||
/* Note: a barrier is implicit in alternative() */
|
||||
alternative("", __stringify(__ASM_STAC), X86_FEATURE_SMAP);
|
||||
alternative("", __ASM_STAC, X86_FEATURE_SMAP);
|
||||
}
|
||||
|
||||
static __always_inline unsigned long smap_save(void)
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
asm volatile (ALTERNATIVE("", "pushf; pop %0; " __ASM_CLAC,
|
||||
X86_FEATURE_SMAP)
|
||||
: "=rm" (flags) : : "memory", "cc");
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
static __always_inline void smap_restore(unsigned long flags)
|
||||
{
|
||||
asm volatile (ALTERNATIVE("", "push %0; popf", X86_FEATURE_SMAP)
|
||||
: : "g" (flags) : "memory", "cc");
|
||||
}
|
||||
|
||||
/* These macros can be used in asm() statements */
|
||||
#define ASM_CLAC \
|
||||
ALTERNATIVE("", __stringify(__ASM_CLAC), X86_FEATURE_SMAP)
|
||||
ALTERNATIVE("", __ASM_CLAC, X86_FEATURE_SMAP)
|
||||
#define ASM_STAC \
|
||||
ALTERNATIVE("", __stringify(__ASM_STAC), X86_FEATURE_SMAP)
|
||||
ALTERNATIVE("", __ASM_STAC, X86_FEATURE_SMAP)
|
||||
|
||||
#else /* CONFIG_X86_SMAP */
|
||||
|
||||
static inline void clac(void) { }
|
||||
static inline void stac(void) { }
|
||||
|
||||
static inline unsigned long smap_save(void) { return 0; }
|
||||
static inline void smap_restore(unsigned long flags) { }
|
||||
|
||||
#define ASM_CLAC
|
||||
#define ASM_STAC
|
||||
|
||||
|
|
|
@ -46,6 +46,7 @@ struct inactive_task_frame {
|
|||
unsigned long r13;
|
||||
unsigned long r12;
|
||||
#else
|
||||
unsigned long flags;
|
||||
unsigned long si;
|
||||
unsigned long di;
|
||||
#endif
|
||||
|
|
|
@ -427,10 +427,11 @@ do { \
|
|||
({ \
|
||||
__label__ __pu_label; \
|
||||
int __pu_err = -EFAULT; \
|
||||
__typeof__(*(ptr)) __pu_val; \
|
||||
__pu_val = x; \
|
||||
__typeof__(*(ptr)) __pu_val = (x); \
|
||||
__typeof__(ptr) __pu_ptr = (ptr); \
|
||||
__typeof__(size) __pu_size = (size); \
|
||||
__uaccess_begin(); \
|
||||
__put_user_size(__pu_val, (ptr), (size), __pu_label); \
|
||||
__put_user_size(__pu_val, __pu_ptr, __pu_size, __pu_label); \
|
||||
__pu_err = 0; \
|
||||
__pu_label: \
|
||||
__uaccess_end(); \
|
||||
|
@ -705,7 +706,7 @@ extern struct movsl_mask {
|
|||
* checking before using them, but you have to surround them with the
|
||||
* user_access_begin/end() pair.
|
||||
*/
|
||||
static __must_check inline bool user_access_begin(const void __user *ptr, size_t len)
|
||||
static __must_check __always_inline bool user_access_begin(const void __user *ptr, size_t len)
|
||||
{
|
||||
if (unlikely(!access_ok(ptr,len)))
|
||||
return 0;
|
||||
|
@ -715,6 +716,9 @@ static __must_check inline bool user_access_begin(const void __user *ptr, size_t
|
|||
#define user_access_begin(a,b) user_access_begin(a,b)
|
||||
#define user_access_end() __uaccess_end()
|
||||
|
||||
#define user_access_save() smap_save()
|
||||
#define user_access_restore(x) smap_restore(x)
|
||||
|
||||
#define unsafe_put_user(x, ptr, label) \
|
||||
__put_user_size((__typeof__(*(ptr)))(x), (ptr), sizeof(*(ptr)), label)
|
||||
|
||||
|
|
|
@ -207,9 +207,6 @@ __copy_from_user_flushcache(void *dst, const void __user *src, unsigned size)
|
|||
return __copy_user_flushcache(dst, src, size);
|
||||
}
|
||||
|
||||
unsigned long
|
||||
copy_user_handle_tail(char *to, char *from, unsigned len);
|
||||
|
||||
unsigned long
|
||||
mcsafe_handle_tail(char *to, char *from, unsigned len);
|
||||
|
||||
|
|
|
@ -217,6 +217,22 @@ xen_single_call(unsigned int call,
|
|||
return (long)__res;
|
||||
}
|
||||
|
||||
static __always_inline void __xen_stac(void)
|
||||
{
|
||||
/*
|
||||
* Suppress objtool seeing the STAC/CLAC and getting confused about it
|
||||
* calling random code with AC=1.
|
||||
*/
|
||||
asm volatile(ANNOTATE_IGNORE_ALTERNATIVE
|
||||
ASM_STAC ::: "memory", "flags");
|
||||
}
|
||||
|
||||
static __always_inline void __xen_clac(void)
|
||||
{
|
||||
asm volatile(ANNOTATE_IGNORE_ALTERNATIVE
|
||||
ASM_CLAC ::: "memory", "flags");
|
||||
}
|
||||
|
||||
static inline long
|
||||
privcmd_call(unsigned int call,
|
||||
unsigned long a1, unsigned long a2,
|
||||
|
@ -225,9 +241,9 @@ privcmd_call(unsigned int call,
|
|||
{
|
||||
long res;
|
||||
|
||||
stac();
|
||||
__xen_stac();
|
||||
res = xen_single_call(call, a1, a2, a3, a4, a5);
|
||||
clac();
|
||||
__xen_clac();
|
||||
|
||||
return res;
|
||||
}
|
||||
|
@ -424,9 +440,9 @@ HYPERVISOR_dm_op(
|
|||
domid_t dom, unsigned int nr_bufs, struct xen_dm_op_buf *bufs)
|
||||
{
|
||||
int ret;
|
||||
stac();
|
||||
__xen_stac();
|
||||
ret = _hypercall3(int, dm_op, dom, nr_bufs, bufs);
|
||||
clac();
|
||||
__xen_clac();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
@ -127,6 +127,13 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
|
|||
struct task_struct *tsk;
|
||||
int err;
|
||||
|
||||
/*
|
||||
* For a new task use the RESET flags value since there is no before.
|
||||
* All the status flags are zero; DF and all the system flags must also
|
||||
* be 0, specifically IF must be 0 because we context switch to the new
|
||||
* task with interrupts disabled.
|
||||
*/
|
||||
frame->flags = X86_EFLAGS_FIXED;
|
||||
frame->bp = 0;
|
||||
frame->ret_addr = (unsigned long) ret_from_fork;
|
||||
p->thread.sp = (unsigned long) fork_frame;
|
||||
|
|
|
@ -392,6 +392,7 @@ int copy_thread_tls(unsigned long clone_flags, unsigned long sp,
|
|||
childregs = task_pt_regs(p);
|
||||
fork_frame = container_of(childregs, struct fork_frame, regs);
|
||||
frame = &fork_frame->frame;
|
||||
|
||||
frame->bp = 0;
|
||||
frame->ret_addr = (unsigned long) ret_from_fork;
|
||||
p->thread.sp = (unsigned long) fork_frame;
|
||||
|
|
|
@ -132,16 +132,6 @@ static int restore_sigcontext(struct pt_regs *regs,
|
|||
COPY_SEG_CPL3(cs);
|
||||
COPY_SEG_CPL3(ss);
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
/*
|
||||
* Fix up SS if needed for the benefit of old DOSEMU and
|
||||
* CRIU.
|
||||
*/
|
||||
if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) &&
|
||||
user_64bit_mode(regs)))
|
||||
force_valid_ss(regs);
|
||||
#endif
|
||||
|
||||
get_user_ex(tmpflags, &sc->flags);
|
||||
regs->flags = (regs->flags & ~FIX_EFLAGS) | (tmpflags & FIX_EFLAGS);
|
||||
regs->orig_ax = -1; /* disable syscall checks */
|
||||
|
@ -150,6 +140,15 @@ static int restore_sigcontext(struct pt_regs *regs,
|
|||
buf = (void __user *)buf_val;
|
||||
} get_user_catch(err);
|
||||
|
||||
#ifdef CONFIG_X86_64
|
||||
/*
|
||||
* Fix up SS if needed for the benefit of old DOSEMU and
|
||||
* CRIU.
|
||||
*/
|
||||
if (unlikely(!(uc_flags & UC_STRICT_RESTORE_SS) && user_64bit_mode(regs)))
|
||||
force_valid_ss(regs);
|
||||
#endif
|
||||
|
||||
err |= fpu__restore_sig(buf, IS_ENABLED(CONFIG_X86_32));
|
||||
|
||||
force_iret();
|
||||
|
@ -461,6 +460,7 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig,
|
|||
{
|
||||
struct rt_sigframe __user *frame;
|
||||
void __user *fp = NULL;
|
||||
unsigned long uc_flags;
|
||||
int err = 0;
|
||||
|
||||
frame = get_sigframe(&ksig->ka, regs, sizeof(struct rt_sigframe), &fp);
|
||||
|
@ -473,9 +473,11 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig,
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
uc_flags = frame_uc_flags(regs);
|
||||
|
||||
put_user_try {
|
||||
/* Create the ucontext. */
|
||||
put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags);
|
||||
put_user_ex(uc_flags, &frame->uc.uc_flags);
|
||||
put_user_ex(0, &frame->uc.uc_link);
|
||||
save_altstack_ex(&frame->uc.uc_stack, regs->sp);
|
||||
|
||||
|
@ -541,6 +543,7 @@ static int x32_setup_rt_frame(struct ksignal *ksig,
|
|||
{
|
||||
#ifdef CONFIG_X86_X32_ABI
|
||||
struct rt_sigframe_x32 __user *frame;
|
||||
unsigned long uc_flags;
|
||||
void __user *restorer;
|
||||
int err = 0;
|
||||
void __user *fpstate = NULL;
|
||||
|
@ -555,9 +558,11 @@ static int x32_setup_rt_frame(struct ksignal *ksig,
|
|||
return -EFAULT;
|
||||
}
|
||||
|
||||
uc_flags = frame_uc_flags(regs);
|
||||
|
||||
put_user_try {
|
||||
/* Create the ucontext. */
|
||||
put_user_ex(frame_uc_flags(regs), &frame->uc.uc_flags);
|
||||
put_user_ex(uc_flags, &frame->uc.uc_flags);
|
||||
put_user_ex(0, &frame->uc.uc_link);
|
||||
compat_save_altstack_ex(&frame->uc.uc_stack, regs->sp);
|
||||
put_user_ex(0, &frame->uc.uc__pad0);
|
||||
|
|
|
@ -16,6 +16,30 @@
|
|||
#include <asm/smap.h>
|
||||
#include <asm/export.h>
|
||||
|
||||
.macro ALIGN_DESTINATION
|
||||
/* check for bad alignment of destination */
|
||||
movl %edi,%ecx
|
||||
andl $7,%ecx
|
||||
jz 102f /* already aligned */
|
||||
subl $8,%ecx
|
||||
negl %ecx
|
||||
subl %ecx,%edx
|
||||
100: movb (%rsi),%al
|
||||
101: movb %al,(%rdi)
|
||||
incq %rsi
|
||||
incq %rdi
|
||||
decl %ecx
|
||||
jnz 100b
|
||||
102:
|
||||
.section .fixup,"ax"
|
||||
103: addl %ecx,%edx /* ecx is zerorest also */
|
||||
jmp copy_user_handle_tail
|
||||
.previous
|
||||
|
||||
_ASM_EXTABLE_UA(100b, 103b)
|
||||
_ASM_EXTABLE_UA(101b, 103b)
|
||||
.endm
|
||||
|
||||
/*
|
||||
* copy_user_generic_unrolled - memory copy with exception handling.
|
||||
* This version is for CPUs like P4 that don't have efficient micro
|
||||
|
@ -193,6 +217,30 @@ ENTRY(copy_user_enhanced_fast_string)
|
|||
ENDPROC(copy_user_enhanced_fast_string)
|
||||
EXPORT_SYMBOL(copy_user_enhanced_fast_string)
|
||||
|
||||
/*
|
||||
* Try to copy last bytes and clear the rest if needed.
|
||||
* Since protection fault in copy_from/to_user is not a normal situation,
|
||||
* it is not necessary to optimize tail handling.
|
||||
*
|
||||
* Input:
|
||||
* rdi destination
|
||||
* rsi source
|
||||
* rdx count
|
||||
*
|
||||
* Output:
|
||||
* eax uncopied bytes or 0 if successful.
|
||||
*/
|
||||
ALIGN;
|
||||
copy_user_handle_tail:
|
||||
movl %edx,%ecx
|
||||
1: rep movsb
|
||||
2: mov %ecx,%eax
|
||||
ASM_CLAC
|
||||
ret
|
||||
|
||||
_ASM_EXTABLE_UA(1b, 2b)
|
||||
ENDPROC(copy_user_handle_tail)
|
||||
|
||||
/*
|
||||
* copy_user_nocache - Uncached memory copy with exception handling
|
||||
* This will force destination out of cache for more performance.
|
||||
|
|
|
@ -257,6 +257,7 @@ ENTRY(__memcpy_mcsafe)
|
|||
/* Copy successful. Return zero */
|
||||
.L_done_memcpy_trap:
|
||||
xorl %eax, %eax
|
||||
.L_done:
|
||||
ret
|
||||
ENDPROC(__memcpy_mcsafe)
|
||||
EXPORT_SYMBOL_GPL(__memcpy_mcsafe)
|
||||
|
@ -273,7 +274,7 @@ EXPORT_SYMBOL_GPL(__memcpy_mcsafe)
|
|||
addl %edx, %ecx
|
||||
.E_trailing_bytes:
|
||||
mov %ecx, %eax
|
||||
ret
|
||||
jmp .L_done
|
||||
|
||||
/*
|
||||
* For write fault handling, given the destination is unaligned,
|
||||
|
|
|
@ -54,26 +54,6 @@ unsigned long clear_user(void __user *to, unsigned long n)
|
|||
}
|
||||
EXPORT_SYMBOL(clear_user);
|
||||
|
||||
/*
|
||||
* Try to copy last bytes and clear the rest if needed.
|
||||
* Since protection fault in copy_from/to_user is not a normal situation,
|
||||
* it is not necessary to optimize tail handling.
|
||||
*/
|
||||
__visible unsigned long
|
||||
copy_user_handle_tail(char *to, char *from, unsigned len)
|
||||
{
|
||||
for (; len; --len, to++) {
|
||||
char c;
|
||||
|
||||
if (__get_user_nocheck(c, from++, sizeof(char)))
|
||||
break;
|
||||
if (__put_user_nocheck(c, to, sizeof(char)))
|
||||
break;
|
||||
}
|
||||
clac();
|
||||
return len;
|
||||
}
|
||||
|
||||
/*
|
||||
* Similar to copy_user_handle_tail, probe for the write fault point,
|
||||
* but reuse __memcpy_mcsafe in case a new read error is encountered.
|
||||
|
|
|
@ -1667,6 +1667,7 @@ static int eb_copy_relocations(const struct i915_execbuffer *eb)
|
|||
len)) {
|
||||
end_user:
|
||||
user_access_end();
|
||||
end:
|
||||
kvfree(relocs);
|
||||
err = -EFAULT;
|
||||
goto err;
|
||||
|
@ -1686,7 +1687,7 @@ end_user:
|
|||
* relocations were valid.
|
||||
*/
|
||||
if (!user_access_begin(urelocs, size))
|
||||
goto end_user;
|
||||
goto end;
|
||||
|
||||
for (copied = 0; copied < nreloc; copied++)
|
||||
unsafe_put_user(-1,
|
||||
|
@ -2695,7 +2696,7 @@ i915_gem_execbuffer2_ioctl(struct drm_device *dev, void *data,
|
|||
* when we did the "copy_from_user()" above.
|
||||
*/
|
||||
if (!user_access_begin(user_exec_list, count * sizeof(*user_exec_list)))
|
||||
goto end_user;
|
||||
goto end;
|
||||
|
||||
for (i = 0; i < args->buffer_count; i++) {
|
||||
if (!(exec2_list[i].offset & UPDATE))
|
||||
|
@ -2709,6 +2710,7 @@ i915_gem_execbuffer2_ioctl(struct drm_device *dev, void *data,
|
|||
}
|
||||
end_user:
|
||||
user_access_end();
|
||||
end:;
|
||||
}
|
||||
|
||||
args->flags &= ~__I915_EXEC_UNKNOWN_FLAGS;
|
||||
|
|
|
@ -67,7 +67,7 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
|||
.line = __LINE__, \
|
||||
}; \
|
||||
______r = !!(cond); \
|
||||
______f.miss_hit[______r]++; \
|
||||
______r ? ______f.miss_hit[1]++ : ______f.miss_hit[0]++;\
|
||||
______r; \
|
||||
}))
|
||||
#endif /* CONFIG_PROFILE_ALL_BRANCHES */
|
||||
|
|
|
@ -268,6 +268,8 @@ extern long strncpy_from_unsafe(char *dst, const void *unsafe_addr, long count);
|
|||
#define user_access_end() do { } while (0)
|
||||
#define unsafe_get_user(x, ptr, err) do { if (unlikely(__get_user(x, ptr))) goto err; } while (0)
|
||||
#define unsafe_put_user(x, ptr, err) do { if (unlikely(__put_user(x, ptr))) goto err; } while (0)
|
||||
static inline unsigned long user_access_save(void) { return 0UL; }
|
||||
static inline void user_access_restore(unsigned long flags) { }
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HARDENED_USERCOPY
|
||||
|
|
|
@ -30,6 +30,7 @@ KCOV_INSTRUMENT_extable.o := n
|
|||
# Don't self-instrument.
|
||||
KCOV_INSTRUMENT_kcov.o := n
|
||||
KASAN_SANITIZE_kcov.o := n
|
||||
CFLAGS_kcov.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
|
||||
# cond_syscall is currently not LTO compatible
|
||||
CFLAGS_sys_ni.o = $(DISABLE_LTO)
|
||||
|
|
|
@ -205,6 +205,8 @@ void trace_likely_condition(struct ftrace_likely_data *f, int val, int expect)
|
|||
void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
||||
int expect, int is_constant)
|
||||
{
|
||||
unsigned long flags = user_access_save();
|
||||
|
||||
/* A constant is always correct */
|
||||
if (is_constant) {
|
||||
f->constant++;
|
||||
|
@ -223,6 +225,8 @@ void ftrace_likely_update(struct ftrace_likely_data *f, int val,
|
|||
f->data.correct++;
|
||||
else
|
||||
f->data.incorrect++;
|
||||
|
||||
user_access_restore(flags);
|
||||
}
|
||||
EXPORT_SYMBOL(ftrace_likely_update);
|
||||
|
||||
|
|
|
@ -279,6 +279,7 @@ obj-$(CONFIG_UCS2_STRING) += ucs2_string.o
|
|||
obj-$(CONFIG_UBSAN) += ubsan.o
|
||||
|
||||
UBSAN_SANITIZE_ubsan.o := n
|
||||
CFLAGS_ubsan.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
|
||||
obj-$(CONFIG_SBITMAP) += sbitmap.o
|
||||
|
||||
|
|
|
@ -23,10 +23,11 @@
|
|||
* hit it), 'max' is the address space maximum (and we return
|
||||
* -EFAULT if we hit it).
|
||||
*/
|
||||
static inline long do_strncpy_from_user(char *dst, const char __user *src, long count, unsigned long max)
|
||||
static inline long do_strncpy_from_user(char *dst, const char __user *src,
|
||||
unsigned long count, unsigned long max)
|
||||
{
|
||||
const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
|
||||
long res = 0;
|
||||
unsigned long res = 0;
|
||||
|
||||
/*
|
||||
* Truncate 'max' to the user-specified limit, so that
|
||||
|
|
|
@ -28,7 +28,7 @@
|
|||
static inline long do_strnlen_user(const char __user *src, unsigned long count, unsigned long max)
|
||||
{
|
||||
const struct word_at_a_time constants = WORD_AT_A_TIME_CONSTANTS;
|
||||
long align, res = 0;
|
||||
unsigned long align, res = 0;
|
||||
unsigned long c;
|
||||
|
||||
/*
|
||||
|
@ -42,7 +42,7 @@ static inline long do_strnlen_user(const char __user *src, unsigned long count,
|
|||
* Do everything aligned. But that means that we
|
||||
* need to also expand the maximum..
|
||||
*/
|
||||
align = (sizeof(long) - 1) & (unsigned long)src;
|
||||
align = (sizeof(unsigned long) - 1) & (unsigned long)src;
|
||||
src -= align;
|
||||
max += align;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "ubsan.h"
|
||||
|
||||
|
@ -311,6 +312,7 @@ static void handle_object_size_mismatch(struct type_mismatch_data_common *data,
|
|||
static void ubsan_type_mismatch_common(struct type_mismatch_data_common *data,
|
||||
unsigned long ptr)
|
||||
{
|
||||
unsigned long flags = user_access_save();
|
||||
|
||||
if (!ptr)
|
||||
handle_null_ptr_deref(data);
|
||||
|
@ -318,6 +320,8 @@ static void ubsan_type_mismatch_common(struct type_mismatch_data_common *data,
|
|||
handle_misaligned_access(data, ptr);
|
||||
else
|
||||
handle_object_size_mismatch(data, ptr);
|
||||
|
||||
user_access_restore(flags);
|
||||
}
|
||||
|
||||
void __ubsan_handle_type_mismatch(struct type_mismatch_data *data,
|
||||
|
|
|
@ -2,11 +2,13 @@
|
|||
KASAN_SANITIZE := n
|
||||
UBSAN_SANITIZE_common.o := n
|
||||
UBSAN_SANITIZE_generic.o := n
|
||||
UBSAN_SANITIZE_generic_report.o := n
|
||||
UBSAN_SANITIZE_tags.o := n
|
||||
KCOV_INSTRUMENT := n
|
||||
|
||||
CFLAGS_REMOVE_common.o = -pg
|
||||
CFLAGS_REMOVE_generic.o = -pg
|
||||
CFLAGS_REMOVE_generic_report.o = -pg
|
||||
CFLAGS_REMOVE_tags.o = -pg
|
||||
|
||||
# Function splitter causes unnecessary splits in __asan_load1/__asan_store1
|
||||
|
@ -14,6 +16,7 @@ CFLAGS_REMOVE_tags.o = -pg
|
|||
|
||||
CFLAGS_common.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
CFLAGS_generic.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
CFLAGS_generic_report.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
CFLAGS_tags.o := $(call cc-option, -fno-conserve-stack -fno-stack-protector)
|
||||
|
||||
obj-$(CONFIG_KASAN) := common.o init.o report.o
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include <linux/types.h>
|
||||
#include <linux/vmalloc.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
#include "kasan.h"
|
||||
#include "../slab.h"
|
||||
|
@ -614,6 +615,15 @@ void kasan_free_shadow(const struct vm_struct *vm)
|
|||
vfree(kasan_mem_to_shadow(vm->addr));
|
||||
}
|
||||
|
||||
extern void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip);
|
||||
|
||||
void kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
|
||||
{
|
||||
unsigned long flags = user_access_save();
|
||||
__kasan_report(addr, size, is_write, ip);
|
||||
user_access_restore(flags);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_MEMORY_HOTPLUG
|
||||
static bool shadow_mapped(unsigned long addr)
|
||||
{
|
||||
|
|
|
@ -281,8 +281,7 @@ void kasan_report_invalid_free(void *object, unsigned long ip)
|
|||
end_report(&flags);
|
||||
}
|
||||
|
||||
void kasan_report(unsigned long addr, size_t size,
|
||||
bool is_write, unsigned long ip)
|
||||
void __kasan_report(unsigned long addr, size_t size, bool is_write, unsigned long ip)
|
||||
{
|
||||
struct kasan_access_info info;
|
||||
void *tagged_addr;
|
||||
|
|
|
@ -222,6 +222,9 @@ endif
|
|||
ifdef CONFIG_RETPOLINE
|
||||
objtool_args += --retpoline
|
||||
endif
|
||||
ifdef CONFIG_X86_SMAP
|
||||
objtool_args += --uaccess
|
||||
endif
|
||||
|
||||
# 'OBJECT_FILES_NON_STANDARD := y': skip objtool checking for a directory
|
||||
# 'OBJECT_FILES_NON_STANDARD_foo.o := 'y': skip objtool checking for a file
|
||||
|
|
|
@ -33,7 +33,11 @@
|
|||
#define INSN_STACK 8
|
||||
#define INSN_BUG 9
|
||||
#define INSN_NOP 10
|
||||
#define INSN_OTHER 11
|
||||
#define INSN_STAC 11
|
||||
#define INSN_CLAC 12
|
||||
#define INSN_STD 13
|
||||
#define INSN_CLD 14
|
||||
#define INSN_OTHER 15
|
||||
#define INSN_LAST INSN_OTHER
|
||||
|
||||
enum op_dest_type {
|
||||
|
@ -41,6 +45,7 @@ enum op_dest_type {
|
|||
OP_DEST_REG_INDIRECT,
|
||||
OP_DEST_MEM,
|
||||
OP_DEST_PUSH,
|
||||
OP_DEST_PUSHF,
|
||||
OP_DEST_LEAVE,
|
||||
};
|
||||
|
||||
|
@ -55,6 +60,7 @@ enum op_src_type {
|
|||
OP_SRC_REG_INDIRECT,
|
||||
OP_SRC_CONST,
|
||||
OP_SRC_POP,
|
||||
OP_SRC_POPF,
|
||||
OP_SRC_ADD,
|
||||
OP_SRC_AND,
|
||||
};
|
||||
|
|
|
@ -357,19 +357,26 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,
|
|||
/* pushf */
|
||||
*type = INSN_STACK;
|
||||
op->src.type = OP_SRC_CONST;
|
||||
op->dest.type = OP_DEST_PUSH;
|
||||
op->dest.type = OP_DEST_PUSHF;
|
||||
break;
|
||||
|
||||
case 0x9d:
|
||||
/* popf */
|
||||
*type = INSN_STACK;
|
||||
op->src.type = OP_SRC_POP;
|
||||
op->src.type = OP_SRC_POPF;
|
||||
op->dest.type = OP_DEST_MEM;
|
||||
break;
|
||||
|
||||
case 0x0f:
|
||||
|
||||
if (op2 >= 0x80 && op2 <= 0x8f) {
|
||||
if (op2 == 0x01) {
|
||||
|
||||
if (modrm == 0xca)
|
||||
*type = INSN_CLAC;
|
||||
else if (modrm == 0xcb)
|
||||
*type = INSN_STAC;
|
||||
|
||||
} else if (op2 >= 0x80 && op2 <= 0x8f) {
|
||||
|
||||
*type = INSN_JUMP_CONDITIONAL;
|
||||
|
||||
|
@ -444,6 +451,14 @@ int arch_decode_instruction(struct elf *elf, struct section *sec,
|
|||
*type = INSN_CALL;
|
||||
break;
|
||||
|
||||
case 0xfc:
|
||||
*type = INSN_CLD;
|
||||
break;
|
||||
|
||||
case 0xfd:
|
||||
*type = INSN_STD;
|
||||
break;
|
||||
|
||||
case 0xff:
|
||||
if (modrm_reg == 2 || modrm_reg == 3)
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
#include "builtin.h"
|
||||
#include "check.h"
|
||||
|
||||
bool no_fp, no_unreachable, retpoline, module;
|
||||
bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess;
|
||||
|
||||
static const char * const check_usage[] = {
|
||||
"objtool check [<options>] file.o",
|
||||
|
@ -41,6 +41,8 @@ const struct option check_options[] = {
|
|||
OPT_BOOLEAN('u', "no-unreachable", &no_unreachable, "Skip 'unreachable instruction' warnings"),
|
||||
OPT_BOOLEAN('r', "retpoline", &retpoline, "Validate retpoline assumptions"),
|
||||
OPT_BOOLEAN('m', "module", &module, "Indicates the object will be part of a kernel module"),
|
||||
OPT_BOOLEAN('b', "backtrace", &backtrace, "unwind on error"),
|
||||
OPT_BOOLEAN('a', "uaccess", &uaccess, "enable uaccess checking"),
|
||||
OPT_END(),
|
||||
};
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
#include <subcmd/parse-options.h>
|
||||
|
||||
extern const struct option check_options[];
|
||||
extern bool no_fp, no_unreachable, retpoline, module;
|
||||
extern bool no_fp, no_unreachable, retpoline, module, backtrace, uaccess;
|
||||
|
||||
extern int cmd_check(int argc, const char **argv);
|
||||
extern int cmd_orc(int argc, const char **argv);
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
struct alternative {
|
||||
struct list_head list;
|
||||
struct instruction *insn;
|
||||
bool skip_orig;
|
||||
};
|
||||
|
||||
const char *objname;
|
||||
|
@ -104,29 +105,6 @@ static struct instruction *next_insn_same_func(struct objtool_file *file,
|
|||
for (insn = next_insn_same_sec(file, insn); insn; \
|
||||
insn = next_insn_same_sec(file, insn))
|
||||
|
||||
/*
|
||||
* Check if the function has been manually whitelisted with the
|
||||
* STACK_FRAME_NON_STANDARD macro, or if it should be automatically whitelisted
|
||||
* due to its use of a context switching instruction.
|
||||
*/
|
||||
static bool ignore_func(struct objtool_file *file, struct symbol *func)
|
||||
{
|
||||
struct rela *rela;
|
||||
|
||||
/* check for STACK_FRAME_NON_STANDARD */
|
||||
if (file->whitelist && file->whitelist->rela)
|
||||
list_for_each_entry(rela, &file->whitelist->rela->rela_list, list) {
|
||||
if (rela->sym->type == STT_SECTION &&
|
||||
rela->sym->sec == func->sec &&
|
||||
rela->addend == func->offset)
|
||||
return true;
|
||||
if (rela->sym->type == STT_FUNC && rela->sym == func)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* This checks to see if the given function is a "noreturn" function.
|
||||
*
|
||||
|
@ -437,18 +415,107 @@ static void add_ignores(struct objtool_file *file)
|
|||
struct instruction *insn;
|
||||
struct section *sec;
|
||||
struct symbol *func;
|
||||
struct rela *rela;
|
||||
|
||||
for_each_sec(file, sec) {
|
||||
list_for_each_entry(func, &sec->symbol_list, list) {
|
||||
if (func->type != STT_FUNC)
|
||||
continue;
|
||||
sec = find_section_by_name(file->elf, ".rela.discard.func_stack_frame_non_standard");
|
||||
if (!sec)
|
||||
return;
|
||||
|
||||
if (!ignore_func(file, func))
|
||||
list_for_each_entry(rela, &sec->rela_list, list) {
|
||||
switch (rela->sym->type) {
|
||||
case STT_FUNC:
|
||||
func = rela->sym;
|
||||
break;
|
||||
|
||||
case STT_SECTION:
|
||||
func = find_symbol_by_offset(rela->sym->sec, rela->addend);
|
||||
if (!func || func->type != STT_FUNC)
|
||||
continue;
|
||||
break;
|
||||
|
||||
default:
|
||||
WARN("unexpected relocation symbol type in %s: %d", sec->name, rela->sym->type);
|
||||
continue;
|
||||
}
|
||||
|
||||
func_for_each_insn_all(file, func, insn)
|
||||
insn->ignore = true;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This is a whitelist of functions that is allowed to be called with AC set.
|
||||
* The list is meant to be minimal and only contains compiler instrumentation
|
||||
* ABI and a few functions used to implement *_{to,from}_user() functions.
|
||||
*
|
||||
* These functions must not directly change AC, but may PUSHF/POPF.
|
||||
*/
|
||||
static const char *uaccess_safe_builtin[] = {
|
||||
/* KASAN */
|
||||
"kasan_report",
|
||||
"check_memory_region",
|
||||
/* KASAN out-of-line */
|
||||
"__asan_loadN_noabort",
|
||||
"__asan_load1_noabort",
|
||||
"__asan_load2_noabort",
|
||||
"__asan_load4_noabort",
|
||||
"__asan_load8_noabort",
|
||||
"__asan_load16_noabort",
|
||||
"__asan_storeN_noabort",
|
||||
"__asan_store1_noabort",
|
||||
"__asan_store2_noabort",
|
||||
"__asan_store4_noabort",
|
||||
"__asan_store8_noabort",
|
||||
"__asan_store16_noabort",
|
||||
/* KASAN in-line */
|
||||
"__asan_report_load_n_noabort",
|
||||
"__asan_report_load1_noabort",
|
||||
"__asan_report_load2_noabort",
|
||||
"__asan_report_load4_noabort",
|
||||
"__asan_report_load8_noabort",
|
||||
"__asan_report_load16_noabort",
|
||||
"__asan_report_store_n_noabort",
|
||||
"__asan_report_store1_noabort",
|
||||
"__asan_report_store2_noabort",
|
||||
"__asan_report_store4_noabort",
|
||||
"__asan_report_store8_noabort",
|
||||
"__asan_report_store16_noabort",
|
||||
/* KCOV */
|
||||
"write_comp_data",
|
||||
"__sanitizer_cov_trace_pc",
|
||||
"__sanitizer_cov_trace_const_cmp1",
|
||||
"__sanitizer_cov_trace_const_cmp2",
|
||||
"__sanitizer_cov_trace_const_cmp4",
|
||||
"__sanitizer_cov_trace_const_cmp8",
|
||||
"__sanitizer_cov_trace_cmp1",
|
||||
"__sanitizer_cov_trace_cmp2",
|
||||
"__sanitizer_cov_trace_cmp4",
|
||||
"__sanitizer_cov_trace_cmp8",
|
||||
/* UBSAN */
|
||||
"ubsan_type_mismatch_common",
|
||||
"__ubsan_handle_type_mismatch",
|
||||
"__ubsan_handle_type_mismatch_v1",
|
||||
/* misc */
|
||||
"csum_partial_copy_generic",
|
||||
"__memcpy_mcsafe",
|
||||
"ftrace_likely_update", /* CONFIG_TRACE_BRANCH_PROFILING */
|
||||
NULL
|
||||
};
|
||||
|
||||
static void add_uaccess_safe(struct objtool_file *file)
|
||||
{
|
||||
struct symbol *func;
|
||||
const char **name;
|
||||
|
||||
if (!uaccess)
|
||||
return;
|
||||
|
||||
for (name = uaccess_safe_builtin; *name; name++) {
|
||||
func = find_symbol_by_name(file->elf, *name);
|
||||
if (!func)
|
||||
continue;
|
||||
|
||||
func->alias->uaccess_safe = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -458,13 +525,13 @@ static void add_ignores(struct objtool_file *file)
|
|||
* But it at least allows objtool to understand the control flow *around* the
|
||||
* retpoline.
|
||||
*/
|
||||
static int add_nospec_ignores(struct objtool_file *file)
|
||||
static int add_ignore_alternatives(struct objtool_file *file)
|
||||
{
|
||||
struct section *sec;
|
||||
struct rela *rela;
|
||||
struct instruction *insn;
|
||||
|
||||
sec = find_section_by_name(file->elf, ".rela.discard.nospec");
|
||||
sec = find_section_by_name(file->elf, ".rela.discard.ignore_alts");
|
||||
if (!sec)
|
||||
return 0;
|
||||
|
||||
|
@ -476,7 +543,7 @@ static int add_nospec_ignores(struct objtool_file *file)
|
|||
|
||||
insn = find_insn(file, rela->sym->sec, rela->addend);
|
||||
if (!insn) {
|
||||
WARN("bad .discard.nospec entry");
|
||||
WARN("bad .discard.ignore_alts entry");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
@ -525,7 +592,8 @@ static int add_jump_destinations(struct objtool_file *file)
|
|||
continue;
|
||||
} else {
|
||||
/* sibling call */
|
||||
insn->jump_dest = 0;
|
||||
insn->call_dest = rela->sym;
|
||||
insn->jump_dest = NULL;
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -547,25 +615,38 @@ static int add_jump_destinations(struct objtool_file *file)
|
|||
}
|
||||
|
||||
/*
|
||||
* For GCC 8+, create parent/child links for any cold
|
||||
* subfunctions. This is _mostly_ redundant with a similar
|
||||
* initialization in read_symbols().
|
||||
*
|
||||
* If a function has aliases, we want the *first* such function
|
||||
* in the symbol table to be the subfunction's parent. In that
|
||||
* case we overwrite the initialization done in read_symbols().
|
||||
*
|
||||
* However this code can't completely replace the
|
||||
* read_symbols() code because this doesn't detect the case
|
||||
* where the parent function's only reference to a subfunction
|
||||
* is through a switch table.
|
||||
* Cross-function jump.
|
||||
*/
|
||||
if (insn->func && insn->jump_dest->func &&
|
||||
insn->func != insn->jump_dest->func &&
|
||||
!strstr(insn->func->name, ".cold.") &&
|
||||
insn->func != insn->jump_dest->func) {
|
||||
|
||||
/*
|
||||
* For GCC 8+, create parent/child links for any cold
|
||||
* subfunctions. This is _mostly_ redundant with a
|
||||
* similar initialization in read_symbols().
|
||||
*
|
||||
* If a function has aliases, we want the *first* such
|
||||
* function in the symbol table to be the subfunction's
|
||||
* parent. In that case we overwrite the
|
||||
* initialization done in read_symbols().
|
||||
*
|
||||
* However this code can't completely replace the
|
||||
* read_symbols() code because this doesn't detect the
|
||||
* case where the parent function's only reference to a
|
||||
* subfunction is through a switch table.
|
||||
*/
|
||||
if (!strstr(insn->func->name, ".cold.") &&
|
||||
strstr(insn->jump_dest->func->name, ".cold.")) {
|
||||
insn->func->cfunc = insn->jump_dest->func;
|
||||
insn->jump_dest->func->pfunc = insn->func;
|
||||
|
||||
} else if (insn->jump_dest->func->pfunc != insn->func->pfunc &&
|
||||
insn->jump_dest->offset == insn->jump_dest->func->offset) {
|
||||
|
||||
/* sibling class */
|
||||
insn->call_dest = insn->jump_dest->func;
|
||||
insn->jump_dest = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -634,9 +715,6 @@ static int add_call_destinations(struct objtool_file *file)
|
|||
* conditionally jumps to the _end_ of the entry. We have to modify these
|
||||
* jumps' destinations to point back to .text rather than the end of the
|
||||
* entry in .altinstr_replacement.
|
||||
*
|
||||
* 4. It has been requested that we don't validate the !POPCNT feature path
|
||||
* which is a "very very small percentage of machines".
|
||||
*/
|
||||
static int handle_group_alt(struct objtool_file *file,
|
||||
struct special_alt *special_alt,
|
||||
|
@ -652,9 +730,6 @@ static int handle_group_alt(struct objtool_file *file,
|
|||
if (insn->offset >= special_alt->orig_off + special_alt->orig_len)
|
||||
break;
|
||||
|
||||
if (special_alt->skip_orig)
|
||||
insn->type = INSN_NOP;
|
||||
|
||||
insn->alt_group = true;
|
||||
last_orig_insn = insn;
|
||||
}
|
||||
|
@ -696,6 +771,7 @@ static int handle_group_alt(struct objtool_file *file,
|
|||
last_new_insn = insn;
|
||||
|
||||
insn->ignore = orig_insn->ignore_alts;
|
||||
insn->func = orig_insn->func;
|
||||
|
||||
if (insn->type != INSN_JUMP_CONDITIONAL &&
|
||||
insn->type != INSN_JUMP_UNCONDITIONAL)
|
||||
|
@ -818,6 +894,8 @@ static int add_special_section_alts(struct objtool_file *file)
|
|||
}
|
||||
|
||||
alt->insn = new_insn;
|
||||
alt->skip_orig = special_alt->skip_orig;
|
||||
orig_insn->ignore_alts |= special_alt->skip_alt;
|
||||
list_add_tail(&alt->list, &orig_insn->alts);
|
||||
|
||||
list_del(&special_alt->list);
|
||||
|
@ -1239,8 +1317,9 @@ static int decode_sections(struct objtool_file *file)
|
|||
return ret;
|
||||
|
||||
add_ignores(file);
|
||||
add_uaccess_safe(file);
|
||||
|
||||
ret = add_nospec_ignores(file);
|
||||
ret = add_ignore_alternatives(file);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
|
@ -1320,11 +1399,11 @@ static int update_insn_state_regs(struct instruction *insn, struct insn_state *s
|
|||
return 0;
|
||||
|
||||
/* push */
|
||||
if (op->dest.type == OP_DEST_PUSH)
|
||||
if (op->dest.type == OP_DEST_PUSH || op->dest.type == OP_DEST_PUSHF)
|
||||
cfa->offset += 8;
|
||||
|
||||
/* pop */
|
||||
if (op->src.type == OP_SRC_POP)
|
||||
if (op->src.type == OP_SRC_POP || op->src.type == OP_SRC_POPF)
|
||||
cfa->offset -= 8;
|
||||
|
||||
/* add immediate to sp */
|
||||
|
@ -1581,6 +1660,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||
break;
|
||||
|
||||
case OP_SRC_POP:
|
||||
case OP_SRC_POPF:
|
||||
if (!state->drap && op->dest.type == OP_DEST_REG &&
|
||||
op->dest.reg == cfa->base) {
|
||||
|
||||
|
@ -1645,6 +1725,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||
break;
|
||||
|
||||
case OP_DEST_PUSH:
|
||||
case OP_DEST_PUSHF:
|
||||
state->stack_size += 8;
|
||||
if (cfa->base == CFI_SP)
|
||||
cfa->offset += 8;
|
||||
|
@ -1735,7 +1816,7 @@ static int update_insn_state(struct instruction *insn, struct insn_state *state)
|
|||
break;
|
||||
|
||||
case OP_DEST_MEM:
|
||||
if (op->src.type != OP_SRC_POP) {
|
||||
if (op->src.type != OP_SRC_POP && op->src.type != OP_SRC_POPF) {
|
||||
WARN_FUNC("unknown stack-related memory operation",
|
||||
insn->sec, insn->offset);
|
||||
return -1;
|
||||
|
@ -1799,6 +1880,50 @@ static bool insn_state_match(struct instruction *insn, struct insn_state *state)
|
|||
return false;
|
||||
}
|
||||
|
||||
static inline bool func_uaccess_safe(struct symbol *func)
|
||||
{
|
||||
if (func)
|
||||
return func->alias->uaccess_safe;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline const char *insn_dest_name(struct instruction *insn)
|
||||
{
|
||||
if (insn->call_dest)
|
||||
return insn->call_dest->name;
|
||||
|
||||
return "{dynamic}";
|
||||
}
|
||||
|
||||
static int validate_call(struct instruction *insn, struct insn_state *state)
|
||||
{
|
||||
if (state->uaccess && !func_uaccess_safe(insn->call_dest)) {
|
||||
WARN_FUNC("call to %s() with UACCESS enabled",
|
||||
insn->sec, insn->offset, insn_dest_name(insn));
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (state->df) {
|
||||
WARN_FUNC("call to %s() with DF set",
|
||||
insn->sec, insn->offset, insn_dest_name(insn));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int validate_sibling_call(struct instruction *insn, struct insn_state *state)
|
||||
{
|
||||
if (has_modified_stack_frame(state)) {
|
||||
WARN_FUNC("sibling call from callable instruction with modified stack frame",
|
||||
insn->sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
return validate_call(insn, state);
|
||||
}
|
||||
|
||||
/*
|
||||
* Follow the branch starting at the given instruction, and recursively follow
|
||||
* any other branches (jumps). Meanwhile, track the frame pointer state at
|
||||
|
@ -1844,6 +1969,8 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
if (!insn->hint && !insn_state_match(insn, &state))
|
||||
return 1;
|
||||
|
||||
/* If we were here with AC=0, but now have AC=1, go again */
|
||||
if (insn->state.uaccess || !state.uaccess)
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -1893,16 +2020,42 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
insn->visited = true;
|
||||
|
||||
if (!insn->ignore_alts) {
|
||||
bool skip_orig = false;
|
||||
|
||||
list_for_each_entry(alt, &insn->alts, list) {
|
||||
if (alt->skip_orig)
|
||||
skip_orig = true;
|
||||
|
||||
ret = validate_branch(file, alt->insn, state);
|
||||
if (ret)
|
||||
return 1;
|
||||
if (ret) {
|
||||
if (backtrace)
|
||||
BT_FUNC("(alt)", insn);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (skip_orig)
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (insn->type) {
|
||||
|
||||
case INSN_RETURN:
|
||||
if (state.uaccess && !func_uaccess_safe(func)) {
|
||||
WARN_FUNC("return with UACCESS enabled", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!state.uaccess && func_uaccess_safe(func)) {
|
||||
WARN_FUNC("return with UACCESS disabled from a UACCESS-safe function", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (state.df) {
|
||||
WARN_FUNC("return with DF set", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (func && has_modified_stack_frame(&state)) {
|
||||
WARN_FUNC("return with modified stack frame",
|
||||
sec, insn->offset);
|
||||
|
@ -1918,6 +2071,12 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
return 0;
|
||||
|
||||
case INSN_CALL:
|
||||
case INSN_CALL_DYNAMIC:
|
||||
ret = validate_call(insn, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
if (insn->type == INSN_CALL) {
|
||||
if (is_fentry_call(insn))
|
||||
break;
|
||||
|
||||
|
@ -1926,9 +2085,8 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
return 0;
|
||||
if (ret == -1)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* fallthrough */
|
||||
case INSN_CALL_DYNAMIC:
|
||||
if (!no_fp && func && !has_valid_stack_frame(&state)) {
|
||||
WARN_FUNC("call without frame pointer save/setup",
|
||||
sec, insn->offset);
|
||||
|
@ -1938,18 +2096,21 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
|
||||
case INSN_JUMP_CONDITIONAL:
|
||||
case INSN_JUMP_UNCONDITIONAL:
|
||||
if (insn->jump_dest &&
|
||||
if (func && !insn->jump_dest) {
|
||||
ret = validate_sibling_call(insn, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
} else if (insn->jump_dest &&
|
||||
(!func || !insn->jump_dest->func ||
|
||||
insn->jump_dest->func->pfunc == func)) {
|
||||
ret = validate_branch(file, insn->jump_dest,
|
||||
state);
|
||||
if (ret)
|
||||
return 1;
|
||||
|
||||
} else if (func && has_modified_stack_frame(&state)) {
|
||||
WARN_FUNC("sibling call from callable instruction with modified stack frame",
|
||||
sec, insn->offset);
|
||||
return 1;
|
||||
if (ret) {
|
||||
if (backtrace)
|
||||
BT_FUNC("(branch)", insn);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
if (insn->type == INSN_JUMP_UNCONDITIONAL)
|
||||
|
@ -1958,11 +2119,10 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
break;
|
||||
|
||||
case INSN_JUMP_DYNAMIC:
|
||||
if (func && list_empty(&insn->alts) &&
|
||||
has_modified_stack_frame(&state)) {
|
||||
WARN_FUNC("sibling call from callable instruction with modified stack frame",
|
||||
sec, insn->offset);
|
||||
return 1;
|
||||
if (func && list_empty(&insn->alts)) {
|
||||
ret = validate_sibling_call(insn, &state);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -1979,6 +2139,63 @@ static int validate_branch(struct objtool_file *file, struct instruction *first,
|
|||
if (update_insn_state(insn, &state))
|
||||
return 1;
|
||||
|
||||
if (insn->stack_op.dest.type == OP_DEST_PUSHF) {
|
||||
if (!state.uaccess_stack) {
|
||||
state.uaccess_stack = 1;
|
||||
} else if (state.uaccess_stack >> 31) {
|
||||
WARN_FUNC("PUSHF stack exhausted", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
state.uaccess_stack <<= 1;
|
||||
state.uaccess_stack |= state.uaccess;
|
||||
}
|
||||
|
||||
if (insn->stack_op.src.type == OP_SRC_POPF) {
|
||||
if (state.uaccess_stack) {
|
||||
state.uaccess = state.uaccess_stack & 1;
|
||||
state.uaccess_stack >>= 1;
|
||||
if (state.uaccess_stack == 1)
|
||||
state.uaccess_stack = 0;
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case INSN_STAC:
|
||||
if (state.uaccess) {
|
||||
WARN_FUNC("recursive UACCESS enable", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
state.uaccess = true;
|
||||
break;
|
||||
|
||||
case INSN_CLAC:
|
||||
if (!state.uaccess && insn->func) {
|
||||
WARN_FUNC("redundant UACCESS disable", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (func_uaccess_safe(func) && !state.uaccess_stack) {
|
||||
WARN_FUNC("UACCESS-safe disables UACCESS", sec, insn->offset);
|
||||
return 1;
|
||||
}
|
||||
|
||||
state.uaccess = false;
|
||||
break;
|
||||
|
||||
case INSN_STD:
|
||||
if (state.df)
|
||||
WARN_FUNC("recursive STD", sec, insn->offset);
|
||||
|
||||
state.df = true;
|
||||
break;
|
||||
|
||||
case INSN_CLD:
|
||||
if (!state.df && insn->func)
|
||||
WARN_FUNC("redundant CLD", sec, insn->offset);
|
||||
|
||||
state.df = false;
|
||||
break;
|
||||
|
||||
default:
|
||||
|
@ -2015,6 +2232,8 @@ static int validate_unwind_hints(struct objtool_file *file)
|
|||
for_each_insn(file, insn) {
|
||||
if (insn->hint && !insn->visited) {
|
||||
ret = validate_branch(file, insn, state);
|
||||
if (ret && backtrace)
|
||||
BT_FUNC("<=== (hint)", insn);
|
||||
warnings += ret;
|
||||
}
|
||||
}
|
||||
|
@ -2142,7 +2361,11 @@ static int validate_functions(struct objtool_file *file)
|
|||
if (!insn || insn->ignore)
|
||||
continue;
|
||||
|
||||
state.uaccess = func->alias->uaccess_safe;
|
||||
|
||||
ret = validate_branch(file, insn, state);
|
||||
if (ret && backtrace)
|
||||
BT_FUNC("<=== (func)", insn);
|
||||
warnings += ret;
|
||||
}
|
||||
}
|
||||
|
@ -2199,7 +2422,6 @@ int check(const char *_objname, bool orc)
|
|||
|
||||
INIT_LIST_HEAD(&file.insn_list);
|
||||
hash_init(file.insn_hash);
|
||||
file.whitelist = find_section_by_name(file.elf, ".discard.func_stack_frame_non_standard");
|
||||
file.c_file = find_section_by_name(file.elf, ".comment");
|
||||
file.ignore_unreachables = no_unreachable;
|
||||
file.hints = false;
|
||||
|
|
|
@ -31,7 +31,8 @@ struct insn_state {
|
|||
int stack_size;
|
||||
unsigned char type;
|
||||
bool bp_scratch;
|
||||
bool drap, end;
|
||||
bool drap, end, uaccess, df;
|
||||
unsigned int uaccess_stack;
|
||||
int drap_reg, drap_offset;
|
||||
struct cfi_reg vals[CFI_NUM_REGS];
|
||||
};
|
||||
|
@ -60,7 +61,6 @@ struct objtool_file {
|
|||
struct elf *elf;
|
||||
struct list_head insn_list;
|
||||
DECLARE_HASHTABLE(insn_hash, 16);
|
||||
struct section *whitelist;
|
||||
bool ignore_unreachables, c_file, hints, rodata;
|
||||
};
|
||||
|
||||
|
|
|
@ -219,7 +219,7 @@ static int read_sections(struct elf *elf)
|
|||
static int read_symbols(struct elf *elf)
|
||||
{
|
||||
struct section *symtab, *sec;
|
||||
struct symbol *sym, *pfunc;
|
||||
struct symbol *sym, *pfunc, *alias;
|
||||
struct list_head *entry, *tmp;
|
||||
int symbols_nr, i;
|
||||
char *coldstr;
|
||||
|
@ -239,6 +239,7 @@ static int read_symbols(struct elf *elf)
|
|||
return -1;
|
||||
}
|
||||
memset(sym, 0, sizeof(*sym));
|
||||
alias = sym;
|
||||
|
||||
sym->idx = i;
|
||||
|
||||
|
@ -288,11 +289,17 @@ static int read_symbols(struct elf *elf)
|
|||
break;
|
||||
}
|
||||
|
||||
if (sym->offset == s->offset && sym->len >= s->len) {
|
||||
if (sym->offset == s->offset) {
|
||||
if (sym->len == s->len && alias == sym)
|
||||
alias = s;
|
||||
|
||||
if (sym->len >= s->len) {
|
||||
entry = tmp;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
sym->alias = alias;
|
||||
list_add(&sym->list, entry);
|
||||
hash_add(sym->sec->symbol_hash, &sym->hash, sym->idx);
|
||||
}
|
||||
|
|
|
@ -61,7 +61,8 @@ struct symbol {
|
|||
unsigned char bind, type;
|
||||
unsigned long offset;
|
||||
unsigned int len;
|
||||
struct symbol *pfunc, *cfunc;
|
||||
struct symbol *pfunc, *cfunc, *alias;
|
||||
bool uaccess_safe;
|
||||
};
|
||||
|
||||
struct rela {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "builtin.h"
|
||||
#include "special.h"
|
||||
#include "warn.h"
|
||||
|
||||
|
@ -42,6 +43,7 @@
|
|||
#define ALT_NEW_LEN_OFFSET 11
|
||||
|
||||
#define X86_FEATURE_POPCNT (4*32+23)
|
||||
#define X86_FEATURE_SMAP (9*32+20)
|
||||
|
||||
struct special_entry {
|
||||
const char *sec;
|
||||
|
@ -110,6 +112,22 @@ static int get_alt_entry(struct elf *elf, struct special_entry *entry,
|
|||
*/
|
||||
if (feature == X86_FEATURE_POPCNT)
|
||||
alt->skip_orig = true;
|
||||
|
||||
/*
|
||||
* If UACCESS validation is enabled; force that alternative;
|
||||
* otherwise force it the other way.
|
||||
*
|
||||
* What we want to avoid is having both the original and the
|
||||
* alternative code flow at the same time, in that case we can
|
||||
* find paths that see the STAC but take the NOP instead of
|
||||
* CLAC and the other way around.
|
||||
*/
|
||||
if (feature == X86_FEATURE_SMAP) {
|
||||
if (uaccess)
|
||||
alt->skip_orig = true;
|
||||
else
|
||||
alt->skip_alt = true;
|
||||
}
|
||||
}
|
||||
|
||||
orig_rela = find_rela_by_dest(sec, offset + entry->orig);
|
||||
|
|
|
@ -26,6 +26,7 @@ struct special_alt {
|
|||
|
||||
bool group;
|
||||
bool skip_orig;
|
||||
bool skip_alt;
|
||||
bool jump_or_nop;
|
||||
|
||||
struct section *orig_sec;
|
||||
|
|
|
@ -64,6 +64,14 @@ static inline char *offstr(struct section *sec, unsigned long offset)
|
|||
free(_str); \
|
||||
})
|
||||
|
||||
#define BT_FUNC(format, insn, ...) \
|
||||
({ \
|
||||
struct instruction *_insn = (insn); \
|
||||
char *_str = offstr(_insn->sec, _insn->offset); \
|
||||
WARN(" %s: " format, _str, ##__VA_ARGS__); \
|
||||
free(_str); \
|
||||
})
|
||||
|
||||
#define WARN_ELF(format, ...) \
|
||||
WARN(format ": %s", ##__VA_ARGS__, elf_errmsg(-1))
|
||||
|
||||
|
|
Loading…
Reference in New Issue