Commit 3ae36655 authored by Andy Lutomirski's avatar Andy Lutomirski Committed by H. Peter Anvin

x86-64: Rework vsyscall emulation and add vsyscall= parameter

There are three choices:

vsyscall=native: Vsyscalls are native code that issues the
corresponding syscalls.

vsyscall=emulate (default): Vsyscalls are emulated by instruction
fault traps, tested in the bad_area path.  The actual contents of
the vsyscall page is the same as the vsyscall=native case except
that it's marked NX.  This way programs that make assumptions about
what the code in the page does will not be confused when they read
that code.

vsyscall=none: Trying to execute a vsyscall will segfault.
Signed-off-by: default avatarAndy Lutomirski <luto@mit.edu>
Link: http://lkml.kernel.org/r/8449fb3abf89851fd6b2260972666a6f82542284.1312988155.git.luto@mit.eduSigned-off-by: default avatarH. Peter Anvin <hpa@linux.intel.com>
parent fce8dc06
...@@ -2657,6 +2657,27 @@ bytes respectively. Such letter suffixes can also be entirely omitted. ...@@ -2657,6 +2657,27 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
vmpoff= [KNL,S390] Perform z/VM CP command after power off. vmpoff= [KNL,S390] Perform z/VM CP command after power off.
Format: <command> Format: <command>
vsyscall= [X86-64]
Controls the behavior of vsyscalls (i.e. calls to
fixed addresses of 0xffffffffff600x00 from legacy
code). Most statically-linked binaries and older
versions of glibc use these calls. Because these
functions are at fixed addresses, they make nice
targets for exploits that can control RIP.
emulate [default] Vsyscalls turn into traps and are
emulated reasonably safely.
native Vsyscalls are native syscall instructions.
This is a little bit faster than trapping
and makes a few dynamic recompilers work
better than they would in emulation mode.
It also makes exploits much easier to write.
none Vsyscalls don't work at all. This makes
them quite hard to use for exploits but
might break your system.
vt.cur_default= [VT] Default cursor shape. vt.cur_default= [VT] Default cursor shape.
Format: 0xCCBBAA, where AA, BB, and CC are the same as Format: 0xCCBBAA, where AA, BB, and CC are the same as
the parameters of the <Esc>[?A;B;Cc escape sequence; the parameters of the <Esc>[?A;B;Cc escape sequence;
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
* Vectors 0 ... 31 : system traps and exceptions - hardcoded events * Vectors 0 ... 31 : system traps and exceptions - hardcoded events
* Vectors 32 ... 127 : device interrupts * Vectors 32 ... 127 : device interrupts
* Vector 128 : legacy int80 syscall interface * Vector 128 : legacy int80 syscall interface
* Vector 204 : legacy x86_64 vsyscall emulation
* Vectors 129 ... INVALIDATE_TLB_VECTOR_START-1 except 204 : device interrupts * Vectors 129 ... INVALIDATE_TLB_VECTOR_START-1 except 204 : device interrupts
* Vectors INVALIDATE_TLB_VECTOR_START ... 255 : special interrupts * Vectors INVALIDATE_TLB_VECTOR_START ... 255 : special interrupts
* *
...@@ -51,9 +50,6 @@ ...@@ -51,9 +50,6 @@
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
# define SYSCALL_VECTOR 0x80 # define SYSCALL_VECTOR 0x80
#endif #endif
#ifdef CONFIG_X86_64
# define VSYSCALL_EMU_VECTOR 0xcc
#endif
/* /*
* Vectors 0x30-0x3f are used for ISA interrupts. * Vectors 0x30-0x3f are used for ISA interrupts.
......
...@@ -40,7 +40,6 @@ asmlinkage void alignment_check(void); ...@@ -40,7 +40,6 @@ asmlinkage void alignment_check(void);
asmlinkage void machine_check(void); asmlinkage void machine_check(void);
#endif /* CONFIG_X86_MCE */ #endif /* CONFIG_X86_MCE */
asmlinkage void simd_coprocessor_error(void); asmlinkage void simd_coprocessor_error(void);
asmlinkage void emulate_vsyscall(void);
dotraplinkage void do_divide_error(struct pt_regs *, long); dotraplinkage void do_divide_error(struct pt_regs *, long);
dotraplinkage void do_debug(struct pt_regs *, long); dotraplinkage void do_debug(struct pt_regs *, long);
...@@ -67,7 +66,6 @@ dotraplinkage void do_alignment_check(struct pt_regs *, long); ...@@ -67,7 +66,6 @@ dotraplinkage void do_alignment_check(struct pt_regs *, long);
dotraplinkage void do_machine_check(struct pt_regs *, long); dotraplinkage void do_machine_check(struct pt_regs *, long);
#endif #endif
dotraplinkage void do_simd_coprocessor_error(struct pt_regs *, long); dotraplinkage void do_simd_coprocessor_error(struct pt_regs *, long);
dotraplinkage void do_emulate_vsyscall(struct pt_regs *, long);
#ifdef CONFIG_X86_32 #ifdef CONFIG_X86_32
dotraplinkage void do_iret_error(struct pt_regs *, long); dotraplinkage void do_iret_error(struct pt_regs *, long);
#endif #endif
......
...@@ -27,6 +27,12 @@ extern struct timezone sys_tz; ...@@ -27,6 +27,12 @@ extern struct timezone sys_tz;
extern void map_vsyscall(void); extern void map_vsyscall(void);
/*
* Called on instruction fetch fault in vsyscall page.
* Returns true if handled.
*/
extern bool emulate_vsyscall(struct pt_regs *regs, unsigned long address);
#endif /* __KERNEL__ */ #endif /* __KERNEL__ */
#endif /* _ASM_X86_VSYSCALL_H */ #endif /* _ASM_X86_VSYSCALL_H */
...@@ -1123,7 +1123,6 @@ zeroentry spurious_interrupt_bug do_spurious_interrupt_bug ...@@ -1123,7 +1123,6 @@ zeroentry spurious_interrupt_bug do_spurious_interrupt_bug
zeroentry coprocessor_error do_coprocessor_error zeroentry coprocessor_error do_coprocessor_error
errorentry alignment_check do_alignment_check errorentry alignment_check do_alignment_check
zeroentry simd_coprocessor_error do_simd_coprocessor_error zeroentry simd_coprocessor_error do_simd_coprocessor_error
zeroentry emulate_vsyscall do_emulate_vsyscall
/* Reload gs selector with exception handling */ /* Reload gs selector with exception handling */
......
...@@ -872,12 +872,6 @@ void __init trap_init(void) ...@@ -872,12 +872,6 @@ void __init trap_init(void)
set_bit(SYSCALL_VECTOR, used_vectors); set_bit(SYSCALL_VECTOR, used_vectors);
#endif #endif
#ifdef CONFIG_X86_64
BUG_ON(test_bit(VSYSCALL_EMU_VECTOR, used_vectors));
set_system_intr_gate(VSYSCALL_EMU_VECTOR, &emulate_vsyscall);
set_bit(VSYSCALL_EMU_VECTOR, used_vectors);
#endif
/* /*
* Should be a barrier for any external CPU state: * Should be a barrier for any external CPU state:
*/ */
......
...@@ -71,7 +71,6 @@ PHDRS { ...@@ -71,7 +71,6 @@ PHDRS {
text PT_LOAD FLAGS(5); /* R_E */ text PT_LOAD FLAGS(5); /* R_E */
data PT_LOAD FLAGS(6); /* RW_ */ data PT_LOAD FLAGS(6); /* RW_ */
#ifdef CONFIG_X86_64 #ifdef CONFIG_X86_64
user PT_LOAD FLAGS(5); /* R_E */
#ifdef CONFIG_SMP #ifdef CONFIG_SMP
percpu PT_LOAD FLAGS(6); /* RW_ */ percpu PT_LOAD FLAGS(6); /* RW_ */
#endif #endif
...@@ -174,38 +173,6 @@ SECTIONS ...@@ -174,38 +173,6 @@ SECTIONS
. = ALIGN(__vvar_page + PAGE_SIZE, PAGE_SIZE); . = ALIGN(__vvar_page + PAGE_SIZE, PAGE_SIZE);
#define VSYSCALL_ADDR (-10*1024*1024)
#define VLOAD_OFFSET (VSYSCALL_ADDR - __vsyscall_0 + LOAD_OFFSET)
#define VLOAD(x) (ADDR(x) - VLOAD_OFFSET)
#define VVIRT_OFFSET (VSYSCALL_ADDR - __vsyscall_0)
#define VVIRT(x) (ADDR(x) - VVIRT_OFFSET)
__vsyscall_0 = .;
. = VSYSCALL_ADDR;
.vsyscall : AT(VLOAD(.vsyscall)) {
/* work around gold bug 13023 */
__vsyscall_beginning_hack = .;
*(.vsyscall_0)
. = __vsyscall_beginning_hack + 1024;
*(.vsyscall_1)
. = __vsyscall_beginning_hack + 2048;
*(.vsyscall_2)
. = __vsyscall_beginning_hack + 4096; /* Pad the whole page. */
} :user =0xcc
. = ALIGN(__vsyscall_0 + PAGE_SIZE, PAGE_SIZE);
#undef VSYSCALL_ADDR
#undef VLOAD_OFFSET
#undef VLOAD
#undef VVIRT_OFFSET
#undef VVIRT
#endif /* CONFIG_X86_64 */ #endif /* CONFIG_X86_64 */
/* Init code and data - will be freed after init */ /* Init code and data - will be freed after init */
......
...@@ -56,6 +56,27 @@ DEFINE_VVAR(struct vsyscall_gtod_data, vsyscall_gtod_data) = ...@@ -56,6 +56,27 @@ DEFINE_VVAR(struct vsyscall_gtod_data, vsyscall_gtod_data) =
.lock = __SEQLOCK_UNLOCKED(__vsyscall_gtod_data.lock), .lock = __SEQLOCK_UNLOCKED(__vsyscall_gtod_data.lock),
}; };
static enum { EMULATE, NATIVE, NONE } vsyscall_mode = EMULATE;
static int __init vsyscall_setup(char *str)
{
if (str) {
if (!strcmp("emulate", str))
vsyscall_mode = EMULATE;
else if (!strcmp("native", str))
vsyscall_mode = NATIVE;
else if (!strcmp("none", str))
vsyscall_mode = NONE;
else
return -EINVAL;
return 0;
}
return -EINVAL;
}
early_param("vsyscall", vsyscall_setup);
void update_vsyscall_tz(void) void update_vsyscall_tz(void)
{ {
unsigned long flags; unsigned long flags;
...@@ -100,7 +121,7 @@ static void warn_bad_vsyscall(const char *level, struct pt_regs *regs, ...@@ -100,7 +121,7 @@ static void warn_bad_vsyscall(const char *level, struct pt_regs *regs,
printk("%s%s[%d] %s ip:%lx cs:%lx sp:%lx ax:%lx si:%lx di:%lx\n", printk("%s%s[%d] %s ip:%lx cs:%lx sp:%lx ax:%lx si:%lx di:%lx\n",
level, tsk->comm, task_pid_nr(tsk), level, tsk->comm, task_pid_nr(tsk),
message, regs->ip - 2, regs->cs, message, regs->ip, regs->cs,
regs->sp, regs->ax, regs->si, regs->di); regs->sp, regs->ax, regs->si, regs->di);
} }
...@@ -118,45 +139,39 @@ static int addr_to_vsyscall_nr(unsigned long addr) ...@@ -118,45 +139,39 @@ static int addr_to_vsyscall_nr(unsigned long addr)
return nr; return nr;
} }
void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
{ {
struct task_struct *tsk; struct task_struct *tsk;
unsigned long caller; unsigned long caller;
int vsyscall_nr; int vsyscall_nr;
long ret; long ret;
local_irq_enable(); /*
* No point in checking CS -- the only way to get here is a user mode
* trap to a high address, which means that we're in 64-bit user code.
*/
if (!user_64bit_mode(regs)) { WARN_ON_ONCE(address != regs->ip);
/*
* If we trapped from kernel mode, we might as well OOPS now
* instead of returning to some random address and OOPSing
* then.
*/
BUG_ON(!user_mode(regs));
/* Compat mode and non-compat 32-bit CS should both segfault. */ if (vsyscall_mode == NONE) {
warn_bad_vsyscall(KERN_WARNING, regs, warn_bad_vsyscall(KERN_INFO, regs,
"illegal int 0xcc from 32-bit mode"); "vsyscall attempted with vsyscall=none");
goto sigsegv; return false;
} }
/* vsyscall_nr = addr_to_vsyscall_nr(address);
* x86-ism here: regs->ip points to the instruction after the int 0xcc,
* and int 0xcc is two bytes long.
*/
vsyscall_nr = addr_to_vsyscall_nr(regs->ip - 2);
trace_emulate_vsyscall(vsyscall_nr); trace_emulate_vsyscall(vsyscall_nr);
if (vsyscall_nr < 0) { if (vsyscall_nr < 0) {
warn_bad_vsyscall(KERN_WARNING, regs, warn_bad_vsyscall(KERN_WARNING, regs,
"illegal int 0xcc (exploit attempt?)"); "misaligned vsyscall (exploit attempt or buggy program) -- look up the vsyscall kernel parameter if you need a workaround");
goto sigsegv; goto sigsegv;
} }
if (get_user(caller, (unsigned long __user *)regs->sp) != 0) { if (get_user(caller, (unsigned long __user *)regs->sp) != 0) {
warn_bad_vsyscall(KERN_WARNING, regs, "int 0xcc with bad stack (exploit attempt?)"); warn_bad_vsyscall(KERN_WARNING, regs,
"vsyscall with bad stack (exploit attempt?)");
goto sigsegv; goto sigsegv;
} }
...@@ -201,13 +216,11 @@ void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) ...@@ -201,13 +216,11 @@ void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code)
regs->ip = caller; regs->ip = caller;
regs->sp += 8; regs->sp += 8;
local_irq_disable(); return true;
return;
sigsegv: sigsegv:
regs->ip -= 2; /* The faulting instruction should be the int 0xcc. */
force_sig(SIGSEGV, current); force_sig(SIGSEGV, current);
local_irq_disable(); return true;
} }
/* /*
...@@ -255,15 +268,21 @@ cpu_vsyscall_notifier(struct notifier_block *n, unsigned long action, void *arg) ...@@ -255,15 +268,21 @@ cpu_vsyscall_notifier(struct notifier_block *n, unsigned long action, void *arg)
void __init map_vsyscall(void) void __init map_vsyscall(void)
{ {
extern char __vsyscall_0; extern char __vsyscall_page;
unsigned long physaddr_page0 = __pa_symbol(&__vsyscall_0); unsigned long physaddr_vsyscall = __pa_symbol(&__vsyscall_page);
extern char __vvar_page; extern char __vvar_page;
unsigned long physaddr_vvar_page = __pa_symbol(&__vvar_page); unsigned long physaddr_vvar_page = __pa_symbol(&__vvar_page);
/* Note that VSYSCALL_MAPPED_PAGES must agree with the code below. */ __set_fixmap(VSYSCALL_FIRST_PAGE, physaddr_vsyscall,
__set_fixmap(VSYSCALL_FIRST_PAGE, physaddr_page0, PAGE_KERNEL_VSYSCALL); vsyscall_mode == NATIVE
? PAGE_KERNEL_VSYSCALL
: PAGE_KERNEL_VVAR);
BUILD_BUG_ON((unsigned long)__fix_to_virt(VSYSCALL_FIRST_PAGE) !=
(unsigned long)VSYSCALL_START);
__set_fixmap(VVAR_PAGE, physaddr_vvar_page, PAGE_KERNEL_VVAR); __set_fixmap(VVAR_PAGE, physaddr_vvar_page, PAGE_KERNEL_VVAR);
BUILD_BUG_ON((unsigned long)__fix_to_virt(VVAR_PAGE) != (unsigned long)VVAR_ADDRESS); BUILD_BUG_ON((unsigned long)__fix_to_virt(VVAR_PAGE) !=
(unsigned long)VVAR_ADDRESS);
} }
static int __init vsyscall_init(void) static int __init vsyscall_init(void)
......
...@@ -7,21 +7,31 @@ ...@@ -7,21 +7,31 @@
*/ */
#include <linux/linkage.h> #include <linux/linkage.h>
#include <asm/irq_vectors.h> #include <asm/irq_vectors.h>
#include <asm/page_types.h>
#include <asm/unistd_64.h>
__PAGE_ALIGNED_DATA
.globl __vsyscall_page
.balign PAGE_SIZE, 0xcc
.type __vsyscall_page, @object
__vsyscall_page:
mov $__NR_gettimeofday, %rax
syscall
ret
/* The unused parts of the page are filled with 0xcc by the linker script. */ .balign 1024, 0xcc
mov $__NR_time, %rax
syscall
ret
.section .vsyscall_0, "a" .balign 1024, 0xcc
ENTRY(vsyscall_0) mov $__NR_getcpu, %rax
int $VSYSCALL_EMU_VECTOR syscall
END(vsyscall_0) ret
.section .vsyscall_1, "a" .balign 4096, 0xcc
ENTRY(vsyscall_1)
int $VSYSCALL_EMU_VECTOR
END(vsyscall_1)
.section .vsyscall_2, "a" .size __vsyscall_page, 4096
ENTRY(vsyscall_2)
int $VSYSCALL_EMU_VECTOR
END(vsyscall_2)
...@@ -720,6 +720,18 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code, ...@@ -720,6 +720,18 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
if (is_errata100(regs, address)) if (is_errata100(regs, address))
return; return;
#ifdef CONFIG_X86_64
/*
* Instruction fetch faults in the vsyscall page might need
* emulation.
*/
if (unlikely((error_code & PF_INSTR) &&
((address & ~0xfff) == VSYSCALL_START))) {
if (emulate_vsyscall(regs, address))
return;
}
#endif
if (unlikely(show_unhandled_signals)) if (unlikely(show_unhandled_signals))
show_signal_msg(regs, error_code, address, tsk); show_signal_msg(regs, error_code, address, tsk);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment