Skip to content

Commit 4fc3490

Browse files
amlutoIngo Molnar
authored andcommitted
x86-64: Set siginfo and context on vsyscall emulation faults
To make this work, we teach the page fault handler how to send signals on failed uaccess. This only works for user addresses (kernel addresses will never hit the page fault handler in the first place), so we need to generate signals for those separately. This gets the tricky case right: if the user buffer spans multiple pages and only the second page is invalid, we set cr2 and si_addr correctly. UML relies on this behavior to "fault in" pages as needed. We steal a bit from thread_info.uaccess_err to enable this. Before this change, uaccess_err was a 32-bit boolean value. This fixes issues with UML when vsyscall=emulate. Reported-by: Adrian Bunk <[email protected]> Signed-off-by: Andy Lutomirski <[email protected]> Cc: richard -rw- weinberger <[email protected]> Cc: H. Peter Anvin <[email protected]> Cc: Linus Torvalds <[email protected]> Link: http://lkml.kernel.org/r/4c8f91de7ec5cd2ef0f59521a04e1015f11e42b4.1320712291.git.luto@amacapital.net Signed-off-by: Ingo Molnar <[email protected]>
1 parent 01acc26 commit 4fc3490

File tree

5 files changed

+87
-17
lines changed

5 files changed

+87
-17
lines changed

arch/x86/include/asm/thread_info.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ struct thread_info {
4040
*/
4141
__u8 supervisor_stack[0];
4242
#endif
43-
int uaccess_err;
43+
int sig_on_uaccess_error:1;
44+
int uaccess_err:1; /* uaccess failed */
4445
};
4546

4647
#define INIT_THREAD_INFO(tsk) \

arch/x86/include/asm/uaccess.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,7 @@ struct __large_struct { unsigned long buf[100]; };
462462
barrier();
463463

464464
#define uaccess_catch(err) \
465-
(err) |= current_thread_info()->uaccess_err; \
465+
(err) |= (current_thread_info()->uaccess_err ? -EFAULT : 0); \
466466
current_thread_info()->uaccess_err = prev_err; \
467467
} while (0)
468468

arch/x86/kernel/vsyscall_64.c

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -140,11 +140,40 @@ static int addr_to_vsyscall_nr(unsigned long addr)
140140
return nr;
141141
}
142142

