This should give full support for mremap on the vdso except for
sysenter return.  It will also enable future vvar twiddling on
already-started processes.

Signed-off-by: Andy Lutomirski <l...@amacapital.net>
---
 arch/x86/ia32/ia32_signal.c | 11 ++++-------
 arch/x86/include/asm/elf.h  | 26 ++++++++-----------------
 arch/x86/include/asm/mmu.h  |  4 +++-
 arch/x86/include/asm/vdso.h | 16 +++++++++++++++
 arch/x86/kernel/signal.c    |  9 +++------
 arch/x86/vdso/vma.c         | 47 ++++++++++++++++++++++++++++++++++++++-------
 6 files changed, 74 insertions(+), 39 deletions(-)

diff --git a/arch/x86/ia32/ia32_signal.c b/arch/x86/ia32/ia32_signal.c
index f9e181aaba97..3b335c674059 100644
--- a/arch/x86/ia32/ia32_signal.c
+++ b/arch/x86/ia32/ia32_signal.c
@@ -381,11 +381,8 @@ int ia32_setup_frame(int sig, struct ksignal *ksig,
        if (ksig->ka.sa.sa_flags & SA_RESTORER) {
                restorer = ksig->ka.sa.sa_restorer;
        } else {
-               /* Return stub is in 32bit vsyscall page */
-               if (current->mm->context.vdso)
-                       restorer = current->mm->context.vdso +
-                               selected_vdso32->sym___kernel_sigreturn;
-               else
+               restorer = VDSO_SYM_ADDR(current->mm, __kernel_sigreturn);
+               if (!restorer)
                        restorer = &frame->retcode;
        }
 
@@ -462,8 +459,8 @@ int ia32_setup_rt_frame(int sig, struct ksignal *ksig,
                if (ksig->ka.sa.sa_flags & SA_RESTORER)
                        restorer = ksig->ka.sa.sa_restorer;
                else
-                       restorer = current->mm->context.vdso +
-                               selected_vdso32->sym___kernel_rt_sigreturn;
+                       restorer = VDSO_SYM_ADDR(current->mm,
+                                                __kernel_rt_sigreturn);
                put_user_ex(ptr_to_compat(restorer), &frame->pretcode);
 
                /*
diff --git a/arch/x86/include/asm/elf.h b/arch/x86/include/asm/elf.h
index 1a055c81d864..05df8f03faa5 100644
--- a/arch/x86/include/asm/elf.h
+++ b/arch/x86/include/asm/elf.h
@@ -276,7 +276,7 @@ struct task_struct;
 
 #define        ARCH_DLINFO_IA32                                                
\
 do {                                                                   \
-       if (vdso32_enabled) {                                           \
+       if (current->mm->context.vdso_image) {                          \
                NEW_AUX_ENT(AT_SYSINFO, VDSO_ENTRY);                    \
                NEW_AUX_ENT(AT_SYSINFO_EHDR, VDSO_CURRENT_BASE);        \
        }                                                               \
@@ -295,26 +295,19 @@ do {                                                      
                \
 /* 1GB for 64bit, 8MB for 32bit */
 #define STACK_RND_MASK (test_thread_flag(TIF_ADDR32) ? 0x7ff : 0x3fffff)
 
-#define ARCH_DLINFO                                                    \
+#define ARCH_DLINFO_X86_64                                             \
 do {                                                                   \
-       if (vdso64_enabled)                                             \
-               NEW_AUX_ENT(AT_SYSINFO_EHDR,                            \
-                           (unsigned long __force)current->mm->context.vdso); \
+       if (current->mm->context.vdso_image)                            \
+               NEW_AUX_ENT(AT_SYSINFO_EHDR, VDSO_CURRENT_BASE);        \
 } while (0)
 
-/* As a historical oddity, the x32 and x86_64 vDSOs are controlled together. */
-#define ARCH_DLINFO_X32                                                        
\
-do {                                                                   \
-       if (vdso64_enabled)                                             \
-               NEW_AUX_ENT(AT_SYSINFO_EHDR,                            \
-                           (unsigned long __force)current->mm->context.vdso); \
-} while (0)
+#define ARCH_DLINFO ARCH_DLINFO_X86_64
 
 #define AT_SYSINFO             32
 
 #define COMPAT_ARCH_DLINFO                                             \
 if (test_thread_flag(TIF_X32))                                         \
-       ARCH_DLINFO_X32;                                                \
+       ARCH_DLINFO_X86_64;                                             \
 else                                                                   \
        ARCH_DLINFO_IA32
 
@@ -322,11 +315,8 @@ else                                                       
                \
 
 #endif /* !CONFIG_X86_32 */
 
-#define VDSO_CURRENT_BASE      ((unsigned long)current->mm->context.vdso)
-
-#define VDSO_ENTRY                                                     \
-       ((unsigned long)current->mm->context.vdso +                     \
-        selected_vdso32->sym___kernel_vsyscall)
+#define VDSO_CURRENT_BASE      ((unsigned long)vdso_text_start(current->mm))
+#define VDSO_ENTRY ((unsigned long)VDSO_SYM_ADDR(current->mm, 
__kernel_vsyscall))
 
 struct linux_binprm;
 
