diff options
author | Andy Lutomirski <luto@mit.edu> | 2011-07-13 09:24:09 -0400 |
---|---|---|
committer | H. Peter Anvin <hpa@linux.intel.com> | 2011-07-13 14:22:55 -0400 |
commit | c9712944b2a12373cb6ff8059afcfb7e826a6c54 (patch) | |
tree | 73b58eddce6f56c32b90b5056032a504f3ae4f00 | |
parent | 5cec93c216db77c45f7ce970d46283bcb1933884 (diff) |
x86-64: Improve vsyscall emulation CS and RIP handling
Three fixes here:
- Send SIGSEGV if called from compat code or with a funny CS.
- Don't BUG on impossible addresses.
- Add a missing local_irq_disable.
This patch also removes an unused variable.
Signed-off-by: Andy Lutomirski <luto@mit.edu>
Link: http://lkml.kernel.org/r/6fb2b13ab39b743d1e4f466eef13425854912f7f.1310563276.git.luto@mit.edu
Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
-rw-r--r-- | arch/x86/include/asm/vsyscall.h | 12 | ||||
-rw-r--r-- | arch/x86/kernel/vsyscall_64.c | 61 |
2 files changed, 41 insertions, 32 deletions
diff --git a/arch/x86/include/asm/vsyscall.h b/arch/x86/include/asm/vsyscall.h index bb710cb0cdc1..d55597351f6a 100644 --- a/arch/x86/include/asm/vsyscall.h +++ b/arch/x86/include/asm/vsyscall.h | |||
@@ -31,18 +31,6 @@ extern struct timezone sys_tz; | |||
31 | 31 | ||
32 | extern void map_vsyscall(void); | 32 | extern void map_vsyscall(void); |
33 | 33 | ||
34 | /* Emulation */ | ||
35 | |||
36 | static inline bool is_vsyscall_entry(unsigned long addr) | ||
37 | { | ||
38 | return (addr & ~0xC00UL) == VSYSCALL_START; | ||
39 | } | ||
40 | |||
41 | static inline int vsyscall_entry_nr(unsigned long addr) | ||
42 | { | ||
43 | return (addr & 0xC00UL) >> 10; | ||
44 | } | ||
45 | |||
46 | #endif /* __KERNEL__ */ | 34 | #endif /* __KERNEL__ */ |
47 | 35 | ||
48 | #endif /* _ASM_X86_VSYSCALL_H */ | 36 | #endif /* _ASM_X86_VSYSCALL_H */ |
diff --git a/arch/x86/kernel/vsyscall_64.c b/arch/x86/kernel/vsyscall_64.c index 10cd8ac3395a..a262400c3479 100644 --- a/arch/x86/kernel/vsyscall_64.c +++ b/arch/x86/kernel/vsyscall_64.c | |||
@@ -38,6 +38,7 @@ | |||
38 | 38 | ||
39 | #include <asm/vsyscall.h> | 39 | #include <asm/vsyscall.h> |
40 | #include <asm/pgtable.h> | 40 | #include <asm/pgtable.h> |
41 | #include <asm/compat.h> | ||
41 | #include <asm/page.h> | 42 | #include <asm/page.h> |
42 | #include <asm/unistd.h> | 43 | #include <asm/unistd.h> |
43 | #include <asm/fixmap.h> | 44 | #include <asm/fixmap.h> |
@@ -97,33 +98,63 @@ static void warn_bad_vsyscall(const char *level, struct pt_regs *regs, | |||
97 | 98 | ||
98 | tsk = current; | 99 | tsk = current; |
99 | 100 | ||
100 | printk("%s%s[%d] %s ip:%lx sp:%lx ax:%lx si:%lx di:%lx\n", | 101 | printk("%s%s[%d] %s ip:%lx cs:%lx sp:%lx ax:%lx si:%lx di:%lx\n", |
101 | level, tsk->comm, task_pid_nr(tsk), | 102 | level, tsk->comm, task_pid_nr(tsk), |
102 | message, regs->ip - 2, regs->sp, regs->ax, regs->si, regs->di); | 103 | message, regs->ip - 2, regs->cs, |
104 | regs->sp, regs->ax, regs->si, regs->di); | ||
105 | } | ||
106 | |||
107 | static int addr_to_vsyscall_nr(unsigned long addr) | ||
108 | { | ||
109 | int nr; | ||
110 | |||
111 | if ((addr & ~0xC00UL) != VSYSCALL_START) | ||
112 | return -EINVAL; | ||
113 | |||
114 | nr = (addr & 0xC00UL) >> 10; | ||
115 | if (nr >= 3) | ||
116 | return -EINVAL; | ||
117 | |||
118 | return nr; | ||
103 | } | 119 | } |
104 | 120 | ||
105 | void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) | 121 | void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) |
106 | { | 122 | { |
107 | const char *vsyscall_name; | ||
108 | struct task_struct *tsk; | 123 | struct task_struct *tsk; |
109 | unsigned long caller; | 124 | unsigned long caller; |
110 | int vsyscall_nr; | 125 | int vsyscall_nr; |
111 | long ret; | 126 | long ret; |
112 | 127 | ||
113 | /* Kernel code must never get here. */ | ||
114 | BUG_ON(!user_mode(regs)); | ||
115 | |||
116 | local_irq_enable(); | 128 | local_irq_enable(); |
117 | 129 | ||
118 | /* | 130 | /* |
131 | * Real 64-bit user mode code has cs == __USER_CS. Anything else | ||
132 | * is bogus. | ||
133 | */ | ||
134 | if (regs->cs != __USER_CS) { | ||
135 | /* | ||
136 | * If we trapped from kernel mode, we might as well OOPS now | ||
137 | * instead of returning to some random address and OOPSing | ||
138 | * then. | ||
139 | */ | ||
140 | BUG_ON(!user_mode(regs)); | ||
141 | |||
142 | /* Compat mode and non-compat 32-bit CS should both segfault. */ | ||
143 | warn_bad_vsyscall(KERN_WARNING, regs, | ||
144 | "illegal int 0xcc from 32-bit mode"); | ||
145 | goto sigsegv; | ||
146 | } | ||
147 | |||
148 | /* | ||
119 | * x86-ism here: regs->ip points to the instruction after the int 0xcc, | 149 | * x86-ism here: regs->ip points to the instruction after the int 0xcc, |
120 | * and int 0xcc is two bytes long. | 150 | * and int 0xcc is two bytes long. |
121 | */ | 151 | */ |
122 | if (!is_vsyscall_entry(regs->ip - 2)) { | 152 | vsyscall_nr = addr_to_vsyscall_nr(regs->ip - 2); |
123 | warn_bad_vsyscall(KERN_WARNING, regs, "illegal int 0xcc (exploit attempt?)"); | 153 | if (vsyscall_nr < 0) { |
154 | warn_bad_vsyscall(KERN_WARNING, regs, | ||
155 | "illegal int 0xcc (exploit attempt?)"); | ||
124 | goto sigsegv; | 156 | goto sigsegv; |
125 | } | 157 | } |
126 | vsyscall_nr = vsyscall_entry_nr(regs->ip - 2); | ||
127 | 158 | ||
128 | if (get_user(caller, (unsigned long __user *)regs->sp) != 0) { | 159 | if (get_user(caller, (unsigned long __user *)regs->sp) != 0) { |
129 | warn_bad_vsyscall(KERN_WARNING, regs, "int 0xcc with bad stack (exploit attempt?)"); | 160 | warn_bad_vsyscall(KERN_WARNING, regs, "int 0xcc with bad stack (exploit attempt?)"); |
@@ -136,31 +167,20 @@ void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) | |||
136 | 167 | ||
137 | switch (vsyscall_nr) { | 168 | switch (vsyscall_nr) { |
138 | case 0: | 169 | case 0: |
139 | vsyscall_name = "gettimeofday"; | ||
140 | ret = sys_gettimeofday( | 170 | ret = sys_gettimeofday( |
141 | (struct timeval __user *)regs->di, | 171 | (struct timeval __user *)regs->di, |
142 | (struct timezone __user *)regs->si); | 172 | (struct timezone __user *)regs->si); |
143 | break; | 173 | break; |
144 | 174 | ||
145 | case 1: | 175 | case 1: |
146 | vsyscall_name = "time"; | ||
147 | ret = sys_time((time_t __user *)regs->di); | 176 | ret = sys_time((time_t __user *)regs->di); |
148 | break; | 177 | break; |
149 | 178 | ||
150 | case 2: | 179 | case 2: |
151 | vsyscall_name = "getcpu"; | ||
152 | ret = sys_getcpu((unsigned __user *)regs->di, | 180 | ret = sys_getcpu((unsigned __user *)regs->di, |
153 | (unsigned __user *)regs->si, | 181 | (unsigned __user *)regs->si, |
154 | 0); | 182 | 0); |
155 | break; | 183 | break; |
156 | |||
157 | default: | ||
158 | /* | ||
159 | * If we get here, then vsyscall_nr indicates that int 0xcc | ||
160 | * happened at an address in the vsyscall page that doesn't | ||
161 | * contain int 0xcc. That can't happen. | ||
162 | */ | ||
163 | BUG(); | ||
164 | } | 184 | } |
165 | 185 | ||
166 | if (ret == -EFAULT) { | 186 | if (ret == -EFAULT) { |
@@ -188,6 +208,7 @@ void dotraplinkage do_emulate_vsyscall(struct pt_regs *regs, long error_code) | |||
188 | sigsegv: | 208 | sigsegv: |
189 | regs->ip -= 2; /* The faulting instruction should be the int 0xcc. */ | 209 | regs->ip -= 2; /* The faulting instruction should be the int 0xcc. */ |
190 | force_sig(SIGSEGV, current); | 210 | force_sig(SIGSEGV, current); |
211 | local_irq_disable(); | ||
191 | } | 212 | } |
192 | 213 | ||
193 | /* | 214 | /* |