143+
static bool write_ok_or_segv(unsigned long ptr, size_t size)
144+
{
145+
/*
146+
* XXX: if access_ok, get_user, and put_user handled
147+
* sig_on_uaccess_error, this could go away.
148+
*/
149+
150+
if (!access_ok(VERIFY_WRITE, (void __user *)ptr, size)) {
151+
siginfo_t info;
152+
struct thread_struct *thread = &current->thread;
153+
154+
thread->error_code = 6; /* user fault, no page, write */
155+
thread->cr2 = ptr;
156+
thread->trap_no = 14;
157+
158+
memset(&info, 0, sizeof(info));
159+
info.si_signo = SIGSEGV;
160+
info.si_errno = 0;
161+
info.si_code = SEGV_MAPERR;
162+
info.si_addr = (void __user *)ptr;
163+
164+
force_sig_info(SIGSEGV, &info, current);
165+
return false;
166+
} else {
167+
return true;
168+
}
169+
}
170+
143171
bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
144172
{
145173
struct task_struct *tsk;
146174
unsigned long caller;
147175
int vsyscall_nr;
176+
int prev_sig_on_uaccess_error;
148177
long ret;
149178

150179
/*
@@ -180,35 +209,65 @@ bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
180209
if (seccomp_mode(&tsk->seccomp))
181210
do_exit(SIGKILL);
182211

212+
/*
213+
* With a real vsyscall, page faults cause SIGSEGV. We want to
214+
* preserve that behavior to make writing exploits harder.
215+
*/
216+
prev_sig_on_uaccess_error = current_thread_info()->sig_on_uaccess_error;
217+
current_thread_info()->sig_on_uaccess_error = 1;
218+
219+
/*
220+
* 0 is a valid user pointer (in the access_ok sense) on 32-bit and
221+
* 64-bit, so we don't need to special-case it here. For all the
222+
* vsyscalls, 0 means "don't write anything" not "write it at
223+
* address 0".
224+
*/
225+
ret = -EFAULT;
183226
switch (vsyscall_nr) {
184227
case 0:
228+
if (!write_ok_or_segv(regs->di, sizeof(struct timeval)) ||
229+
!write_ok_or_segv(regs->si, sizeof(struct timezone)))
230+
break;
231+
185232
ret = sys_gettimeofday(
186233
(struct timeval __user *)regs->di,
187234
(struct timezone __user *)regs->si);
188235
break;
189236

190237
case 1:
238+
if (!write_ok_or_segv(regs->di, sizeof(time_t)))
239+
break;
240+
191241
ret = sys_time((time_t __user *)regs->di);
192242
break;
193243

194244
case 2:
245+
if (!write_ok_or_segv(regs->di, sizeof(unsigned)) ||
246+
!write_ok_or_segv(regs->si, sizeof(unsigned)))
247+
break;
248+
195249
ret = sys_getcpu((unsigned __user *)regs->di,
196250
(unsigned __user *)regs->si,
197251
0);
198252
break;
199253
}
200254

255+
current_thread_info()->sig_on_uaccess_error = prev_sig_on_uaccess_error;
256+
201257
if (ret == -EFAULT) {
202-
/*
203-
* Bad news -- userspace fed a bad pointer to a vsyscall.
204-
*
205-
* With a real vsyscall, that would have caused SIGSEGV.
206-
* To make writing reliable exploits using the emulated
207-
* vsyscalls harder, generate SIGSEGV here as well.
208-
*/
258+
/* Bad news -- userspace fed a bad pointer to a vsyscall. */
209259
warn_bad_vsyscall(KERN_INFO, regs,
210260
"vsyscall fault (exploit attempt?)");
211-
goto sigsegv;
261+
262+
/*
263+
* If we failed to generate a signal for any reason,
264+
* generate one here. (This should be impossible.)
265+
*/
266+
if (WARN_ON_ONCE(!sigismember(&tsk->pending.signal, SIGBUS) &&
267+
!sigismember(&tsk->pending.signal, SIGSEGV)))
268+
goto sigsegv;
269+
270+
return true; /* Don't emulate the ret. */
212271
}
213272

214273
regs->ax = ret;

arch/x86/mm/extable.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ int fixup_exception(struct pt_regs *regs)
2525
if (fixup) {
2626
/* If fixup is less than 16, it means uaccess error */
2727
if (fixup->fixup < 16) {
28-
current_thread_info()->uaccess_err = -EFAULT;
28+
current_thread_info()->uaccess_err = 1;
2929
regs->ip += fixup->fixup;
3030
return 1;
3131
}

arch/x86/mm/fault.c

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -626,16 +626,25 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code,
626626

627627
static noinline void
628628
no_context(struct pt_regs *regs, unsigned long error_code,
629-
unsigned long address)
629+
unsigned long address, int signal, int si_code)
630630
{
631631
struct task_struct *tsk = current;
632632
unsigned long *stackend;
633633
unsigned long flags;
634634
int sig;
635635

636636
/* Are we prepared to handle this kernel fault? */
637-
if (fixup_exception(regs))
637+
if (fixup_exception(regs)) {
638+
if (current_thread_info()->sig_on_uaccess_error && signal) {
639+
tsk->thread.trap_no = 14;
640+
tsk->thread.error_code = error_code | PF_USER;
641+
tsk->thread.cr2 = address;
642+
643+
/* XXX: hwpoison faults will set the wrong code. */
644+
force_sig_info_fault(signal, si_code, address, tsk, 0);
645+
}
638646
return;
647+
}
639648

640649
/*
641650
* 32-bit:
@@ -755,7 +764,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,
755764
if (is_f00f_bug(regs, address))
756765
return;
757766

758-
no_context(regs, error_code, address);
767+
no_context(regs, error_code, address, SIGSEGV, si_code);
759768
}
760769

761770
static noinline void
@@ -819,7 +828,7 @@ do_sigbus(struct pt_regs *regs, unsigned long error_code, unsigned long address,
819828

820829
/* Kernel mode? Handle exceptions or die: */
821830
if (!(error_code & PF_USER)) {
822-
no_context(regs, error_code, address);
831+
no_context(regs, error_code, address, SIGBUS, BUS_ADRERR);
823832
return;
824833
}
825834

@@ -854,7 +863,7 @@ mm_fault_error(struct pt_regs *regs, unsigned long error_code,
854863
if (!(fault & VM_FAULT_RETRY))
855864
up_read(&current->mm->mmap_sem);
856865
if (!(error_code & PF_USER))
857-
no_context(regs, error_code, address);
866+
no_context(regs, error_code, address, 0, 0);
858867
return 1;
859868
}
860869
if (!(fault & VM_FAULT_ERROR))
@@ -864,7 +873,8 @@ mm_fault_error(struct pt_regs *regs, unsigned long error_code,
864873
/* Kernel mode? Handle exceptions or die: */
865874
if (!(error_code & PF_USER)) {
866875
up_read(&current->mm->mmap_sem);
867-
no_context(regs, error_code, address);
876+
no_context(regs, error_code, address,
877+
SIGSEGV, SEGV_MAPERR);
868878
return 1;
869879
}
870880

0 commit comments

Comments
 (0)