diff options
Diffstat (limited to 'arch/x86/kernel')
| -rw-r--r-- | arch/x86/kernel/asm-offsets_64.c | 6 | ||||
| -rw-r--r-- | arch/x86/kernel/cpu/mtrr/if.c | 10 | ||||
| -rw-r--r-- | arch/x86/kernel/cpu/perf_event.c | 4 | ||||
| -rw-r--r-- | arch/x86/kernel/entry_64.S | 44 | ||||
| -rw-r--r-- | arch/x86/kernel/process_64.c | 27 | ||||
| -rw-r--r-- | arch/x86/kernel/ptrace.c | 99 | ||||
| -rw-r--r-- | arch/x86/kernel/signal.c | 138 | ||||
| -rw-r--r-- | arch/x86/kernel/sys_x86_64.c | 6 | ||||
| -rw-r--r-- | arch/x86/kernel/syscall_64.c | 8 | 
9 files changed, 312 insertions, 30 deletions
diff --git a/arch/x86/kernel/asm-offsets_64.c b/arch/x86/kernel/asm-offsets_64.c index 834e897b1e2..1b4754f82ba 100644 --- a/arch/x86/kernel/asm-offsets_64.c +++ b/arch/x86/kernel/asm-offsets_64.c @@ -1,6 +1,12 @@  #include <asm/ia32.h>  #define __SYSCALL_64(nr, sym, compat) [nr] = 1, +#define __SYSCALL_COMMON(nr, sym, compat) [nr] = 1, +#ifdef CONFIG_X86_X32_ABI +# define __SYSCALL_X32(nr, sym, compat) [nr] = 1, +#else +# define __SYSCALL_X32(nr, sym, compat) /* nothing */ +#endif  static char syscalls_64[] = {  #include <asm/syscalls_64.h>  }; diff --git a/arch/x86/kernel/cpu/mtrr/if.c b/arch/x86/kernel/cpu/mtrr/if.c index 79289632cb2..a041e094b8b 100644 --- a/arch/x86/kernel/cpu/mtrr/if.c +++ b/arch/x86/kernel/cpu/mtrr/if.c @@ -167,6 +167,7 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)  {  	int err = 0;  	mtrr_type type; +	unsigned long base;  	unsigned long size;  	struct mtrr_sentry sentry;  	struct mtrr_gentry gentry; @@ -267,14 +268,14 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)  #endif  		if (gentry.regnum >= num_var_ranges)  			return -EINVAL; -		mtrr_if->get(gentry.regnum, &gentry.base, &size, &type); +		mtrr_if->get(gentry.regnum, &base, &size, &type);  		/* Hide entries that go above 4GB */ -		if (gentry.base + size - 1 >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT)) +		if (base + size - 1 >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT))  		    || size >= (1UL << (8 * sizeof(gentry.size) - PAGE_SHIFT)))  			gentry.base = gentry.size = gentry.type = 0;  		else { -			gentry.base <<= PAGE_SHIFT; +			gentry.base = base << PAGE_SHIFT;  			gentry.size = size << PAGE_SHIFT;  			gentry.type = type;  		} @@ -321,11 +322,12 @@ mtrr_ioctl(struct file *file, unsigned int cmd, unsigned long __arg)  #endif  		if (gentry.regnum >= num_var_ranges)  			return -EINVAL; -		mtrr_if->get(gentry.regnum, &gentry.base, &size, &type); +		mtrr_if->get(gentry.regnum, &base, &size, &type);  		/* Hide entries that would overflow */  		if (size != (__typeof__(gentry.size))size)  			gentry.base = gentry.size = gentry.type = 0;  		else { +			gentry.base = base;  			gentry.size = size;  			gentry.type = type;  		} diff --git a/arch/x86/kernel/cpu/perf_event.c b/arch/x86/kernel/cpu/perf_event.c index 5adce1040b1..63c0e058a40 100644 --- a/arch/x86/kernel/cpu/perf_event.c +++ b/arch/x86/kernel/cpu/perf_event.c @@ -28,7 +28,6 @@  #include <asm/apic.h>  #include <asm/stacktrace.h>  #include <asm/nmi.h> -#include <asm/compat.h>  #include <asm/smp.h>  #include <asm/alternative.h> @@ -1595,6 +1594,9 @@ perf_callchain_kernel(struct perf_callchain_entry *entry, struct pt_regs *regs)  }  #ifdef CONFIG_COMPAT + +#include <asm/compat.h> +  static inline int  perf_callchain_user32(struct pt_regs *regs, struct perf_callchain_entry *entry)  { diff --git a/arch/x86/kernel/entry_64.S b/arch/x86/kernel/entry_64.S index 1333d985177..2925e14fb1d 100644 --- a/arch/x86/kernel/entry_64.S +++ b/arch/x86/kernel/entry_64.S @@ -482,7 +482,12 @@ GLOBAL(system_call_after_swapgs)  	testl $_TIF_WORK_SYSCALL_ENTRY,TI_flags+THREAD_INFO(%rsp,RIP-ARGOFFSET)  	jnz tracesys  system_call_fastpath: +#if __SYSCALL_MASK == ~0  	cmpq $__NR_syscall_max,%rax +#else +	andl $__SYSCALL_MASK,%eax +	cmpl $__NR_syscall_max,%eax +#endif  	ja badsys  	movq %r10,%rcx  	call *sys_call_table(,%rax,8)  # XXX:	 rip relative @@ -596,7 +601,12 @@ tracesys:  	 */  	LOAD_ARGS ARGOFFSET, 1  	RESTORE_REST +#if __SYSCALL_MASK == ~0  	cmpq $__NR_syscall_max,%rax +#else +	andl $__SYSCALL_MASK,%eax +	cmpl $__NR_syscall_max,%eax +#endif  	ja   int_ret_from_sys_call	/* RAX(%rsp) set to -ENOSYS above */  	movq %r10,%rcx	/* fixup for C */  	call *sys_call_table(,%rax,8) @@ -736,6 +746,40 @@ ENTRY(stub_rt_sigreturn)  	CFI_ENDPROC  END(stub_rt_sigreturn) +#ifdef CONFIG_X86_X32_ABI +	PTREGSCALL stub_x32_sigaltstack, sys32_sigaltstack, %rdx + +ENTRY(stub_x32_rt_sigreturn) +	CFI_STARTPROC +	addq $8, %rsp +	PARTIAL_FRAME 0 +	SAVE_REST +	movq %rsp,%rdi +	FIXUP_TOP_OF_STACK %r11 +	call sys32_x32_rt_sigreturn +	movq %rax,RAX(%rsp) # fixme, this could be done at the higher layer +	RESTORE_REST +	jmp int_ret_from_sys_call +	CFI_ENDPROC +END(stub_x32_rt_sigreturn) + +ENTRY(stub_x32_execve) +	CFI_STARTPROC +	addq $8, %rsp +	PARTIAL_FRAME 0 +	SAVE_REST +	FIXUP_TOP_OF_STACK %r11 +	movq %rsp, %rcx +	call sys32_execve +	RESTORE_TOP_OF_STACK %r11 +	movq %rax,RAX(%rsp) +	RESTORE_REST +	jmp int_ret_from_sys_call +	CFI_ENDPROC +END(stub_x32_execve) + +#endif +  /*   * Build the entry stubs and pointer table with some assembler magic.   * We pack 7 stubs into a single 32-byte chunk, which will fit in a diff --git a/arch/x86/kernel/process_64.c b/arch/x86/kernel/process_64.c index cfa5c90c01d..550e77b1b94 100644 --- a/arch/x86/kernel/process_64.c +++ b/arch/x86/kernel/process_64.c @@ -365,7 +365,9 @@ start_thread(struct pt_regs *regs, unsigned long new_ip, unsigned long new_sp)  void start_thread_ia32(struct pt_regs *regs, u32 new_ip, u32 new_sp)  {  	start_thread_common(regs, new_ip, new_sp, -			    __USER32_CS, __USER32_DS, __USER32_DS); +			    test_thread_flag(TIF_X32) +			    ? __USER_CS : __USER32_CS, +			    __USER_DS, __USER_DS);  }  #endif @@ -488,6 +490,8 @@ void set_personality_64bit(void)  	/* Make sure to be in 64bit mode */  	clear_thread_flag(TIF_IA32); +	clear_thread_flag(TIF_ADDR32); +	clear_thread_flag(TIF_X32);  	/* Ensure the corresponding mm is not marked. */  	if (current->mm) @@ -500,20 +504,31 @@ void set_personality_64bit(void)  	current->personality &= ~READ_IMPLIES_EXEC;  } -void set_personality_ia32(void) +void set_personality_ia32(bool x32)  {  	/* inherit personality from parent */  	/* Make sure to be in 32bit mode */ -	set_thread_flag(TIF_IA32); -	current->personality |= force_personality32; +	set_thread_flag(TIF_ADDR32);  	/* Mark the associated mm as containing 32-bit tasks. */  	if (current->mm)  		current->mm->context.ia32_compat = 1; -	/* Prepare the first "return" to user space */ -	current_thread_info()->status |= TS_COMPAT; +	if (x32) { +		clear_thread_flag(TIF_IA32); +		set_thread_flag(TIF_X32); +		current->personality &= ~READ_IMPLIES_EXEC; +		/* is_compat_task() uses the presence of the x32 +		   syscall bit flag to determine compat status */ +		current_thread_info()->status &= ~TS_COMPAT; +	} else { +		set_thread_flag(TIF_IA32); +		clear_thread_flag(TIF_X32); +		current->personality |= force_personality32; +		/* Prepare the first "return" to user space */ +		current_thread_info()->status |= TS_COMPAT; +	}  }  unsigned long get_wchan(struct task_struct *p) diff --git a/arch/x86/kernel/ptrace.c b/arch/x86/kernel/ptrace.c index 50267386b76..93e7877a19c 100644 --- a/arch/x86/kernel/ptrace.c +++ b/arch/x86/kernel/ptrace.c @@ -1130,6 +1130,100 @@ static int genregs32_set(struct task_struct *target,  	return ret;  } +#ifdef CONFIG_X86_X32_ABI +static long x32_arch_ptrace(struct task_struct *child, +			    compat_long_t request, compat_ulong_t caddr, +			    compat_ulong_t cdata) +{ +	unsigned long addr = caddr; +	unsigned long data = cdata; +	void __user *datap = compat_ptr(data); +	int ret; + +	switch (request) { +	/* Read 32bits at location addr in the USER area.  Only allow +	   to return the lower 32bits of segment and debug registers.  */ +	case PTRACE_PEEKUSR: { +		u32 tmp; + +		ret = -EIO; +		if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user) || +		    addr < offsetof(struct user_regs_struct, cs)) +			break; + +		tmp = 0;  /* Default return condition */ +		if (addr < sizeof(struct user_regs_struct)) +			tmp = getreg(child, addr); +		else if (addr >= offsetof(struct user, u_debugreg[0]) && +			 addr <= offsetof(struct user, u_debugreg[7])) { +			addr -= offsetof(struct user, u_debugreg[0]); +			tmp = ptrace_get_debugreg(child, addr / sizeof(data)); +		} +		ret = put_user(tmp, (__u32 __user *)datap); +		break; +	} + +	/* Write the word at location addr in the USER area.  Only allow +	   to update segment and debug registers with the upper 32bits +	   zero-extended. */ +	case PTRACE_POKEUSR: +		ret = -EIO; +		if ((addr & (sizeof(data) - 1)) || addr >= sizeof(struct user) || +		    addr < offsetof(struct user_regs_struct, cs)) +			break; + +		if (addr < sizeof(struct user_regs_struct)) +			ret = putreg(child, addr, data); +		else if (addr >= offsetof(struct user, u_debugreg[0]) && +			 addr <= offsetof(struct user, u_debugreg[7])) { +			addr -= offsetof(struct user, u_debugreg[0]); +			ret = ptrace_set_debugreg(child, +						  addr / sizeof(data), data); +		} +		break; + +	case PTRACE_GETREGS:	/* Get all gp regs from the child. */ +		return copy_regset_to_user(child, +					   task_user_regset_view(current), +					   REGSET_GENERAL, +					   0, sizeof(struct user_regs_struct), +					   datap); + +	case PTRACE_SETREGS:	/* Set all gp regs in the child. */ +		return copy_regset_from_user(child, +					     task_user_regset_view(current), +					     REGSET_GENERAL, +					     0, sizeof(struct user_regs_struct), +					     datap); + +	case PTRACE_GETFPREGS:	/* Get the child FPU state. */ +		return copy_regset_to_user(child, +					   task_user_regset_view(current), +					   REGSET_FP, +					   0, sizeof(struct user_i387_struct), +					   datap); + +	case PTRACE_SETFPREGS:	/* Set the child FPU state. */ +		return copy_regset_from_user(child, +					     task_user_regset_view(current), +					     REGSET_FP, +					     0, sizeof(struct user_i387_struct), +					     datap); + +		/* normal 64bit interface to access TLS data. +		   Works just like arch_prctl, except that the arguments +		   are reversed. */ +	case PTRACE_ARCH_PRCTL: +		return do_arch_prctl(child, data, addr); + +	default: +		return compat_ptrace_request(child, request, addr, data); +	} + +	return ret; +} +#endif +  long compat_arch_ptrace(struct task_struct *child, compat_long_t request,  			compat_ulong_t caddr, compat_ulong_t cdata)  { @@ -1139,6 +1233,11 @@ long compat_arch_ptrace(struct task_struct *child, compat_long_t request,  	int ret;  	__u32 val; +#ifdef CONFIG_X86_X32_ABI +	if (!is_ia32_task()) +		return x32_arch_ptrace(child, request, caddr, cdata); +#endif +  	switch (request) {  	case PTRACE_PEEKUSR:  		ret = getreg32(child, addr, &val); diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c index 46a01bdc27e..c3846b6fb72 100644 --- a/arch/x86/kernel/signal.c +++ b/arch/x86/kernel/signal.c @@ -10,10 +10,8 @@  #include <linux/mm.h>  #include <linux/smp.h>  #include <linux/kernel.h> -#include <linux/signal.h>  #include <linux/errno.h>  #include <linux/wait.h> -#include <linux/ptrace.h>  #include <linux/tracehook.h>  #include <linux/unistd.h>  #include <linux/stddef.h> @@ -26,10 +24,12 @@  #include <asm/i387.h>  #include <asm/vdso.h>  #include <asm/mce.h> +#include <asm/sighandling.h>  #ifdef CONFIG_X86_64  #include <asm/proto.h>  #include <asm/ia32_unistd.h> +#include <asm/sys_ia32.h>  #endif /* CONFIG_X86_64 */  #include <asm/syscall.h> @@ -37,13 +37,6 @@  #include <asm/sigframe.h> -#define _BLOCKABLE (~(sigmask(SIGKILL) | sigmask(SIGSTOP))) - -#define __FIX_EFLAGS	(X86_EFLAGS_AC | X86_EFLAGS_OF | \ -			 X86_EFLAGS_DF | X86_EFLAGS_TF | X86_EFLAGS_SF | \ -			 X86_EFLAGS_ZF | X86_EFLAGS_AF | X86_EFLAGS_PF | \ -			 X86_EFLAGS_CF) -  #ifdef CONFIG_X86_32  # define FIX_EFLAGS	(__FIX_EFLAGS | X86_EFLAGS_RF)  #else @@ -68,9 +61,8 @@  	regs->seg = GET_SEG(seg) | 3;			\  } while (0) -static int -restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, -		   unsigned long *pax) +int restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc, +		       unsigned long *pax)  {  	void __user *buf;  	unsigned int tmpflags; @@ -125,9 +117,8 @@ restore_sigcontext(struct pt_regs *regs, struct sigcontext __user *sc,  	return err;  } -static int -setup_sigcontext(struct sigcontext __user *sc, void __user *fpstate, -		 struct pt_regs *regs, unsigned long mask) +int setup_sigcontext(struct sigcontext __user *sc, void __user *fpstate, +		     struct pt_regs *regs, unsigned long mask)  {  	int err = 0; @@ -642,6 +633,16 @@ static int signr_convert(int sig)  #define is_ia32	0  #endif /* CONFIG_IA32_EMULATION */ +#ifdef CONFIG_X86_X32_ABI +#define is_x32	test_thread_flag(TIF_X32) + +static int x32_setup_rt_frame(int sig, struct k_sigaction *ka, +			      siginfo_t *info, compat_sigset_t *set, +			      struct pt_regs *regs); +#else /* !CONFIG_X86_X32_ABI */ +#define is_x32	0 +#endif /* CONFIG_X86_X32_ABI */ +  int ia32_setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,  		sigset_t *set, struct pt_regs *regs);  int ia32_setup_frame(int sig, struct k_sigaction *ka, @@ -666,8 +667,14 @@ setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,  			ret = ia32_setup_rt_frame(usig, ka, info, set, regs);  		else  			ret = ia32_setup_frame(usig, ka, set, regs); -	} else +#ifdef CONFIG_X86_X32_ABI +	} else if (is_x32) { +		ret = x32_setup_rt_frame(usig, ka, info, +					 (compat_sigset_t *)set, regs); +#endif +	} else {  		ret = __setup_rt_frame(sig, ka, info, set, regs); +	}  	if (ret) {  		force_sigsegv(sig, current); @@ -850,3 +857,102 @@ void signal_fault(struct pt_regs *regs, void __user *frame, char *where)  	force_sig(SIGSEGV, me);  } + +#ifdef CONFIG_X86_X32_ABI +static int x32_setup_rt_frame(int sig, struct k_sigaction *ka, +			      siginfo_t *info, compat_sigset_t *set, +			      struct pt_regs *regs) +{ +	struct rt_sigframe_x32 __user *frame; +	void __user *restorer; +	int err = 0; +	void __user *fpstate = NULL; + +	frame = get_sigframe(ka, regs, sizeof(*frame), &fpstate); + +	if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) +		return -EFAULT; + +	if (ka->sa.sa_flags & SA_SIGINFO) { +		if (copy_siginfo_to_user32(&frame->info, info)) +			return -EFAULT; +	} + +	put_user_try { +		/* Create the ucontext.  */ +		if (cpu_has_xsave) +			put_user_ex(UC_FP_XSTATE, &frame->uc.uc_flags); +		else +			put_user_ex(0, &frame->uc.uc_flags); +		put_user_ex(0, &frame->uc.uc_link); +		put_user_ex(current->sas_ss_sp, &frame->uc.uc_stack.ss_sp); +		put_user_ex(sas_ss_flags(regs->sp), +			    &frame->uc.uc_stack.ss_flags); +		put_user_ex(current->sas_ss_size, &frame->uc.uc_stack.ss_size); +		put_user_ex(0, &frame->uc.uc__pad0); +		err |= setup_sigcontext(&frame->uc.uc_mcontext, fpstate, +					regs, set->sig[0]); +		err |= __copy_to_user(&frame->uc.uc_sigmask, set, sizeof(*set)); + +		if (ka->sa.sa_flags & SA_RESTORER) { +			restorer = ka->sa.sa_restorer; +		} else { +			/* could use a vstub here */ +			restorer = NULL; +			err |= -EFAULT; +		} +		put_user_ex(restorer, &frame->pretcode); +	} put_user_catch(err); + +	if (err) +		return -EFAULT; + +	/* Set up registers for signal handler */ +	regs->sp = (unsigned long) frame; +	regs->ip = (unsigned long) ka->sa.sa_handler; + +	/* We use the x32 calling convention here... */ +	regs->di = sig; +	regs->si = (unsigned long) &frame->info; +	regs->dx = (unsigned long) &frame->uc; + +	loadsegment(ds, __USER_DS); +	loadsegment(es, __USER_DS); + +	regs->cs = __USER_CS; +	regs->ss = __USER_DS; + +	return 0; +} + +asmlinkage long sys32_x32_rt_sigreturn(struct pt_regs *regs) +{ +	struct rt_sigframe_x32 __user *frame; +	sigset_t set; +	unsigned long ax; +	struct pt_regs tregs; + +	frame = (struct rt_sigframe_x32 __user *)(regs->sp - 8); + +	if (!access_ok(VERIFY_READ, frame, sizeof(*frame))) +		goto badframe; +	if (__copy_from_user(&set, &frame->uc.uc_sigmask, sizeof(set))) +		goto badframe; + +	sigdelsetmask(&set, ~_BLOCKABLE); +	set_current_blocked(&set); + +	if (restore_sigcontext(regs, &frame->uc.uc_mcontext, &ax)) +		goto badframe; + +	tregs = *regs; +	if (sys32_sigaltstack(&frame->uc.uc_stack, NULL, &tregs) == -EFAULT) +		goto badframe; + +	return ax; + +badframe: +	signal_fault(regs, frame, "x32 rt_sigreturn"); +	return 0; +} +#endif diff --git a/arch/x86/kernel/sys_x86_64.c b/arch/x86/kernel/sys_x86_64.c index 051489082d5..f921df8c209 100644 --- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -98,7 +98,7 @@ out:  static void find_start_end(unsigned long flags, unsigned long *begin,  			   unsigned long *end)  { -	if (!test_thread_flag(TIF_IA32) && (flags & MAP_32BIT)) { +	if (!test_thread_flag(TIF_ADDR32) && (flags & MAP_32BIT)) {  		unsigned long new_begin;  		/* This is usually used needed to map code in small  		   model, so it needs to be in the first 31bit. Limit @@ -144,7 +144,7 @@ arch_get_unmapped_area(struct file *filp, unsigned long addr,  		    (!vma || addr + len <= vma->vm_start))  			return addr;  	} -	if (((flags & MAP_32BIT) || test_thread_flag(TIF_IA32)) +	if (((flags & MAP_32BIT) || test_thread_flag(TIF_ADDR32))  	    && len <= mm->cached_hole_size) {  		mm->cached_hole_size = 0;  		mm->free_area_cache = begin; @@ -205,7 +205,7 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0,  		return addr;  	/* for MAP_32BIT mappings we force the legact mmap base */ -	if (!test_thread_flag(TIF_IA32) && (flags & MAP_32BIT)) +	if (!test_thread_flag(TIF_ADDR32) && (flags & MAP_32BIT))  		goto bottomup;  	/* requesting a specific address */ diff --git a/arch/x86/kernel/syscall_64.c b/arch/x86/kernel/syscall_64.c index 7ac7943be02..5c7f8c20da7 100644 --- a/arch/x86/kernel/syscall_64.c +++ b/arch/x86/kernel/syscall_64.c @@ -5,6 +5,14 @@  #include <linux/cache.h>  #include <asm/asm-offsets.h> +#define __SYSCALL_COMMON(nr, sym, compat) __SYSCALL_64(nr, sym, compat) + +#ifdef CONFIG_X86_X32_ABI +# define __SYSCALL_X32(nr, sym, compat) __SYSCALL_64(nr, sym, compat) +#else +# define __SYSCALL_X32(nr, sym, compat) /* nothing */ +#endif +  #define __SYSCALL_64(nr, sym, compat) extern asmlinkage void sym(void) ;  #include <asm/syscalls_64.h>  #undef __SYSCALL_64  |