diff --git a/arch/x86/include/asm/mmu.h b/arch/x86/include/asm/mmu.h
index 876e74e8eec7..bbba90ebd2c8 100644
--- a/arch/x86/include/asm/mmu.h
+++ b/arch/x86/include/asm/mmu.h
@@ -18,7 +18,9 @@ typedef struct {
 #endif
 
        struct mutex lock;
-       void __user *vdso;
+
+       unsigned long vvar_vma_start;
+       const struct vdso_image *vdso_image;
 } mm_context_t;
 
 #ifdef CONFIG_SMP
diff --git a/arch/x86/include/asm/vdso.h b/arch/x86/include/asm/vdso.h
index 8021bd28c0f1..3aa1f830c551 100644
--- a/arch/x86/include/asm/vdso.h
+++ b/arch/x86/include/asm/vdso.h
@@ -49,6 +49,22 @@ extern const struct vdso_image *selected_vdso32;
 
 extern void __init init_vdso_image(const struct vdso_image *image);
 
+static inline void __user *vdso_text_start(const struct mm_struct *mm)
+{
+       if (!mm->context.vdso_image)
+               return NULL;
+
+       return (void __user *)ACCESS_ONCE(mm->context.vvar_vma_start) -
+               mm->context.vdso_image->sym_vvar_start;
+}
+
+#define VDSO_SYM_ADDR(mm, sym) (                                       \
+               (mm)->context.vdso_image ?                              \
+               vdso_text_start((mm)) +                                 \
+                       (mm)->context.vdso_image->sym_ ## sym           \
+               : NULL                                                  \
+       )
+
 #endif /* __ASSEMBLER__ */
 
 #endif /* _ASM_X86_VDSO_H */
diff --git a/arch/x86/kernel/signal.c b/arch/x86/kernel/signal.c
index 2851d63c1202..d8b21e37e292 100644
--- a/arch/x86/kernel/signal.c
+++ b/arch/x86/kernel/signal.c
@@ -297,10 +297,8 @@ __setup_frame(int sig, struct ksignal *ksig, sigset_t *set,
                        return -EFAULT;
        }
 
-       if (current->mm->context.vdso)
-               restorer = current->mm->context.vdso +
-                       selected_vdso32->sym___kernel_sigreturn;
-       else
+       restorer = VDSO_SYM_ADDR(current->mm, __kernel_sigreturn);
+       if (!restorer)
                restorer = &frame->retcode;
        if (ksig->ka.sa.sa_flags & SA_RESTORER)
                restorer = ksig->ka.sa.sa_restorer;
