[RFC] fixing the UML failure root cause

From: Andrew Lutomirski
Date: Tue Oct 11 2011 - 13:24:37 EST


On 10/10/2011 11:22 PM, Ingo Molnar wrote:
>
> * Andrew Lutomirski <luto@xxxxxxx> wrote:
>
>>> Andrew?
>>
>> I think I know what the root cause is and I have most of a patch to
>> fix it. It doesn't compile (yet), it's a little less trivial than
>> I'd like for something this late in the -rc cycle, and it adds 16
>> bytes to thread_struct (ugh!).
>>
>> I think I can make a follow-up patch that removes 32 bytes of
>> per-thread state to restore my karma, though, but that will
>> definitely not be 3.1 material.
>
> Ok, i've queued up the vsyscall=native patch in tip:x86/urgent for
> now - we can re-try in v3.2 (perhaps) if a satisfactory solution is
> found.

Getting full cause information for uaccess failure was messy enough that
I gave up. There are a *lot* of uaccess failure paths to work through.

So here's a different approach. It's not perfect: it always blames
SEGV_MAPERR instead of SEGV_ACCERR. I implemented it for vgettimeofday
but not the other two vsyscalls.

What do you think of this approach? If it seems good, I'll finish the
patch and submit it.

With this patch applied, UML appears to work, but it fills the log with
exploit attempt warnings. Any ideas on what to do about that?

--Andy

>
> Thanks,
>
> Ingo

diff --git a/arch/x86/kernel/vsyscall_64.c b/arch/x86/kernel/vsyscall_64.c
index 18ae83d..c0bafec 100644
--- a/arch/x86/kernel/vsyscall_64.c
+++ b/arch/x86/kernel/vsyscall_64.c
@@ -139,6 +139,42 @@ static int addr_to_vsyscall_nr(unsigned long addr)
return nr;
}

+/* Copy data to user space, forcing signals on failure. */
+static int copy_to_user_sig(unsigned long dest, const void *src, size_t len)
+{
+ /*
+ * This may be the slowest memcpy ever written. We don't really care.
+ */
+ size_t i;
+ for (i = 0; i < len; i++) {
+ char __user *user_byte = (char __user *)(dest + i);
+ if (put_user(((char*)src)[i], user_byte) != 0) {
+ /* Report full siginfo and context */
+ struct task_struct *tsk = current;
+ siginfo_t info;
+ memset(&info, 0, sizeof(info));
+ info.si_signo = SIGSEGV;
+ /*
+ * Could be SEGV_ACCERR -- we don't distinguish it
+ * correctly.
+ */
+ info.si_code = SEGV_MAPERR;
+ info.si_addr = user_byte;
+ /*
+ * Write fault in user mode. We don't distinguish
+ * protection fault from no page found.
+ */
+ tsk->thread.error_code = 6;
+ tsk->thread.cr2 = (unsigned long)user_byte;
+ tsk->thread.trap_no = 14;
+ force_sig_info(SIGSEGV, &info, tsk);
+ return -EFAULT;
+ }
+ }
+
+ return 0;
+}
+
bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
{
struct task_struct *tsk;
@@ -181,10 +217,19 @@ bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)

switch (vsyscall_nr) {
case 0:
- ret = sys_gettimeofday(
- (struct timeval __user *)regs->di,
- (struct timezone __user *)regs->si);
+ {
+ struct timeval tv;
+ do_gettimeofday(&tv);
+
+ if (regs->di && copy_to_user_sig(regs->di, &tv, sizeof(tv)))
+ goto warn_fault;
+ if (regs->si && copy_to_user_sig(regs->si, &sys_tz,
+ sizeof(struct timezone)))
+ goto warn_fault;
+
+ ret = 0;
break;
+ }

case 1:
ret = sys_time((time_t __user *)regs->di);
@@ -197,19 +242,6 @@ bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
break;
}

- if (ret == -EFAULT) {
- /*
- * Bad news -- userspace fed a bad pointer to a vsyscall.
- *
- * With a real vsyscall, that would have caused SIGSEGV.
- * To make writing reliable exploits using the emulated
- * vsyscalls harder, generate SIGSEGV here as well.
- */
- warn_bad_vsyscall(KERN_INFO, regs,
- "vsyscall fault (exploit attempt?)");
- goto sigsegv;
- }
-
regs->ax = ret;

/* Emulate a ret instruction. */
@@ -221,6 +253,19 @@ bool emulate_vsyscall(struct pt_regs *regs, unsigned long address)
sigsegv:
force_sig(SIGSEGV, current);
return true;
+
+warn_fault:
+ /*
+ * Bad news -- userspace fed a bad pointer to a vsyscall.
+ *
+ * With a real vsyscall, that would have caused SIGSEGV.
+ * To make writing reliable exploits using the emulated
+ * vsyscalls harder, generate SIGSEGV here as well.
+ */
+
+ warn_bad_vsyscall(KERN_INFO, regs,
+ "vsyscall fault (exploit attempt?)");
+ return true;
}

/*