@@ -362,8 +360,7 @@ static int __setup_rt_frame(int sig, struct ksignal *ksig,
                save_altstack_ex(&frame->uc.uc_stack, regs->sp);
 
                /* Set up to return from userspace.  */
-               restorer = current->mm->context.vdso +
-                       selected_vdso32->sym___kernel_rt_sigreturn;
+               restorer = VDSO_SYM_ADDR(current->mm, __kernel_rt_sigreturn);
                if (ksig->ka.sa.sa_flags & SA_RESTORER)
                        restorer = ksig->ka.sa.sa_restorer;
                put_user_ex(restorer, &frame->pretcode);
diff --git a/arch/x86/vdso/vma.c b/arch/x86/vdso/vma.c
index 970463b566cf..7f99c2ed1a3e 100644
--- a/arch/x86/vdso/vma.c
+++ b/arch/x86/vdso/vma.c
@@ -89,6 +89,38 @@ static unsigned long vdso_addr(unsigned long start, unsigned 
len)
 #endif
 }
 
+static void vvar_start_set(struct vm_special_mapping *sm,
+                          struct mm_struct *mm, unsigned long start_addr)
+{
+       if (start_addr >= TASK_SIZE_MAX - mm->context.vdso_image->size) {
+               /*
+                * We were just relocated out of bounds.  Malicious
+                * user code can cause this by mremapping only the
+                * first page of a multi-page vdso.
+                *
+                * We can't actually fail here, but it's not safe to
+                * allow vdso symbols to resolve to potentially
+                * non-canonical addresses.  Instead, just ignore
+                * the update.
+                */
+
+               return;
+       }
+
+       mm->context.vvar_vma_start = start_addr;
+
+       /*
+        * If we're here as a result of an mremap call, there are two
+        * major gotchas.  First, if that call came from the vdso, we're
+        * about to crash (i.e. don't do that).  Second, if we have more
+        * than one thread, this won't update the other threads.
+        */
+       if (mm->context.vdso_image->sym_VDSO32_SYSENTER_RETURN)
+               current_thread_info()->sysenter_return =
+                       VDSO_SYM_ADDR(current->mm, VDSO32_SYSENTER_RETURN);
+
+}
+
 static int map_vdso(const struct vdso_image *image, bool calculate_addr)
 {
        struct mm_struct *mm = current->mm;
@@ -99,6 +131,12 @@ static int map_vdso(const struct vdso_image *image, bool 
calculate_addr)
        static struct vm_special_mapping vvar_mapping = {
                .name = "[vvar]",
                .pages = no_pages,
+
+               /*
+                * Tracking the vdso is roughly equivalent to tracking the
+                * vvar area, and the latter is slightly easier.
+                */
+               .start_addr_set = vvar_start_set,
        };
 
        if (calculate_addr) {
@@ -118,7 +156,7 @@ static int map_vdso(const struct vdso_image *image, bool 
calculate_addr)
        }
 
        text_start = addr - image->sym_vvar_start;
-       current->mm->context.vdso = (void __user *)text_start;
+       current->mm->context.vdso_image = image;
 
        /*
         * MAYWRITE to allow gdb to COW and set breakpoints
@@ -171,7 +209,7 @@ static int map_vdso(const struct vdso_image *image, bool 
calculate_addr)
 
 up_fail:
        if (ret)
-               current->mm->context.vdso = NULL;
+               current->mm->context.vdso_image = NULL;
 
        up_write(&mm->mmap_sem);
        return ret;
@@ -189,11 +227,6 @@ static int load_vdso32(void)
        if (ret)
                return ret;
 
-       if (selected_vdso32->sym_VDSO32_SYSENTER_RETURN)
-               current_thread_info()->sysenter_return =
-                       current->mm->context.vdso +
-                       selected_vdso32->sym_VDSO32_SYSENTER_RETURN;
-
        return 0;
 }
 #endif
-- 
1.9.3

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Reply via email to